├── LICENSE ├── README.md ├── benchmarks ├── config.toml ├── fb.toml ├── fio.toml ├── helpers │ ├── bcache_off.sh │ ├── bcache_on.sh │ ├── dis_off.sh │ ├── dis_on.sh │ ├── rbd_off.sh │ └── rbd_on.sh └── run.py ├── kernel ├── .clang-format ├── Makefile ├── dm-disbd.c └── dm-disbd.h ├── run.sh ├── run_benchmarks.sh ├── test-mkfs_mnt.sh └── userspace ├── backend ├── backend.go ├── file │ └── file.go ├── null │ └── null.go └── object │ ├── api │ ├── rados │ │ └── rados.go │ └── s3 │ │ └── s3.go │ ├── extmap │ └── extmap.go │ ├── gc.go │ ├── gc │ └── gc.go │ ├── object.go │ ├── read.go │ └── write.go ├── cache └── cache.go ├── config.toml ├── dis.go ├── extent └── extent.go ├── go.mod ├── go.sum ├── ioctl ├── c.go ├── ioctl.go ├── read.go └── write.go ├── l2cache └── l2cache.go ├── parser └── parser.go └── profiler └── profiler.go /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DIS: blockDevice over Immutable Storage 2 | 3 | ## Description 4 | 5 | `DIS` is a block devices which uses another block device as a cache and is backed by one of several backends. E.g. fast local NVMe as a cache and object backend with Amazon S3 API results to a block device with speeds close to the local NVMe and advantages of remote object store with unlimited size. Compared to `bcache`, `DIS` is crash-consistent, supports various backends and is easily extensible. 6 | 7 | ## Available caching devices 8 | 9 | - Any block device (local drive, loop device, ...) 10 | 11 | ## Available backends 12 | 13 | - File 14 | - Object with Amazon S3 API 15 | - Object with Ceph Rados API 16 | 17 | ## Requirements 18 | 19 | - Linux Kernel 5.0.0 + Headers (Newer kernels not supported.) 20 | - Go 1.16 or newer 21 | 22 | ## EuroSys 2022 23 | 24 | This work was accepted to EuroSys 2022 conference under the title **Beating the I/O bottleneck: A case for log-structured virtual disks**. 25 | 26 | **NOTE:** Throughout the paper we use `LSVD` abbreviation instead of `DIS`. However the code uses `DIS` abbreviation everywhere. 27 | 28 | ## Usage 29 | 30 | `DIS` consists from a kernel module and a userspace daemon. `run.sh` contains script which builds the kernel module, load it, setup new device mapper device and run the userspace daemon. Please take your time and edit it before running. 31 | 32 | More detailed instructions: 33 | 34 | 1. Build and load the kernel module. 35 | 36 | ```bash 37 | $ cd kernel 38 | $ make 39 | $ insmod dm-disbd.ko 40 | ``` 41 | 42 | 2. Run the device mapper device. 43 | 44 | The device mapper accepts following parameters: 45 | 46 | ``` 47 | 0 disbd disa 48 | ``` 49 | 50 | | Parameter | Description | 51 | | --- | --- | 52 | | 0, | `dmsetup` parameters, N is virtual volume size in 512-byte sectors | 53 | | "disbd" | `dmsetup` parameter, use "disbd" module (must be loaded already) | 54 | | | the local cache device or partition | 55 | | "disa" | name of the character device for user-space communication (e.g. `/dev/disbd/disa`) | 56 | | | start offset of write cache in (in sectors) | 57 | | | end (+1) of write cache in , again in sectors | 58 | | | max writes to queue to backend before blocking (in sectors) | 59 | 60 | The read cache is managed by the userspace, and the write cache by the kernel device mapper; data fetched for reads will be stored in the range from to the end of the device. 61 | 62 | The following example will create the block device `/dev/mapper/disa` (last argument to `dmsetup`) with the control device `/dev/disbd/disa` (5th value in device mapper table): 63 | 64 | ```bash 65 | $ virt_size=$((80*1024*1024/2)) # 80GB 66 | $ dev=/dev/nvme0n1p1 67 | $ devsz=$(blockdev --getsz /dev/nvme0n1p1) 68 | $ limit=$((devsz/2)) 69 | $ backlog=$((64*1024)) # 128 MB 70 | 71 | $ echo 0 $virt_size disbd $dev disa 0 $limit $backlog | dmsetup --noudevsync create disa 72 | ``` 73 | 3. Build, configure and run the userspace daemon. 74 | 75 | The userspace component must be started after the device mapper is configured. Note that this can cause a deadlock with `udev`, which normally tries to read the volume partition before `dmsetup` returns; however the map is not available until the userspace is running. 76 | 77 | To **build** the userspace daemon: 78 | 79 | ```bash 80 | $ cd userspace 81 | $ go build 82 | ``` 83 | 84 | **Configuration** is via the [spf13/viper](https://github.com/spf13/viper) system, reading configuration from (a) `config.toml` in the current directory, and (b) environment variables of the form `DIS_`, in that order. 85 | 86 | Key configuration parameters for the configuration file are following: 87 | 88 | ```toml 89 | [cache] 90 | base = "" 91 | bound = "" 92 | file = "nvme device / partition" 93 | 94 | [backend] 95 | enabled = "file | null | object -- only use object" 96 | 97 | [backend.object] 98 | api = "s3 | rados" 99 | gcMode = "on | off | statsOnly | silent" 100 | gcVersion = 2 101 | objectSizeM = "object size (MB)" 102 | 103 | [backend.object.s3] 104 | bucket = "" 105 | region = "" 106 | remote = " (e.g. http://1.2.3.4:5678)" 107 | 108 | [backend.object.rados] 109 | pool = "" 110 | 111 | [ioctl] 112 | ctl = "/dev/disbd/disa" # character device interface to device mapper 113 | extents = 128 # internal parameter 114 | ``` 115 | 116 | Environment variables take precedence, and are of the form DIS_..., with all names upper-cased, e.g. DIS_BACKEND_OBJECT_S3_BUCKET=testbucket. 117 | 118 | To **run** the userspace daemon: 119 | 120 | ```bash 121 | $ cd userspace 122 | $ go run . 123 | ``` 124 | 125 | ## Benchmarks 126 | 127 | 1. Configuration 128 | 129 | The configuration for the complete benchmark set is taken from `benchmarks/config.toml`. It has following parameters: 130 | 131 | | Parameter | Description | 132 | | --- | --- | 133 | | `iterations` | Array with iteration suffixes. E.g. `[1,2,9]` will run 3 iterations and create output files with suffixes 1, 2 and 9. | 134 | | `enabled` | Array with named configurations to be run. See below. | 135 | | `benchmarks` | Array with benchmarks to be run. Can contain `fio` and `fb` (Filebench). | 136 | 137 | Named configurations present different `DIS` setups to be benchmarked. Every named configuration can contain following parameters: 138 | 139 | | Parameter | Description | 140 | | --- | --- | 141 | | `dev` | Blockdevice path (e.g. `/dev/mapper/disa`). | 142 | | `cache_size_M` | Array with cache sizes in MB to be tested (e.g. `[10240, 716800]`). | 143 | | `env` | Environment variables to be set. | 144 | 145 | Note that the current configuration includes RGW S3 endpoints and RADOS pools which are specific to our test configuration. Tests were performed for RBD in both replicated and erasure-coded (not reported) configurations, using separate RADOS pool names for each, and for DIS over S3 with replicated (not reported) and erasure-coded pools, using a separate RGW instance configured for each pool. 146 | 147 | The `fio.toml` file specifies the fio tests, running all combinations of the following fio parameters: 148 | 149 | | Parameter | Description | 150 | | --- | --- | 151 | | `rw` | Array containing a subset of following values: write, randwrite, read, randread. | 152 | | `bs` | Array with benchmarked block sizes. | 153 | | `iodepth` | Array with benchmarked iodepths. | 154 | 155 | The `common` section specifies parameters common to all fio runs like `runtime` or `ioengine` to use. 156 | 157 | The `fb.toml` file specifies the Filebench tests, and contains full Filebench configuration files for each of the tested configurations: "fileserver", fileserver-fsync", "oltp", and "varmail". 158 | 159 | 2. Running benchmarks 160 | 161 | ```bash 162 | run_benchmarks.sh 163 | ``` 164 | 165 | or 166 | 167 | ```bash 168 | $ cd benchmarks 169 | $ ./run.py 170 | ``` 171 | -------------------------------------------------------------------------------- /benchmarks/config.toml: -------------------------------------------------------------------------------- 1 | iterations = [ 2 | 9, 3 | ] 4 | 5 | enabled = [ 6 | 'bcache_rbd_ec', 7 | 'rbd_ec', 8 | 'dis_rados_ec', 9 | 'dis_rgw_ec', 10 | ] 11 | 12 | benchmarks = [ 13 | 'fio', 14 | #'fb', 15 | ] 16 | 17 | [dis_rgw_replicated] 18 | dev = '/dev/mapper/disa' 19 | cache_size_M = [ 20 | 10240, 21 | 716800, 22 | ] 23 | 24 | [dis_rgw_replicated.env] 25 | DIS_BACKEND_OBJECT_API = 's3' 26 | DIS_BACKEND_OBJECT_S3_REMOTE = 'http://10.0.0.6:80' 27 | 28 | [dis_rgw_ec] 29 | dev = '/dev/mapper/disa' 30 | cache_size_M = [ 31 | 10240, 32 | 716800, 33 | ] 34 | 35 | [dis_rgw_ec.env] 36 | DIS_BACKEND_OBJECT_API = 's3' 37 | DIS_BACKEND_OBJECT_S3_REMOTE = 'http://10.0.0.6:8080' 38 | 39 | [dis_rados_replicated] 40 | dev = '/dev/mapper/disa' 41 | cache_size_M = [ 42 | 10240, 43 | 716800, 44 | ] 45 | 46 | [dis_rados_replicated.env] 47 | DIS_BACKEND_OBJECT_API = 'rados' 48 | DIS_BACKEND_OBJECT_RADOS_POOL = 'replicated-pool' 49 | 50 | [dis_rados_ec] 51 | dev = '/dev/mapper/disa' 52 | cache_size_M = [ 53 | 10240, 54 | 716800, 55 | ] 56 | 57 | [dis_rados_ec.env] 58 | DIS_BACKEND_OBJECT_API = 'rados' 59 | DIS_BACKEND_OBJECT_RADOS_POOL = 'ec-pool' 60 | 61 | 62 | [bcache_rbd_replicated] 63 | dev = '/dev/bcache0' 64 | cache_size_M = [ 65 | 10240, 66 | 716800, 67 | ] 68 | 69 | [bcache_rbd_replicated.env] 70 | RBD_POOL = 'rbd' 71 | 72 | [bcache_rbd_ec] 73 | dev = '/dev/bcache0' 74 | cache_size_M = [ 75 | 10240, 76 | 716800, 77 | ] 78 | 79 | [bcache_rbd_ec.env] 80 | RBD_POOL = 'rbd-ec' 81 | 82 | [rbd_replicated] 83 | dev = '/dev/rbd0' 84 | cache_size_M = [1] 85 | 86 | [rbd_replicated.env] 87 | RBD_POOL = 'rbd' 88 | 89 | [rbd_ec] 90 | dev = '/dev/rbd0' 91 | cache_size_M = [1] 92 | 93 | [rbd_ec.env] 94 | RBD_POOL = 'rbd-ec' 95 | -------------------------------------------------------------------------------- /benchmarks/fb.toml: -------------------------------------------------------------------------------- 1 | enabled = [ 2 | 'fileserver', 3 | 'fileserver-fsync', 4 | 'oltp', 5 | 'varmail', 6 | ] 7 | 8 | header = 'workload,backend,iteration,bw,ops' 9 | 10 | fileserver = ''' 11 | set $dir=/mnt 12 | set $nfiles=200000 13 | set $meandirwidth=20 14 | set $filesize=cvar(type=cvar-gamma,parameters=mean:131072;gamma:1.5) 15 | #set $filesize=cvar(type=cvar-gamma,parameters=mean:262144;gamma:1.5) 16 | set $nthreads=50 17 | set $iosize=1m 18 | set $meanappendsize=16k 19 | 20 | define fileset name=bigfileset,path=$dir,size=$filesize,entries=$nfiles,dirwidth=$meandirwidth,prealloc=80 21 | 22 | define process name=filereader,instances=1 23 | { 24 | thread name=filereaderthread,memsize=10m,instances=$nthreads 25 | { 26 | flowop createfile name=createfile1,filesetname=bigfileset,fd=1 27 | flowop writewholefile name=wrtfile1,srcfd=1,fd=1,iosize=$iosize 28 | flowop closefile name=closefile1,fd=1 29 | flowop openfile name=openfile1,filesetname=bigfileset,fd=1 30 | flowop appendfilerand name=appendfilerand1,iosize=$meanappendsize,fd=1 31 | #flowop fsync name=fsyncfile2,fd=1 32 | flowop closefile name=closefile2,fd=1 33 | flowop openfile name=openfile2,filesetname=bigfileset,fd=1 34 | flowop readwholefile name=readfile1,fd=1,iosize=$iosize 35 | flowop closefile name=closefile3,fd=1 36 | flowop deletefile name=deletefile1,filesetname=bigfileset 37 | flowop statfile name=statfile1,filesetname=bigfileset 38 | } 39 | } 40 | 41 | #create files 42 | 43 | run 300 44 | ''' 45 | 46 | fileserver-fsync = ''' 47 | set $dir=/mnt 48 | set $nfiles=200000 49 | set $meandirwidth=20 50 | set $filesize=cvar(type=cvar-gamma,parameters=mean:131072;gamma:1.5) 51 | #set $filesize=cvar(type=cvar-gamma,parameters=mean:262144;gamma:1.5) 52 | set $nthreads=50 53 | set $iosize=1m 54 | set $meanappendsize=16k 55 | 56 | define fileset name=bigfileset,path=$dir,size=$filesize,entries=$nfiles,dirwidth=$meandirwidth,prealloc=80 57 | 58 | define process name=filereader,instances=1 59 | { 60 | thread name=filereaderthread,memsize=10m,instances=$nthreads 61 | { 62 | flowop createfile name=createfile1,filesetname=bigfileset,fd=1 63 | flowop writewholefile name=wrtfile1,srcfd=1,fd=1,iosize=$iosize 64 | flowop closefile name=closefile1,fd=1 65 | flowop openfile name=openfile1,filesetname=bigfileset,fd=1 66 | flowop appendfilerand name=appendfilerand1,iosize=$meanappendsize,fd=1 67 | flowop fsync name=fsyncfile2,fd=1 68 | flowop closefile name=closefile2,fd=1 69 | flowop openfile name=openfile2,filesetname=bigfileset,fd=1 70 | flowop readwholefile name=readfile1,fd=1,iosize=$iosize 71 | flowop closefile name=closefile3,fd=1 72 | flowop deletefile name=deletefile1,filesetname=bigfileset 73 | flowop statfile name=statfile1,filesetname=bigfileset 74 | } 75 | } 76 | 77 | 78 | #create files 79 | run 300 80 | ''' 81 | 82 | oltp = ''' 83 | set $dir=/mnt 84 | 85 | set $eventrate=0 86 | set $runtime=30 87 | set $iosize=2k 88 | set $nshadows=200 89 | set $ndbwriters=10 90 | set $usermode=200000 91 | set $filesize=100m 92 | set $memperthread=1m 93 | set $workingset=0 94 | set $logfilesize=100m 95 | set $nfiles=250 96 | set $nlogfiles=1 97 | set $directio=0 98 | 99 | eventgen rate = $eventrate 100 | 101 | # Define a datafile and logfile 102 | define fileset name=datafiles,path=$dir,size=$filesize,entries=$nfiles,dirwidth=1024,prealloc=100,reuse 103 | define fileset name=logfile,path=$dir,size=$logfilesize,entries=$nlogfiles,dirwidth=1024,prealloc=100,reuse 104 | 105 | define process name=lgwr,instances=1 106 | { 107 | thread name=lgwr,memsize=$memperthread 108 | { 109 | flowop aiowrite name=lg-write,filesetname=logfile, 110 | iosize=256k,random,directio=$directio,dsync 111 | flowop aiowait name=lg-aiowait 112 | flowop semblock name=lg-block,value=3200,highwater=1000 113 | } 114 | } 115 | 116 | # Define database writer processes 117 | define process name=dbwr,instances=$ndbwriters 118 | { 119 | thread name=dbwr,memsize=$memperthread 120 | { 121 | flowop aiowrite name=dbwrite-a,filesetname=datafiles, 122 | iosize=$iosize,workingset=$workingset,random,iters=100,opennext,directio=$directio,dsync 123 | flowop hog name=dbwr-hog,value=10000 124 | flowop semblock name=dbwr-block,value=1000,highwater=2000 125 | flowop aiowait name=dbwr-aiowait 126 | } 127 | } 128 | 129 | define process name=shadow,instances=$nshadows 130 | { 131 | thread name=shadow,memsize=$memperthread,useism 132 | { 133 | flowop read name=shadowread,filesetname=datafiles, 134 | iosize=$iosize,workingset=$workingset,random,opennext,directio=$directio 135 | flowop hog name=shadowhog,value=$usermode 136 | flowop sempost name=shadow-post-lg,value=1,target=lg-block,blocking 137 | flowop sempost name=shadow-post-dbwr,value=1,target=dbwr-block,blocking 138 | flowop eventlimit name=random-rate 139 | } 140 | } 141 | 142 | #create files 143 | run 300 144 | ''' 145 | 146 | varmail = ''' 147 | set $dir=/mnt 148 | set $nfiles=900000 149 | set $meandirwidth=1000000 150 | #set $filesize=cvar(type=cvar-gamma,parameters=mean:16384;gamma:1.5) 151 | set $filesize=cvar(type=cvar-gamma,parameters=mean:32768;gamma:1.5) 152 | set $nthreads=16 153 | set $iosize=1m 154 | set $meanappendsize=16k 155 | 156 | define fileset name=bigfileset,path=$dir,size=$filesize,entries=$nfiles,dirwidth=$meandirwidth,prealloc=80 157 | 158 | define process name=filereader,instances=1 159 | { 160 | thread name=filereaderthread,memsize=10m,instances=$nthreads 161 | { 162 | flowop deletefile name=deletefile1,filesetname=bigfileset 163 | flowop createfile name=createfile2,filesetname=bigfileset,fd=1 164 | flowop appendfilerand name=appendfilerand2,iosize=$meanappendsize,fd=1 165 | flowop fsync name=fsyncfile2,fd=1 166 | flowop closefile name=closefile2,fd=1 167 | flowop openfile name=openfile3,filesetname=bigfileset,fd=1 168 | flowop readwholefile name=readfile3,fd=1,iosize=$iosize 169 | flowop appendfilerand name=appendfilerand3,iosize=$meanappendsize,fd=1 170 | flowop fsync name=fsyncfile3,fd=1 171 | flowop closefile name=closefile3,fd=1 172 | flowop openfile name=openfile4,filesetname=bigfileset,fd=1 173 | flowop readwholefile name=readfile4,fd=1,iosize=$iosize 174 | flowop closefile name=closefile4,fd=1 175 | } 176 | } 177 | 178 | #create files 179 | run 300 180 | ''' 181 | -------------------------------------------------------------------------------- /benchmarks/fio.toml: -------------------------------------------------------------------------------- 1 | header = 'rw,bs,iodepth,backend,iteration,bw,iops' 2 | 3 | rw = [ 4 | 'randwrite', 5 | 'write', 6 | #'read', 7 | #'randread', 8 | ] 9 | 10 | bs = [ 11 | '4k', 12 | '16k', 13 | '64k', 14 | ] 15 | 16 | iodepth = [ 17 | '1', 18 | '2', 19 | '4', 20 | '32', 21 | ] 22 | 23 | nooptions = [ 24 | 'group_reporting', 25 | 'minimal', 26 | ] 27 | 28 | [common] 29 | name = 'experiment' 30 | ioengine = 'libaio' 31 | direct = 1 32 | refill_buffers = 1 33 | runtime = '120s' 34 | offset = 0 35 | -------------------------------------------------------------------------------- /benchmarks/helpers/bcache_off.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # Copyright (C) 2020-2021 Vojtech Aschenbrenner 4 | 5 | set -euxo pipefail 6 | 7 | sudo bash -c 'echo 1 > /sys/block/nvme0n1/nvme0n1p1/bcache/set/stop' 8 | sleep 1 9 | 10 | sudo wipefs -a /dev/nvme0n1p1 /dev/rbd0 11 | sudo rbd unmap /dev/rbd0 12 | -------------------------------------------------------------------------------- /benchmarks/helpers/bcache_on.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # Copyright (C) 2020-2021 Vojtech Aschenbrenner 4 | 5 | set -euxo pipefail 6 | 7 | if [[ $RBD_POOL == "rbd" ]]; then 8 | sudo rbd map drive_80G 9 | else 10 | sudo rbd map drive_80G-EC 11 | fi 12 | 13 | sudo wipefs -a /dev/nvme0n1p1 /dev/rbd0 14 | sudo make-bcache --wipe-cache -C /dev/nvme0n1p1 -B /dev/rbd0 15 | sleep 5 16 | 17 | set +e 18 | sudo bash -c 'echo /dev/rbd0 > /sys/fs/bcache/register' 19 | sudo bash -c 'echo /dev/nvme0n1p1 > /sys/fs/bcache/register' 20 | set -e 21 | 22 | sudo bash -c 'echo writeback > /sys/block/bcache0/bcache/cache_mode' 23 | sudo bash -c 'echo 0 > /sys/block/bcache0/bcache/sequential_cutoff' 24 | sudo bash -c 'echo 0 > /sys/fs/bcache/**/congested_read_threshold_us' 25 | sudo bash -c 'echo 0 > /sys/fs/bcache/**/congested_write_threshold_us' 26 | -------------------------------------------------------------------------------- /benchmarks/helpers/dis_off.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # Copyright (C) 2020-2021 Vojtech Aschenbrenner 4 | 5 | set -euxo pipefail 6 | 7 | sudo pkill -2 dis$ 8 | sleep 1 9 | sudo dmsetup remove --retry disa 10 | sudo rmmod dm_disbd 11 | -------------------------------------------------------------------------------- /benchmarks/helpers/dis_on.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # Copyright (C) 2020-2021 Vojtech Aschenbrenner 4 | 5 | set -euxo pipefail 6 | 7 | cache_sectors=$((cache_size_M*1024*1024/512)) 8 | 9 | store_size_M=$((1024*80)) 10 | store_sectors=$((store_size_M*1024*1024/512)) 11 | 12 | loop=/dev/nvme0n1p1 13 | 14 | sudo insmod ../kernel/dm-disbd.ko 15 | 16 | echo 0 $store_sectors disbd $loop disa 0 $((cache_sectors/2)) $((cache_sectors/2 - 4*1024*1024*1024/512)) | sudo dmsetup --noudevsync create disa 17 | 18 | sleep 1 19 | 20 | cd ../userspace 21 | export DIS_CACHE_BASE=$((cache_sectors/2)) 22 | export DIS_CACHE_BOUND=$cache_sectors 23 | export DIS_CACHE_FILE=$loop 24 | export DIS_IOCTL_CTL=/dev/disbd/disa 25 | export AWS_ACCESS_KEY_ID="79RPB6L1D0AH02U1GS1Y" 26 | export AWS_SECRET_ACCESS_KEY="cP7OEGfleOwkTksvKdIxigT0CKA4MCv510ffz1Vg" 27 | 28 | echo "n" | sudo -E go run . 29 | -------------------------------------------------------------------------------- /benchmarks/helpers/rbd_off.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # Copyright (C) 2020-2021 Vojtech Aschenbrenner 4 | 5 | set -euxo pipefail 6 | 7 | sudo wipefs -a /dev/rbd0 8 | sudo rbd unmap /dev/rbd0 9 | -------------------------------------------------------------------------------- /benchmarks/helpers/rbd_on.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # Copyright (C) 2020-2021 Vojtech Aschenbrenner 4 | 5 | set -euxo pipefail 6 | 7 | if [[ $RBD_POOL == "rbd" ]]; then 8 | sudo rbd map drive_80G 9 | else 10 | sudo rbd map drive_80G-EC 11 | fi 12 | 13 | sudo wipefs -a /dev/rbd0 14 | -------------------------------------------------------------------------------- /benchmarks/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # Copyright (C) 2020-2021 Vojtech Aschenbrenner 4 | 5 | import os 6 | import re 7 | import shlex 8 | import subprocess 9 | import time 10 | import toml 11 | import uuid 12 | 13 | def discard(): 14 | print("Running discard and parted") 15 | subprocess.run(shlex.split('sudo blkdiscard /dev/nvme0n1'), check=True) 16 | subprocess.run(shlex.split(f'sudo parted --script /dev/nvme0n1 mklabel gpt mkpart primary 4MiB {cache_size_M + 4}MiB'), check=True) 17 | time.sleep(2) 18 | 19 | def rbd_on(): 20 | discard() 21 | subprocess.run(shlex.split('./helpers/rbd_on.sh'), check=True) 22 | 23 | def rbd_off(): 24 | subprocess.run(shlex.split('./helpers/rbd_off.sh'), check=True) 25 | 26 | def bcache_on(): 27 | discard() 28 | subprocess.run(shlex.split('./helpers/bcache_on.sh'), check=True) 29 | 30 | def bcache_off(): 31 | time.sleep(5) 32 | subprocess.run(shlex.split('./helpers/bcache_off.sh'), check=True) 33 | 34 | def dis_on(): 35 | discard() 36 | subprocess.run(shlex.split('sudo rm -f /tmp/dis_ready'), check=True) 37 | subprocess.Popen(shlex.split('./helpers/dis_on.sh')) 38 | while not(os.path.isfile('/tmp/dis_ready')): 39 | time.sleep(1) 40 | time.sleep(1) 41 | 42 | def dis_off(): 43 | time.sleep(10) 44 | subprocess.run(shlex.split('./helpers/dis_off.sh'), check=True) 45 | 46 | def fio(dev, pre, post, fd, backend): 47 | t = toml.load('fio.toml') 48 | args = [f"--filename={dev}"] 49 | for o in t['common']: 50 | args.append(f"--{o}={t['common'][o]}") 51 | for o in t['nooptions']: 52 | args.append(f"--{o}") 53 | for rw in t['rw']: 54 | for bs in t['bs']: 55 | for iodepth in t['iodepth']: 56 | pre() 57 | 58 | size = '100%' 59 | if 'read' in rw: 60 | size = '20g' 61 | plain_rbd = 'rbd' in backend and 'bcache' not in backend 62 | small_bcache = 'bcache' in backend and '10GBcache' in backend 63 | if not (plain_rbd or small_bcache): 64 | subprocess.run(shlex.split(f'sudo fio --filename={dev} --name=experiment --ioengine=libaio --direct=1 --refill_buffers=1 --minimal --group_reporting --rw=write --bs=64k --iodepth=32 --size=30g --offset=0'), check=True) 65 | #subprocess.run(shlex.split(f'sudo dd if=/dev/zero of={dev} bs=8M oflag=direct count={(30*1024**3)//(8*1024**2)}'), check=True) 66 | 67 | all_args = ['sudo', 'fio'] + args + [f"--rw={rw}", f"--bs={bs}", f"--iodepth={iodepth}", f"--size={size}"] 68 | print(all_args) 69 | 70 | subprocess.run(shlex.split('sleep 2'), check=True) 71 | f = subprocess.run(all_args, capture_output=True, universal_newlines=True) 72 | 73 | bw = float(f.stdout.split(';')[47]) / 1024 74 | iops = f.stdout.split(';')[48] 75 | if 'read' in rw: 76 | bw = float(f.stdout.split(';')[6]) / 1024 77 | iops = f.stdout.split(';')[7] 78 | fd.write(f'{rw},{bs},{iodepth},{backend},{iteration},{bw},{iops}\n') 79 | print(f.stdout) 80 | post() 81 | 82 | def fb(dev, pre, post, fd, backend): 83 | os.system("sudo sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space'") 84 | t = toml.load('fb.toml') 85 | for w in t['enabled']: 86 | pre() 87 | subprocess.run(shlex.split(f'sudo mkfs.ext4 -F -F {dev}'), check=True) 88 | subprocess.run(shlex.split(f'sudo mount {dev} /mnt'), check=True) 89 | 90 | fname = str(uuid.uuid1()) 91 | with open(fname, "w+") as tmp_file: 92 | tmp_file.write(t[w]) 93 | 94 | args = ['sudo', 'filebench', '-f'] + [fname] 95 | print(args) 96 | subprocess.run(shlex.split('sleep 2'), check=True) 97 | f = subprocess.run(args, capture_output=True, universal_newlines=True) 98 | subprocess.run(shlex.split('sudo umount /mnt'), check=True) 99 | os.remove(fname) 100 | 101 | try: 102 | bw = re.search('Summary:.*\ +(\d+\.\d+)mb/s', f.stdout).group(1) 103 | ops = re.search('Summary:.*\ +(\d+\.\d+) ops/s', f.stdout).group(1) 104 | fd.write(f'{w},{backend},{iteration},{bw},{ops}\n') 105 | except: 106 | fd.write(f'{w},{backend},{iteration},-1,-1\n') 107 | 108 | print(f.stdout) 109 | post() 110 | 111 | if __name__ == '__main__': 112 | t = toml.load('config.toml') 113 | 114 | for benchmark in t['benchmarks']: 115 | tt = toml.load(benchmark + ".toml") 116 | with open(benchmark + ".csv", "w") as fd: 117 | fd.write(tt['header'] + "\n") 118 | 119 | for iteration in t['iterations']: 120 | for benchmark in t['benchmarks']: 121 | with open(benchmark + ".csv", "a") as fd: 122 | b_f = eval(benchmark) 123 | for backend_ in t['enabled']: 124 | backend = backend_.split(sep='_')[0] 125 | d = t[backend_]['dev'] 126 | on_f = eval(backend + "_on") 127 | off_f = eval(backend + "_off") 128 | for cache_size_M in t[backend_]['cache_size_M']: 129 | os.environ["cache_size_M"] = str(cache_size_M) 130 | for env in t[backend_]["env"]: 131 | os.environ[env] = str(t[backend_]["env"][env]) 132 | 133 | cache_size_G = cache_size_M // 1024 134 | b_f(dev=d, pre=on_f, post=off_f, fd=fd, backend=backend_ + f"_{cache_size_G}GBcache") 135 | -------------------------------------------------------------------------------- /kernel/.clang-format: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | # 3 | # clang-format configuration file. Intended for clang-format >= 4. 4 | # 5 | # For more information, see: 6 | # 7 | # Documentation/process/clang-format.rst 8 | # https://clang.llvm.org/docs/ClangFormat.html 9 | # https://clang.llvm.org/docs/ClangFormatStyleOptions.html 10 | # 11 | --- 12 | AccessModifierOffset: -4 13 | AlignAfterOpenBracket: Align 14 | AlignConsecutiveAssignments: false 15 | AlignConsecutiveDeclarations: false 16 | #AlignEscapedNewlines: Left # Unknown to clang-format-4.0 17 | AlignOperands: true 18 | AlignTrailingComments: false 19 | AllowAllParametersOfDeclarationOnNextLine: false 20 | AllowShortBlocksOnASingleLine: false 21 | AllowShortCaseLabelsOnASingleLine: false 22 | AllowShortFunctionsOnASingleLine: None 23 | AllowShortIfStatementsOnASingleLine: false 24 | AllowShortLoopsOnASingleLine: false 25 | AlwaysBreakAfterDefinitionReturnType: None 26 | AlwaysBreakAfterReturnType: None 27 | AlwaysBreakBeforeMultilineStrings: false 28 | AlwaysBreakTemplateDeclarations: false 29 | BinPackArguments: true 30 | BinPackParameters: true 31 | BraceWrapping: 32 | AfterClass: false 33 | AfterControlStatement: false 34 | AfterEnum: false 35 | AfterFunction: true 36 | AfterNamespace: true 37 | AfterObjCDeclaration: false 38 | AfterStruct: false 39 | AfterUnion: false 40 | #AfterExternBlock: false # Unknown to clang-format-5.0 41 | BeforeCatch: false 42 | BeforeElse: false 43 | IndentBraces: false 44 | #SplitEmptyFunction: true # Unknown to clang-format-4.0 45 | #SplitEmptyRecord: true # Unknown to clang-format-4.0 46 | #SplitEmptyNamespace: true # Unknown to clang-format-4.0 47 | BreakBeforeBinaryOperators: None 48 | BreakBeforeBraces: Custom 49 | #BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0 50 | BreakBeforeTernaryOperators: false 51 | BreakConstructorInitializersBeforeComma: false 52 | #BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0 53 | BreakAfterJavaFieldAnnotations: false 54 | BreakStringLiterals: false 55 | ColumnLimit: 100 56 | CommentPragmas: '^ IWYU pragma:' 57 | #CompactNamespaces: false # Unknown to clang-format-4.0 58 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 59 | ConstructorInitializerIndentWidth: 8 60 | ContinuationIndentWidth: 8 61 | Cpp11BracedListStyle: false 62 | DerivePointerAlignment: false 63 | DisableFormat: false 64 | ExperimentalAutoDetectBinPacking: false 65 | #FixNamespaceComments: false # Unknown to clang-format-4.0 66 | 67 | # Taken from: 68 | # git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ \ 69 | # | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \ 70 | # | sort | uniq 71 | ForEachMacros: 72 | - 'apei_estatus_for_each_section' 73 | - 'ata_for_each_dev' 74 | - 'ata_for_each_link' 75 | - '__ata_qc_for_each' 76 | - 'ata_qc_for_each' 77 | - 'ata_qc_for_each_raw' 78 | - 'ata_qc_for_each_with_internal' 79 | - 'ax25_for_each' 80 | - 'ax25_uid_for_each' 81 | - '__bio_for_each_bvec' 82 | - 'bio_for_each_bvec' 83 | - 'bio_for_each_bvec_all' 84 | - 'bio_for_each_integrity_vec' 85 | - '__bio_for_each_segment' 86 | - 'bio_for_each_segment' 87 | - 'bio_for_each_segment_all' 88 | - 'bio_list_for_each' 89 | - 'bip_for_each_vec' 90 | - 'bitmap_for_each_clear_region' 91 | - 'bitmap_for_each_set_region' 92 | - 'blkg_for_each_descendant_post' 93 | - 'blkg_for_each_descendant_pre' 94 | - 'blk_queue_for_each_rl' 95 | - 'bond_for_each_slave' 96 | - 'bond_for_each_slave_rcu' 97 | - 'bpf_for_each_spilled_reg' 98 | - 'btree_for_each_safe128' 99 | - 'btree_for_each_safe32' 100 | - 'btree_for_each_safe64' 101 | - 'btree_for_each_safel' 102 | - 'card_for_each_dev' 103 | - 'cgroup_taskset_for_each' 104 | - 'cgroup_taskset_for_each_leader' 105 | - 'cpufreq_for_each_entry' 106 | - 'cpufreq_for_each_entry_idx' 107 | - 'cpufreq_for_each_valid_entry' 108 | - 'cpufreq_for_each_valid_entry_idx' 109 | - 'css_for_each_child' 110 | - 'css_for_each_descendant_post' 111 | - 'css_for_each_descendant_pre' 112 | - 'device_for_each_child_node' 113 | - 'dma_fence_chain_for_each' 114 | - 'do_for_each_ftrace_op' 115 | - 'drm_atomic_crtc_for_each_plane' 116 | - 'drm_atomic_crtc_state_for_each_plane' 117 | - 'drm_atomic_crtc_state_for_each_plane_state' 118 | - 'drm_atomic_for_each_plane_damage' 119 | - 'drm_client_for_each_connector_iter' 120 | - 'drm_client_for_each_modeset' 121 | - 'drm_connector_for_each_possible_encoder' 122 | - 'drm_for_each_bridge_in_chain' 123 | - 'drm_for_each_connector_iter' 124 | - 'drm_for_each_crtc' 125 | - 'drm_for_each_encoder' 126 | - 'drm_for_each_encoder_mask' 127 | - 'drm_for_each_fb' 128 | - 'drm_for_each_legacy_plane' 129 | - 'drm_for_each_plane' 130 | - 'drm_for_each_plane_mask' 131 | - 'drm_for_each_privobj' 132 | - 'drm_mm_for_each_hole' 133 | - 'drm_mm_for_each_node' 134 | - 'drm_mm_for_each_node_in_range' 135 | - 'drm_mm_for_each_node_safe' 136 | - 'flow_action_for_each' 137 | - 'for_each_active_dev_scope' 138 | - 'for_each_active_drhd_unit' 139 | - 'for_each_active_iommu' 140 | - 'for_each_aggr_pgid' 141 | - 'for_each_available_child_of_node' 142 | - 'for_each_bio' 143 | - 'for_each_board_func_rsrc' 144 | - 'for_each_bvec' 145 | - 'for_each_card_auxs' 146 | - 'for_each_card_auxs_safe' 147 | - 'for_each_card_components' 148 | - 'for_each_card_dapms' 149 | - 'for_each_card_pre_auxs' 150 | - 'for_each_card_prelinks' 151 | - 'for_each_card_rtds' 152 | - 'for_each_card_rtds_safe' 153 | - 'for_each_card_widgets' 154 | - 'for_each_card_widgets_safe' 155 | - 'for_each_cgroup_storage_type' 156 | - 'for_each_child_of_node' 157 | - 'for_each_clear_bit' 158 | - 'for_each_clear_bit_from' 159 | - 'for_each_cmsghdr' 160 | - 'for_each_compatible_node' 161 | - 'for_each_component_dais' 162 | - 'for_each_component_dais_safe' 163 | - 'for_each_comp_order' 164 | - 'for_each_console' 165 | - 'for_each_cpu' 166 | - 'for_each_cpu_and' 167 | - 'for_each_cpu_not' 168 | - 'for_each_cpu_wrap' 169 | - 'for_each_dapm_widgets' 170 | - 'for_each_dev_addr' 171 | - 'for_each_dev_scope' 172 | - 'for_each_displayid_db' 173 | - 'for_each_dma_cap_mask' 174 | - 'for_each_dpcm_be' 175 | - 'for_each_dpcm_be_rollback' 176 | - 'for_each_dpcm_be_safe' 177 | - 'for_each_dpcm_fe' 178 | - 'for_each_drhd_unit' 179 | - 'for_each_dss_dev' 180 | - 'for_each_efi_memory_desc' 181 | - 'for_each_efi_memory_desc_in_map' 182 | - 'for_each_element' 183 | - 'for_each_element_extid' 184 | - 'for_each_element_id' 185 | - 'for_each_endpoint_of_node' 186 | - 'for_each_evictable_lru' 187 | - 'for_each_fib6_node_rt_rcu' 188 | - 'for_each_fib6_walker_rt' 189 | - 'for_each_free_mem_pfn_range_in_zone' 190 | - 'for_each_free_mem_pfn_range_in_zone_from' 191 | - 'for_each_free_mem_range' 192 | - 'for_each_free_mem_range_reverse' 193 | - 'for_each_func_rsrc' 194 | - 'for_each_hstate' 195 | - 'for_each_if' 196 | - 'for_each_iommu' 197 | - 'for_each_ip_tunnel_rcu' 198 | - 'for_each_irq_nr' 199 | - 'for_each_link_codecs' 200 | - 'for_each_link_cpus' 201 | - 'for_each_link_platforms' 202 | - 'for_each_lru' 203 | - 'for_each_matching_node' 204 | - 'for_each_matching_node_and_match' 205 | - 'for_each_member' 206 | - 'for_each_mem_region' 207 | - 'for_each_memblock_type' 208 | - 'for_each_memcg_cache_index' 209 | - 'for_each_mem_pfn_range' 210 | - '__for_each_mem_range' 211 | - 'for_each_mem_range' 212 | - '__for_each_mem_range_rev' 213 | - 'for_each_mem_range_rev' 214 | - 'for_each_migratetype_order' 215 | - 'for_each_msi_entry' 216 | - 'for_each_msi_entry_safe' 217 | - 'for_each_net' 218 | - 'for_each_net_continue_reverse' 219 | - 'for_each_netdev' 220 | - 'for_each_netdev_continue' 221 | - 'for_each_netdev_continue_rcu' 222 | - 'for_each_netdev_continue_reverse' 223 | - 'for_each_netdev_feature' 224 | - 'for_each_netdev_in_bond_rcu' 225 | - 'for_each_netdev_rcu' 226 | - 'for_each_netdev_reverse' 227 | - 'for_each_netdev_safe' 228 | - 'for_each_net_rcu' 229 | - 'for_each_new_connector_in_state' 230 | - 'for_each_new_crtc_in_state' 231 | - 'for_each_new_mst_mgr_in_state' 232 | - 'for_each_new_plane_in_state' 233 | - 'for_each_new_private_obj_in_state' 234 | - 'for_each_node' 235 | - 'for_each_node_by_name' 236 | - 'for_each_node_by_type' 237 | - 'for_each_node_mask' 238 | - 'for_each_node_state' 239 | - 'for_each_node_with_cpus' 240 | - 'for_each_node_with_property' 241 | - 'for_each_nonreserved_multicast_dest_pgid' 242 | - 'for_each_of_allnodes' 243 | - 'for_each_of_allnodes_from' 244 | - 'for_each_of_cpu_node' 245 | - 'for_each_of_pci_range' 246 | - 'for_each_old_connector_in_state' 247 | - 'for_each_old_crtc_in_state' 248 | - 'for_each_old_mst_mgr_in_state' 249 | - 'for_each_oldnew_connector_in_state' 250 | - 'for_each_oldnew_crtc_in_state' 251 | - 'for_each_oldnew_mst_mgr_in_state' 252 | - 'for_each_oldnew_plane_in_state' 253 | - 'for_each_oldnew_plane_in_state_reverse' 254 | - 'for_each_oldnew_private_obj_in_state' 255 | - 'for_each_old_plane_in_state' 256 | - 'for_each_old_private_obj_in_state' 257 | - 'for_each_online_cpu' 258 | - 'for_each_online_node' 259 | - 'for_each_online_pgdat' 260 | - 'for_each_pci_bridge' 261 | - 'for_each_pci_dev' 262 | - 'for_each_pci_msi_entry' 263 | - 'for_each_pcm_streams' 264 | - 'for_each_physmem_range' 265 | - 'for_each_populated_zone' 266 | - 'for_each_possible_cpu' 267 | - 'for_each_present_cpu' 268 | - 'for_each_prime_number' 269 | - 'for_each_prime_number_from' 270 | - 'for_each_process' 271 | - 'for_each_process_thread' 272 | - 'for_each_property_of_node' 273 | - 'for_each_registered_fb' 274 | - 'for_each_requested_gpio' 275 | - 'for_each_requested_gpio_in_range' 276 | - 'for_each_reserved_mem_range' 277 | - 'for_each_reserved_mem_region' 278 | - 'for_each_rtd_codec_dais' 279 | - 'for_each_rtd_codec_dais_rollback' 280 | - 'for_each_rtd_components' 281 | - 'for_each_rtd_cpu_dais' 282 | - 'for_each_rtd_cpu_dais_rollback' 283 | - 'for_each_rtd_dais' 284 | - 'for_each_set_bit' 285 | - 'for_each_set_bit_from' 286 | - 'for_each_set_clump8' 287 | - 'for_each_sg' 288 | - 'for_each_sg_dma_page' 289 | - 'for_each_sg_page' 290 | - 'for_each_sgtable_dma_page' 291 | - 'for_each_sgtable_dma_sg' 292 | - 'for_each_sgtable_page' 293 | - 'for_each_sgtable_sg' 294 | - 'for_each_sibling_event' 295 | - 'for_each_subelement' 296 | - 'for_each_subelement_extid' 297 | - 'for_each_subelement_id' 298 | - '__for_each_thread' 299 | - 'for_each_thread' 300 | - 'for_each_unicast_dest_pgid' 301 | - 'for_each_wakeup_source' 302 | - 'for_each_zone' 303 | - 'for_each_zone_zonelist' 304 | - 'for_each_zone_zonelist_nodemask' 305 | - 'fwnode_for_each_available_child_node' 306 | - 'fwnode_for_each_child_node' 307 | - 'fwnode_graph_for_each_endpoint' 308 | - 'gadget_for_each_ep' 309 | - 'genradix_for_each' 310 | - 'genradix_for_each_from' 311 | - 'hash_for_each' 312 | - 'hash_for_each_possible' 313 | - 'hash_for_each_possible_rcu' 314 | - 'hash_for_each_possible_rcu_notrace' 315 | - 'hash_for_each_possible_safe' 316 | - 'hash_for_each_rcu' 317 | - 'hash_for_each_safe' 318 | - 'hctx_for_each_ctx' 319 | - 'hlist_bl_for_each_entry' 320 | - 'hlist_bl_for_each_entry_rcu' 321 | - 'hlist_bl_for_each_entry_safe' 322 | - 'hlist_for_each' 323 | - 'hlist_for_each_entry' 324 | - 'hlist_for_each_entry_continue' 325 | - 'hlist_for_each_entry_continue_rcu' 326 | - 'hlist_for_each_entry_continue_rcu_bh' 327 | - 'hlist_for_each_entry_from' 328 | - 'hlist_for_each_entry_from_rcu' 329 | - 'hlist_for_each_entry_rcu' 330 | - 'hlist_for_each_entry_rcu_bh' 331 | - 'hlist_for_each_entry_rcu_notrace' 332 | - 'hlist_for_each_entry_safe' 333 | - '__hlist_for_each_rcu' 334 | - 'hlist_for_each_safe' 335 | - 'hlist_nulls_for_each_entry' 336 | - 'hlist_nulls_for_each_entry_from' 337 | - 'hlist_nulls_for_each_entry_rcu' 338 | - 'hlist_nulls_for_each_entry_safe' 339 | - 'i3c_bus_for_each_i2cdev' 340 | - 'i3c_bus_for_each_i3cdev' 341 | - 'ide_host_for_each_port' 342 | - 'ide_port_for_each_dev' 343 | - 'ide_port_for_each_present_dev' 344 | - 'idr_for_each_entry' 345 | - 'idr_for_each_entry_continue' 346 | - 'idr_for_each_entry_continue_ul' 347 | - 'idr_for_each_entry_ul' 348 | - 'in_dev_for_each_ifa_rcu' 349 | - 'in_dev_for_each_ifa_rtnl' 350 | - 'inet_bind_bucket_for_each' 351 | - 'inet_lhash2_for_each_icsk_rcu' 352 | - 'key_for_each' 353 | - 'key_for_each_safe' 354 | - 'klp_for_each_func' 355 | - 'klp_for_each_func_safe' 356 | - 'klp_for_each_func_static' 357 | - 'klp_for_each_object' 358 | - 'klp_for_each_object_safe' 359 | - 'klp_for_each_object_static' 360 | - 'kunit_suite_for_each_test_case' 361 | - 'kvm_for_each_memslot' 362 | - 'kvm_for_each_vcpu' 363 | - 'list_for_each' 364 | - 'list_for_each_codec' 365 | - 'list_for_each_codec_safe' 366 | - 'list_for_each_continue' 367 | - 'list_for_each_entry' 368 | - 'list_for_each_entry_continue' 369 | - 'list_for_each_entry_continue_rcu' 370 | - 'list_for_each_entry_continue_reverse' 371 | - 'list_for_each_entry_from' 372 | - 'list_for_each_entry_from_rcu' 373 | - 'list_for_each_entry_from_reverse' 374 | - 'list_for_each_entry_lockless' 375 | - 'list_for_each_entry_rcu' 376 | - 'list_for_each_entry_reverse' 377 | - 'list_for_each_entry_safe' 378 | - 'list_for_each_entry_safe_continue' 379 | - 'list_for_each_entry_safe_from' 380 | - 'list_for_each_entry_safe_reverse' 381 | - 'list_for_each_prev' 382 | - 'list_for_each_prev_safe' 383 | - 'list_for_each_safe' 384 | - 'llist_for_each' 385 | - 'llist_for_each_entry' 386 | - 'llist_for_each_entry_safe' 387 | - 'llist_for_each_safe' 388 | - 'mci_for_each_dimm' 389 | - 'media_device_for_each_entity' 390 | - 'media_device_for_each_intf' 391 | - 'media_device_for_each_link' 392 | - 'media_device_for_each_pad' 393 | - 'nanddev_io_for_each_page' 394 | - 'netdev_for_each_lower_dev' 395 | - 'netdev_for_each_lower_private' 396 | - 'netdev_for_each_lower_private_rcu' 397 | - 'netdev_for_each_mc_addr' 398 | - 'netdev_for_each_uc_addr' 399 | - 'netdev_for_each_upper_dev_rcu' 400 | - 'netdev_hw_addr_list_for_each' 401 | - 'nft_rule_for_each_expr' 402 | - 'nla_for_each_attr' 403 | - 'nla_for_each_nested' 404 | - 'nlmsg_for_each_attr' 405 | - 'nlmsg_for_each_msg' 406 | - 'nr_neigh_for_each' 407 | - 'nr_neigh_for_each_safe' 408 | - 'nr_node_for_each' 409 | - 'nr_node_for_each_safe' 410 | - 'of_for_each_phandle' 411 | - 'of_property_for_each_string' 412 | - 'of_property_for_each_u32' 413 | - 'pci_bus_for_each_resource' 414 | - 'pcm_for_each_format' 415 | - 'ping_portaddr_for_each_entry' 416 | - 'plist_for_each' 417 | - 'plist_for_each_continue' 418 | - 'plist_for_each_entry' 419 | - 'plist_for_each_entry_continue' 420 | - 'plist_for_each_entry_safe' 421 | - 'plist_for_each_safe' 422 | - 'pnp_for_each_card' 423 | - 'pnp_for_each_dev' 424 | - 'protocol_for_each_card' 425 | - 'protocol_for_each_dev' 426 | - 'queue_for_each_hw_ctx' 427 | - 'radix_tree_for_each_slot' 428 | - 'radix_tree_for_each_tagged' 429 | - 'rbtree_postorder_for_each_entry_safe' 430 | - 'rdma_for_each_block' 431 | - 'rdma_for_each_port' 432 | - 'rdma_umem_for_each_dma_block' 433 | - 'resource_list_for_each_entry' 434 | - 'resource_list_for_each_entry_safe' 435 | - 'rhl_for_each_entry_rcu' 436 | - 'rhl_for_each_rcu' 437 | - 'rht_for_each' 438 | - 'rht_for_each_entry' 439 | - 'rht_for_each_entry_from' 440 | - 'rht_for_each_entry_rcu' 441 | - 'rht_for_each_entry_rcu_from' 442 | - 'rht_for_each_entry_safe' 443 | - 'rht_for_each_from' 444 | - 'rht_for_each_rcu' 445 | - 'rht_for_each_rcu_from' 446 | - '__rq_for_each_bio' 447 | - 'rq_for_each_bvec' 448 | - 'rq_for_each_segment' 449 | - 'scsi_for_each_prot_sg' 450 | - 'scsi_for_each_sg' 451 | - 'sctp_for_each_hentry' 452 | - 'sctp_skb_for_each' 453 | - 'shdma_for_each_chan' 454 | - '__shost_for_each_device' 455 | - 'shost_for_each_device' 456 | - 'sk_for_each' 457 | - 'sk_for_each_bound' 458 | - 'sk_for_each_entry_offset_rcu' 459 | - 'sk_for_each_from' 460 | - 'sk_for_each_rcu' 461 | - 'sk_for_each_safe' 462 | - 'sk_nulls_for_each' 463 | - 'sk_nulls_for_each_from' 464 | - 'sk_nulls_for_each_rcu' 465 | - 'snd_array_for_each' 466 | - 'snd_pcm_group_for_each_entry' 467 | - 'snd_soc_dapm_widget_for_each_path' 468 | - 'snd_soc_dapm_widget_for_each_path_safe' 469 | - 'snd_soc_dapm_widget_for_each_sink_path' 470 | - 'snd_soc_dapm_widget_for_each_source_path' 471 | - 'tb_property_for_each' 472 | - 'tcf_exts_for_each_action' 473 | - 'udp_portaddr_for_each_entry' 474 | - 'udp_portaddr_for_each_entry_rcu' 475 | - 'usb_hub_for_each_child' 476 | - 'v4l2_device_for_each_subdev' 477 | - 'v4l2_m2m_for_each_dst_buf' 478 | - 'v4l2_m2m_for_each_dst_buf_safe' 479 | - 'v4l2_m2m_for_each_src_buf' 480 | - 'v4l2_m2m_for_each_src_buf_safe' 481 | - 'virtio_device_for_each_vq' 482 | - 'while_for_each_ftrace_op' 483 | - 'xa_for_each' 484 | - 'xa_for_each_marked' 485 | - 'xa_for_each_range' 486 | - 'xa_for_each_start' 487 | - 'xas_for_each' 488 | - 'xas_for_each_conflict' 489 | - 'xas_for_each_marked' 490 | - 'xbc_array_for_each_value' 491 | - 'xbc_for_each_key_value' 492 | - 'xbc_node_for_each_array_value' 493 | - 'xbc_node_for_each_child' 494 | - 'xbc_node_for_each_key_value' 495 | - 'zorro_for_each_dev' 496 | 497 | #IncludeBlocks: Preserve # Unknown to clang-format-5.0 498 | IncludeCategories: 499 | - Regex: '.*' 500 | Priority: 1 501 | IncludeIsMainRegex: '(Test)?$' 502 | IndentCaseLabels: false 503 | #IndentPPDirectives: None # Unknown to clang-format-5.0 504 | IndentWidth: 8 505 | IndentWrappedFunctionNames: false 506 | JavaScriptQuotes: Leave 507 | JavaScriptWrapImports: true 508 | KeepEmptyLinesAtTheStartOfBlocks: false 509 | MacroBlockBegin: '' 510 | MacroBlockEnd: '' 511 | MaxEmptyLinesToKeep: 1 512 | NamespaceIndentation: None 513 | #ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0 514 | ObjCBlockIndentWidth: 8 515 | ObjCSpaceAfterProperty: true 516 | ObjCSpaceBeforeProtocolList: true 517 | 518 | # Taken from git's rules 519 | #PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0 520 | PenaltyBreakBeforeFirstCallParameter: 30 521 | PenaltyBreakComment: 10 522 | PenaltyBreakFirstLessLess: 0 523 | PenaltyBreakString: 10 524 | PenaltyExcessCharacter: 100 525 | PenaltyReturnTypeOnItsOwnLine: 60 526 | 527 | PointerAlignment: Right 528 | ReflowComments: false 529 | SortIncludes: false 530 | #SortUsingDeclarations: false # Unknown to clang-format-4.0 531 | SpaceAfterCStyleCast: false 532 | SpaceAfterTemplateKeyword: true 533 | SpaceBeforeAssignmentOperators: true 534 | #SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0 535 | #SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0 536 | SpaceBeforeParens: ControlStatements 537 | #SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0 538 | SpaceInEmptyParentheses: false 539 | SpacesBeforeTrailingComments: 1 540 | SpacesInAngles: false 541 | SpacesInContainerLiterals: false 542 | SpacesInCStyleCastParentheses: false 543 | SpacesInParentheses: false 544 | SpacesInSquareBrackets: false 545 | Standard: Cpp03 546 | TabWidth: 8 547 | UseTab: Always 548 | ... 549 | -------------------------------------------------------------------------------- /kernel/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | # Copyright (C) 2016 Peter Desnoyers 3 | # Copyright (C) 2020-2021 Vojtech Aschenbrenner 4 | 5 | CONFIG_MODULE_SIG=n 6 | 7 | module := disbd 8 | obj-m := dm-$(module).o 9 | 10 | KDIR := /lib/modules/5.0.0-13-generic/build 11 | 12 | PWD := $(shell pwd) 13 | CFLAGS_dm-$(module).o += -DDEBUG -ggdb3 -Wno-unused-function -Wno-declaration-after-statement -Og 14 | 15 | all: 16 | $(MAKE) -C $(KDIR) M=$(PWD) modules 17 | 18 | clean: 19 | $(MAKE) -C $(KDIR) M=$(PWD) clean 20 | -------------------------------------------------------------------------------- /kernel/dm-disbd.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2016 Peter Desnoyers 3 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include "dm-disbd.h" 26 | 27 | #define DM_MSG_PREFIX "dis" 28 | 29 | struct write_record { 30 | struct list_head list; 31 | struct disbd *dis; 32 | sector_t lba; 33 | sector_t pba; 34 | int len; 35 | }; 36 | 37 | struct disbd { 38 | spinlock_t lock; 39 | spinlock_t rb_r_lock; 40 | spinlock_t rb_w_lock; 41 | struct rb_root rb_r; 42 | struct rb_root rb_w; 43 | 44 | int trim_me; /* 0..7 */ 45 | 46 | struct bio_list pending_reads; 47 | wait_queue_head_t read_wait; 48 | struct bio_list faulted_reads; 49 | 50 | struct bio_list undone_writes; 51 | struct list_head done_writes; 52 | wait_queue_head_t write_wait; 53 | int done_count; 54 | sector_t done_sectors; 55 | sector_t prev_done_sectors; 56 | sector_t max_sectors; 57 | atomic_t n_undone; 58 | atomic_t n_done; 59 | atomic_t seq; 60 | 61 | mempool_t *extent_pool; 62 | mempool_t *page_pool; 63 | struct bio_set bs; 64 | 65 | struct dm_dev *dev; 66 | 67 | char nodename[32]; 68 | struct miscdevice misc; 69 | 70 | sector_t base; 71 | sector_t bound; 72 | sector_t frontier; 73 | 74 | struct dm_target *ti; 75 | }; 76 | 77 | /* total size = 48 bytes (64b). fits in 1 cache line 78 | * for 32b is 32 bytes, fits in ARM cache line 79 | */ 80 | struct extent { 81 | struct rb_node rb; /* 24 bytes */ 82 | sector_t lba; /* 512B LBA (8) */ 83 | sector_t pba; /* (8) */ 84 | int32_t len; /* (4) */ 85 | }; 86 | 87 | static struct kmem_cache *_extent_cache; 88 | 89 | static void extent_init(struct extent *e, sector_t lba, sector_t pba, sector_t len) 90 | { 91 | memset(e, 0, sizeof(*e)); 92 | e->lba = lba; 93 | e->pba = pba; 94 | e->len = len; 95 | } 96 | 97 | #define MIN_EXTENTS 16 98 | #define MIN_POOL_PAGES 16 99 | #define MIN_POOL_IOS 16 100 | 101 | /************** Extent map management *****************/ 102 | 103 | enum map_type { MAP_READ = 1, MAP_WRITE = 2 }; 104 | 105 | /* find a map entry containing 'lba' or the next higher entry. 106 | * see Documentation/rbtree.txt 107 | */ 108 | static struct extent *_dis_rb_geq(struct rb_root *root, off_t lba) 109 | { 110 | struct rb_node *node = root->rb_node; /* top of the tree */ 111 | struct extent *higher = NULL; 112 | 113 | while (node) { 114 | struct extent *e = container_of(node, struct extent, rb); 115 | if (e->lba >= lba && (!higher || e->lba < higher->lba)) { 116 | higher = e; 117 | } 118 | if (lba < e->lba) { 119 | node = node->rb_left; 120 | } else if (lba >= e->lba + e->len) { 121 | node = node->rb_right; 122 | } else { 123 | return e; 124 | } 125 | } 126 | return higher; 127 | } 128 | 129 | static struct extent *dis_rb_geq(struct disbd *dis, off_t lba, enum map_type map) 130 | { 131 | struct extent *e = NULL; 132 | unsigned long flags; 133 | 134 | struct rb_root *root = (map == MAP_WRITE) ? &dis->rb_w : &dis->rb_r; 135 | spinlock_t *rb_lock = (map == MAP_WRITE) ? &dis->rb_w_lock : &dis->rb_r_lock; 136 | 137 | spin_lock_irqsave(rb_lock, flags); 138 | e = _dis_rb_geq(root, lba); 139 | spin_unlock_irqrestore(rb_lock, flags); 140 | 141 | return e; 142 | } 143 | 144 | int _dis_verbose; 145 | 146 | /* must hold rb_lock */ 147 | static void dis_rb_insert(struct disbd *dis, struct extent *new, enum map_type map) 148 | { 149 | struct rb_root *root = (map == MAP_WRITE) ? &dis->rb_w : &dis->rb_r; 150 | 151 | struct rb_node **link = &root->rb_node, *parent = NULL; 152 | struct extent *e = NULL; 153 | 154 | RB_CLEAR_NODE(&new->rb); 155 | 156 | /* Go to the bottom of the tree */ 157 | while (*link) { 158 | parent = *link; 159 | e = container_of(parent, struct extent, rb); 160 | if (new->lba < e->lba) { 161 | link = &(*link)->rb_left; 162 | } else { 163 | link = &(*link)->rb_right; 164 | } 165 | } 166 | /* Put the new node there */ 167 | rb_link_node(&new->rb, parent, link); 168 | rb_insert_color(&new->rb, root); 169 | } 170 | 171 | /* must hold rb_lock */ 172 | static void dis_rb_remove(struct disbd *dis, struct extent *e, enum map_type map) 173 | { 174 | struct rb_root *root = (map == MAP_WRITE) ? &dis->rb_w : &dis->rb_r; 175 | rb_erase(&e->rb, root); 176 | } 177 | 178 | /* must hold rb_lock */ 179 | static struct extent *_dis_rb_next(struct extent *e) 180 | { 181 | struct rb_node *node = rb_next(&e->rb); 182 | return (node == NULL) ? NULL : container_of(node, struct extent, rb); 183 | } 184 | 185 | /* 186 | static struct extent *dis_rb_next(struct disbd *dis, struct extent *e) 187 | { 188 | unsigned long flags; 189 | spin_lock_irqsave(&dis->rb_lock, flags); 190 | e = _dis_rb_next(e); 191 | spin_unlock_irqrestore(&dis->rb_lock, flags); 192 | return e; 193 | } 194 | */ 195 | 196 | /* Update mapping. Removes any total overlaps, edits any partial 197 | * overlaps, adds new extent to map. 198 | */ 199 | static struct extent *dis_update_range(struct disbd *dis, sector_t lba, sector_t pba, sector_t len, 200 | enum map_type map) 201 | { 202 | struct extent *e = NULL, *_new = NULL, *_new2 = NULL; 203 | unsigned long flags; 204 | struct rb_root *root = (map == MAP_WRITE) ? &dis->rb_w : &dis->rb_r; 205 | 206 | //DMINFO("update range %llu %llu %llu", (u64)lba, (u64)pba, (u64)len); 207 | BUG_ON(len == 0); 208 | 209 | if (unlikely(!(_new = mempool_alloc(dis->extent_pool, GFP_NOWAIT)))) 210 | return NULL; 211 | _new2 = mempool_alloc(dis->extent_pool, GFP_NOWAIT); 212 | 213 | spinlock_t *rb_lock = (map == MAP_WRITE) ? &dis->rb_w_lock : &dis->rb_r_lock; 214 | 215 | spin_lock_irqsave(rb_lock, flags); 216 | e = _dis_rb_geq(root, lba); 217 | 218 | if (e != NULL) { 219 | /* [----------------------] e new new2 220 | * [++++++] -> [-----][+++++][--------] 221 | */ 222 | if (e->lba < lba && e->lba + e->len > lba + len) { 223 | sector_t new_lba = lba + len; 224 | sector_t new_len = e->lba + e->len - new_lba; 225 | sector_t new_pba = e->pba + (e->len - new_len); 226 | 227 | if (_new2 == NULL) 228 | goto fail; 229 | extent_init(_new2, lba + len, new_pba, new_len); 230 | e->len = lba - e->lba; /* do this *before* inserting below */ 231 | dis_rb_insert(dis, _new2, map); 232 | e = _new2; 233 | _new2 = NULL; 234 | } 235 | /* [------------] 236 | * [+++++++++] -> [------][+++++++++] 237 | */ 238 | else if (e->lba < lba) { 239 | e->len = lba - e->lba; 240 | if (e->len == 0) { 241 | DMERR("zero-length extent"); 242 | goto fail; 243 | } 244 | e = _dis_rb_next(e); 245 | } 246 | /* [------] 247 | * [+++++++++++++++] -> [+++++++++++++++] 248 | */ 249 | while (e != NULL && e->lba + e->len <= lba + len) { 250 | struct extent *tmp = _dis_rb_next(e); 251 | dis_rb_remove(dis, e, map); 252 | mempool_free(e, dis->extent_pool); 253 | e = tmp; 254 | } 255 | /* [------] 256 | * [+++++++++] -> [++++++++++][---] 257 | */ 258 | if (e != NULL && lba + len > e->lba) { 259 | sector_t n = (lba + len) - e->lba; 260 | e->lba += n; 261 | e->pba += n; 262 | e->len -= n; 263 | } 264 | } 265 | 266 | /* TRIM indicated by pba = -1 */ 267 | if (pba != -1) { 268 | extent_init(_new, lba, pba, len); 269 | dis_rb_insert(dis, _new, map); 270 | } 271 | spin_unlock_irqrestore(rb_lock, flags); 272 | if (_new2 != NULL) { 273 | mempool_free(_new2, dis->extent_pool); 274 | } 275 | return NULL; 276 | 277 | fail: 278 | spin_unlock_irqrestore(rb_lock, flags); 279 | DMERR("could not allocate extent"); 280 | if (_new) 281 | mempool_free(_new, dis->extent_pool); 282 | if (_new2) 283 | mempool_free(_new2, dis->extent_pool); 284 | return NULL; 285 | } 286 | 287 | /************** Received I/O handling *****************/ 288 | 289 | static void queue_read(struct disbd *dis, struct bio *bio) 290 | { 291 | unsigned long flags; 292 | 293 | //DMINFO("%llx (%llu %d) -> pending_reads", (u64)bio, (u64)bio->bi_iter.bi_sector, bio_sectors(bio)); 294 | 295 | spin_lock_irqsave(&dis->lock, flags); 296 | bio_list_add(&dis->pending_reads, bio); 297 | spin_unlock_irqrestore(&dis->lock, flags); 298 | wake_up(&dis->read_wait); 299 | } 300 | 301 | static int split_read_io(struct disbd *dis, struct bio *bio) 302 | { 303 | struct bio *split = NULL; 304 | //DMINFO("%llx read %llu %d", (u64)bio, (u64)bio->bi_iter.bi_sector, bio_sectors(bio)); 305 | 306 | do { 307 | sector_t sector = bio->bi_iter.bi_sector; 308 | unsigned sectors = bio_sectors(bio); 309 | struct extent *ew = dis_rb_geq(dis, sector, MAP_WRITE); 310 | struct extent *er = dis_rb_geq(dis, sector, MAP_READ); 311 | 312 | struct extent *e = NULL; 313 | 314 | // Selection between Write and Read extent map 315 | // 1. If the extent is present only in one map => choose it 316 | if (er == NULL || ew == NULL) { 317 | e = (ew != NULL) ? ew : er; 318 | goto map_selected; 319 | } 320 | 321 | // 2. If the extent map is present in both Read and Write Maps => prefer Write Map 322 | if (ew->lba <= sector) { 323 | e = ew; 324 | } else if (er->lba <= sector) { 325 | e = er; 326 | } else if (ew->lba < sector + sectors) { 327 | e = ew; 328 | } else if (er->lba < sector + sectors) { 329 | e = er; 330 | } 331 | 332 | struct extent tmp; 333 | if (e == er && er->lba + er->len >= ew->lba) { 334 | tmp = *er; 335 | tmp.len = ew->lba - er->lba; 336 | e = &tmp; 337 | } 338 | map_selected: 339 | 340 | /* [----bio-----] [eeeeeee] - no map at all, fault it */ 341 | if (e == NULL || e->lba >= sector + sectors) { 342 | queue_read(dis, bio); 343 | return DM_MAPIO_SUBMITTED; 344 | } 345 | 346 | /* . [eeeeeeeeee... 347 | . [---------bio------] - bio prefix not mapped */ 348 | else if (sector < e->lba) { 349 | sector_t overlap = e->lba - sector; 350 | split = bio_split(bio, overlap, GFP_NOIO, &fs_bio_set); 351 | //DMINFO("%llx split %llu", (u64)bio, (u64)overlap); 352 | bio_chain(split, bio); 353 | split_read_io(dis, split); 354 | } 355 | 356 | /* . [eeeeeeeeeeee] 357 | . [---------bio------] - bio prefix mapped - submit 358 | . ^ overlap ^ */ 359 | else { 360 | sector_t overlap = e->lba + e->len - sector; 361 | if (overlap < sectors) { 362 | sectors = overlap; 363 | split = bio_split(bio, sectors, GFP_NOIO, &fs_bio_set); 364 | //DMINFO("%llx split2 %llu", (u64)bio, (u64)overlap); 365 | bio_chain(split, bio); 366 | } else { 367 | split = bio; 368 | } 369 | 370 | sector = e->pba + sector - e->lba; /* nonsense if zerofill */ 371 | split->bi_iter.bi_sector = sector; 372 | //DMINFO("%llu rmap %llu", (u64)split, sector); 373 | bio_set_dev(split, dis->dev->bdev); 374 | generic_make_request(split); 375 | } 376 | } while (split != bio); 377 | 378 | return DM_MAPIO_SUBMITTED; 379 | } 380 | 381 | /* TODO: not sure how to get the map update properly synchronized. 382 | * We have to avoid the case where following reads get the new PBA, but 383 | * pass the write to that PBA and get bogus data. (it's OK to get the 384 | * old data for that *LBA* until we complete a write, but not the arbitrary 385 | * contents of the new PBA) 386 | * Unfortunately we can't just use bio_chain to link the bios together, 387 | * because the device mapper framework uses endio. 388 | * 389 | * possible solution: 390 | * - clone the bio and submit 391 | * - on cloned bio endio: 392 | * - update map 393 | * - complete parent bio successfully 394 | * 395 | * with this solution the header endio just has to free the bio and page 396 | */ 397 | 398 | /* endio function for write header. This is a total hack at the moment - 399 | * bi_private points to a struct write_record, which has the extent info 400 | * to put in the write map; doesn't handled batched writes 401 | */ 402 | static void dis_hdr_endio(struct bio *bio) 403 | { 404 | struct bio_vec *bv; 405 | struct write_record *rec = bio->bi_private; 406 | struct disbd *dis = rec->dis; 407 | //struct bvec_iter_all iter; -- different kernel version 408 | int i; 409 | 410 | //DMINFO("%llu hdr endio %llu %d : %llu %llu %d", 411 | //(u64)bio, (u64)bio->bi_iter.bi_sector, bio_sectors(bio), 412 | //(u64)rec->lba, (u64)rec->pba, (int)rec->len); 413 | //bio_for_each_segment_all(bv, bio, iter) { 414 | 415 | if (rec->lba != -1) 416 | dis_update_range(dis, rec->lba, rec->pba, rec->len, MAP_WRITE); 417 | 418 | bio_for_each_segment_all (bv, bio, i) { 419 | mempool_free(bv->bv_page, dis->page_pool); 420 | bv->bv_page = NULL; 421 | } 422 | 423 | bio_put(bio); 424 | if (rec->lba != -1) { 425 | unsigned long flags; 426 | spin_lock_irqsave(&dis->lock, flags); 427 | list_add_tail(&rec->list, &dis->done_writes); 428 | atomic_inc(&dis->n_done); 429 | spin_unlock_irqrestore(&dis->lock, flags); 430 | wake_up(&dis->write_wait); 431 | } else 432 | kfree(rec); 433 | } 434 | 435 | /* note that ppage returns a pointer to the last page in the bio - 436 | * i.e. the only page if it's a 1-page bio for header/trailer 437 | * TODO overly complicated - we only need 1 page 438 | */ 439 | static struct bio *dis_alloc_bio(struct disbd *dis, unsigned sectors, struct page **ppage) 440 | { 441 | int i, val, remainder, npages; 442 | struct bio_vec *bv = NULL; 443 | struct bio *bio; 444 | struct page *page; 445 | //struct bvec_iter_all iter; 446 | 447 | npages = sectors / 8; 448 | remainder = (sectors * 512) - npages * PAGE_SIZE; 449 | 450 | if (!(bio = bio_alloc_bioset(GFP_NOIO, npages + (remainder > 0), &dis->bs))) 451 | goto fail; 452 | for (i = 0; i < npages; i++) { 453 | if (!(page = mempool_alloc(dis->page_pool, GFP_NOIO))) 454 | goto fail; 455 | val = bio_add_page(bio, page, PAGE_SIZE, 0); 456 | if (ppage != NULL) 457 | *ppage = page; 458 | } 459 | if (remainder > 0) { 460 | if (!(page = mempool_alloc(dis->page_pool, GFP_NOIO))) 461 | goto fail; 462 | if (ppage != NULL) 463 | *ppage = page; 464 | val = bio_add_page(bio, page, remainder, 0); 465 | } 466 | return bio; 467 | 468 | fail: 469 | printk(KERN_INFO "dis_alloc_bio: FAIL (%d pages + %d)\n", npages, sectors % 8); 470 | WARN_ON(1); 471 | if (bio != NULL) { 472 | //bio_for_each_segment_all(bv, bio, iter) { 473 | bio_for_each_segment_all (bv, bio, i) { 474 | mempool_free(bv->bv_page, dis->page_pool); 475 | } 476 | bio_put(bio); 477 | } 478 | return NULL; 479 | } 480 | 481 | /* Hmm. We could aggregate writes by having a window of W outstanding 482 | * writes to the device, then queueing any writes that arrive before one 483 | * of them completes and submitting them all together when the completion 484 | * comes in. 485 | */ 486 | 487 | /* create a bio for a journal header 488 | */ 489 | static struct bio *alloc_header(struct disbd *dis, struct dis_header **hdr) 490 | { 491 | struct page *page; 492 | struct bio *bio = dis_alloc_bio(dis, 8, &page); 493 | if (bio == NULL) 494 | return NULL; 495 | bio_add_page(bio, page, DIS_HDR_SIZE, 0); 496 | bio->bi_end_io = dis_hdr_endio; 497 | bio_set_dev(bio, dis->dev->bdev); 498 | bio->bi_opf = WRITE; 499 | 500 | *hdr = page_address(page); 501 | memset(*hdr, 0, DIS_HDR_SIZE); 502 | return bio; 503 | } 504 | 505 | static void sign_header(struct dis_header *h) 506 | { 507 | h->h.crc32 = crc32_le(0, (u8 *)h, DIS_HDR_SIZE); 508 | } 509 | 510 | #define NO_LBA 0xFFFFFFFFFFUL 511 | 512 | static sector_t maybe_wrap(struct disbd *dis, int sectors) 513 | { 514 | sector_t lba, wrap_lba = NO_LBA; 515 | unsigned long flags; 516 | 517 | sectors = round_up(sectors, 8); 518 | 519 | spin_lock_irqsave(&dis->lock, flags); 520 | if (dis->frontier + sectors + 8 >= dis->bound) { 521 | wrap_lba = dis->frontier; 522 | dis->frontier = dis->base; 523 | //DMINFO("wrapping at %d (%d)", (int)wrap_lba, sectors); 524 | } 525 | lba = dis->frontier; 526 | dis->frontier += (sectors + 8); 527 | spin_unlock_irqrestore(&dis->lock, flags); 528 | 529 | if (wrap_lba != NO_LBA) { 530 | struct dis_header *h; 531 | struct bio *bio = alloc_header(dis, &h); 532 | if (bio == NULL) 533 | goto bail; 534 | h->h = (struct _disheader){ .magic = DIS_HDR_MAGIC, 535 | .seq = atomic_inc_return(&dis->seq), 536 | .n_extents = 0, 537 | .n_sectors = dis->bound - wrap_lba }; 538 | sign_header(h); 539 | bio->bi_iter.bi_sector = wrap_lba; 540 | 541 | /* god this is a hack */ 542 | struct write_record *rec = kmalloc(sizeof(*rec), GFP_NOIO); 543 | rec->lba = -1; 544 | rec->dis = dis; 545 | bio->bi_private = rec; 546 | 547 | generic_make_request(bio); 548 | } 549 | 550 | /* Divide the log in 8ths (octants). 551 | * - dis->trim_me is the number of the oldest non-empty octant. 552 | * - when the write frontier enters the octant before that, we trim 553 | * the next octant. 554 | * thus trim_me starts at 0, frontier starts at 0. After writing 0..6, 555 | * the write frontier crosses over into octant 7 and we trim octant 0. 556 | */ 557 | int span = dis->bound - dis->base; 558 | int octant = (dis->frontier - dis->base) * 8 / span; 559 | 560 | if (((octant + 1) % 8) == dis->trim_me) { 561 | struct extent *e, *tmp; 562 | sector_t low = dis->base + dis->trim_me * span / 8; 563 | sector_t high = low + span / 8; 564 | 565 | spin_lock_irqsave(&dis->rb_w_lock, flags); 566 | spin_lock(&dis->rb_r_lock); 567 | for (e = _dis_rb_geq(&dis->rb_w, 0); e != NULL; e = tmp) { 568 | tmp = _dis_rb_next(e); 569 | if (e->pba + e->len <= low || e->pba >= high) 570 | continue; 571 | 572 | /* 573 | * Remove also extents from read map which 574 | * overlaps remove write map extents 575 | */ 576 | // 577 | // TODO: For now remove the whole extent 578 | // Later improve with update range 579 | // Don't forget to remove data from read cache 580 | while (true) { 581 | struct extent *er = _dis_rb_geq(&dis->rb_r, e->lba); 582 | if (er == NULL || er->lba >= e->lba + e->len) 583 | break; 584 | dis_rb_remove(dis, er, MAP_READ); 585 | mempool_free(er, dis->extent_pool); 586 | } 587 | 588 | dis_rb_remove(dis, e, MAP_WRITE); 589 | mempool_free(e, dis->extent_pool); 590 | } 591 | dis->trim_me = (dis->trim_me + 1) % 8; 592 | spin_unlock(&dis->rb_r_lock); 593 | spin_unlock_irqrestore(&dis->rb_w_lock, flags); 594 | } 595 | 596 | bail: 597 | return lba; 598 | } 599 | 600 | /* this prepends a header to each write, with no batching. Maybe not the 601 | * most efficient way, but it works. 602 | * indirectly locks dis->lock (via maybe_wrap) 603 | */ 604 | static int do_map_write_io(struct disbd *dis, struct bio *bio) 605 | { 606 | sector_t lba = bio->bi_iter.bi_sector; 607 | unsigned sectors = bio_sectors(bio); 608 | unsigned n_pages = DIV_ROUND_UP(sectors, 8); 609 | 610 | struct write_record *rec = kmalloc(sizeof(*rec), GFP_NOIO); 611 | if (!rec) 612 | return DM_MAPIO_DELAY_REQUEUE; /* ??? */ 613 | 614 | /* first prep the header and extent list 615 | */ 616 | sector_t hdr_pba = maybe_wrap(dis, n_pages * 8); 617 | struct dis_header *h; 618 | struct bio *hdr_bio = alloc_header(dis, &h); 619 | hdr_bio->bi_iter.bi_sector = hdr_pba; 620 | h->h = (struct _disheader){ .magic = DIS_HDR_MAGIC, 621 | .seq = atomic_inc_return(&dis->seq), 622 | .n_extents = 1, 623 | .n_sectors = sectors }; 624 | sector_t pba = hdr_pba + 8; 625 | 626 | //DMINFO("%llu do map write %llu %d -> %llu", (u64)bio, 627 | //bio->bi_iter.bi_sector, bio_sectors(bio), (u64)pba); 628 | 629 | h->extents[0].lba = lba; 630 | h->extents[0].len = sectors; 631 | sign_header(h); 632 | 633 | /* (chain the bios so hdr_bio completes before bio, updating map) 634 | * TODO - bio_chain doesn't work here, because device mapper has 635 | * already set the endio function. (works in read, because we only 636 | * chain clones) 637 | */ 638 | bio_set_dev(bio, dis->dev->bdev); 639 | bio->bi_iter.bi_sector = pba; 640 | //bio_chain(bio, hdr_bio); doesn't work 641 | 642 | rec->lba = lba; /* map info for endio */ 643 | rec->pba = pba; 644 | rec->len = sectors; 645 | rec->dis = dis; 646 | hdr_bio->bi_private = rec; 647 | 648 | generic_make_request(hdr_bio); 649 | generic_make_request(bio); 650 | 651 | return DM_MAPIO_SUBMITTED; 652 | } 653 | 654 | static int map_write_io(struct disbd *dis, struct bio *bio) 655 | { 656 | unsigned long flags; 657 | int n; 658 | 659 | //DMINFO("%llu map write %llu %d", (u64)bio, bio->bi_iter.bi_sector, bio_sectors(bio)); 660 | 661 | spin_lock_irqsave(&dis->lock, flags); 662 | if ((n = dis->done_sectors) >= dis->max_sectors) { 663 | atomic_inc(&dis->n_undone); 664 | bio_list_add(&dis->undone_writes, bio); 665 | } else { 666 | dis->done_count++; 667 | dis->done_sectors += bio_sectors(bio); 668 | } 669 | spin_unlock_irqrestore(&dis->lock, flags); 670 | if (n >= dis->max_sectors) 671 | return DM_MAPIO_SUBMITTED; 672 | else 673 | return do_map_write_io(dis, bio); 674 | } 675 | 676 | static int dis_map(struct dm_target *ti, struct bio *bio) 677 | { 678 | struct disbd *dis = ti->private; 679 | 680 | switch (bio_op(bio)) { 681 | // case REQ_OP_DISCARD: 682 | // return dis_discard(dis, bio); 683 | 684 | case REQ_OP_FLUSH: 685 | bio_set_dev(bio, dis->dev->bdev); 686 | generic_make_request(bio); 687 | return DM_MAPIO_SUBMITTED; 688 | 689 | case REQ_OP_READ: 690 | return split_read_io(dis, bio); 691 | 692 | case REQ_OP_WRITE: 693 | return map_write_io(dis, bio); 694 | 695 | default: 696 | printk(KERN_INFO "unknown bio op: %d \n", bio_op(bio)); 697 | return DM_MAPIO_KILL; 698 | } 699 | } 700 | 701 | static void purge_map(struct disbd *dis, enum map_type map) 702 | { 703 | struct rb_root *root = (map == MAP_WRITE) ? &dis->rb_w : &dis->rb_r; 704 | struct rb_node *node = rb_first(root); 705 | while (node) { 706 | struct rb_node *tmp = rb_next(node); 707 | struct extent *e = container_of(node, struct extent, rb); 708 | rb_erase(node, root); 709 | mempool_free(e, dis->extent_pool); 710 | node = tmp; 711 | } 712 | } 713 | 714 | static void dis_dtr(struct dm_target *ti) 715 | { 716 | struct disbd *dis = ti->private; 717 | 718 | ti->private = NULL; 719 | 720 | misc_deregister(&dis->misc); 721 | purge_map(dis, MAP_READ); 722 | purge_map(dis, MAP_WRITE); 723 | 724 | mempool_destroy(dis->extent_pool); 725 | mempool_destroy(dis->page_pool); 726 | bioset_exit(&dis->bs); 727 | dm_put_device(ti, dis->dev); 728 | 729 | kfree(dis); 730 | } 731 | 732 | static struct disbd *_dis; 733 | static const struct file_operations dis_misc_fops; 734 | 735 | /* 736 | * argv[0] = blockdev name 737 | * argv[1] = misc device name 738 | */ 739 | static int dis_ctr(struct dm_target *ti, unsigned int argc, char **argv) 740 | { 741 | int r = -ENOMEM; 742 | struct disbd *dis; 743 | unsigned long base, bound; 744 | sector_t max_sectors; 745 | char d; 746 | 747 | //DMINFO("ctr %s %s %s %s", argv[0], argv[1], argv[2], argv[3]); 748 | 749 | if (argc != 5) { 750 | ti->error = "dm-disbd: Invalid argument count"; 751 | if (argc > 5) 752 | DMINFO("6th %s", argv[5]); 753 | return -EINVAL; 754 | } 755 | 756 | if (sscanf(argv[2], "%lu%c", &base, &d) != 1) { 757 | ti->error = "dm-disbd: Invalid cache base"; 758 | return -EINVAL; 759 | } 760 | if (sscanf(argv[3], "%lu%c", &bound, &d) != 1) { 761 | ti->error = "dm-disbd: Invalid cache bound"; 762 | return -EINVAL; 763 | } 764 | if (sscanf(argv[4], "%lu%c", &max_sectors, &d) != 1) { 765 | ti->error = "dm-disbd: Invalid max sector size"; 766 | return -EINVAL; 767 | } 768 | 769 | if (!(_dis = dis = kzalloc(sizeof(*dis), GFP_KERNEL))) 770 | return -ENOMEM; 771 | ti->private = dis; 772 | 773 | #if 0 774 | dm_table_set_type(ti->table, DM_TYPE_REQUEST_BASED); 775 | #endif 776 | if ((r = dm_get_device(ti, argv[0], dm_table_get_mode(ti->table), &dis->dev))) { 777 | ti->error = "dm-disbd: Device lookup failed."; 778 | return r; 779 | } 780 | if (bound > dis->dev->bdev->bd_inode->i_size / 512) { 781 | ti->error = "dm-disbd: Invalid cache bound"; 782 | dm_put_device(ti, dis->dev); 783 | return -EINVAL; 784 | } 785 | dis->base = base; 786 | dis->bound = bound; 787 | dis->max_sectors = max_sectors; 788 | 789 | sprintf(dis->nodename, "disbd/%s", argv[1]); 790 | 791 | spin_lock_init(&dis->lock); 792 | spin_lock_init(&dis->rb_r_lock); 793 | spin_lock_init(&dis->rb_w_lock); 794 | bio_list_init(&dis->pending_reads); 795 | bio_list_init(&dis->faulted_reads); 796 | INIT_LIST_HEAD(&dis->done_writes); 797 | bio_list_init(&dis->undone_writes); 798 | 799 | init_waitqueue_head(&dis->read_wait); 800 | init_waitqueue_head(&dis->write_wait); 801 | 802 | r = -ENOMEM; 803 | ti->error = "dm-disbd: No memory"; 804 | 805 | dis->extent_pool = mempool_create_slab_pool(MIN_EXTENTS, _extent_cache); 806 | if (!dis->extent_pool) 807 | goto fail; 808 | dis->page_pool = mempool_create_page_pool(MIN_POOL_PAGES, 0); 809 | if (!dis->page_pool) 810 | goto fail; 811 | 812 | if (bioset_init(&dis->bs, 32, 0, BIOSET_NEED_BVECS)) 813 | goto fail; 814 | 815 | dis->rb_r = RB_ROOT; 816 | dis->rb_w = RB_ROOT; 817 | 818 | ti->error = "dm-disbd: misc_register failed"; 819 | dis->misc.minor = MISC_DYNAMIC_MINOR; 820 | dis->misc.name = "dm-disbd"; 821 | dis->misc.nodename = dis->nodename; 822 | dis->misc.fops = &dis_misc_fops; 823 | 824 | dis->ti = ti; 825 | 826 | if (misc_register(&dis->misc)) 827 | goto fail; 828 | 829 | return 0; 830 | 831 | fail: 832 | bioset_exit(&dis->bs); 833 | if (dis->page_pool) 834 | mempool_destroy(dis->page_pool); 835 | if (dis->extent_pool) 836 | mempool_destroy(dis->extent_pool); 837 | if (dis->dev) 838 | dm_put_device(ti, dis->dev); 839 | kfree(dis); 840 | 841 | return r; 842 | } 843 | 844 | static void dis_io_hints(struct dm_target *ti, struct queue_limits *limits) 845 | { 846 | limits->physical_block_size = 4096; 847 | limits->io_min = 4096; 848 | limits->max_hw_sectors = 512; /* want max I/O to be 64 pages */ 849 | } 850 | 851 | static int dis_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data) 852 | { 853 | struct disbd *dis = ti->private; 854 | return fn(ti, dis->dev, dis->base, dis->bound - dis->base, data); 855 | } 856 | 857 | #if 0 858 | /* some day I'll try to get request-based mapping to work 859 | */ 860 | static int dis_clone_and_map(struct dm_target *ti, struct request *rq, 861 | union map_info *map_context, 862 | struct request **__clone) 863 | { 864 | DMERR("clone_and_map"); 865 | return DM_MAPIO_SUBMITTED; 866 | } 867 | 868 | static void dis_release_clone(struct request *clone) 869 | { 870 | DMERR("release clone"); 871 | } 872 | 873 | static int dis_end_io(struct dm_target *ti, struct request *clone, 874 | blk_status_t error, union map_info *map_context) 875 | { 876 | DMERR("end_io"); 877 | return DM_ENDIO_DONE; 878 | } 879 | 880 | static int dis_busy(struct dm_target *ti) 881 | { 882 | return false; 883 | } 884 | #endif 885 | 886 | static struct target_type dis_target = { 887 | .name = "disbd", 888 | .features = DM_TARGET_IMMUTABLE, 889 | .version = { 1, 0, 0 }, 890 | .module = THIS_MODULE, 891 | .ctr = dis_ctr, 892 | .dtr = dis_dtr, 893 | .map = dis_map, 894 | #if 0 895 | .clone_and_map_rq = dis_clone_and_map, 896 | .release_clone_rq = dis_release_clone, 897 | .rq_end_io = dis_end_io, 898 | #endif 899 | .status = 0 /*dis_status*/, 900 | .prepare_ioctl = 0 /*dis_prepare_ioctl*/, 901 | .message = 0 /*dis_message*/, 902 | .iterate_devices = dis_iterate_devices, 903 | .io_hints = dis_io_hints, 904 | }; 905 | 906 | struct { 907 | char *name; 908 | int value; 909 | } ioctl_map[] = { 910 | { "IOCTL_DIS_GET_MAP", IOCTL_DIS_GET_MAP }, 911 | { "IOCTL_DIS_WRITES", IOCTL_DIS_WRITES }, 912 | { "IOCTL_DIS_READS", IOCTL_DIS_READS }, 913 | { "IOCTL_DIS_RESOLVE", IOCTL_DIS_RESOLVE }, 914 | { 0, 0 }, 915 | }; 916 | static char *ioctl_name(int code) 917 | { 918 | int i; 919 | for (i = 0; ioctl_map[i].name != NULL; i++) 920 | if (ioctl_map[i].value == code) 921 | return ioctl_map[i].name; 922 | return "IOCTL UNKNOWN"; 923 | } 924 | 925 | static int ioctl_get_map(struct disbd *dis, void *arg) 926 | { 927 | return 0; // copy over from old version 928 | } 929 | 930 | static int ioctl_write_wait(struct disbd *dis, void *arg) 931 | { 932 | struct ioctl_writes iw; 933 | struct dis_extent *extents; 934 | unsigned long flags; 935 | struct bio_list tmp = BIO_EMPTY_LIST; 936 | struct list_head tmp_writes; 937 | int i = 0; 938 | 939 | spin_lock_irqsave(&dis->lock, flags); 940 | dis->done_sectors -= dis->prev_done_sectors; 941 | dis->prev_done_sectors = 0; 942 | //spin_unlock_irqrestore(&dis->lock, flags); 943 | 944 | /* if we free up any stalled bios, grab them here under the lock 945 | */ 946 | //spin_lock_irqsave(&dis->lock, flags); 947 | sector_t n = dis->done_sectors; 948 | while (!bio_list_empty(&dis->undone_writes) && n < dis->max_sectors) { 949 | struct bio *bio = bio_list_pop(&dis->undone_writes); 950 | atomic_dec(&dis->n_undone); 951 | bio_list_add(&tmp, bio); 952 | n += bio_sectors(bio); 953 | } 954 | spin_unlock_irqrestore(&dis->lock, flags); 955 | 956 | /* now run them through map_write_io again. hysteresis is to 957 | * minimize the chance that it goes back on the list again. 958 | * (map_write_io locks dis->lock in maybe_wrap) 959 | */ 960 | while (!bio_list_empty(&tmp)) { 961 | struct bio *bio = bio_list_pop(&tmp); 962 | //DMINFO("%llu write wait recycle", (u64)bio); 963 | map_write_io(dis, bio); 964 | } 965 | 966 | if (copy_from_user(&iw, arg, sizeof(iw))) 967 | return -EFAULT; 968 | extents = iw.extents; 969 | 970 | wait_event_interruptible(dis->write_wait, !list_empty(&dis->done_writes)); 971 | 972 | INIT_LIST_HEAD(&tmp_writes); 973 | 974 | spin_lock_irqsave(&dis->lock, flags); 975 | for (i = 0; !list_empty(&dis->done_writes) && i < iw.n_extents; i++) { 976 | struct write_record *rec = 977 | list_first_entry(&dis->done_writes, struct write_record, list); 978 | list_del(&rec->list); 979 | atomic_dec(&dis->n_done); 980 | list_add_tail(&rec->list, &tmp_writes); 981 | dis->done_count--; 982 | dis->prev_done_sectors += rec->len; 983 | //dis->done_sectors -= rec->len; 984 | } 985 | spin_unlock_irqrestore(&dis->lock, flags); 986 | 987 | for (i = 0; !list_empty(&tmp_writes) && i < iw.n_extents; i++) { 988 | struct write_record *rec = list_first_entry(&tmp_writes, struct write_record, list); 989 | list_del(&rec->list); 990 | struct dis_extent e = { .lba = rec->lba, .pba = rec->pba, .len = rec->len }; 991 | kfree(rec); 992 | if (copy_to_user(extents, &e, sizeof(e))) { 993 | return -EFAULT; 994 | } 995 | extents++; 996 | } 997 | 998 | //DMINFO("write_wait: %llu = %d", (u64)arg, i); 999 | iw.n_extents = i; 1000 | if (copy_to_user(arg, &iw, sizeof(iw))) 1001 | return -EFAULT; 1002 | return 0; 1003 | } 1004 | 1005 | /* TODO - I don't like this idea of only resolving the faulted reads, since that may 1006 | * make readahead less effective. we'll see. It makes locking easier, though... 1007 | */ 1008 | static int ioctl_resolve(struct disbd *dis, void *arg) 1009 | { 1010 | struct dm_target *ti = dis->ti; 1011 | struct ioctl_resolve ir; 1012 | unsigned long flags; 1013 | int i; 1014 | 1015 | if (copy_from_user(&ir, arg, sizeof(ir))) 1016 | return -EFAULT; 1017 | struct dis_extent *extents = ir.extents; 1018 | sector_t max_pba = dis->dev->bdev->bd_inode->i_size / 512; 1019 | 1020 | for (i = 0; i < ir.n_extents; i++) { 1021 | struct dis_extent e; 1022 | if (copy_from_user(&e, &extents[i], sizeof(e))) 1023 | return -EFAULT; 1024 | if (e.lba < ti->begin || e.lba + e.len > ti->begin + ti->len || e.len == 0 || 1025 | e.pba + e.len > max_pba) { 1026 | // || e.len % 8*) 1027 | DMERR("put: invalid range: %lu %lu (%llu-%llu)", (long)e.lba, (long)e.len, 1028 | (u64)ti->begin, (u64)(ti->begin + ti->len)); 1029 | return -EINVAL; 1030 | } 1031 | //DMINFO("resolve: %llu -> %llu +%d", (u64)e.lba, (u64)e.pba, e.len); 1032 | dis_update_range(dis, e.lba, e.pba, e.len, MAP_READ); 1033 | } 1034 | 1035 | /* take all the read bios off the list, run them back through again 1036 | */ 1037 | struct bio *bio; 1038 | while ((bio = bio_list_pop(&dis->faulted_reads)) != NULL) { 1039 | //DMINFO("%llx (%llu %d) -> recycle", (u64)bio, (u64)bio->bi_iter.bi_sector, bio_sectors(bio)); 1040 | split_read_io(dis, bio); 1041 | } 1042 | 1043 | /* trim map entries if requested 1044 | */ 1045 | if (ir.clear_lo != ir.clear_hi) { 1046 | struct extent *e, *tmp; 1047 | sector_t low = ir.clear_lo; 1048 | sector_t high = ir.clear_hi; 1049 | 1050 | spin_lock_irqsave(&dis->rb_r_lock, flags); 1051 | for (e = _dis_rb_geq(&dis->rb_r, 0); e != NULL; e = tmp) { 1052 | tmp = _dis_rb_next(e); 1053 | if (e->pba + e->len <= low || e->pba >= high) 1054 | continue; 1055 | dis_rb_remove(dis, e, MAP_READ); 1056 | mempool_free(e, dis->extent_pool); 1057 | } 1058 | spin_unlock_irqrestore(&dis->rb_r_lock, flags); 1059 | } 1060 | 1061 | return 0; 1062 | } 1063 | 1064 | static int ioctl_read_wait(struct disbd *dis, void *arg) 1065 | { 1066 | unsigned long flags = 0; 1067 | struct ioctl_reads ir; 1068 | struct dis_extent *extents; 1069 | 1070 | if (copy_from_user(&ir, arg, sizeof(ir))) 1071 | return -EFAULT; 1072 | extents = ir.extents; 1073 | 1074 | //DMINFO("read_wait pending %d nfaulted %d", !bio_list_empty(&dis->pending_reads), dis->n_faulted); 1075 | //DMINFO("read_wait pending %d", !bio_list_empty(&dis->pending_reads)); 1076 | wait_event_interruptible(dis->read_wait, !bio_list_empty(&dis->pending_reads)); 1077 | //DMINFO("read_wait pending %d", !bio_list_empty(&dis->pending_reads)); 1078 | 1079 | struct bio_list tmp = BIO_EMPTY_LIST; 1080 | int i; 1081 | 1082 | spin_lock_irqsave(&dis->lock, flags); 1083 | for (i = 0; !bio_list_empty(&dis->pending_reads) && i < ir.n_extents; i++) { 1084 | struct bio *bio = bio_list_pop(&dis->pending_reads); 1085 | bio_list_add(&tmp, bio); 1086 | } 1087 | spin_unlock_irqrestore(&dis->lock, flags); 1088 | 1089 | for (i = 0; !bio_list_empty(&tmp) && i < ir.n_extents; i++) { 1090 | struct bio *bio = bio_list_pop(&tmp); 1091 | struct dis_extent e = { .lba = bio->bi_iter.bi_sector, 1092 | .pba = 0, 1093 | .len = bio_sectors(bio) }; 1094 | if (copy_to_user(extents, &e, sizeof(e))) 1095 | return -EFAULT; 1096 | extents++; 1097 | bio_list_add(&dis->faulted_reads, bio); 1098 | } 1099 | 1100 | ir.n_extents = i; 1101 | if (copy_to_user(arg, &ir, sizeof(ir))) 1102 | return -EFAULT; 1103 | return 0; 1104 | } 1105 | 1106 | static long dis_dev_ioctl(struct file *fp, unsigned int num, unsigned long arg) 1107 | { 1108 | // https://elixir.bootlin.com/linux/latest/source/drivers/char/misc.c#L173 1109 | struct miscdevice *m = fp->private_data; 1110 | struct disbd *dis = container_of(m, struct disbd, misc); 1111 | 1112 | //printk(KERN_INFO "ioctl %d (%s) dis %p\n", num, ioctl_name(num), dis); 1113 | 1114 | switch (num) { 1115 | case IOCTL_DIS_NO_OP: 1116 | printk(KERN_INFO "n_done %d n_undone %d\n", atomic_read(&dis->n_done), 1117 | atomic_read(&dis->n_undone)); 1118 | return 0; 1119 | 1120 | case IOCTL_DIS_GET_MAP: 1121 | return ioctl_get_map(dis, (void *)arg); 1122 | 1123 | case IOCTL_DIS_WRITES: 1124 | return ioctl_write_wait(dis, (void *)arg); 1125 | 1126 | case IOCTL_DIS_READS: 1127 | return ioctl_read_wait(dis, (void *)arg); 1128 | 1129 | case IOCTL_DIS_RESOLVE: 1130 | return ioctl_resolve(dis, (void *)arg); 1131 | 1132 | default: 1133 | return -EINVAL; 1134 | } 1135 | } 1136 | 1137 | static int dis_dev_release(struct inode *in, struct file *fp) 1138 | { 1139 | struct miscdevice *m = fp->private_data; 1140 | struct disbd *dis = container_of(m, struct disbd, misc); 1141 | 1142 | while (!bio_list_empty(&dis->faulted_reads)) { 1143 | struct bio *bio = bio_list_pop(&dis->faulted_reads); 1144 | bio->bi_status = BLK_STS_IOERR; 1145 | bio_endio(bio); 1146 | } 1147 | 1148 | while (!bio_list_empty(&dis->undone_writes)) { 1149 | struct bio *bio = bio_list_pop(&dis->undone_writes); 1150 | bio->bi_status = BLK_STS_IOERR; 1151 | bio_endio(bio); 1152 | } 1153 | 1154 | return 0; 1155 | } 1156 | 1157 | static const struct file_operations dis_misc_fops = { 1158 | .owner = THIS_MODULE, 1159 | .unlocked_ioctl = dis_dev_ioctl, 1160 | .release = dis_dev_release, 1161 | }; 1162 | 1163 | static int __init dm_dis_init(void) 1164 | { 1165 | int r = -ENOMEM; 1166 | 1167 | if (!(_extent_cache = KMEM_CACHE(extent, 0))) 1168 | goto fail; 1169 | if ((r = dm_register_target(&dis_target)) < 0) 1170 | goto fail; 1171 | return 0; 1172 | 1173 | fail: 1174 | if (_extent_cache) 1175 | kmem_cache_destroy(_extent_cache); 1176 | return r; 1177 | } 1178 | 1179 | static void __exit dm_dis_exit(void) 1180 | { 1181 | dm_unregister_target(&dis_target); 1182 | kmem_cache_destroy(_extent_cache); 1183 | } 1184 | 1185 | module_init(dm_dis_init); 1186 | module_exit(dm_dis_exit); 1187 | 1188 | MODULE_DESCRIPTION(DM_NAME " generic user-space-controlled Object Translation Layer"); 1189 | MODULE_LICENSE("GPL"); 1190 | -------------------------------------------------------------------------------- /kernel/dm-disbd.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2016 Peter Desnoyers 3 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 4 | 5 | #ifndef __DISBD_U_H__ 6 | #define __DISBD_U_H__ 7 | 8 | #include 9 | 10 | #define DIS_HDR_MAGIC 0x4c443353 11 | #define DIS_HDR_SIZE 4096 12 | 13 | struct _disheader { 14 | uint32_t magic; 15 | uint32_t seq; 16 | uint32_t crc32; 17 | uint16_t n_extents; 18 | uint16_t n_sectors; 19 | } __attribute__((packed)); 20 | 21 | struct _ext_local { 22 | uint64_t lba : 47; 23 | uint64_t len : 16; 24 | uint64_t dirty : 1; 25 | } __attribute__((packed)); 26 | 27 | #define HDR_EXTS ((DIS_HDR_SIZE - sizeof(struct _disheader)) / sizeof(struct _ext_local)) 28 | 29 | struct dis_header { 30 | struct _disheader h; 31 | struct _ext_local extents[HDR_EXTS]; 32 | }; 33 | 34 | #ifdef _KERNEL_ 35 | BUILD_BUG_ON(sizeof(struct dis_header) != DIS_HDR_SIZE) 36 | #endif 37 | 38 | #define MAGIC_NUMBER 100 39 | #define IOCTL_DIS_NO_OP _IO(MAGIC_NUMBER, 0) 40 | 41 | #define PBA_NONE (0x7FULL << 40) 42 | 43 | struct dis_extent { 44 | uint64_t lba; 45 | uint64_t pba; 46 | uint64_t len; 47 | }; 48 | 49 | struct ioctl_get_map { 50 | uint32_t map; 51 | uint64_t start; 52 | uint32_t n_extents; 53 | uint32_t total_extents; 54 | struct dis_extent *extents; 55 | }; 56 | 57 | #define IOCTL_DIS_GET_MAP _IOWR(MAGIC_NUMBER, 1, struct ioctl_get_map) 58 | 59 | struct ioctl_writes { 60 | uint64_t n_extents; 61 | struct dis_extent *extents; 62 | }; 63 | 64 | #define IOCTL_DIS_WRITES _IOWR(MAGIC_NUMBER, 2, struct ioctl_writes) 65 | 66 | struct ioctl_reads { 67 | uint64_t n_extents; 68 | struct dis_extent *extents; 69 | }; 70 | 71 | #define IOCTL_DIS_READS _IOWR(MAGIC_NUMBER, 3, struct ioctl_reads) 72 | 73 | struct ioctl_resolve { 74 | uint64_t n_extents; 75 | struct dis_extent *extents; 76 | uint64_t clear_lo; 77 | uint64_t clear_hi; 78 | }; 79 | 80 | #define IOCTL_DIS_RESOLVE _IOW(MAGIC_NUMBER, 4, struct ioctl_resolve) 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # Copyright (C) 2020-2021 Vojtech Aschenbrenner 4 | 5 | set -euxo pipefail 6 | 7 | clean() { 8 | sudo dmsetup remove --retry disa 9 | sudo rmmod dm_disbd 10 | sudo losetup -d $loop 11 | sudo rm -f $cache_path $store_path 12 | } 13 | 14 | make -C kernel 15 | 16 | cache_path=$(pwd)/cache.raw 17 | store_path=$(pwd)/store.raw 18 | 19 | #cache_size_M=1 20 | cache_size_M=4096 21 | cache_sectors=$((cache_size_M*1024*1024/512)) 22 | 23 | l2cache_size_M=8 24 | l2cache_sectors=$((l2cache_size_M*1024*1024/512)) 25 | 26 | #store_size_M=128 27 | store_size_M=$((10*1024)) 28 | store_sectors=$((store_size_M*1024*1024/512)) 29 | 30 | sudo insmod kernel/dm-disbd.ko 31 | 32 | rm -f $store_path && truncate -s ${store_size_M}M $store_path 33 | rm -f $cache_path && truncate -s $((cache_size_M + l2cache_size_M))M $cache_path 34 | 35 | loop=$(sudo losetup -f --show $cache_path) 36 | trap clean 0 37 | 38 | max_w_ioctl_sectors=$((420*1024/512)) 39 | #max_w_ioctl_sectors=0 40 | echo 0 $store_sectors disbd $loop disa 0 $((cache_sectors/2)) $((cache_sectors/2 - max_w_ioctl_sectors)) | sudo dmsetup --noudevsync create disa 41 | sleep 1 42 | 43 | ( 44 | cd userspace 45 | export DIS_CACHE_BASE=$((cache_sectors/2)) 46 | export DIS_CACHE_BOUND=$cache_sectors 47 | export DIS_CACHE_FILE=$loop 48 | export DIS_L2CACHE_BASE=$cache_sectors 49 | export DIS_L2CACHE_BOUND=$((cache_sectors + l2cache_sectors)) 50 | export DIS_L2CACHE_FILE=$loop 51 | export DIS_L2CACHE_CHUNKSIZE=$((1024*1024)) 52 | export DIS_BACKEND_ENABLED="object" 53 | export DIS_BACKEND_FILE_FILE=$store_path 54 | export DIS_BACKEND_OBJECT_API="s3" 55 | #export DIS_BACKEND_OBJECT_OBJECTSIZEM=4 56 | export DIS_BACKEND_OBJECT_OBJECTSIZEM=32 57 | export DIS_BACKEND_OBJECT_GCMODE="off" 58 | 59 | # What gcthread function should be used 60 | # 1: Old version with range reads 61 | # 2: New version downloading the whole object first 62 | export DIS_BACKEND_OBJECT_GCVERSION=2 63 | 64 | export DIS_BACKEND_OBJECT_S3_BUCKET="dis2" 65 | export DIS_BACKEND_OBJECT_S3_REGION="us-east-1" 66 | export DIS_BACKEND_OBJECT_S3_REMOTE="http://192.168.122.1:9000" 67 | export DIS_BACKEND_OBJECT_RADOS_POOL="ec-pool" 68 | export DIS_BACKEND_NULL_SKIPREADINWRITEPATH="false" 69 | export DIS_BACKEND_NULL_WAITFORIOCTLROUND="true" 70 | export DIS_IOCTL_CTL=/dev/disbd/disa 71 | #export DIS_IOCTL_EXTENTS=8 72 | export DIS_IOCTL_EXTENTS=128 73 | export AWS_ACCESS_KEY_ID="Server-Access-Key" 74 | export AWS_SECRET_ACCESS_KEY="Server-Secret-Key" 75 | 76 | /bin/time sudo -E go run . 77 | ) 78 | -------------------------------------------------------------------------------- /run_benchmarks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # Copyright (C) 2020-2021 Vojtech Aschenbrenner 4 | 5 | set -euxo pipefail 6 | 7 | cd benchmarks 8 | ./run.py 9 | -------------------------------------------------------------------------------- /test-mkfs_mnt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # Copyright (C) 2020-2021 Vojtech Aschenbrenner 4 | 5 | set -euxo pipefail 6 | 7 | sudo mkfs.ext4 -F -F /dev/mapper/disa 8 | sudo fsck.ext4 -f /dev/mapper/disa 9 | 10 | sudo mount /dev/mapper/disa /mnt 11 | sudo umount /mnt 12 | -------------------------------------------------------------------------------- /userspace/backend/backend.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package backend 5 | 6 | import ( 7 | "dis/backend/file" 8 | "dis/backend/null" 9 | "dis/backend/object" 10 | "dis/extent" 11 | "dis/parser" 12 | "reflect" 13 | ) 14 | 15 | const ( 16 | configSection = "backend" 17 | envPrefix = "dis_backend" 18 | ) 19 | 20 | var ( 21 | enabled string 22 | instance backend 23 | 24 | backendMap = map[string]reflect.Type{ 25 | "file": reflect.TypeOf(file.FileBackend{}), 26 | "null": reflect.TypeOf(null.NullBackend{}), 27 | "object": reflect.TypeOf(object.ObjectBackend{}), 28 | } 29 | ) 30 | 31 | type backend interface { 32 | Init() 33 | Read(*[]extent.Extent) 34 | Write(*[]extent.Extent) 35 | } 36 | 37 | func Init() { 38 | v := parser.Sub(configSection) 39 | v.SetEnvPrefix(envPrefix) 40 | v.BindEnv("enabled") 41 | enabled = v.GetString("enabled") 42 | 43 | T := backendMap[enabled] 44 | instance = reflect.New(T).Interface().(backend) 45 | instance.Init() 46 | } 47 | 48 | func Read(e *[]extent.Extent) { 49 | instance.Read(e) 50 | } 51 | 52 | func Write(e *[]extent.Extent) { 53 | instance.Write(e) 54 | } 55 | -------------------------------------------------------------------------------- /userspace/backend/file/file.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package file 5 | 6 | import ( 7 | "dis/cache" 8 | "dis/extent" 9 | "dis/parser" 10 | "golang.org/x/sys/unix" 11 | "sync" 12 | ) 13 | 14 | const ( 15 | configSection = "backend.file" 16 | envPrefix = "dis_backend_file" 17 | ) 18 | 19 | type FileBackend struct{} 20 | 21 | var ( 22 | file string 23 | fd int 24 | ) 25 | 26 | func (this *FileBackend) Init() { 27 | v := parser.Sub(configSection) 28 | v.SetEnvPrefix(envPrefix) 29 | v.BindEnv("file") 30 | file = v.GetString("file") 31 | 32 | if file == "" { 33 | panic("") 34 | } 35 | 36 | var err error 37 | fd, err = unix.Open(file, unix.O_RDWR, 0) 38 | if err != nil { 39 | panic(err) 40 | } 41 | } 42 | 43 | func (this *FileBackend) Write(extents *[]extent.Extent) { 44 | bufs := make(map[*extent.Extent]*[]byte) 45 | 46 | var reads sync.WaitGroup 47 | reads.Add(len(*extents)) 48 | for i := range *extents { 49 | e := &(*extents)[i] 50 | buf := make([]byte, e.Len*512) 51 | bufs[e] = &buf 52 | 53 | go func() { 54 | cache.Read(&buf, e.PBA*512) 55 | reads.Done() 56 | }() 57 | } 58 | reads.Wait() 59 | 60 | var writes sync.WaitGroup 61 | writes.Add(len(*extents)) 62 | for i := range *extents { 63 | e := &(*extents)[i] 64 | buf := bufs[e] 65 | go func() { 66 | _, err := unix.Pwrite(fd, *buf, e.LBA*512) 67 | if err != nil { 68 | panic(err) 69 | } 70 | writes.Done() 71 | }() 72 | } 73 | writes.Wait() 74 | } 75 | 76 | func (this *FileBackend) Read(extents *[]extent.Extent) { 77 | var reads sync.WaitGroup 78 | reads.Add(len(*extents)) 79 | for i := range *extents { 80 | e := &(*extents)[i] 81 | cache.Reserve(e) 82 | 83 | go func() { 84 | buf := make([]byte, e.Len*512) 85 | _, err := unix.Pread(fd, buf, e.LBA*512) 86 | if err != nil { 87 | panic(err) 88 | } 89 | cache.Write(&buf, e.PBA*512) 90 | reads.Done() 91 | }() 92 | } 93 | reads.Wait() 94 | } 95 | -------------------------------------------------------------------------------- /userspace/backend/null/null.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package null 5 | 6 | import ( 7 | "dis/cache" 8 | "dis/extent" 9 | "dis/parser" 10 | "fmt" 11 | "sync" 12 | ) 13 | 14 | const ( 15 | configSection = "backend.null" 16 | envPrefix = "dis_backend_null" 17 | ) 18 | 19 | var ( 20 | skipReadInWritePath bool 21 | waitForIoctlRound bool 22 | ) 23 | 24 | type NullBackend struct{} 25 | 26 | func (this *NullBackend) Init() { 27 | v := parser.Sub(configSection) 28 | v.SetEnvPrefix(envPrefix) 29 | 30 | v.BindEnv("skipReadInWritePath") 31 | v.BindEnv("waitForIoctlRound") 32 | skipReadInWritePath = v.GetBool("skipReadInWritePath") 33 | waitForIoctlRound = v.GetBool("waitForIoctlRound") 34 | } 35 | 36 | func (this *NullBackend) Write(extents *[]extent.Extent) { 37 | if skipReadInWritePath { 38 | return 39 | } 40 | 41 | var wg sync.WaitGroup 42 | wg.Add(len(*extents)) 43 | for i := range *extents { 44 | e := &(*extents)[i] 45 | go func() { 46 | buffer := make([]byte, e.Len*512) 47 | cache.Read(&buffer, e.PBA*512) 48 | wg.Done() 49 | }() 50 | } 51 | 52 | if waitForIoctlRound { 53 | wg.Wait() 54 | } 55 | } 56 | 57 | func (this *NullBackend) Read(extents *[]extent.Extent) { 58 | fmt.Println("NullBackend.Read()") 59 | } 60 | -------------------------------------------------------------------------------- /userspace/backend/object/api/rados/rados.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package rados 5 | 6 | import ( 7 | "dis/parser" 8 | "fmt" 9 | "github.com/ceph/go-ceph/rados" 10 | ) 11 | 12 | const ( 13 | configSection = "backend.object.rados" 14 | envPrefix = "dis_backend_object_rados" 15 | ) 16 | 17 | var ( 18 | conn *rados.Conn 19 | pool string 20 | ) 21 | 22 | func Init() { 23 | v := parser.Sub(configSection) 24 | v.SetEnvPrefix(envPrefix) 25 | v.BindEnv("pool") 26 | pool = v.GetString("pool") 27 | 28 | if pool == "" { 29 | panic("") 30 | } 31 | 32 | var err error 33 | conn, err = rados.NewConn() 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | err = conn.ReadDefaultConfigFile() 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | err = conn.Connect() 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | ioctx, err := conn.OpenIOContext(pool) 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | ioctx.ListObjects(func(oid string) { ioctx.Delete(oid) }) 54 | 55 | go func() { ioctx.Destroy() }() 56 | } 57 | 58 | const keyFmt = "%08d" 59 | 60 | func Upload(key int64, buf *[]byte) { 61 | ioctx, err := conn.OpenIOContext(pool) 62 | if err != nil { 63 | panic(err) 64 | } 65 | 66 | err = ioctx.Write(fmt.Sprintf(keyFmt, key), *buf, 0) 67 | if err != nil { 68 | panic(err) 69 | } 70 | 71 | go func() { ioctx.Destroy() }() 72 | } 73 | 74 | func Download(key int64, buf *[]byte, from, to int64) { 75 | if to-from+1 != int64(len(*buf)) { 76 | panic("") 77 | } 78 | ioctx, err := conn.OpenIOContext(pool) 79 | if err != nil { 80 | panic(err) 81 | } 82 | 83 | _, err = ioctx.Read(fmt.Sprintf(keyFmt, key), *buf, uint64(from)) 84 | if err != nil { 85 | panic(err) 86 | } 87 | 88 | go func() { ioctx.Destroy() }() 89 | } 90 | -------------------------------------------------------------------------------- /userspace/backend/object/api/s3/s3.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package s3 5 | 6 | import ( 7 | "bufio" 8 | "bytes" 9 | "dis/parser" 10 | "fmt" 11 | "github.com/aws/aws-sdk-go/aws" 12 | "github.com/aws/aws-sdk-go/aws/request" 13 | "github.com/aws/aws-sdk-go/aws/session" 14 | "github.com/aws/aws-sdk-go/service/s3" 15 | "github.com/aws/aws-sdk-go/service/s3/s3manager" 16 | "os" 17 | "strconv" 18 | "time" 19 | ) 20 | 21 | const ( 22 | configSection = "backend.object.s3" 23 | envPrefix = "dis_backend_object_s3" 24 | ) 25 | 26 | var ( 27 | uploader *s3manager.Uploader 28 | downloader *s3manager.Downloader 29 | client *s3.S3 30 | bucket string 31 | remote string 32 | region string 33 | FnHeaderToMap func(header *[]byte, key, size int64) 34 | ) 35 | 36 | func Init() { 37 | v := parser.Sub(configSection) 38 | v.SetEnvPrefix(envPrefix) 39 | v.BindEnv("bucket") 40 | v.BindEnv("region") 41 | v.BindEnv("remote") 42 | bucket = v.GetString("bucket") 43 | region = v.GetString("region") 44 | remote = v.GetString("remote") 45 | 46 | if bucket == "" || region == "" || remote == "" { 47 | panic("") 48 | } 49 | 50 | connect() 51 | } 52 | 53 | const keyFmt = "%08d" 54 | 55 | func Upload(key int64, buf *[]byte) { 56 | var err error 57 | for i := 0; i < 200; i++ { 58 | _, err = uploader.Upload(&s3manager.UploadInput{ 59 | Bucket: &bucket, 60 | Key: aws.String(fmt.Sprintf(keyFmt, key)), 61 | Body: bytes.NewReader(*buf), 62 | }) 63 | if err == nil { 64 | break 65 | } 66 | time.Sleep(time.Duration(i) * time.Millisecond) 67 | } 68 | if err != nil { 69 | panic(err) 70 | } 71 | } 72 | 73 | func Download(key int64, buf *[]byte, from, to int64) { 74 | if to-from+1 != int64(len(*buf)) { 75 | panic("") 76 | } 77 | rng := fmt.Sprintf("bytes=%d-%d", from, to) 78 | b := aws.NewWriteAtBuffer(*buf) 79 | var err error 80 | for i := 0; i < 200; i++ { 81 | _, err = downloader.Download(b, &s3.GetObjectInput{ 82 | Bucket: &bucket, 83 | Key: aws.String(fmt.Sprintf(keyFmt, key)), 84 | Range: &rng, 85 | }) 86 | if err == nil { 87 | break 88 | } 89 | time.Sleep(time.Duration(i) * time.Millisecond) 90 | } 91 | if err != nil { 92 | panic(err) 93 | } 94 | } 95 | 96 | func Delete(key int64) { 97 | _, err := client.DeleteObject(&s3.DeleteObjectInput{Bucket: &bucket, Key: aws.String(fmt.Sprintf(keyFmt, key))}) 98 | if err != nil { 99 | fmt.Println(err) 100 | } 101 | } 102 | 103 | func Void(key int64) { 104 | _, err := uploader.Upload(&s3manager.UploadInput{ 105 | Bucket: &bucket, 106 | Key: aws.String(fmt.Sprintf(keyFmt, key)), 107 | Body: bytes.NewReader(make([]byte, 0)), 108 | }) 109 | if err != nil { 110 | fmt.Println(err) 111 | } 112 | } 113 | 114 | func connect() { 115 | sess, err := session.NewSession(&aws.Config{ 116 | Endpoint: &remote, 117 | Region: ®ion, 118 | S3ForcePathStyle: aws.Bool(true), 119 | S3DisableContentMD5Validation: aws.Bool(true), 120 | }) 121 | if err != nil { 122 | panic(err) 123 | } 124 | 125 | client = s3.New(sess) 126 | uploader = s3manager.NewUploader(sess) 127 | downloader = s3manager.NewDownloader(sess) 128 | 129 | uploader.Concurrency = 1 130 | s3manager.WithUploaderRequestOptions(request.Option(func(r *request.Request) { 131 | r.HTTPRequest.Header.Add("X-Amz-Content-Sha256", "UNSIGNED-PAYLOAD") 132 | }))(uploader) 133 | downloader.Concurrency = 1 134 | 135 | _, err = client.HeadBucket(&s3.HeadBucketInput{Bucket: aws.String(bucket)}) 136 | if err == nil { 137 | fmt.Println("Do you want to recover volume from", bucket, "? [Y/n]") 138 | yn, _ := bufio.NewReader(os.Stdin).ReadString('\n') 139 | 140 | if yn == "N\n" || yn == "n\n" { 141 | err = client.ListObjectsV2Pages(&s3.ListObjectsV2Input{ 142 | Bucket: &bucket, 143 | }, func(page *s3.ListObjectsV2Output, last bool) bool { 144 | for _, o := range page.Contents { 145 | client.DeleteObject(&s3.DeleteObjectInput{Bucket: &bucket, Key: o.Key}) 146 | } 147 | return true 148 | }) 149 | if err != nil { 150 | panic(err) 151 | } 152 | 153 | //_, err = client.DeleteBucket(&s3.DeleteBucketInput{Bucket: &bucket}) 154 | //if err != nil { 155 | // panic(err) 156 | //} 157 | //_, err = client.CreateBucket(&s3.CreateBucketInput{Bucket: &bucket}) 158 | //if err != nil { 159 | // panic(err) 160 | //} 161 | err = client.WaitUntilBucketExists(&s3.HeadBucketInput{Bucket: &bucket}) 162 | if err != nil { 163 | panic(err) 164 | } 165 | return 166 | } 167 | 168 | var lastKey int64 = -1 169 | var finished bool 170 | err = client.ListObjectsV2Pages(&s3.ListObjectsV2Input{ 171 | Bucket: aws.String(bucket), 172 | }, func(page *s3.ListObjectsV2Output, last bool) bool { 173 | for _, o := range page.Contents { 174 | key, _ := strconv.ParseInt(*o.Key, 10, 64) 175 | if finished { 176 | Delete(key) 177 | continue 178 | } 179 | if lastKey != -1 && key != lastKey+1 { 180 | finished = true 181 | continue 182 | } 183 | lastKey = key 184 | if *o.Size == 0 { 185 | continue 186 | } 187 | headerSize := (*o.Size / 512) * 16 188 | buf := make([]byte, headerSize) 189 | Download(key, &buf, 0, headerSize-1) 190 | FnHeaderToMap(&buf, key, *o.Size) 191 | } 192 | return true 193 | }) 194 | if err != nil { 195 | panic(err) 196 | } 197 | } else { 198 | var err error 199 | for i := 0; i < 200; i++ { 200 | _, err = client.CreateBucket(&s3.CreateBucketInput{Bucket: &bucket}) 201 | if err == nil { 202 | break 203 | } 204 | time.Sleep(time.Duration(i) * time.Millisecond) 205 | } 206 | if err != nil { 207 | panic(err) 208 | } 209 | 210 | err = client.WaitUntilBucketExists(&s3.HeadBucketInput{Bucket: &bucket}) 211 | if err != nil { 212 | panic(err) 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /userspace/backend/object/extmap/extmap.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package extmap 5 | 6 | import ( 7 | "dis/backend/object/gc" 8 | "dis/extent" 9 | "github.com/emirpasic/gods/trees/redblacktree" 10 | "github.com/emirpasic/gods/utils" 11 | "sync" 12 | ) 13 | 14 | type ExtentMap struct { 15 | rbt *redblacktree.Tree 16 | mutex sync.RWMutex 17 | } 18 | 19 | type Extent struct { 20 | LBA int64 21 | PBA int64 22 | Len int64 23 | Key int64 24 | } 25 | 26 | func New() *ExtentMap { 27 | m := ExtentMap{rbt: redblacktree.NewWith(utils.Int64Comparator)} 28 | return &m 29 | } 30 | 31 | func (this *ExtentMap) Update(e *[]*Extent) { 32 | this.mutex.Lock() 33 | for _, ee := range *e { 34 | this.update(ee) 35 | } 36 | this.mutex.Unlock() 37 | } 38 | 39 | func (this *ExtentMap) UpdateSingle(e *Extent) { 40 | this.mutex.Lock() 41 | this.update(e) 42 | this.mutex.Unlock() 43 | } 44 | 45 | func (this *ExtentMap) Find(e *extent.Extent) *[]*Extent { 46 | this.mutex.RLock() 47 | extents := this.find(&Extent{e.LBA, -1, e.Len, -1}) 48 | 49 | this.mutex.RUnlock() 50 | return extents 51 | } 52 | 53 | func (this *ExtentMap) RLock() { 54 | this.mutex.RLock() 55 | } 56 | 57 | func (this *ExtentMap) RUnlock() { 58 | this.mutex.RUnlock() 59 | } 60 | 61 | func (this *ExtentMap) Lock() { 62 | this.mutex.Lock() 63 | } 64 | 65 | func (this *ExtentMap) Unlock() { 66 | this.mutex.Unlock() 67 | } 68 | 69 | func (this *ExtentMap) GenerateWritelist(purgeList *map[int64]bool) *[]*Extent { 70 | writelist := new([]*Extent) 71 | 72 | it := this.rbt.Iterator() 73 | for it.Next() { 74 | e := it.Value().(*Extent) 75 | if (*purgeList)[e.Key] { 76 | *writelist = append(*writelist, e) 77 | } 78 | } 79 | 80 | return writelist 81 | } 82 | 83 | func (this *ExtentMap) insert(e *Extent) { 84 | this.rbt.Put(e.LBA, e) 85 | } 86 | 87 | func (this *ExtentMap) next(e *Extent) *Extent { 88 | next, _ := this.rbt.Ceiling(e.LBA + 1) 89 | if next == nil { 90 | return nil 91 | } 92 | return next.Value.(*Extent) 93 | } 94 | 95 | func (this *ExtentMap) remove(e *Extent) { 96 | this.rbt.Remove(e.LBA) 97 | } 98 | 99 | func (this *ExtentMap) geq(e *Extent) *redblacktree.Node { 100 | 101 | if e == nil { 102 | return nil 103 | } 104 | 105 | if f, _ := this.rbt.Floor(e.LBA); f != nil { 106 | if fVal := f.Value.(*Extent); fVal.LBA+fVal.Len > e.LBA { 107 | return f 108 | } 109 | } 110 | 111 | if c, _ := this.rbt.Ceiling(e.LBA); c != nil { 112 | return c 113 | } 114 | 115 | return nil 116 | } 117 | 118 | func (this *ExtentMap) update(e *Extent) { 119 | node := this.geq(e) 120 | if node != nil { 121 | geq := node.Value.(*Extent) 122 | if geq.LBA < e.LBA && geq.LBA+geq.Len > e.LBA+e.Len { 123 | n := &Extent{ 124 | LBA: e.LBA + e.Len, 125 | Len: geq.LBA + geq.Len - (e.LBA + e.Len), 126 | Key: geq.Key, 127 | } 128 | n.PBA = geq.PBA + geq.Len - n.Len 129 | 130 | gc.Free(geq.Key, n.Len) 131 | gc.Add(n.Key, n.Len) 132 | 133 | geq.Len = e.LBA - geq.LBA 134 | this.insert(n) 135 | geq = n 136 | node = this.geq(geq) 137 | 138 | } else if geq.LBA < e.LBA { 139 | gc.Free(geq.Key, geq.Len-e.LBA+geq.LBA) 140 | geq.Len = e.LBA - geq.LBA 141 | geq = this.next(geq) 142 | node = this.geq(geq) 143 | } 144 | 145 | for geq != nil && geq.LBA+geq.Len <= e.LBA+e.Len { 146 | tmp := this.next(geq) 147 | this.remove(geq) 148 | gc.Free(geq.Key, geq.Len) 149 | geq = tmp 150 | node = this.geq(geq) 151 | } 152 | 153 | if geq != nil && e.LBA+e.Len > geq.LBA { 154 | n := e.LBA + e.Len - geq.LBA 155 | geq.LBA += n 156 | node.Key = geq.LBA 157 | geq.PBA += n 158 | geq.Len -= n 159 | gc.Free(geq.Key, n) 160 | } 161 | } 162 | 163 | this.insert(&Extent{e.LBA, e.PBA, e.Len, e.Key}) 164 | gc.Add(e.Key, e.Len) 165 | } 166 | 167 | func (this *ExtentMap) find(e *Extent) *[]*Extent { 168 | l := make([]*Extent, 0, 256) 169 | for { 170 | node := this.geq(e) 171 | var geq *Extent 172 | if node != nil { 173 | geq = node.Value.(*Extent) 174 | } 175 | 176 | if geq == nil || geq.LBA >= e.LBA+e.Len { 177 | if len(l) == cap(l) { 178 | println("extent list size to small #1") 179 | } 180 | l = append(l, &Extent{e.LBA, -1, e.Len, -1}) 181 | 182 | return &l 183 | } 184 | 185 | if e.LBA < geq.LBA { 186 | if len(l) == cap(l) { 187 | println("extent list size to small #2") 188 | } 189 | l = append(l, &Extent{e.LBA, -1, geq.LBA - e.LBA, -1}) 190 | 191 | e.Len -= geq.LBA - e.LBA 192 | 193 | e.LBA = geq.LBA 194 | e.PBA = geq.PBA 195 | e.Key = geq.Key 196 | } else { 197 | if geq.LBA+geq.Len-e.LBA < e.Len { 198 | if len(l) == cap(l) { 199 | println("extent list size to small #3") 200 | } 201 | l = append(l, &Extent{ 202 | LBA: e.LBA, 203 | PBA: geq.PBA + e.LBA - geq.LBA, 204 | Len: geq.LBA + geq.Len - e.LBA, 205 | Key: geq.Key, 206 | }) 207 | 208 | e.Len -= geq.LBA + geq.Len - e.LBA 209 | 210 | e.LBA = geq.LBA + geq.Len 211 | e.PBA = -1 212 | e.Key = -1 213 | } else { 214 | if len(l) == cap(l) { 215 | println("extent list size to small #4") 216 | } 217 | l = append(l, &Extent{e.LBA, geq.PBA + e.LBA - geq.LBA, e.Len, geq.Key}) 218 | return &l 219 | } 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /userspace/backend/object/gc.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package object 5 | 6 | import ( 7 | "dis/backend/object/api/s3" 8 | "dis/backend/object/extmap" 9 | "dis/backend/object/gc" 10 | "fmt" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | func getDownloadChan() chan downloadJob { 16 | ch := make(chan downloadJob) 17 | for i := 0; i < 5; i++ { 18 | go func() { 19 | for c := range ch { 20 | for { 21 | mutex.RLock() 22 | wait := uploading[c.e.Key] 23 | mutex.RUnlock() 24 | if wait != true { 25 | break 26 | } 27 | time.Sleep(500 * time.Microsecond) 28 | } 29 | partDownload(c.e, c.buf) 30 | c.reads.Done() 31 | } 32 | }() 33 | } 34 | 35 | return ch 36 | } 37 | 38 | func getUploadChan() (chan *Object, *sync.WaitGroup) { 39 | ch := make(chan *Object) 40 | var uploadsWG sync.WaitGroup 41 | for i := 0; i < 5; i++ { 42 | go func() { 43 | for c := range ch { 44 | c.reads.Wait() 45 | *c.buf = (*c.buf)[:cap(*c.buf)] 46 | s3.Upload(c.key, c.buf) 47 | uploadsWG.Done() 48 | } 49 | }() 50 | } 51 | 52 | return ch, &uploadsWG 53 | } 54 | 55 | func gcthread() { 56 | if gcMode != "on" && gcMode != "silent" { 57 | return 58 | } 59 | const gcPeriod = 5 * time.Second 60 | for { 61 | time.Sleep(gcPeriod) 62 | if !gc.Needed() { 63 | continue 64 | } 65 | gc.Running.Lock() 66 | em.RLock() 67 | fmt.Println("GC Started") 68 | purgeSet := gc.GetPurgeSetGreedy() 69 | fmt.Println("Objects viable for GC: ", len(*purgeSet)) 70 | wl := em.GenerateWritelist(purgeSet) 71 | newPBAs := make([]int64, len(*wl)) 72 | newKeys := make([]int64, len(*wl)) 73 | slicedKeys := newKeys 74 | 75 | downloader := getDownloadChan() 76 | uploader, uploadsWG := getUploadChan() 77 | 78 | o := nextObject(true) 79 | upload := func() { 80 | if o.extents == 0 { 81 | return 82 | } 83 | o.assignKey() 84 | 85 | var i int64 86 | for i = 0; i < o.extents; i++ { 87 | slicedKeys[i] = o.key 88 | } 89 | slicedKeys = slicedKeys[i:] 90 | 91 | uploadsWG.Add(1) 92 | uploader <- o 93 | o = nextObject(true) 94 | } 95 | 96 | for i, e := range *wl { 97 | if o.size()+e.Len*512 > objectSize { 98 | upload() 99 | } 100 | 101 | newPBAs[i] = o.blocks 102 | slice := o.add(e.LBA, e.Len, true) 103 | 104 | o.reads.Add(1) 105 | go func(o *Object, e *extmap.Extent) { 106 | downloader <- downloadJob{e, &slice, o.reads} 107 | }(o, e) 108 | } 109 | upload() 110 | 111 | uploadsWG.Wait() 112 | em.RUnlock() 113 | em.Lock() 114 | 115 | for i, e := range *wl { 116 | e.PBA = newPBAs[i] 117 | e.Key = newKeys[i] 118 | gc.Add(e.Key, e.Len) 119 | } 120 | 121 | em.Unlock() 122 | gc.Running.Unlock() 123 | 124 | for key := range *purgeSet { 125 | s3.Void(key) 126 | gc.Destroy(key) 127 | } 128 | 129 | fmt.Println("GC Done") 130 | } 131 | } 132 | 133 | func gcthread2() { 134 | if gcMode != "on" && gcMode != "silent" { 135 | return 136 | } 137 | const gcPeriod = 5 * time.Second 138 | for { 139 | time.Sleep(gcPeriod) 140 | if !gc.Needed() { 141 | continue 142 | } 143 | gc.Running.Lock() 144 | em.RLock() 145 | 146 | fmt.Println("GC Started") 147 | purgeSet := gc.GetPurgeSetGreedy() 148 | fmt.Println("Objects viable for GC: ", len(*purgeSet)) 149 | 150 | gc.Running.Unlock() 151 | em.RUnlock() 152 | 153 | downloader := getDownloadChan() 154 | 155 | // Buffer for downloaded objects 156 | buffer := make(map[int64][]byte) 157 | 158 | // WaitGroup for running downloads 159 | var wg sync.WaitGroup 160 | 161 | for k, _ := range *purgeSet { 162 | // Object 163 | b := make([]byte, objectSize) 164 | 165 | // Extent for the whole object 166 | e := extmap.Extent{ 167 | LBA: 0, 168 | PBA: 0, 169 | Len: objectSize / 512, 170 | Key: k, 171 | } 172 | 173 | wg.Add(1) 174 | downloader <- downloadJob{&e, &b, &wg} 175 | 176 | // Store object to buffer. 177 | // 178 | // Does not need to wait for download, just slice is 179 | // copied and waiting is done after the end of the loop. 180 | buffer[k] = b 181 | } 182 | wg.Wait() 183 | 184 | gc.Running.Lock() 185 | em.RLock() 186 | 187 | wl := em.GenerateWritelist(purgeSet) 188 | newPBAs := make([]int64, len(*wl)) 189 | newKeys := make([]int64, len(*wl)) 190 | slicedKeys := newKeys 191 | 192 | uploader, uploadsWG := getUploadChan() 193 | 194 | o := nextObject(true) 195 | upload := func() { 196 | if o.extents == 0 { 197 | return 198 | } 199 | o.assignKey() 200 | 201 | var i int64 202 | for i = 0; i < o.extents; i++ { 203 | slicedKeys[i] = o.key 204 | } 205 | slicedKeys = slicedKeys[i:] 206 | 207 | uploadsWG.Add(1) 208 | uploader <- o 209 | o = nextObject(true) 210 | } 211 | 212 | for i, e := range *wl { 213 | if o.size()+e.Len*512 > objectSize { 214 | upload() 215 | } 216 | 217 | newPBAs[i] = o.blocks 218 | slice := o.add(e.LBA, e.Len, true) 219 | 220 | // Just copy needed extents from buffered objects 221 | copy(slice, buffer[e.Key][e.PBA*512:(e.PBA+e.Len)*512]) 222 | } 223 | upload() 224 | 225 | uploadsWG.Wait() 226 | em.RUnlock() 227 | em.Lock() 228 | 229 | for i, e := range *wl { 230 | e.PBA = newPBAs[i] 231 | e.Key = newKeys[i] 232 | gc.Add(e.Key, e.Len) 233 | } 234 | 235 | em.Unlock() 236 | gc.Running.Unlock() 237 | 238 | for key := range *purgeSet { 239 | s3.Void(key) 240 | gc.Destroy(key) 241 | } 242 | 243 | fmt.Println("GC Done") 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /userspace/backend/object/gc/gc.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package gc 5 | 6 | import ( 7 | "fmt" 8 | "github.com/emirpasic/gods/trees/redblacktree" 9 | "github.com/emirpasic/gods/utils" 10 | "sync" 11 | "sync/atomic" 12 | ) 13 | 14 | const ratio = 0.2 15 | const gcTarget = 0.3 16 | 17 | var ( 18 | mutex sync.RWMutex 19 | usage = make(map[int64]*objectUsage) 20 | Running = new(sync.Mutex) 21 | total int64 22 | valid int64 23 | statcnt int64 24 | ) 25 | 26 | type objectUsage struct { 27 | total int64 28 | used int64 29 | } 30 | 31 | func Free(key, size int64) { 32 | mutex.RLock() 33 | o := usage[key] 34 | mutex.RUnlock() 35 | 36 | atomic.AddInt64(&o.used, -size) 37 | atomic.AddInt64(&valid, -size) 38 | } 39 | 40 | func Add(key, size int64) { 41 | mutex.RLock() 42 | o := usage[key] 43 | mutex.RUnlock() 44 | 45 | atomic.AddInt64(&o.used, size) 46 | atomic.AddInt64(&total, size) 47 | atomic.AddInt64(&valid, size) 48 | } 49 | 50 | func Create(key, total int64) { 51 | mutex.Lock() 52 | defer mutex.Unlock() 53 | 54 | usage[key] = &objectUsage{total, 0} 55 | } 56 | 57 | func Destroy(key int64) { 58 | mutex.Lock() 59 | defer mutex.Unlock() 60 | 61 | o := usage[key] 62 | 63 | atomic.AddInt64(&valid, -o.used) 64 | atomic.AddInt64(&total, -o.total) 65 | delete(usage, key) 66 | } 67 | 68 | func PrintStats(delay int64, gcMode string) { 69 | total := atomic.LoadInt64(&total) 70 | valid := atomic.LoadInt64(&valid) 71 | garbage := total - valid 72 | 73 | fmt.Printf("STATS: %v,%v,%v,%v,%v,%v\n", statcnt, total, valid, garbage, float64(garbage)/float64(total), gcMode) 74 | 75 | statcnt += delay 76 | } 77 | 78 | func Needed() bool { 79 | total := atomic.LoadInt64(&total) 80 | valid := atomic.LoadInt64(&valid) 81 | garbage := total - valid 82 | 83 | if float64(garbage)/float64(total) >= gcTarget { 84 | return true 85 | } 86 | return false 87 | } 88 | 89 | func GetPurgeSetGreedy() *map[int64]bool { 90 | t := redblacktree.NewWith(func(a, b interface{}) int { 91 | return -utils.Int64Comparator(a, b) 92 | }) 93 | 94 | purgeSet := map[int64]bool{} 95 | 96 | mutex.RLock() 97 | for k, v := range usage { 98 | t.Put(v.total-v.used, k) 99 | } 100 | mutex.RUnlock() 101 | 102 | total := atomic.LoadInt64(&total) 103 | valid := atomic.LoadInt64(&valid) 104 | invalid := total - valid 105 | toCollect := float64(invalid) - gcTarget*float64(total) 106 | 107 | it := t.Iterator() 108 | for it.Next() { 109 | k := it.Value().(int64) 110 | purgeSet[k] = true 111 | invalid := it.Key().(int64) 112 | toCollect -= float64(invalid) 113 | if toCollect < 0 { 114 | break 115 | } 116 | } 117 | 118 | return &purgeSet 119 | } 120 | 121 | func GetPurgeSetUniform() *map[int64]bool { 122 | purgeSet := map[int64]bool{} 123 | 124 | mutex.RLock() 125 | for k, v := range usage { 126 | if r := float64(v.used) / float64(v.total); r < ratio { 127 | purgeSet[k] = true 128 | } 129 | } 130 | mutex.RUnlock() 131 | 132 | return &purgeSet 133 | } 134 | -------------------------------------------------------------------------------- /userspace/backend/object/object.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package object 5 | 6 | import ( 7 | "dis/backend/object/api/rados" 8 | "dis/backend/object/api/s3" 9 | "dis/backend/object/extmap" 10 | "dis/backend/object/gc" 11 | "dis/extent" 12 | "dis/parser" 13 | "encoding/binary" 14 | "fmt" 15 | "sync/atomic" 16 | "time" 17 | ) 18 | 19 | const ( 20 | configSection = "backend.object" 21 | envPrefix = "dis_backend_object" 22 | ) 23 | 24 | var ( 25 | bucket string 26 | region string 27 | remote string 28 | em *extmap.ExtentMap 29 | workloads chan *[]extent.Extent 30 | seqNumber int64 31 | api string 32 | gcMode string 33 | gcVersion int64 34 | objectSizeM int64 35 | objectSize int64 36 | uploadF func(key int64, buf *[]byte) 37 | downloadF func(key int64, buf *[]byte, from, to int64) 38 | ) 39 | 40 | type ObjectBackend struct{} 41 | 42 | func (this *ObjectBackend) Init() { 43 | v := parser.Sub(configSection) 44 | v.SetEnvPrefix(envPrefix) 45 | 46 | v.BindEnv("api") 47 | v.BindEnv("gcMode") 48 | v.BindEnv("gcVersion") 49 | v.BindEnv("objectSizeM") 50 | api = v.GetString("api") 51 | gcMode = v.GetString("gcMode") 52 | gcVersion = v.GetInt64("gcVersion") 53 | objectSizeM = v.GetInt64("objectSizeM") 54 | objectSize = objectSizeM * 1024 * 1024 55 | 56 | if gcMode != "on" && gcMode != "statsOnly" && gcMode != "off" && gcMode != "silent" && objectSize == 0 { 57 | panic("") 58 | } 59 | 60 | em = extmap.New() 61 | 62 | if api == "s3" { 63 | uploadF = s3.Upload 64 | downloadF = s3.Download 65 | s3.FnHeaderToMap = func(header *[]byte, key, size int64) { 66 | atomic.StoreInt64(&seqNumber, key+1) 67 | const int64Size = 8 68 | var blocks int64 = (writelistLen * 16) / 512 69 | 70 | gc.Create(key, size/512) 71 | for i := 0; i < len(*header); i += 2 * int64Size { 72 | var e extmap.Extent 73 | e.Key = key 74 | e.PBA = blocks 75 | e.LBA, _ = binary.Varint((*header)[i : i+int64Size]) 76 | e.Len, _ = binary.Varint((*header)[i+int64Size : i+2*int64Size]) 77 | if e.Len == 0 { 78 | break 79 | } 80 | em.UpdateSingle(&e) 81 | blocks += e.Len 82 | } 83 | } 84 | s3.Init() 85 | } else if api == "rados" { 86 | uploadF = rados.Upload 87 | downloadF = rados.Download 88 | rados.Init() 89 | } else { 90 | panic("") 91 | } 92 | 93 | workloads = make(chan *[]extent.Extent) 94 | go writer() 95 | 96 | for i := 0; i < cacheWriteWorkers; i++ { 97 | go cacheWriteWorker(cacheWriteChan) 98 | } 99 | 100 | for i := 0; i < downloadWorkers; i++ { 101 | //go downloadWorker(downloadChan) 102 | go func() { 103 | for d := range downloadChan { 104 | for { 105 | mutex.RLock() 106 | wait := uploading[d.e.Key] 107 | mutex.RUnlock() 108 | if wait != true { 109 | break 110 | } 111 | time.Sleep(500 * time.Microsecond) 112 | } 113 | partDownload(d.e, d.buf) 114 | d.reads.Done() 115 | } 116 | }() 117 | } 118 | 119 | switch gcVersion { 120 | case 1: 121 | go gcthread() 122 | case 2: 123 | go gcthread2() 124 | default: 125 | go gcthread() 126 | } 127 | 128 | go func() { 129 | if gcMode != "on" && gcMode != "statsOnly" { 130 | return 131 | } 132 | 133 | fmt.Println("STATS: time,total,valid,invalid,ratio,gcmode") 134 | const delaySec = 5 135 | for { 136 | gc.PrintStats(delaySec, gcMode) 137 | time.Sleep(delaySec * time.Second) 138 | } 139 | }() 140 | } 141 | -------------------------------------------------------------------------------- /userspace/backend/object/read.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package object 5 | 6 | import ( 7 | "dis/backend/object/extmap" 8 | "dis/cache" 9 | "dis/extent" 10 | "dis/l2cache" 11 | "sync" 12 | "time" 13 | //"fmt" 14 | ) 15 | 16 | const ( 17 | downloadWorkers = 20 18 | cacheWriteWorkers = 20 19 | ) 20 | 21 | var ( 22 | cacheWriteChan = make(chan cacheWriteJob) 23 | downloadChan = make(chan downloadJob) 24 | ) 25 | 26 | func partDownload(e *extmap.Extent, slice *[]byte) { 27 | downloadF(e.Key, slice, e.PBA*512, (e.PBA+e.Len)*512-1) 28 | } 29 | 30 | type cacheWriteJob struct { 31 | e *extent.Extent 32 | reads *sync.WaitGroup 33 | } 34 | 35 | type downloadJob struct { 36 | e *extmap.Extent 37 | buf *[]byte 38 | reads *sync.WaitGroup 39 | } 40 | 41 | func fillPartFromChunk(slice []byte, chunkI int64, chunkTo, chunkFrom int64, wg *sync.WaitGroup, key int64) { 42 | id := func(key, chunk int64) int64 { 43 | return key*1000 + chunk 44 | } 45 | 46 | cacheKey := id(key, chunkI) 47 | again: 48 | chunk, ok := l2cache.GetOrReserveChunk(cacheKey) 49 | if !ok { 50 | buf := make([]byte, l2cache.ChunkSize) 51 | downloadF(key, &buf, chunkI*l2cache.ChunkSize, chunkI*l2cache.ChunkSize+l2cache.ChunkSize-1) 52 | l2cache.PutChunk(cacheKey, &buf) 53 | chunk = &buf 54 | } else if chunk == nil { 55 | time.Sleep(100 * time.Microsecond) 56 | goto again 57 | } 58 | copy(slice, (*chunk)[chunkFrom:chunkTo]) 59 | wg.Done() 60 | } 61 | 62 | func downloadWorker(jobs <-chan downloadJob) { 63 | for job := range jobs { 64 | first := job.e.PBA * 512 / l2cache.ChunkSize 65 | last := (job.e.PBA + job.e.Len - 1) * 512 / l2cache.ChunkSize 66 | part := *job.buf 67 | var waitChunks sync.WaitGroup 68 | waitChunks.Add(int(last - first + 1)) 69 | for i := first; i <= last; i++ { 70 | chunkFrom, chunkTo := int64(0), int64(l2cache.ChunkSize) 71 | if i == first { 72 | chunkFrom = job.e.PBA * 512 % l2cache.ChunkSize 73 | } 74 | 75 | if i == last { 76 | chunkTo = ((job.e.PBA+job.e.Len)*512-1)%l2cache.ChunkSize + 1 77 | } 78 | go fillPartFromChunk(part, i, chunkTo, chunkFrom, &waitChunks, job.e.Key) 79 | 80 | if i != last { 81 | part = part[chunkTo-chunkFrom:] 82 | } 83 | } 84 | waitChunks.Wait() 85 | job.reads.Done() 86 | } 87 | } 88 | 89 | func cacheWriteWorker(jobs <-chan cacheWriteJob) { 90 | for job := range jobs { 91 | buf := make([]byte, job.e.Len*512) 92 | s3reads := new(sync.WaitGroup) 93 | 94 | //em.RLock() 95 | 96 | for _, e := range *em.Find(job.e) { 97 | if e.Key == -1 { 98 | //em.Dump() 99 | continue 100 | } 101 | s := (e.LBA - job.e.LBA) * 512 102 | ss := s + e.Len*512 103 | slice := buf[s:ss] 104 | s3reads.Add(1) 105 | downloadChan <- downloadJob{e, &slice, s3reads} 106 | } 107 | 108 | //em.RUnlock() 109 | 110 | s3reads.Wait() 111 | cache.Write(&buf, job.e.PBA*512) 112 | job.reads.Done() 113 | } 114 | } 115 | 116 | func (this *ObjectBackend) Read(extents *[]extent.Extent) { 117 | var reads sync.WaitGroup 118 | 119 | reads.Add(len(*extents)) 120 | for i := range *extents { 121 | e := &(*extents)[i] 122 | cacheWriteChan <- cacheWriteJob{e, &reads} 123 | } 124 | reads.Wait() 125 | //e := extent.Extent{296, -1, 8} 126 | //r := *em.Find(&e) 127 | //fmt.Println("XXX:", *r[0]) 128 | } 129 | -------------------------------------------------------------------------------- /userspace/backend/object/write.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package object 5 | 6 | import ( 7 | "dis/backend/object/extmap" 8 | "dis/backend/object/gc" 9 | "dis/cache" 10 | "dis/extent" 11 | "encoding/binary" 12 | "sync" 13 | "sync/atomic" 14 | "time" 15 | //"fmt" 16 | ) 17 | 18 | const ( 19 | uploadWorkers = 30 20 | cacheReadWorkers = 30 21 | maxWritePeriod = 5 * time.Second 22 | ) 23 | 24 | var ( 25 | mutex sync.RWMutex 26 | uploading = make(map[int64]bool) 27 | writelistLen int64 28 | ) 29 | 30 | type cacheReadJob struct { 31 | e *extent.Extent 32 | buf *[]byte 33 | reads *sync.WaitGroup 34 | allReads *sync.WaitGroup 35 | } 36 | 37 | type Object struct { 38 | buf *[]byte 39 | writelist *[]*extmap.Extent 40 | blocks int64 41 | reads *sync.WaitGroup 42 | key int64 43 | extents int64 44 | } 45 | 46 | func nextObject(inGC bool) *Object { 47 | buf := make([]byte, 0, objectSize) 48 | var writelist []*extmap.Extent 49 | if inGC { 50 | writelist = make([]*extmap.Extent, 0, 0) 51 | } else { 52 | writelist = make([]*extmap.Extent, 0, writelistLen) 53 | } 54 | var reads sync.WaitGroup 55 | var headerBlocks int64 = (writelistLen * 16) / 512 56 | 57 | o := Object{ 58 | buf: &buf, 59 | writelist: &writelist, 60 | reads: &reads, 61 | blocks: headerBlocks, 62 | } 63 | 64 | return &o 65 | } 66 | 67 | func (this *Object) size() int64 { 68 | return this.blocks * 512 69 | } 70 | 71 | func (this *Object) assignKey() { 72 | this.key = atomic.LoadInt64(&seqNumber) 73 | atomic.AddInt64(&seqNumber, 1) 74 | var headerBlocks int64 = (writelistLen * 16) / 512 75 | gc.Create(this.key, this.blocks-headerBlocks) 76 | 77 | for _, e := range *this.writelist { 78 | e.Key = this.key 79 | } 80 | } 81 | 82 | func (o *Object) add(lba, length int64, inGC bool) []byte { 83 | *o.buf = (*o.buf)[:(o.blocks+length)*512] 84 | slice := (*o.buf)[o.blocks*512:] 85 | 86 | if !inGC { 87 | *o.writelist = append(*o.writelist, &extmap.Extent{ 88 | LBA: lba, 89 | PBA: o.blocks, 90 | Len: length, 91 | Key: o.key}) 92 | } 93 | 94 | o.fillHeader(lba, length) 95 | o.extents++ 96 | o.blocks += length 97 | 98 | return slice 99 | } 100 | 101 | func (o *Object) fillHeader(lba, length int64) { 102 | const int64Size = 8 103 | off := o.extents * int64Size * 2 104 | 105 | headerSlice := (*o.buf)[off:] 106 | binary.PutVarint(headerSlice, lba) 107 | 108 | headerSlice = headerSlice[int64Size:] 109 | binary.PutVarint(headerSlice, length) 110 | } 111 | 112 | func writer() { 113 | writelistLen = objectSize / 512 114 | cacheReadChan := make(chan cacheReadJob) 115 | for i := 0; i < cacheReadWorkers; i++ { 116 | go func() { 117 | for c := range cacheReadChan { 118 | cache.Read(c.buf, c.e.PBA*512) 119 | c.reads.Done() 120 | c.allReads.Done() 121 | } 122 | }() 123 | } 124 | 125 | uploadChan := make(chan *Object) 126 | for i := 0; i < uploadWorkers; i++ { 127 | go func() { 128 | for u := range uploadChan { 129 | *u.buf = (*u.buf)[:cap(*u.buf)] 130 | u.reads.Wait() 131 | uploadF(u.key, u.buf) 132 | mutex.Lock() 133 | delete(uploading, u.key) 134 | mutex.Unlock() 135 | } 136 | }() 137 | } 138 | 139 | ticker := time.NewTicker(maxWritePeriod) 140 | 141 | o := nextObject(false) 142 | upload := func() { 143 | if o.extents == 0 { 144 | return 145 | } 146 | gc.Running.Lock() 147 | o.assignKey() 148 | mutex.Lock() 149 | uploading[o.key] = true 150 | mutex.Unlock() 151 | em.Update(o.writelist) 152 | 153 | gc.Running.Unlock() 154 | 155 | uploadChan <- o 156 | o = nextObject(false) 157 | for len(ticker.C) > 0 { 158 | <-ticker.C 159 | } 160 | ticker.Reset(maxWritePeriod) 161 | } 162 | 163 | var allReads sync.WaitGroup 164 | for { 165 | select { 166 | case extents := <-workloads: 167 | for i := range *extents { 168 | e := &(*extents)[i] 169 | 170 | //fmt.Println("Writing:", *e) 171 | 172 | if o.size()+e.Len*512 > objectSize || len(ticker.C) > 0 { 173 | upload() 174 | } 175 | 176 | slice := o.add(e.LBA, e.Len, false) 177 | o.reads.Add(1) 178 | allReads.Add(1) 179 | cacheReadChan <- cacheReadJob{e, &slice, o.reads, &allReads} 180 | } 181 | allReads.Wait() 182 | case <-ticker.C: 183 | upload() 184 | } 185 | } 186 | } 187 | 188 | //func computeSectors(extents *[]extent.Extent) int64 { 189 | // var sectors int64 190 | // for i := range *extents { 191 | // e := &(*extents)[i] 192 | // sectors += e.Len 193 | // } 194 | // 195 | // return sectors 196 | //} 197 | 198 | //func writer2() { 199 | // mapUpdateChan := make(chan *[]*extmap.Extent, mapUpdateBuf) 200 | // //go mapUpdateWorker(mapUpdateChan) 201 | // 202 | // uploadChan := make(chan *Object, uploadBuf) 203 | // for i := 0; i < uploadWorkers; i++ { 204 | // go uploadWorker(uploadChan) 205 | // } 206 | // 207 | // o := nextObject(0) 208 | // for extents := range workloads { 209 | // pr := cache.NewPrereader(extents) 210 | // //cache.WriteUntrackMulti(extents) 211 | // sectors := computeSectors(extents) 212 | // for i := range *extents { 213 | // e := &(*extents)[i] 214 | // 215 | // from := o.blocks * 512 216 | // to := (o.blocks + e.Len) * 512 217 | // 218 | // *o.writelist = append(*o.writelist, &extmap.Extent{ 219 | // LBA: e.LBA, 220 | // PBA: o.blocks, 221 | // Len: e.Len, 222 | // Key: o.key, 223 | // }) 224 | // 225 | // o.blocks += e.Len 226 | // pr.Copy((*o.buf)[from:to], e.PBA*512) 227 | // } 228 | // 229 | // if (o.blocks+sectors)*512 > objectSize { 230 | // mapUpdateChan <- o.writelist 231 | // //uploadChan <- uploadJob{key, (*buf)[:blocks*512]} 232 | // o = nextObject(o.key + 1) 233 | // } 234 | // } 235 | //} 236 | 237 | func (this *ObjectBackend) Write(extents *[]extent.Extent) { 238 | workloads <- extents 239 | } 240 | -------------------------------------------------------------------------------- /userspace/cache/cache.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package cache 5 | 6 | import ( 7 | "dis/extent" 8 | "dis/parser" 9 | "math" 10 | 11 | "golang.org/x/sys/unix" 12 | ) 13 | 14 | const ( 15 | configSection = "cache" 16 | envPrefix = "dis_cache" 17 | ) 18 | 19 | var ( 20 | Base int64 21 | Bound int64 22 | Frontier int64 23 | file string 24 | fd int 25 | headerSectors int64 = 8 26 | ) 27 | 28 | func Init() { 29 | v := parser.Sub(configSection) 30 | v.SetEnvPrefix(envPrefix) 31 | v.BindEnv("base") 32 | v.BindEnv("bound") 33 | v.BindEnv("file") 34 | Base = v.GetInt64("base") 35 | Bound = v.GetInt64("bound") 36 | file = v.GetString("file") 37 | 38 | if Base == 0 || Bound == 0 || file == "" { 39 | panic("") 40 | } 41 | Frontier = Base 42 | 43 | var err error 44 | fd, err = unix.Open(file, unix.O_RDWR|unix.O_DIRECT, 0) 45 | if err != nil { 46 | panic(err) 47 | } 48 | } 49 | 50 | func Write(buf *[]byte, dest int64) { 51 | _, err := unix.Pwrite(fd, *buf, dest) 52 | if err != nil { 53 | panic(err) 54 | } 55 | } 56 | 57 | func Read(buf *[]byte, dest int64) { 58 | _, err := unix.Pread(fd, *buf, dest) 59 | if err != nil { 60 | panic(err) 61 | } 62 | } 63 | 64 | func Reserve(e *extent.Extent) { 65 | if Frontier+e.Len >= Bound { 66 | Frontier = Base 67 | } 68 | e.PBA = Frontier 69 | Frontier += roundUp(e.Len, 8) 70 | } 71 | 72 | func roundDown(x, y int64) int64 { return x - x%y } 73 | func roundUp(x, y int64) int64 { return roundDown(x+y-1, y) } 74 | 75 | type Prereader struct { 76 | buf []byte 77 | off1 int64 78 | off2 int64 79 | } 80 | 81 | func NewPrereader(extents *[]extent.Extent) *Prereader { 82 | prereader := new(Prereader) 83 | var begin, end bool 84 | for i := range *extents { 85 | e := &(*extents)[i] 86 | if e.PBA == 0+headerSectors { 87 | begin = true 88 | } 89 | if e.PBA > 2*Base/3 { 90 | end = true 91 | } 92 | } 93 | wrapped := begin && end 94 | 95 | if wrapped { 96 | minL := int64(0) 97 | maxL := minL 98 | minR := Base 99 | maxR := int64(math.MinInt64) 100 | for i := range *extents { 101 | e := &(*extents)[i] 102 | if e.PBA < maxL || math.Abs(float64(e.PBA-maxL)) < math.Abs(float64(e.PBA+e.Len-minR)) { 103 | if e.PBA+e.Len > maxL { 104 | maxL = e.PBA + e.Len 105 | } 106 | } else { 107 | if e.PBA < minR { 108 | minR = e.PBA 109 | } 110 | if e.PBA+e.Len > maxR { 111 | maxR = e.PBA + e.Len 112 | } 113 | } 114 | } 115 | prereader.buf = make([]byte, (maxL-minL+maxR-minR)*512) 116 | prereader.off1 = minL 117 | prereader.off2 = minR 118 | bufL := prereader.buf[:(maxL-minL)*512] 119 | bufR := prereader.buf[(maxR-minR)*512:] 120 | Read(&bufL, minL*512) 121 | Read(&bufR, minR*512) 122 | } else { 123 | var min, max int64 = math.MaxInt64, math.MinInt64 124 | for i := range *extents { 125 | e := &(*extents)[i] 126 | if e.PBA < min { 127 | min = e.PBA 128 | } 129 | if e.PBA+e.Len > max { 130 | max = e.PBA + e.Len 131 | } 132 | } 133 | prereader.buf = make([]byte, (max-min)*512) 134 | prereader.off1 = min 135 | prereader.off2 = 0 136 | Read(&prereader.buf, min*512) 137 | } 138 | 139 | return prereader 140 | } 141 | 142 | func (this *Prereader) Copy(buf []byte, dest int64) { 143 | if this.off2 == 0 || dest < this.off2 { 144 | dest -= this.off1 * 512 145 | } else { 146 | dest -= this.off2 * 512 147 | } 148 | copy(buf, this.buf[dest:]) 149 | } 150 | -------------------------------------------------------------------------------- /userspace/config.toml: -------------------------------------------------------------------------------- 1 | [ioctl] 2 | extents = 128 3 | ctl = "" 4 | 5 | [cache] 6 | base = 0 7 | bound = 0 8 | file = "" 9 | 10 | [l2cache] 11 | base = 0 12 | bound = 0 13 | file = "" 14 | chunksize = 0 15 | 16 | [backend] 17 | enabled = "object" 18 | 19 | [backend.file] 20 | file = "store.raw" 21 | 22 | [backend.object] 23 | api = "s3" 24 | gcMode = "off" # on | silent | off | statsOnly 25 | gcVersion = 2 # 1: Range reads | 2: Whole object download 26 | objectSizeM = 32 27 | 28 | [backend.object.s3] 29 | bucket = "dis" 30 | region = "us-east-1" 31 | remote = "http://192.168.122.1:9000" 32 | 33 | [backend.object.rados] 34 | pool = "ec-pool" 35 | 36 | [backend.null] 37 | skipReadInWritePath = false 38 | waitForIoctlRound = true 39 | -------------------------------------------------------------------------------- /userspace/dis.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package main 5 | 6 | import ( 7 | "dis/backend" 8 | "dis/cache" 9 | "dis/ioctl" 10 | //"dis/l2cache" 11 | "dis/parser" 12 | "os" 13 | ) 14 | 15 | func main() { 16 | print("Initializing... ") 17 | 18 | parser.Init() 19 | cache.Init() 20 | //l2cache.Init() 21 | ioctl.Init() 22 | backend.Init() 23 | 24 | println("Done") 25 | 26 | f, err := os.Create("/tmp/dis_ready") 27 | if err != nil { 28 | panic(err) 29 | } 30 | f.Close() 31 | 32 | done := make(chan struct{}) 33 | go ioctl.Read() 34 | go ioctl.Write() 35 | <-done 36 | } 37 | -------------------------------------------------------------------------------- /userspace/extent/extent.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package extent 5 | 6 | type Extent struct { 7 | LBA int64 8 | PBA int64 9 | Len int64 10 | } 11 | -------------------------------------------------------------------------------- /userspace/go.mod: -------------------------------------------------------------------------------- 1 | module dis 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.35.8 7 | github.com/ceph/go-ceph v0.4.0 8 | github.com/emirpasic/gods v1.12.0 9 | github.com/hashicorp/golang-lru v0.5.1 10 | github.com/spf13/pflag v1.0.3 11 | github.com/spf13/viper v1.7.1 12 | golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 13 | ) 14 | -------------------------------------------------------------------------------- /userspace/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 14 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 15 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 16 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 17 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 18 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 19 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 20 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 21 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 22 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 23 | github.com/aws/aws-sdk-go v1.35.8 h1:+S3BTWePYImKLh7DUJxMVm+/FQUnmHADsMzo/psHFr4= 24 | github.com/aws/aws-sdk-go v1.35.8/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= 25 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 26 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 27 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 28 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 29 | github.com/ceph/go-ceph v0.4.0 h1:KJsT6j1IbsEtui3ZtDcZO//uZ+IVBNT6KO7u9PuMovE= 30 | github.com/ceph/go-ceph v0.4.0/go.mod h1:wd+keAOqrcsN//20VQnHBGtnBnY0KHl0PA024Ng8HfQ= 31 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 32 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 33 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 34 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 35 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 36 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 37 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 38 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 39 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 40 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 41 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 42 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 43 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 44 | github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= 45 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 46 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 47 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 48 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 49 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 50 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 51 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 52 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 53 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 54 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 55 | github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= 56 | github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 57 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 58 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 59 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 60 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 61 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 62 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 63 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 64 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 65 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 66 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 67 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 68 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 69 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 70 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 71 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 72 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 73 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 74 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 75 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 76 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 77 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 78 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 79 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 80 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 81 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 82 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 83 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 84 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 85 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 86 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 87 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 88 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 89 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 90 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 91 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 92 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 93 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 94 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 95 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 96 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 97 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= 98 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 99 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 100 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 101 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 102 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 103 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 104 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 105 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 106 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 107 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 108 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 109 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 110 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 111 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 112 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 113 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 114 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 115 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 116 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 117 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 118 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 119 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 120 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 121 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 122 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 123 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 124 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= 125 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 126 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 127 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 128 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 129 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 130 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 131 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 132 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 133 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 134 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 135 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 136 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 137 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 138 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 139 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 140 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 141 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 142 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 143 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 144 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 145 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 146 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 147 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 148 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 149 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 150 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 151 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 152 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 153 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 154 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 155 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 156 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 157 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 158 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 159 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 160 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 161 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 162 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 163 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 164 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 165 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 166 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 167 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 168 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 169 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 170 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 171 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 172 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 173 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 174 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 175 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 176 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 177 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 178 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 179 | github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= 180 | github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 181 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 182 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 183 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 184 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 185 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 186 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 187 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 188 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 189 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 190 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 191 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 192 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 193 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 194 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 195 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 196 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 197 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 198 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 199 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 200 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 201 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 202 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 203 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 204 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 205 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 206 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 207 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 208 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 209 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 210 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 211 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 212 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 213 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 214 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 215 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 216 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 217 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 218 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 219 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 220 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 221 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 222 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 223 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 224 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 225 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 226 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 227 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 228 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 229 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 230 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 231 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 232 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 233 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 234 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 235 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= 236 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 237 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 238 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 239 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 240 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 241 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 242 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 243 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 244 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 245 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 246 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 247 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 248 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 249 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 250 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 251 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 252 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 253 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 254 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 255 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 256 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 257 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 258 | golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 259 | golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88= 260 | golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 261 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 262 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 263 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 264 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 265 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 266 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 267 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 268 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 269 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 270 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 271 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 272 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 273 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 274 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 275 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 276 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 277 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 278 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 279 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 280 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 281 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 282 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 283 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 284 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 285 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 286 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 287 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 288 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 289 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 290 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 291 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 292 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 293 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 294 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 295 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 296 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 297 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 298 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 299 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 300 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 301 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 302 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 303 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 304 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 305 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 306 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 307 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 308 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 309 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 310 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 311 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 312 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 313 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= 314 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 315 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 316 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 317 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 318 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 319 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 320 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 321 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 322 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 323 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 324 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 325 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 326 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 327 | -------------------------------------------------------------------------------- /userspace/ioctl/c.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package ioctl 5 | 6 | // #include 7 | // #include "../../kernel/dm-disbd.h" 8 | import "C" 9 | 10 | func readNo() uint { 11 | return C.IOCTL_DIS_READS 12 | } 13 | 14 | func writeNo() uint { 15 | return C.IOCTL_DIS_WRITES 16 | } 17 | 18 | func resolveNo() uint { 19 | return C.IOCTL_DIS_RESOLVE 20 | } 21 | -------------------------------------------------------------------------------- /userspace/ioctl/ioctl.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package ioctl 5 | 6 | import ( 7 | "dis/extent" 8 | "dis/parser" 9 | "golang.org/x/sys/unix" 10 | "reflect" 11 | "unsafe" 12 | ) 13 | 14 | const ( 15 | configSection = "ioctl" 16 | envPrefix = "dis_ioctl" 17 | ) 18 | 19 | var ( 20 | n int 21 | ctl string 22 | ctlFD int 23 | ) 24 | 25 | func Init() { 26 | v := parser.Sub(configSection) 27 | v.SetEnvPrefix(envPrefix) 28 | v.BindEnv("ctl") 29 | v.BindEnv("extents") 30 | ctl = v.GetString("ctl") 31 | n = v.GetInt("extents") 32 | 33 | if n == 0 || ctl == "" { 34 | panic("") 35 | } 36 | 37 | var err error 38 | ctlFD, err = unix.Open(ctl, unix.O_RDWR, 0) 39 | if err != nil { 40 | panic(err) 41 | } 42 | } 43 | 44 | type ioctlRW struct { 45 | extentsN int 46 | extents unsafe.Pointer 47 | } 48 | 49 | type ioctlResolve struct { 50 | extentsN int 51 | extents unsafe.Pointer 52 | clearLO, clearHI int64 53 | } 54 | 55 | func RWIOCTL(ioctlNo uint) *[]extent.Extent { 56 | extents := make([]extent.Extent, n) 57 | 58 | ioctl := ioctlRW{ 59 | extentsN: len(extents), 60 | extents: rawData(extents), 61 | } 62 | 63 | p := unsafe.Pointer(&ioctl) 64 | err := unix.IoctlSetInt(ctlFD, ioctlNo, int(uintptr(p))) 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | updateLen(&extents) 70 | 71 | return &extents 72 | } 73 | 74 | func rawData(e []extent.Extent) unsafe.Pointer { 75 | hdr := (*reflect.SliceHeader)(unsafe.Pointer(&e)) 76 | raw := unsafe.Pointer(hdr.Data) 77 | 78 | return raw 79 | } 80 | 81 | func updateLen(extents *[]extent.Extent) { 82 | for i := range *extents { 83 | e := &(*extents)[i] 84 | if e.Len == 0 { 85 | *extents = (*extents)[:i] 86 | break 87 | } 88 | } 89 | } 90 | 91 | func resolveIOCTL(extents *[]extent.Extent, clearLO, clearHI int64) { 92 | resolve := ioctlResolve{ 93 | extentsN: len(*extents), 94 | extents: rawData(*extents), 95 | clearHI: clearHI, 96 | clearLO: clearLO, 97 | } 98 | 99 | p := unsafe.Pointer(&resolve) 100 | err := unix.IoctlSetInt(ctlFD, resolveNo(), int(uintptr(p))) 101 | if err != nil { 102 | panic(err) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /userspace/ioctl/read.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package ioctl 5 | 6 | import ( 7 | "dis/backend" 8 | "dis/cache" 9 | "fmt" 10 | ) 11 | 12 | func Read() { 13 | var nextClean int64 14 | 15 | for { 16 | extents := RWIOCTL(readNo()) 17 | // FIXME: Probable bug in kernel code, sometimes zero-length ioctl set is being sent 18 | if len(*extents) == 0 { 19 | println("R IOCTL: Zero-length extent set received from kernel!") 20 | continue 21 | } 22 | for i := range *extents { 23 | e := &(*extents)[i] 24 | cache.Reserve(e) 25 | } 26 | backend.Read(extents) 27 | 28 | // FIXME: If the length of read extents in this round makes the 29 | // frontier to jump over two octants it fails to clean the 30 | // skipped octant. 31 | octant := 8 * (cache.Frontier - cache.Base) / (cache.Bound - cache.Base) 32 | eight := (cache.Bound - cache.Base) / 8 33 | 34 | var clearLO, clearHI int64 35 | if (octant+2)%8 == nextClean { 36 | clearLO = cache.Base + nextClean*eight 37 | clearHI = clearLO + eight 38 | nextClean = (nextClean + 1) % 8 39 | fmt.Println("Cleaning from ", clearLO, "to ", clearHI) 40 | } 41 | 42 | resolveIOCTL(extents, clearLO, clearHI) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /userspace/ioctl/write.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package ioctl 5 | 6 | import ( 7 | "dis/backend" 8 | ) 9 | 10 | func Write() { 11 | for { 12 | extents := RWIOCTL(writeNo()) 13 | // FIXME: Probable bug in kernel code, sometimes zero-length ioctl set is being sent 14 | if len(*extents) == 0 { 15 | println("W IOCTL: Zero-length extent set received from kernel!") 16 | continue 17 | } 18 | backend.Write(extents) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /userspace/l2cache/l2cache.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package l2cache 5 | 6 | import ( 7 | "dis/parser" 8 | "sync" 9 | "sync/atomic" 10 | 11 | "github.com/hashicorp/golang-lru" 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | const ( 16 | configSection = "l2cache" 17 | envPrefix = "dis_l2cache" 18 | ) 19 | 20 | var ( 21 | cache *lru.Cache 22 | base int64 23 | bound int64 24 | file string 25 | fd int 26 | chunks int64 27 | freeChunk int64 28 | ChunkSize int64 29 | ) 30 | 31 | func Init() { 32 | v := parser.Sub(configSection) 33 | v.SetEnvPrefix(envPrefix) 34 | v.BindEnv("base") 35 | v.BindEnv("bound") 36 | v.BindEnv("file") 37 | v.BindEnv("chunksize") 38 | base = v.GetInt64("base") 39 | bound = v.GetInt64("bound") 40 | file = v.GetString("file") 41 | ChunkSize = v.GetInt64("chunksize") 42 | 43 | if ChunkSize == 0 || bound == 0 || file == "" { 44 | panic("") 45 | } 46 | 47 | chunks = (bound - base) * 512 / ChunkSize 48 | 49 | var err error 50 | cache, err = lru.NewWithEvict(int(chunks-1), onEvict) 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | fd, err = unix.Open(file, unix.O_RDWR|unix.O_DIRECT, 0) 56 | if err != nil { 57 | panic(err) 58 | } 59 | } 60 | 61 | var mutex sync.Mutex 62 | 63 | func GetOrReserveChunk(id int64) (*[]byte, bool) { 64 | mutex.Lock() 65 | chunk, ok := cache.Get(id) 66 | if !ok { 67 | cache.Add(id, nil) 68 | mutex.Unlock() 69 | return nil, false 70 | } 71 | mutex.Unlock() 72 | 73 | if chunk == interface{}(nil) { 74 | return nil, true 75 | } 76 | 77 | buf := readChunk(chunk.(int64)) 78 | return buf, true 79 | } 80 | 81 | func PutChunk(id int64, content *[]byte) { 82 | mutex.Lock() 83 | defer mutex.Unlock() 84 | 85 | current := atomic.LoadInt64(&freeChunk) 86 | if cache.Len() < int(chunks-1) { 87 | atomic.AddInt64(&freeChunk, 1) 88 | } 89 | 90 | writeChunk(current, content) 91 | cache.Add(id, current) 92 | } 93 | 94 | func onEvict(key interface{}, value interface{}) { 95 | println("l2cache: Eviction happened ...") 96 | atomic.StoreInt64(&freeChunk, value.(int64)) 97 | } 98 | 99 | func writeChunk(chunk int64, buf *[]byte) { 100 | _, err := unix.Pwrite(fd, *buf, base*512+chunk*ChunkSize) 101 | if err != nil { 102 | panic(err) 103 | } 104 | } 105 | 106 | func readChunk(chunk int64) *[]byte { 107 | buf := make([]byte, ChunkSize) 108 | _, err := unix.Pread(fd, buf, base*512+chunk*ChunkSize) 109 | if err != nil { 110 | panic(err) 111 | } 112 | return &buf 113 | } 114 | 115 | var i int64 116 | 117 | func ReadRandChunk(buf *[]byte) { 118 | _, err := unix.Pread(fd, *buf, base*512+i*ChunkSize) 119 | i = (i + 1) % chunks 120 | if err != nil { 121 | panic(err) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /userspace/parser/parser.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package parser 5 | 6 | import ( 7 | flag "github.com/spf13/pflag" 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | const ( 12 | envPrefix = "dis" 13 | configType = "toml" 14 | configName = "config" 15 | ) 16 | 17 | var v *viper.Viper 18 | 19 | func Init() { 20 | config := flag.StringP("config", "c", configName+"."+configType, "Path to config file") 21 | flag.Parse() 22 | 23 | v = viper.New() 24 | v.SetConfigFile(*config) 25 | v.SetConfigType(configType) 26 | v.SetEnvPrefix(envPrefix) 27 | 28 | err := v.ReadInConfig() 29 | if err != nil { 30 | panic(err) 31 | } 32 | } 33 | 34 | func Sub(section string) *viper.Viper { 35 | return v.Sub(section) 36 | } 37 | -------------------------------------------------------------------------------- /userspace/profiler/profiler.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Copyright (C) 2020-2021 Vojtech Aschenbrenner 3 | 4 | package profiler 5 | 6 | import ( 7 | "log" 8 | "net/http" 9 | _ "net/http/pprof" 10 | "os" 11 | "runtime" 12 | "runtime/pprof" 13 | "time" 14 | ) 15 | 16 | func enable() { 17 | go func() { 18 | log.Println(http.ListenAndServe("localhost:6060", nil)) 19 | }() 20 | 21 | f, err := os.Create("cpu.pprof") 22 | if err != nil { 23 | panic(err) 24 | } 25 | pprof.StartCPUProfile(f) 26 | 27 | ff, err := os.Create("mem.pprof") 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | go func() { 33 | time.Sleep(10 * time.Minute) 34 | pprof.StopCPUProfile() 35 | runtime.GC() 36 | pprof.WriteHeapProfile(ff) 37 | ff.Close() 38 | println("Profiling Stopped!") 39 | 40 | }() 41 | } 42 | --------------------------------------------------------------------------------