├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src ├── config ├── condition.rs ├── mod.rs ├── operation.rs └── speed.rs ├── http.rs ├── localfile.rs ├── main.rs ├── metrics.rs ├── mizumochi.rs └── state.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ansi_term" 5 | version = "0.11.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 8 | dependencies = [ 9 | "winapi 0.3.4", 10 | ] 11 | 12 | [[package]] 13 | name = "atomic_immut" 14 | version = "0.1.4" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "4b9fcea66a65a49890058406499cca8906e4e9cd1173bfeb272dcd2ac603e4fa" 17 | 18 | [[package]] 19 | name = "atty" 20 | version = "0.2.10" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "2fc4a1aa4c24c0718a250f0681885c1af91419d242f29eb8f2ab28502d80dbd1" 23 | dependencies = [ 24 | "libc", 25 | "termion", 26 | "winapi 0.3.4", 27 | ] 28 | 29 | [[package]] 30 | name = "bitflags" 31 | version = "1.0.3" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789" 34 | 35 | [[package]] 36 | name = "bytecodec" 37 | version = "0.4.4" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "5252f52de2189d69808eca08fa869f1f4bded98cbc2465c699fc899c5a9ccf48" 40 | dependencies = [ 41 | "byteorder", 42 | "serde", 43 | "serde_json", 44 | "trackable", 45 | ] 46 | 47 | [[package]] 48 | name = "byteorder" 49 | version = "1.2.3" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "74c0b906e9446b0a2e4f760cdb3fa4b2c48cdc6db8766a845c54b6ff063fd2e9" 52 | 53 | [[package]] 54 | name = "cfg-if" 55 | version = "0.1.3" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18" 58 | 59 | [[package]] 60 | name = "chrono" 61 | version = "0.4.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "1cce36c92cb605414e9b824f866f5babe0a0368e39ea07393b9b63cf3844c0e6" 64 | dependencies = [ 65 | "num-integer", 66 | "num-traits", 67 | "time", 68 | ] 69 | 70 | [[package]] 71 | name = "clap" 72 | version = "2.31.2" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536" 75 | dependencies = [ 76 | "ansi_term", 77 | "atty", 78 | "bitflags", 79 | "strsim", 80 | "textwrap", 81 | "unicode-width", 82 | "vec_map", 83 | ] 84 | 85 | [[package]] 86 | name = "dtoa" 87 | version = "0.4.2" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" 90 | 91 | [[package]] 92 | name = "factory" 93 | version = "0.1.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "12035ccde1cff7c507200fd9e7ad0dbceda81d3cf6f68a265974a213d24acfde" 96 | 97 | [[package]] 98 | name = "fibers" 99 | version = "0.1.10" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "fd2cd0b641968d7390b8a5a0137fca20a1ff7bfb66c4bf64fe9c01db887f19b2" 102 | dependencies = [ 103 | "futures", 104 | "mio", 105 | "nbchan", 106 | "num_cpus", 107 | "splay_tree", 108 | ] 109 | 110 | [[package]] 111 | name = "fibers_http_server" 112 | version = "0.2.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "b094e6cd9e30fa86e33b0d181f11aac187cd184bc1ede0ddeea71bff8d6012ca" 115 | dependencies = [ 116 | "atomic_immut", 117 | "bytecodec", 118 | "factory", 119 | "fibers", 120 | "futures", 121 | "httpcodec", 122 | "prometrics", 123 | "slog", 124 | "trackable", 125 | "url", 126 | ] 127 | 128 | [[package]] 129 | name = "fuchsia-zircon" 130 | version = "0.3.3" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 133 | dependencies = [ 134 | "bitflags", 135 | "fuchsia-zircon-sys", 136 | ] 137 | 138 | [[package]] 139 | name = "fuchsia-zircon-sys" 140 | version = "0.3.3" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 143 | 144 | [[package]] 145 | name = "fuse" 146 | version = "0.3.1" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "80e57070510966bfef93662a81cb8aa2b1c7db0964354fa9921434f04b9e8660" 149 | dependencies = [ 150 | "libc", 151 | "log 0.3.9", 152 | "pkg-config", 153 | "thread-scoped", 154 | "time", 155 | ] 156 | 157 | [[package]] 158 | name = "futures" 159 | version = "0.1.21" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "1a70b146671de62ec8c8ed572219ca5d594d9b06c0b364d5e67b722fc559b48c" 162 | 163 | [[package]] 164 | name = "httpcodec" 165 | version = "0.2.2" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "235f5e05cc336f97b050fe0d678be8cbe1bd7dfdd54417ec386d7bba92ebf0de" 168 | dependencies = [ 169 | "bytecodec", 170 | "trackable", 171 | ] 172 | 173 | [[package]] 174 | name = "idna" 175 | version = "0.2.0" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" 178 | dependencies = [ 179 | "matches", 180 | "unicode-bidi", 181 | "unicode-normalization", 182 | ] 183 | 184 | [[package]] 185 | name = "iovec" 186 | version = "0.1.2" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" 189 | dependencies = [ 190 | "libc", 191 | "winapi 0.2.8", 192 | ] 193 | 194 | [[package]] 195 | name = "isatty" 196 | version = "0.1.8" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "6c324313540cd4d7ba008d43dc6606a32a5579f13cc17b2804c13096f0a5c522" 199 | dependencies = [ 200 | "libc", 201 | "redox_syscall", 202 | "winapi 0.3.4", 203 | ] 204 | 205 | [[package]] 206 | name = "itoa" 207 | version = "0.4.1" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "c069bbec61e1ca5a596166e55dfe4773ff745c3d16b700013bcaff9a6df2c682" 210 | 211 | [[package]] 212 | name = "kernel32-sys" 213 | version = "0.2.2" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 216 | dependencies = [ 217 | "winapi 0.2.8", 218 | "winapi-build", 219 | ] 220 | 221 | [[package]] 222 | name = "lazy_static" 223 | version = "1.0.1" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739" 226 | 227 | [[package]] 228 | name = "lazycell" 229 | version = "0.6.0" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef" 232 | 233 | [[package]] 234 | name = "libc" 235 | version = "0.2.41" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "ac8ebf8343a981e2fa97042b14768f02ed3e1d602eac06cae6166df3c8ced206" 238 | 239 | [[package]] 240 | name = "log" 241 | version = "0.3.9" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 244 | dependencies = [ 245 | "log 0.4.1", 246 | ] 247 | 248 | [[package]] 249 | name = "log" 250 | version = "0.4.1" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" 253 | dependencies = [ 254 | "cfg-if", 255 | ] 256 | 257 | [[package]] 258 | name = "matches" 259 | version = "0.1.6" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" 262 | 263 | [[package]] 264 | name = "mio" 265 | version = "0.6.14" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "6d771e3ef92d58a8da8df7d6976bfca9371ed1de6619d9d5a5ce5b1f29b85bfe" 268 | dependencies = [ 269 | "fuchsia-zircon", 270 | "fuchsia-zircon-sys", 271 | "iovec", 272 | "kernel32-sys", 273 | "lazycell", 274 | "libc", 275 | "log 0.4.1", 276 | "miow", 277 | "net2", 278 | "slab", 279 | "winapi 0.2.8", 280 | ] 281 | 282 | [[package]] 283 | name = "miow" 284 | version = "0.2.1" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 287 | dependencies = [ 288 | "kernel32-sys", 289 | "net2", 290 | "winapi 0.2.8", 291 | "ws2_32-sys", 292 | ] 293 | 294 | [[package]] 295 | name = "mizumochi" 296 | version = "0.1.0" 297 | dependencies = [ 298 | "atomic_immut", 299 | "bytecodec", 300 | "clap", 301 | "fibers", 302 | "fibers_http_server", 303 | "fuse", 304 | "futures", 305 | "httpcodec", 306 | "libc", 307 | "prometrics", 308 | "serde", 309 | "serde_derive", 310 | "slog", 311 | "slog-async", 312 | "slog-term", 313 | "time", 314 | ] 315 | 316 | [[package]] 317 | name = "nbchan" 318 | version = "0.1.2" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "76d53a9330df67f8bd9efa3416ddfabf1e8c6f0455580ccf2b8d8609bfd08b93" 321 | 322 | [[package]] 323 | name = "net2" 324 | version = "0.2.32" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "9044faf1413a1057267be51b5afba8eb1090bd2231c693664aa1db716fe1eae0" 327 | dependencies = [ 328 | "cfg-if", 329 | "libc", 330 | "winapi 0.3.4", 331 | ] 332 | 333 | [[package]] 334 | name = "nom" 335 | version = "2.2.1" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff" 338 | 339 | [[package]] 340 | name = "num-integer" 341 | version = "0.1.38" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "6ac0ea58d64a89d9d6b7688031b3be9358d6c919badcf7fbb0527ccfd891ee45" 344 | dependencies = [ 345 | "num-traits", 346 | ] 347 | 348 | [[package]] 349 | name = "num-traits" 350 | version = "0.2.4" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "775393e285254d2f5004596d69bb8bc1149754570dcc08cf30cabeba67955e28" 353 | 354 | [[package]] 355 | name = "num_cpus" 356 | version = "1.8.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" 359 | dependencies = [ 360 | "libc", 361 | ] 362 | 363 | [[package]] 364 | name = "percent-encoding" 365 | version = "2.1.0" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 368 | 369 | [[package]] 370 | name = "pkg-config" 371 | version = "0.3.11" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "110d5ee3593dbb73f56294327fe5668bcc997897097cbc76b51e7aed3f52452f" 374 | 375 | [[package]] 376 | name = "proc-macro2" 377 | version = "0.4.4" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "1fa93823f53cfd0f5ac117b189aed6cfdfb2cfc0a9d82e956dd7927595ed7d46" 380 | dependencies = [ 381 | "unicode-xid 0.1.0", 382 | ] 383 | 384 | [[package]] 385 | name = "proc-macro2" 386 | version = "1.0.18" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 389 | dependencies = [ 390 | "unicode-xid 0.2.1", 391 | ] 392 | 393 | [[package]] 394 | name = "procinfo" 395 | version = "0.4.2" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "6ab1427f3d2635891f842892dda177883dca0639e05fe66796a62c9d2f23b49c" 398 | dependencies = [ 399 | "byteorder", 400 | "libc", 401 | "nom", 402 | "rustc_version", 403 | ] 404 | 405 | [[package]] 406 | name = "prometrics" 407 | version = "0.1.11" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "f7eb3e65f1053a5d62bd464c71de8e75c25c739cdbcd6e19b527e4d31ea64026" 410 | dependencies = [ 411 | "atomic_immut", 412 | "lazy_static", 413 | "libc", 414 | "procinfo", 415 | "trackable", 416 | ] 417 | 418 | [[package]] 419 | name = "quote" 420 | version = "0.6.3" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "e44651a0dc4cdd99f71c83b561e221f714912d11af1a4dff0631f923d53af035" 423 | dependencies = [ 424 | "proc-macro2 0.4.4", 425 | ] 426 | 427 | [[package]] 428 | name = "quote" 429 | version = "1.0.7" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 432 | dependencies = [ 433 | "proc-macro2 1.0.18", 434 | ] 435 | 436 | [[package]] 437 | name = "redox_syscall" 438 | version = "0.1.40" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" 441 | 442 | [[package]] 443 | name = "redox_termios" 444 | version = "0.1.1" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 447 | dependencies = [ 448 | "redox_syscall", 449 | ] 450 | 451 | [[package]] 452 | name = "rustc_version" 453 | version = "0.2.2" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "a54aa04a10c68c1c4eacb4337fd883b435997ede17a9385784b990777686b09a" 456 | dependencies = [ 457 | "semver", 458 | ] 459 | 460 | [[package]] 461 | name = "semver" 462 | version = "0.9.0" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 465 | dependencies = [ 466 | "semver-parser", 467 | ] 468 | 469 | [[package]] 470 | name = "semver-parser" 471 | version = "0.7.0" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 474 | 475 | [[package]] 476 | name = "serde" 477 | version = "1.0.64" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "fba5be06346c5200249c8c8ca4ccba4a09e8747c71c16e420bd359a0db4d8f91" 480 | 481 | [[package]] 482 | name = "serde_derive" 483 | version = "1.0.64" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "79e4620ba6fbe051fc7506fab6f84205823564d55da18d55b695160fb3479cd8" 486 | dependencies = [ 487 | "proc-macro2 0.4.4", 488 | "quote 0.6.3", 489 | "syn 0.14.1", 490 | ] 491 | 492 | [[package]] 493 | name = "serde_json" 494 | version = "1.0.19" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "93aee34bb692dde91e602871bc792dd319e489c7308cdbbe5f27cf27c64280f5" 497 | dependencies = [ 498 | "dtoa", 499 | "itoa", 500 | "serde", 501 | ] 502 | 503 | [[package]] 504 | name = "slab" 505 | version = "0.4.0" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "fdeff4cd9ecff59ec7e3744cbca73dfe5ac35c2aedb2cfba8a1c715a18912e9d" 508 | 509 | [[package]] 510 | name = "slog" 511 | version = "2.2.3" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "2f7bfce6405155042d42ec0e645efe43eddedd7be280063ce0623b120014e7f9" 514 | 515 | [[package]] 516 | name = "slog-async" 517 | version = "2.3.0" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "e544d16c6b230d84c866662fe55e31aacfca6ae71e6fc49ae9a311cb379bfc2f" 520 | dependencies = [ 521 | "slog", 522 | "take_mut", 523 | "thread_local", 524 | ] 525 | 526 | [[package]] 527 | name = "slog-term" 528 | version = "2.4.0" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "5951a808c40f419922ee014c15b6ae1cd34d963538b57d8a4778b9ca3fff1e0b" 531 | dependencies = [ 532 | "chrono", 533 | "isatty", 534 | "slog", 535 | "term", 536 | "thread_local", 537 | ] 538 | 539 | [[package]] 540 | name = "splay_tree" 541 | version = "0.2.10" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "309dee0d93c0a8f7a852cbd9a86e01e1a94781b64d98d86a191f4af7f095ecc1" 544 | 545 | [[package]] 546 | name = "strsim" 547 | version = "0.7.0" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" 550 | 551 | [[package]] 552 | name = "syn" 553 | version = "0.14.1" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "6dfd71b2be5a58ee30a6f8ea355ba8290d397131c00dfa55c3d34e6e13db5101" 556 | dependencies = [ 557 | "proc-macro2 0.4.4", 558 | "quote 0.6.3", 559 | "unicode-xid 0.1.0", 560 | ] 561 | 562 | [[package]] 563 | name = "syn" 564 | version = "1.0.33" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" 567 | dependencies = [ 568 | "proc-macro2 1.0.18", 569 | "quote 1.0.7", 570 | "unicode-xid 0.2.1", 571 | ] 572 | 573 | [[package]] 574 | name = "take_mut" 575 | version = "0.2.2" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" 578 | 579 | [[package]] 580 | name = "term" 581 | version = "0.5.1" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "5e6b677dd1e8214ea1ef4297f85dbcbed8e8cdddb561040cc998ca2551c37561" 584 | dependencies = [ 585 | "byteorder", 586 | "winapi 0.3.4", 587 | ] 588 | 589 | [[package]] 590 | name = "termion" 591 | version = "1.5.1" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 594 | dependencies = [ 595 | "libc", 596 | "redox_syscall", 597 | "redox_termios", 598 | ] 599 | 600 | [[package]] 601 | name = "textwrap" 602 | version = "0.9.0" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" 605 | dependencies = [ 606 | "unicode-width", 607 | ] 608 | 609 | [[package]] 610 | name = "thread-scoped" 611 | version = "1.0.2" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "bcbb6aa301e5d3b0b5ef639c9a9c7e2f1c944f177b460c04dc24c69b1fa2bd99" 614 | 615 | [[package]] 616 | name = "thread_local" 617 | version = "0.3.5" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" 620 | dependencies = [ 621 | "lazy_static", 622 | "unreachable", 623 | ] 624 | 625 | [[package]] 626 | name = "time" 627 | version = "0.1.40" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" 630 | dependencies = [ 631 | "libc", 632 | "redox_syscall", 633 | "winapi 0.3.4", 634 | ] 635 | 636 | [[package]] 637 | name = "trackable" 638 | version = "0.2.23" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "11475c3c53b075360eac9794965822cb053996046545f91cf61d90e00b72efa5" 641 | dependencies = [ 642 | "trackable_derive", 643 | ] 644 | 645 | [[package]] 646 | name = "trackable_derive" 647 | version = "0.1.3" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "edcf0b9b2caa5f4804ef77aeee1b929629853d806117c48258f402b69737e65c" 650 | dependencies = [ 651 | "quote 1.0.7", 652 | "syn 1.0.33", 653 | ] 654 | 655 | [[package]] 656 | name = "unicode-bidi" 657 | version = "0.3.4" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 660 | dependencies = [ 661 | "matches", 662 | ] 663 | 664 | [[package]] 665 | name = "unicode-normalization" 666 | version = "0.1.7" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25" 669 | 670 | [[package]] 671 | name = "unicode-width" 672 | version = "0.1.5" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 675 | 676 | [[package]] 677 | name = "unicode-xid" 678 | version = "0.1.0" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 681 | 682 | [[package]] 683 | name = "unicode-xid" 684 | version = "0.2.1" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 687 | 688 | [[package]] 689 | name = "unreachable" 690 | version = "1.0.0" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 693 | dependencies = [ 694 | "void", 695 | ] 696 | 697 | [[package]] 698 | name = "url" 699 | version = "2.1.1" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" 702 | dependencies = [ 703 | "idna", 704 | "matches", 705 | "percent-encoding", 706 | ] 707 | 708 | [[package]] 709 | name = "vec_map" 710 | version = "0.8.1" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 713 | 714 | [[package]] 715 | name = "void" 716 | version = "1.0.2" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 719 | 720 | [[package]] 721 | name = "winapi" 722 | version = "0.2.8" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 725 | 726 | [[package]] 727 | name = "winapi" 728 | version = "0.3.4" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" 731 | dependencies = [ 732 | "winapi-i686-pc-windows-gnu", 733 | "winapi-x86_64-pc-windows-gnu", 734 | ] 735 | 736 | [[package]] 737 | name = "winapi-build" 738 | version = "0.1.1" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 741 | 742 | [[package]] 743 | name = "winapi-i686-pc-windows-gnu" 744 | version = "0.4.0" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 747 | 748 | [[package]] 749 | name = "winapi-x86_64-pc-windows-gnu" 750 | version = "0.4.0" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 753 | 754 | [[package]] 755 | name = "ws2_32-sys" 756 | version = "0.2.1" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 759 | dependencies = [ 760 | "winapi 0.2.8", 761 | "winapi-build", 762 | ] 763 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2018" 3 | name = "mizumochi" 4 | version = "0.1.0" 5 | authors = ["mopp "] 6 | license = "MIT OR Apache-2.0" 7 | description = """ 8 | mizumochi is a tool to simulate unstable disk I/O for testing stability/robustness of system. 9 | The word unstable here means read/write speed is slowdown. 10 | """ 11 | homepage = "https://github.com/dwango/mizumochi" 12 | repository = "https://github.com/dwango/mizumochi" 13 | readme = "README.md" 14 | categories = ["command-line-utilities"] 15 | 16 | [dependencies] 17 | atomic_immut = "0.1" 18 | bytecodec = {version = "0.4", features = ["json_codec"]} 19 | clap = "2" 20 | fibers = "0.1" 21 | fibers_http_server = "0.2" 22 | futures = "0.1" 23 | fuse = "0.3" 24 | httpcodec = "0.2" 25 | libc = "0.2" 26 | prometrics = "0.1" 27 | serde = "1" 28 | serde_derive = "1" 29 | slog = "2" 30 | slog-async = "2" 31 | slog-term = "2" 32 | time = "0.1" 33 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2018 DWANGO Co., Ltd. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 DWANGO Co., Ltd. All Rights Reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mizumochi 2 | [![Crates.io](https://img.shields.io/crates/v/mizumochi.svg)](https://crates.io/crates/mizumochi) 3 | [![Crates.io](https://img.shields.io/crates/d/mizumochi.svg)](https://crates.io/crates/mizumochi) 4 | [![License: Apache](https://img.shields.io/badge/License-Apache%202.0-red.svg)](LICENSE-APACHE) 5 | OR 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE-MIT) 7 | 8 | mizumochi is a tool to simulate unstable disk I/O for testing stability/robustness of system. 9 | The word unstable here means read/write speed is slowdown. 10 | 11 | We assume mizumochi works on develop environment with target system. 12 | 13 | [zargony/rust-fuse](https://github.com/zargony/rust-fuse) are used to maps actual files in the given directory to files on the mountpoint. 14 | *Note that some FUSE callbacks (e.g., link) are not implemented yet. (work in progress)* 15 | 16 | 17 | ## Install 18 | You have to install [OSXFUSE](http://osxfuse.github.io) for macOS or [FUSE](http://fuse.sourceforge.net) for Linux before installing mizumochi. 19 | 20 | ```console 21 | cargo install mizumochi 22 | ``` 23 | 24 | 25 | ## Features 26 | - Mode 27 | + Periodic 28 | * The stable/unstable is toggled periodically. 29 | - Interfaces 30 | + Command line interface (CLI) 31 | * CLI is primary interface. 32 | * Refers `mizumochi --help` in details. 33 | + HTTP API 34 | * There are some TODOs. 35 | * The config (e.g., speed, condition to switch stable/unstable) can be modified on runtime via this interface. 36 | 37 | ## Examples 38 | ```console 39 | # Emulate files in `real_dir` at `emulated_dir` and the read/write speed is slowdown every 30 minutes for 10 minutes. 40 | # Slowdown happens in `emulated_dir`. 41 | mizumochi /tmp/real_dir/ /tmp/emulated_dir/ --speed 1024KBps periodic --duration 10m --frequency 30m 42 | ``` 43 | 44 | 45 | ## License 46 | Licensed under either of 47 | 48 | * Apache License, Version 2.0 49 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 50 | * MIT license 51 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 52 | 53 | at your option. 54 | 55 | 56 | ## Contribution 57 | Unless you explicitly state otherwise, any contribution intentionally submitted 58 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 59 | dual licensed as above, without any additional terms or conditions. 60 | -------------------------------------------------------------------------------- /src/config/condition.rs: -------------------------------------------------------------------------------- 1 | use crate::state::State; 2 | use std::time::Duration; 3 | 4 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 5 | pub enum Condition { 6 | Periodic { 7 | duration: Duration, 8 | frequency: Duration, 9 | }, 10 | Always(State), 11 | } 12 | 13 | impl Condition { 14 | pub fn default_periodic() -> Condition { 15 | Condition::Periodic { 16 | duration: Duration::from_secs(10 * 60), 17 | frequency: Duration::from_secs(30 * 60), 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/config/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | pub use self::condition::Condition; 4 | pub use self::operation::Operation; 5 | pub use self::speed::Speed; 6 | 7 | mod condition; 8 | mod operation; 9 | mod speed; 10 | 11 | #[derive(Debug, Clone, Serialize, Deserialize)] 12 | pub struct Config { 13 | pub speed: Speed, 14 | pub operations: Vec, 15 | pub condition: Condition, 16 | } 17 | 18 | impl Default for Config { 19 | fn default() -> Config { 20 | Config { 21 | speed: Speed::PassThrough, 22 | operations: vec![Operation::Read, Operation::Write], 23 | condition: Condition::default_periodic(), 24 | } 25 | } 26 | } 27 | 28 | impl fmt::Display for Config { 29 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 30 | let ops = self 31 | .operations 32 | .iter() 33 | .map(|x| x.to_string()) 34 | .collect::>() 35 | .join(":"); 36 | write!( 37 | fmt, 38 | "config {{speed: {}, operations: {}, condition: {:?}}}", 39 | ops, self.speed, self.condition 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/config/operation.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(Debug, Clone, Serialize, Deserialize)] 4 | pub enum Operation { 5 | Read, 6 | Write, 7 | } 8 | 9 | impl fmt::Display for Operation { 10 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 11 | match *self { 12 | Operation::Read => write!(f, "Read"), 13 | Operation::Write => write!(f, "Write"), 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/config/speed.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str::FromStr; 3 | 4 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 5 | pub enum Speed { 6 | Bps(usize), 7 | PassThrough, 8 | } 9 | 10 | impl FromStr for Speed { 11 | type Err = String; 12 | 13 | fn from_str(s: &str) -> Result { 14 | if s == "pass_through" { 15 | Ok(Speed::PassThrough) 16 | } else if s.ends_with("Bps") { 17 | let (n, _) = s.split_at(s.len() - 3); 18 | let mut s = n.to_string(); 19 | 20 | let scale: usize = match s.pop().ok_or("Invalid speed")? { 21 | 'K' => 1 << 10, 22 | 'M' => 1 << 20, 23 | 'G' => 1 << 30, 24 | r => { 25 | s.push(r); 26 | 1 27 | } 28 | }; 29 | 30 | let speed = s.parse::().map_err(|e| e.to_string())?; 31 | let speed = speed.checked_mul(scale).ok_or("overflow")?; 32 | 33 | Ok(Speed::Bps(speed)) 34 | } else { 35 | let speed = s.parse::().map_err(|e| e.to_string())?; 36 | 37 | Ok(Speed::Bps(speed)) 38 | } 39 | } 40 | } 41 | 42 | impl fmt::Display for Speed { 43 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 44 | match *self { 45 | Speed::Bps(bps) if bps < 1 << 10 => write!(f, "{}Bps", bps), 46 | Speed::Bps(bps) if bps < 1 << 20 => write!(f, "{}KBps", bps as f64 / (1 << 10) as f64), 47 | Speed::Bps(bps) if bps < 1 << 30 => write!(f, "{}MBps", bps as f64 / (1 << 20) as f64), 48 | Speed::Bps(bps) => write!(f, "{}GBps", bps as f64 / (1 << 30) as f64), 49 | Speed::PassThrough => write!(f, "PassThrough"), 50 | } 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use super::*; 57 | 58 | #[test] 59 | fn test_speed_new() { 60 | assert!(Speed::from_str("").is_err()); 61 | assert!(Speed::from_str("alskjaslkdfjhasjdhfb").is_err()); 62 | assert!(Speed::from_str("Bps").is_err()); 63 | assert_eq!(Ok(Speed::Bps(1 << 10)), Speed::from_str("1024")); 64 | assert_eq!(Ok(Speed::Bps(1 << 10)), Speed::from_str("1024Bps")); 65 | assert_eq!(Ok(Speed::Bps(1 << 20)), Speed::from_str("1024KBps")); 66 | assert_eq!(Ok(Speed::Bps(1 << 30)), Speed::from_str("1024MBps")); 67 | assert_eq!(Ok(Speed::Bps(1 << 40)), Speed::from_str("1024GBps")); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/http.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use atomic_immut::AtomicImmut; 3 | use bytecodec::json_codec::{JsonDecoder, JsonEncoder}; 4 | use bytecodec::null::{NullDecoder, NullEncoder}; 5 | use fibers::{Executor, InPlaceExecutor, Spawn}; 6 | use fibers_http_server::metrics::MetricsHandler; 7 | use fibers_http_server::{HandleRequest, Reply, Req, Res, ServerBuilder, Status}; 8 | use futures::future::ok; 9 | use futures::Future; 10 | use httpcodec::{BodyDecoder, BodyEncoder}; 11 | use slog::Logger; 12 | use std::net::SocketAddr; 13 | use std::sync::Arc; 14 | 15 | pub fn start_server( 16 | logger: Logger, 17 | port: u16, 18 | config: Arc>, 19 | ) -> Result<(), Box> { 20 | let executor = InPlaceExecutor::new()?; 21 | let addr = SocketAddr::from(([0, 0, 0, 0], port)); 22 | let mut builder = ServerBuilder::new(addr); 23 | builder.add_handler(GetConfigHandler(Arc::clone(&config)))?; 24 | builder.add_handler(PutConfigHandler { logger, config })?; 25 | 26 | // Enables process metrics and registers a HTTP endpoint for exporting metrics 27 | prometrics::default_registry().register(prometrics::metrics::ProcessMetricsCollector::new()); 28 | builder.add_handler(MetricsHandler)?; 29 | 30 | // Starts HTTP server 31 | let http_server = builder.finish(executor.handle()); 32 | executor.spawn(http_server.map_err(|e| panic!("{}", e))); 33 | std::thread::spawn(move || { 34 | if let Err(e) = executor.run() { 35 | panic!("{}", e); 36 | } 37 | }); 38 | Ok(()) 39 | } 40 | 41 | struct GetConfigHandler(Arc>); 42 | impl HandleRequest for GetConfigHandler { 43 | const METHOD: &'static str = "GET"; 44 | const PATH: &'static str = "/config"; 45 | 46 | type ReqBody = (); 47 | type ResBody = Config; 48 | type Decoder = BodyDecoder; 49 | type Encoder = BodyEncoder>; 50 | type Reply = Reply; 51 | 52 | fn handle_request(&self, _req: Req) -> Self::Reply { 53 | let config = (*self.0.load()).clone(); 54 | Box::new(ok(Res::new(Status::Ok, config))) 55 | } 56 | } 57 | 58 | struct PutConfigHandler { 59 | logger: Logger, 60 | config: Arc>, 61 | } 62 | impl HandleRequest for PutConfigHandler { 63 | const METHOD: &'static str = "PUT"; 64 | const PATH: &'static str = "/config"; 65 | 66 | type ReqBody = Config; 67 | type ResBody = (); 68 | type Decoder = BodyDecoder>; 69 | type Encoder = BodyEncoder; 70 | type Reply = Reply; 71 | 72 | fn handle_request(&self, req: Req) -> Self::Reply { 73 | let config = req.into_body(); 74 | self.config.store(config.clone()); 75 | info!(self.logger, "new config: {:?}", config); 76 | 77 | Box::new(ok(Res::new(Status::Ok, ()))) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/localfile.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | pub type Inode = u64; 4 | 5 | #[derive(Debug, Clone)] 6 | pub enum LocalFile { 7 | RegularFile(PathBuf), 8 | // Note that the `PathBuf` in Vec<(Inode, PathBuf)> refers filename (not filepath). 9 | Directory(PathBuf, Option>), 10 | } 11 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate atomic_immut; 2 | extern crate bytecodec; 3 | #[macro_use] 4 | extern crate clap; 5 | extern crate fibers; 6 | extern crate fibers_http_server; 7 | extern crate fuse; 8 | extern crate futures; 9 | extern crate httpcodec; 10 | extern crate libc; 11 | extern crate prometrics; 12 | extern crate serde; 13 | #[macro_use] 14 | extern crate serde_derive; 15 | extern crate time; 16 | #[macro_use] 17 | extern crate slog; 18 | extern crate slog_async; 19 | extern crate slog_term; 20 | 21 | mod config; 22 | mod http; 23 | mod localfile; 24 | mod metrics; 25 | mod mizumochi; 26 | mod state; 27 | 28 | use crate::config::*; 29 | use crate::mizumochi::Mizumochi; 30 | use atomic_immut::AtomicImmut; 31 | use clap::{Arg, SubCommand}; 32 | use slog::{Drain, Level}; 33 | use std::sync::Arc; 34 | use std::time::Duration; 35 | 36 | fn main() -> Result<(), Box> { 37 | let matches = app_from_crate!() 38 | .arg( 39 | Arg::with_name("SPEED") 40 | .short("s") 41 | .long("speed") 42 | .value_name("BytePerSecond") 43 | .help("Sets byte per second to limit file operations") 44 | .long_help("you can put suffixes (KBps, MBps, GBps) at the tail (examples: 1024Bps, 4096KBps, 5Mbps)\nthe default is Bps") 45 | .takes_value(true), 46 | ) 47 | .arg( 48 | Arg::with_name("HTTP_PORT") 49 | .short("p") 50 | .long("http-port") 51 | .help("Sets HTTP server listen portis listening") 52 | .takes_value(true) 53 | .default_value("33133"), 54 | ) 55 | .arg( 56 | Arg::with_name("ORIGINAL_DIR") 57 | .help("Sets a directory has original files") 58 | .required(true) 59 | .index(1), 60 | ) 61 | .arg( 62 | Arg::with_name("MOUNTPOINT") 63 | .help("Mountpoint directory") 64 | .required(true) 65 | .index(2), 66 | ) 67 | .subcommand( 68 | SubCommand::with_name("periodic") 69 | .about("Stable/unstable mode toggles periodically under this condition") 70 | .arg( 71 | Arg::with_name("DURATION") 72 | .short("d") 73 | .long("duration") 74 | .takes_value(true) 75 | .default_value("30m") 76 | .required(true) 77 | .help("Sets period during the operations are unstable"), 78 | ) 79 | .arg( 80 | Arg::with_name("FREQUENCY") 81 | .short("f") 82 | .long("frequency") 83 | .takes_value(true) 84 | .default_value("60m") 85 | .required(true) 86 | .help("Sets frequency of making operations unstable"), 87 | )) 88 | .get_matches(); 89 | 90 | let original_dir = matches.value_of("ORIGINAL_DIR").unwrap(); 91 | let mountpoint = matches.value_of("MOUNTPOINT").unwrap(); 92 | let http_port: u16 = matches.value_of("HTTP_PORT").unwrap().parse()?; 93 | 94 | let mut config: Config = Default::default(); 95 | 96 | // Override the config if there are given options. 97 | if let Some(speed) = matches.value_of("SPEED") { 98 | config.speed = speed.parse()?; 99 | } 100 | 101 | if let Some(matches) = matches.subcommand_matches("periodic") { 102 | let mut p = config::Condition::default_periodic(); 103 | 104 | if let Some(duration) = matches.value_of("DURATION") { 105 | let secs = parse_time(String::from(duration))?; 106 | 107 | if let Condition::Periodic { 108 | ref mut duration, .. 109 | } = p 110 | { 111 | *duration = Duration::from_secs(secs); 112 | } 113 | } 114 | 115 | if let Some(frequency) = matches.value_of("FREQUENCY") { 116 | let secs = parse_time(String::from(frequency))?; 117 | 118 | if let Condition::Periodic { 119 | ref mut frequency, .. 120 | } = p 121 | { 122 | *frequency = Duration::from_secs(secs); 123 | } 124 | } 125 | 126 | config.condition = p; 127 | } 128 | 129 | let decorator = slog_term::TermDecorator::new().build(); 130 | let drain = slog_term::FullFormat::new(decorator).build().fuse(); 131 | let drain = slog_async::Async::new(drain).build().fuse(); 132 | let drain = slog::Fuse::new(slog::LevelFilter::new(drain, Level::Info)); 133 | let logger = slog::Logger::root(drain, o!()); 134 | 135 | info!(logger, "original directory: {}", original_dir); 136 | info!(logger, "mountpoint: {}", mountpoint); 137 | info!(logger, "config: {}", config); 138 | 139 | let config = Arc::new(AtomicImmut::new(config)); 140 | http::start_server(logger.clone(), http_port, Arc::clone(&config))?; 141 | 142 | let m = Mizumochi::new( 143 | logger.clone(), 144 | original_dir.into(), 145 | mountpoint.into(), 146 | config, 147 | ); 148 | 149 | if let Err(error) = m.mount() { 150 | error!(logger, "{}", error); 151 | Err(Box::new(error)) 152 | } else { 153 | Ok(()) 154 | } 155 | } 156 | 157 | fn parse_time(mut input: String) -> Result> { 158 | let suffix = input.pop().ok_or(std::fmt::Error)?; 159 | let t: u64 = input.parse()?; 160 | 161 | use std::io::{Error, ErrorKind}; 162 | match suffix { 163 | 's' => Ok(t), 164 | 'm' => Ok(t * 60), 165 | 'h' => Ok(t * 60 * 60), 166 | _ => Err(Box::new(Error::new( 167 | ErrorKind::Other, 168 | "time suffix accepts s, m or h", 169 | ))), 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/metrics.rs: -------------------------------------------------------------------------------- 1 | use prometrics::metrics::{Counter, MetricBuilder}; 2 | 3 | #[derive(Debug)] 4 | pub struct Metrics { 5 | pub io_operations_lookup: Counter, 6 | pub io_operations_getattr: Counter, 7 | pub io_operations_readdir: Counter, 8 | pub io_operations_read: Counter, 9 | pub io_operations_setattr: Counter, 10 | pub io_operations_write: Counter, 11 | pub io_operations_open: Counter, 12 | pub io_operations_flush: Counter, 13 | pub io_operations_release: Counter, 14 | pub io_operations_fsync: Counter, 15 | pub io_operations_getxattr: Counter, 16 | pub io_operations_destroy: Counter, 17 | pub io_operations_forget: Counter, 18 | pub io_operations_readlink: Counter, 19 | pub io_operations_unlink: Counter, 20 | pub io_operations_symlink: Counter, 21 | pub io_operations_link: Counter, 22 | pub io_operations_mknod: Counter, 23 | pub io_operations_mkdir: Counter, 24 | pub io_operations_rmdir: Counter, 25 | pub io_operations_rename: Counter, 26 | pub io_operations_opendir: Counter, 27 | pub io_operations_releasedir: Counter, 28 | pub io_operations_fsyncdir: Counter, 29 | pub io_operations_statfs: Counter, 30 | pub io_operations_setxattr: Counter, 31 | pub io_operations_listxattr: Counter, 32 | pub io_operations_removexattr: Counter, 33 | pub io_operations_access: Counter, 34 | pub io_operations_create: Counter, 35 | pub io_operations_getlk: Counter, 36 | pub io_operations_setlk: Counter, 37 | pub io_operations_bmap: Counter, 38 | pub speed_limit_enabled: Counter, 39 | pub speed_limit_disabled: Counter, 40 | } 41 | impl Metrics { 42 | pub fn new() -> Self { 43 | let mut builder = MetricBuilder::new(); 44 | builder.namespace("mizumochi"); 45 | let build_io_operations_metric = |name| { 46 | builder 47 | .counter("io_operations_total") 48 | .label("operation", name) 49 | .help("Number of I/O operations") 50 | .finish() 51 | .expect("Never fails") 52 | }; 53 | Metrics { 54 | io_operations_lookup: build_io_operations_metric("lookup"), 55 | io_operations_getattr: build_io_operations_metric("getattr"), 56 | io_operations_readdir: build_io_operations_metric("readdir"), 57 | io_operations_read: build_io_operations_metric("read"), 58 | io_operations_setattr: build_io_operations_metric("setattr"), 59 | io_operations_write: build_io_operations_metric("write"), 60 | io_operations_open: build_io_operations_metric("open"), 61 | io_operations_flush: build_io_operations_metric("flush"), 62 | io_operations_release: build_io_operations_metric("release"), 63 | io_operations_fsync: build_io_operations_metric("fsync"), 64 | io_operations_getxattr: build_io_operations_metric("getxattr"), 65 | io_operations_destroy: build_io_operations_metric("destroy"), 66 | io_operations_forget: build_io_operations_metric("forget"), 67 | io_operations_readlink: build_io_operations_metric("read_link"), 68 | io_operations_unlink: build_io_operations_metric("unlink"), 69 | io_operations_symlink: build_io_operations_metric("symlink"), 70 | io_operations_link: build_io_operations_metric("link"), 71 | io_operations_mknod: build_io_operations_metric("mknod"), 72 | io_operations_mkdir: build_io_operations_metric("mkdir"), 73 | io_operations_rmdir: build_io_operations_metric("rmdir"), 74 | io_operations_rename: build_io_operations_metric("rename"), 75 | io_operations_opendir: build_io_operations_metric("opendir"), 76 | io_operations_releasedir: build_io_operations_metric("releasedir"), 77 | io_operations_fsyncdir: build_io_operations_metric("fsyncdir"), 78 | io_operations_statfs: build_io_operations_metric("statfs"), 79 | io_operations_setxattr: build_io_operations_metric("setxattr"), 80 | io_operations_listxattr: build_io_operations_metric("listxattr"), 81 | io_operations_removexattr: build_io_operations_metric("removexattr"), 82 | io_operations_access: build_io_operations_metric("access"), 83 | io_operations_create: build_io_operations_metric("create"), 84 | io_operations_getlk: build_io_operations_metric("getlk"), 85 | io_operations_setlk: build_io_operations_metric("setlk"), 86 | io_operations_bmap: build_io_operations_metric("bmap"), 87 | speed_limit_enabled: builder 88 | .counter("speed_limit_enabled_total") 89 | .help("Number of times speed limit has been enabled") 90 | .finish() 91 | .expect("Never fails"), 92 | speed_limit_disabled: builder 93 | .counter("speed_limit_disabled_total") 94 | .help("Number of times speed limit has been disabled") 95 | .finish() 96 | .expect("Never fails"), 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/mizumochi.rs: -------------------------------------------------------------------------------- 1 | // FIXME: Refactor error 2 | use crate::config::{Config, Operation, Speed}; 3 | use crate::localfile::{Inode, LocalFile}; 4 | use crate::metrics::Metrics; 5 | use crate::state::{State, StateManager}; 6 | use atomic_immut::AtomicImmut; 7 | use fuse::{self, *}; 8 | use slog::Logger; 9 | use std::collections::HashMap; 10 | use std::ffi::OsStr; 11 | use std::fs::{self, File}; 12 | use std::io::{self, Read, Seek, SeekFrom, Write}; 13 | use std::mem; 14 | use std::os::raw::c_int; 15 | use std::path::{Path, PathBuf}; 16 | use std::result::Result; 17 | use std::sync::Arc; 18 | use std::thread::sleep; 19 | use std::time::Duration; 20 | use time::{PreciseTime, Timespec}; 21 | 22 | type FileHandler = u64; 23 | 24 | const TTL: Timespec = Timespec { sec: 1, nsec: 0 }; 25 | const ROOT_DIR_INO: u64 = 1; 26 | 27 | pub struct Mizumochi { 28 | logger: Logger, 29 | 30 | state_manager: StateManager, 31 | config: Arc>, 32 | 33 | // FIXME: use simple allocator. 34 | ino_count: Inode, 35 | fh_count: FileHandler, 36 | 37 | fh_map: HashMap, 38 | file_map: HashMap, 39 | 40 | original_dir: PathBuf, 41 | mountpoint: PathBuf, 42 | 43 | metrics: Metrics, 44 | } 45 | 46 | impl Mizumochi { 47 | pub fn new( 48 | logger: Logger, 49 | original_dir: PathBuf, 50 | mountpoint: PathBuf, 51 | config: Arc>, 52 | ) -> Mizumochi { 53 | let cond = config.load().condition.clone(); 54 | let state_manager = StateManager::new(cond); 55 | 56 | Mizumochi { 57 | logger, 58 | 59 | state_manager, 60 | config, 61 | 62 | fh_count: 1, 63 | // inode number begins from the next of `ROOT_DIR_INO`. 64 | ino_count: ROOT_DIR_INO + 1, 65 | fh_map: HashMap::new(), 66 | file_map: HashMap::new(), 67 | 68 | mountpoint, 69 | original_dir, 70 | 71 | metrics: Metrics::new(), 72 | } 73 | } 74 | 75 | pub fn mount(self) -> Result<(), io::Error> { 76 | let mountpoint = self.mountpoint.clone(); 77 | fuse::mount(self, &mountpoint, &[]) 78 | } 79 | 80 | fn init(&mut self) -> Result<(), io::Error> { 81 | if !self.original_dir.is_dir() { 82 | error!( 83 | self.logger, 84 | "Original filepath is not directory: {:?}", self.original_dir 85 | ); 86 | return Err(io::Error::new(io::ErrorKind::InvalidInput, "Not directory")); 87 | } 88 | 89 | // Initialize the state. 90 | self.state_manager.init(); 91 | info!(self.logger, "State: {:?}", self.state_manager.state()); 92 | 93 | let path = self.original_dir.clone(); 94 | self.fetch_files_if_not_found(ROOT_DIR_INO, &path)?; 95 | 96 | Ok(()) 97 | } 98 | 99 | fn fetch_files_if_not_found( 100 | &mut self, 101 | root_ino: Inode, 102 | root_dir: &PathBuf, 103 | ) -> Result<(), io::Error> { 104 | if !root_dir.is_dir() { 105 | error!( 106 | self.logger, 107 | "fetch_files_if_not_found error: path: {:?} is directory, ino: {}", 108 | root_dir, 109 | root_ino 110 | ); 111 | return Err(io::Error::new(io::ErrorKind::Other, "Not directory")); 112 | } 113 | 114 | if let Some(LocalFile::Directory(_, Some(_))) = self.file_map.get(&root_ino) { 115 | // Already fetched. 116 | return Ok(()); 117 | } 118 | 119 | info!( 120 | self.logger, 121 | "fetch_files_if_not_found: ino: {}, path: {:?}", root_ino, root_dir 122 | ); 123 | 124 | let mut files = Vec::new(); 125 | 126 | // Fetch the all files in the directory. 127 | for entry in fs::read_dir(root_dir)? { 128 | let entry = entry?; 129 | let path = entry.path(); 130 | 131 | let filename = path 132 | .file_name() 133 | .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Cannot get filename"))?; 134 | 135 | let file = if path.is_dir() { 136 | // The files in the directory is loaded later (see lookup). 137 | LocalFile::Directory(path.clone(), None) 138 | } else { 139 | LocalFile::RegularFile(path.clone()) 140 | }; 141 | 142 | let ino = self.ino_count; 143 | self.ino_count += 1; 144 | self.file_map.insert(ino, file); 145 | 146 | files.push((ino, filename.into())); 147 | } 148 | 149 | self.file_map 150 | .insert(root_ino, LocalFile::Directory(root_dir.into(), Some(files))); 151 | 152 | Ok(()) 153 | } 154 | 155 | fn change_state_if_necessary(&mut self, op: Operation) -> &State { 156 | let prev_state = self.state_manager.state().clone(); 157 | 158 | { 159 | let cond = &self.config.load().condition; 160 | let state = if let Ok(state) = self.state_manager.on_operated_after(op, cond) { 161 | state.clone() 162 | } else { 163 | crit!( 164 | self.logger, 165 | "change_state_if_necessary crit: let the state stable" 166 | ); 167 | State::Stable 168 | }; 169 | 170 | match (prev_state, state) { 171 | (State::Stable, State::Unstable) => { 172 | self.metrics.speed_limit_enabled.increment(); 173 | info!(self.logger, "--- Enable unstable mode ---") 174 | } 175 | (State::Unstable, State::Stable) => { 176 | self.metrics.speed_limit_enabled.increment(); 177 | info!(self.logger, "--- Enable stable mode ---") 178 | } 179 | _ => {} 180 | } 181 | } 182 | 183 | self.state_manager.state() 184 | } 185 | 186 | fn lookup(&mut self, parent: u64, name: &OsStr) -> Result { 187 | let (inode, path) = match self 188 | .file_map 189 | .get(&parent) 190 | .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, ""))? 191 | { 192 | LocalFile::Directory(_, None) => { 193 | Err(io::Error::new(io::ErrorKind::Other, "not fetched")) 194 | } 195 | LocalFile::Directory(path, Some(files)) => { 196 | // Find the file by the given name. 197 | let f = files 198 | .iter() 199 | .find(|(_, path)| Some(name) == path.file_name()); 200 | 201 | let (inode, _) = f.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, ""))?; 202 | 203 | let mut path = path.clone(); 204 | path.push(name); 205 | 206 | Ok((*inode, path)) 207 | } 208 | _ => Err(io::Error::new( 209 | io::ErrorKind::InvalidInput, 210 | "it is not directory", 211 | )), 212 | }?; 213 | 214 | if path.is_dir() { 215 | self.fetch_files_if_not_found(inode, &path)?; 216 | } 217 | 218 | fetch_fileattr(inode, &path) 219 | } 220 | 221 | fn read(&mut self, fh: u64, buffer: &mut [u8], offset: i64, size: u32) -> Result { 222 | let logger = &self.logger; 223 | let f = self.fh_map.get_mut(&fh).ok_or(libc::ENOENT)?; 224 | 225 | let file_size = f.metadata().map_err(|_| libc::EIO)?.len(); 226 | 227 | let offset = offset as u64; 228 | if offset < file_size { 229 | if let Err(error) = f.seek(SeekFrom::Start(offset)) { 230 | error!(logger, "seek error {}", error); 231 | return Err(libc::EIO); 232 | } 233 | 234 | // Truncate the size to avoid overreading. 235 | let size = if file_size < (offset + u64::from(size)) { 236 | (file_size - offset) as usize 237 | } else { 238 | size as usize 239 | }; 240 | 241 | f.read(&mut buffer[0..size]).map_err(|error| { 242 | error!(logger, "read error {}", error); 243 | libc::EIO 244 | }) 245 | } else { 246 | Ok(0) 247 | } 248 | } 249 | 250 | fn write(&mut self, fh: u64, buffer: &[u8], offset: i64) -> Result { 251 | let logger = &self.logger; 252 | let f = self.fh_map.get_mut(&fh).ok_or(libc::ENOENT)?; 253 | if let Err(error) = f.seek(SeekFrom::Start(offset as u64)) { 254 | error!(self.logger, "seek error {}", error); 255 | return Err(libc::EIO); 256 | } 257 | 258 | let written_size = f.write(buffer).map_err(|error| { 259 | error!(logger, "write error {}", error); 260 | libc::EIO 261 | })?; 262 | 263 | // Reflect the written result to the actual file. 264 | let _ = f.sync_all().map_err(|error| { 265 | error!(logger, "write error {}", error); 266 | libc::EIO 267 | })?; 268 | let _ = f.sync_data().map_err(|error| { 269 | error!(logger, "write error {}", error); 270 | libc::EIO 271 | })?; 272 | 273 | Ok(written_size) 274 | } 275 | 276 | fn readdir( 277 | &mut self, 278 | _req: &Request, 279 | ino: u64, 280 | _: u64, 281 | offset: i64, 282 | reply: &mut ReplyDirectory, 283 | ) -> Result<(), io::Error> { 284 | if offset != 0 { 285 | // From the FUSE document https://libfuse.github.io/doxygen/structfuse__operations.html#ae269583c4bfaf4d9a82e1d51a902cd5c 286 | // Filesystem can ignore the offset. 287 | // > 1) The readdir implementation ignores the offset parameter, and passes zero to the filler function's offset. 288 | // > The filler function will not return '1' (unless an error happens), so the whole directory is read in a single readdir operation. 289 | return Ok(()); 290 | } 291 | 292 | let files = match self.file_map.get(&ino) { 293 | Some(LocalFile::Directory(_, Some(files))) => Ok(files), 294 | _ => Err(io::Error::new(io::ErrorKind::NotFound, "")), 295 | }?; 296 | 297 | // Add itself and the parent. 298 | reply.add(1, 0, FileType::Directory, "."); 299 | reply.add(1, 1, FileType::Directory, ".."); 300 | 301 | // Add the files in the directory. 302 | let mut offset = 2i64; 303 | for (fino, _) in files { 304 | let (ftype, path) = match self.file_map.get(&fino) { 305 | Some(LocalFile::RegularFile(path)) => (FileType::RegularFile, path), 306 | Some(LocalFile::Directory(path, _)) => (FileType::Directory, path), 307 | None => { 308 | crit!(self.logger, "file_map is inconsistent: {:?}", self.file_map); 309 | crit!(self.logger, "directory ino: {}, file ino: {}", ino, fino); 310 | return Err(io::Error::new(io::ErrorKind::Other, "meybe bug")); 311 | } 312 | }; 313 | 314 | let filename = path 315 | .file_name() 316 | .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Cannot get filename"))?; 317 | reply.add(*fino, offset, ftype, filename); 318 | 319 | offset += 1; 320 | } 321 | 322 | Ok(()) 323 | } 324 | 325 | fn create( 326 | &mut self, 327 | _req: &Request, 328 | parent: u64, 329 | name: &OsStr, 330 | _mode: u32, 331 | _flags: u32, 332 | ) -> Result<(FileAttr, FileHandler), io::Error> { 333 | let name = name 334 | .to_str() 335 | .ok_or_else(|| io::Error::new(io::ErrorKind::Other, ""))?; 336 | 337 | let (attr, fh, ino, f) = { 338 | let (mut path, files) = match self.file_map.get_mut(&parent) { 339 | Some(LocalFile::Directory(path, Some(files))) => Ok((path.clone(), files)), 340 | _ => Err(io::Error::new(io::ErrorKind::Other, "")), 341 | }?; 342 | 343 | path.push(name); 344 | 345 | let file = File::create(&path)?; 346 | 347 | let ino = self.ino_count; 348 | self.ino_count += 1; 349 | 350 | let attr = fetch_fileattr(ino, &path)?; 351 | 352 | let fh = self.fh_count; 353 | self.fh_count += 1; 354 | 355 | self.fh_map.insert(fh, file); 356 | files.push((ino, name.into())); 357 | 358 | (attr, fh, ino, LocalFile::RegularFile(path)) 359 | }; 360 | 361 | self.file_map.insert(ino, f); 362 | 363 | Ok((attr, fh)) 364 | } 365 | } 366 | 367 | impl Filesystem for Mizumochi { 368 | fn init(&mut self, _req: &Request) -> Result<(), c_int> { 369 | info!(self.logger, "init"); 370 | 371 | Mizumochi::init(self).map_err(|error| { 372 | error!(self.logger, "init error: {}", error); 373 | libc::EIO 374 | }) 375 | } 376 | 377 | fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { 378 | debug!(self.logger, "lookup: parent: {}, name: {:?}", parent, name); 379 | self.metrics.io_operations_lookup.increment(); 380 | 381 | match Mizumochi::lookup(self, parent, name) { 382 | Ok(ref attr) => reply.entry(&TTL, attr, 0), 383 | Err(error) => match error.kind() { 384 | io::ErrorKind::NotFound => reply.error(libc::ENOENT), 385 | _ => { 386 | error!(self.logger, "lookup error: {}", error); 387 | reply.error(libc::EIO) 388 | } 389 | }, 390 | } 391 | } 392 | 393 | fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) { 394 | debug!(self.logger, "getattr: ino: {:?}", ino); 395 | self.metrics.io_operations_getattr.increment(); 396 | 397 | match self.file_map.get(&ino) { 398 | Some(LocalFile::RegularFile(path)) => match fetch_fileattr(ino, path) { 399 | Ok(attr) => reply.attr(&TTL, &attr), 400 | Err(error) => { 401 | error!( 402 | self.logger, 403 | "getattr error: ino = {}, path = {:?}, error = {}", ino, path, error 404 | ); 405 | reply.error(libc::EIO) 406 | } 407 | }, 408 | Some(LocalFile::Directory(path, _)) => match fetch_fileattr(ino, path) { 409 | Ok(attr) => reply.attr(&TTL, &attr), 410 | Err(error) => { 411 | error!( 412 | self.logger, 413 | "getattr error: ino = {}, path = {:?}, error = {}", ino, path, error 414 | ); 415 | reply.error(libc::EIO) 416 | } 417 | }, 418 | _ => { 419 | reply.error(libc::ENOENT); 420 | } 421 | } 422 | } 423 | 424 | fn readdir( 425 | &mut self, 426 | req: &Request, 427 | ino: u64, 428 | fh: u64, 429 | offset: i64, 430 | mut reply: ReplyDirectory, 431 | ) { 432 | debug!( 433 | self.logger, 434 | "readdir: ino: {}, fh: {}, offset: {}", ino, fh, offset 435 | ); 436 | self.metrics.io_operations_readdir.increment(); 437 | 438 | use self::io::ErrorKind; 439 | if let Err(error) = self.readdir(req, ino, fh, offset, &mut reply) { 440 | let e = match error.kind() { 441 | ErrorKind::NotFound => libc::ENOENT, 442 | _ => { 443 | error!(self.logger, "readdir error: {}", error); 444 | libc::EIO 445 | } 446 | }; 447 | 448 | reply.error(e); 449 | } else { 450 | reply.ok(); 451 | } 452 | } 453 | 454 | fn read( 455 | &mut self, 456 | _req: &Request, 457 | ino: u64, 458 | fh: u64, 459 | offset: i64, 460 | size: u32, 461 | reply: ReplyData, 462 | ) { 463 | debug!( 464 | self.logger, 465 | "read: ino: {}, fh: {}, offset: {}, size: {}", ino, fh, offset, size 466 | ); 467 | self.metrics.io_operations_read.increment(); 468 | 469 | let start = PreciseTime::now(); 470 | 471 | let mut buffer = vec![0; size as usize]; 472 | 473 | match Mizumochi::read(self, fh, &mut buffer, offset, size) { 474 | Ok(read_size) => { 475 | reply.data(&buffer[0..read_size]); 476 | 477 | if State::Unstable == *self.change_state_if_necessary(Operation::Read) { 478 | if let Speed::Bps(bps) = self.config.load().speed { 479 | // Mesure elapsed time and wait if necessary. 480 | sleep(compute_sleep_duration_to_adjust_speed( 481 | bps, 482 | read_size, 483 | start.to(PreciseTime::now()).num_milliseconds() as u64, 484 | )); 485 | } 486 | } 487 | } 488 | Err(error) => { 489 | error!(self.logger, "read error: {}", error); 490 | reply.error(libc::EIO); 491 | } 492 | } 493 | } 494 | 495 | fn setattr( 496 | &mut self, 497 | _req: &Request, 498 | ino: u64, 499 | _mode: Option, 500 | _uid: Option, 501 | _gid: Option, 502 | _size: Option, 503 | _atime: Option, 504 | _mtime: Option, 505 | fh: Option, 506 | _crtime: Option, 507 | _chgtime: Option, 508 | _bkuptime: Option, 509 | _flags: Option, 510 | reply: ReplyAttr, 511 | ) { 512 | debug!(self.logger, "setattr: ino: {}, fh: {:?}", ino, fh); 513 | self.metrics.io_operations_setattr.increment(); 514 | 515 | match self.file_map.get(&ino) { 516 | Some(LocalFile::RegularFile(path)) => match fetch_fileattr(ino, path) { 517 | Ok(attr) => reply.attr(&TTL, &attr), 518 | Err(error) => { 519 | error!( 520 | self.logger, 521 | "getattr error: ino = {}, path = {:?}, error = {}", ino, path, error 522 | ); 523 | reply.error(libc::EIO) 524 | } 525 | }, 526 | Some(LocalFile::Directory(path, _)) => match fetch_fileattr(ino, path) { 527 | Ok(attr) => reply.attr(&TTL, &attr), 528 | Err(error) => { 529 | error!( 530 | self.logger, 531 | "getattr error: ino = {}, path = {:?}, error = {}", ino, path, error 532 | ); 533 | reply.error(libc::EIO) 534 | } 535 | }, 536 | _ => { 537 | reply.error(libc::ENOENT); 538 | } 539 | } 540 | } 541 | 542 | fn write( 543 | &mut self, 544 | _req: &Request, 545 | ino: u64, 546 | fh: u64, 547 | offset: i64, 548 | data: &[u8], 549 | _flags: u32, 550 | reply: ReplyWrite, 551 | ) { 552 | debug!( 553 | self.logger, 554 | "write: ino: {}, fh: {}, offset: {}, size: {}", 555 | ino, 556 | fh, 557 | offset, 558 | data.len() 559 | ); 560 | self.metrics.io_operations_write.increment(); 561 | 562 | let start = PreciseTime::now(); 563 | 564 | match Mizumochi::write(self, fh, data, offset) { 565 | Ok(written_size) => { 566 | reply.written(written_size as u32); 567 | 568 | if State::Unstable == *self.change_state_if_necessary(Operation::Write) { 569 | if let Speed::Bps(bps) = self.config.load().speed { 570 | sleep(compute_sleep_duration_to_adjust_speed( 571 | bps, 572 | written_size, 573 | start.to(PreciseTime::now()).num_milliseconds() as u64, 574 | )); 575 | } 576 | } 577 | } 578 | Err(ecode) => { 579 | error!(self.logger, " read error: {:?}", ecode); 580 | reply.error(ecode); 581 | } 582 | } 583 | } 584 | 585 | fn open(&mut self, _req: &Request, ino: u64, flags: u32, reply: ReplyOpen) { 586 | // TODO: handle the flags. 587 | info!(self.logger, "open ino: {}, flags: {}", ino, flags); 588 | self.metrics.io_operations_open.increment(); 589 | 590 | match self.file_map.get(&ino) { 591 | Some(LocalFile::RegularFile(filepath)) => { 592 | let mut options = fs::OpenOptions::new(); 593 | options.read(true).write(true).create(false); 594 | 595 | info!(self.logger, "filepath: {:?}", filepath); 596 | match options.open(filepath) { 597 | Ok(f) => { 598 | let fh = self.fh_count; 599 | self.fh_count += 1; 600 | self.fh_map.insert(fh, f); 601 | 602 | reply.opened(fh, 0); 603 | } 604 | Err(error) => { 605 | error!(self.logger, "open error: {}", error); 606 | reply.error(libc::EIO) 607 | } 608 | } 609 | } 610 | Some(LocalFile::Directory(filepath, _)) => { 611 | error!(self.logger, "directory: {:?}", filepath); 612 | reply.error(libc::ENOENT) 613 | } 614 | None => { 615 | error!(self.logger, "readdir error: inode {} is not found", ino); 616 | reply.error(libc::ENOENT) 617 | } 618 | } 619 | } 620 | 621 | fn flush(&mut self, _req: &Request, ino: u64, fh: u64, _lock_owner: u64, reply: ReplyEmpty) { 622 | debug!(self.logger, "flush: ino: {}, fh: {}", ino, fh); 623 | self.metrics.io_operations_flush.increment(); 624 | 625 | if let Some(f) = self.fh_map.get_mut(&fh) { 626 | if let Err(error) = f.seek(SeekFrom::Start(0)) { 627 | info!(self.logger, "flush seek error: {}", error); 628 | reply.error(libc::EIO); 629 | } else { 630 | reply.ok(); 631 | } 632 | } else { 633 | error!(self.logger, "flush error: no entry"); 634 | reply.error(libc::ENOENT); 635 | } 636 | } 637 | 638 | fn release( 639 | &mut self, 640 | _req: &Request, 641 | ino: u64, 642 | fh: u64, 643 | _flags: u32, 644 | _lock_owner: u64, 645 | _flush: bool, 646 | reply: ReplyEmpty, 647 | ) { 648 | info!(self.logger, "release: ino: {}, fh: {}", ino, fh); 649 | self.metrics.io_operations_release.increment(); 650 | 651 | if let Some(f) = self.fh_map.remove(&fh) { 652 | if let Err(error) = f.sync_data() { 653 | error!(self.logger, "sync_data error: {}", error); 654 | reply.error(libc::EIO); 655 | } else { 656 | reply.ok(); 657 | } 658 | } else { 659 | error!(self.logger, "release error: no entry"); 660 | reply.error(libc::ENOENT); 661 | } 662 | } 663 | 664 | fn fsync(&mut self, _req: &Request, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty) { 665 | debug!( 666 | self.logger, 667 | "fsync ino: {}, fh: {}, datasync: {}", ino, fh, datasync 668 | ); 669 | self.metrics.io_operations_fsync.increment(); 670 | 671 | if let Some(f) = self.fh_map.get(&fh) { 672 | if let Err(error) = f.sync_data() { 673 | error!(self.logger, "sync_data error: {}", error); 674 | reply.error(libc::EIO); 675 | } else { 676 | reply.ok(); 677 | } 678 | } else { 679 | error!(self.logger, "fsync error: no entry"); 680 | reply.error(libc::ENOENT); 681 | } 682 | } 683 | 684 | fn getxattr( 685 | &mut self, 686 | _req: &Request, 687 | _ino: u64, 688 | _name: &OsStr, 689 | _size: u32, 690 | reply: ReplyXattr, 691 | ) { 692 | debug!(self.logger, "getxattr"); 693 | self.metrics.io_operations_getxattr.increment(); 694 | 695 | reply.error(libc::ENOSYS); 696 | } 697 | 698 | fn destroy(&mut self, _req: &Request) { 699 | debug!(self.logger, "destroy"); 700 | self.metrics.io_operations_destroy.increment(); 701 | } 702 | 703 | fn forget(&mut self, _req: &Request, _ino: u64, _nlookup: u64) { 704 | debug!(self.logger, "forget"); 705 | self.metrics.io_operations_forget.increment(); 706 | } 707 | 708 | fn readlink(&mut self, _req: &Request, _ino: u64, reply: ReplyData) { 709 | debug!(self.logger, "readlink"); 710 | self.metrics.io_operations_readlink.increment(); 711 | reply.error(libc::ENOSYS); 712 | } 713 | 714 | fn mknod( 715 | &mut self, 716 | _req: &Request, 717 | _parent: u64, 718 | _name: &OsStr, 719 | _mode: u32, 720 | _rdev: u32, 721 | reply: ReplyEntry, 722 | ) { 723 | debug!(self.logger, "mknod"); 724 | self.metrics.io_operations_mknod.increment(); 725 | reply.error(libc::ENOSYS); 726 | } 727 | 728 | fn mkdir( 729 | &mut self, 730 | _req: &Request, 731 | _parent: u64, 732 | _name: &OsStr, 733 | _mode: u32, 734 | reply: ReplyEntry, 735 | ) { 736 | debug!(self.logger, "mkdir"); 737 | self.metrics.io_operations_mkdir.increment(); 738 | reply.error(libc::ENOSYS); 739 | } 740 | 741 | fn unlink(&mut self, _req: &Request, _parent: u64, _name: &OsStr, reply: ReplyEmpty) { 742 | debug!(self.logger, "unlink"); 743 | self.metrics.io_operations_unlink.increment(); 744 | reply.error(libc::ENOSYS); 745 | } 746 | 747 | fn rmdir(&mut self, _req: &Request, _parent: u64, _name: &OsStr, reply: ReplyEmpty) { 748 | debug!(self.logger, "rmdir"); 749 | self.metrics.io_operations_rmdir.increment(); 750 | reply.error(libc::ENOSYS); 751 | } 752 | 753 | fn symlink( 754 | &mut self, 755 | _req: &Request, 756 | _parent: u64, 757 | _name: &OsStr, 758 | _link: &Path, 759 | reply: ReplyEntry, 760 | ) { 761 | debug!(self.logger, "symlink"); 762 | self.metrics.io_operations_symlink.increment(); 763 | reply.error(libc::ENOSYS); 764 | } 765 | 766 | fn rename( 767 | &mut self, 768 | _req: &Request, 769 | _parent: u64, 770 | _name: &OsStr, 771 | _newparent: u64, 772 | _newname: &OsStr, 773 | reply: ReplyEmpty, 774 | ) { 775 | debug!(self.logger, "rename"); 776 | self.metrics.io_operations_rename.increment(); 777 | reply.error(libc::ENOSYS); 778 | } 779 | 780 | fn link( 781 | &mut self, 782 | _req: &Request, 783 | _ino: u64, 784 | _newparent: u64, 785 | _newname: &OsStr, 786 | reply: ReplyEntry, 787 | ) { 788 | debug!(self.logger, "link"); 789 | self.metrics.io_operations_link.increment(); 790 | reply.error(libc::ENOSYS); 791 | } 792 | 793 | fn opendir(&mut self, _req: &Request, ino: u64, _flags: u32, reply: ReplyOpen) { 794 | debug!(self.logger, "opendir: ino: {}", ino); 795 | self.metrics.io_operations_opendir.increment(); 796 | 797 | reply.opened(0, 0); 798 | } 799 | 800 | fn releasedir(&mut self, _req: &Request, _ino: u64, _fh: u64, _flags: u32, reply: ReplyEmpty) { 801 | debug!(self.logger, "releasedir"); 802 | self.metrics.io_operations_releasedir.increment(); 803 | reply.ok(); 804 | } 805 | 806 | fn fsyncdir( 807 | &mut self, 808 | _req: &Request, 809 | _ino: u64, 810 | _fh: u64, 811 | _datasync: bool, 812 | reply: ReplyEmpty, 813 | ) { 814 | debug!(self.logger, "fsyncdir"); 815 | self.metrics.io_operations_fsyncdir.increment(); 816 | reply.error(libc::ENOSYS); 817 | } 818 | 819 | fn statfs(&mut self, _req: &Request, _ino: u64, reply: ReplyStatfs) { 820 | // debug!(self.logger, "statfs"); 821 | reply.statfs(0, 0, 0, 0, 0, 512, 255, 0); 822 | self.metrics.io_operations_statfs.increment(); 823 | } 824 | 825 | fn setxattr( 826 | &mut self, 827 | _req: &Request, 828 | _ino: u64, 829 | _name: &OsStr, 830 | _value: &[u8], 831 | _flags: u32, 832 | _position: u32, 833 | reply: ReplyEmpty, 834 | ) { 835 | debug!(self.logger, "setxattr"); 836 | self.metrics.io_operations_setxattr.increment(); 837 | reply.error(libc::ENOSYS); 838 | } 839 | 840 | fn listxattr(&mut self, _req: &Request, _ino: u64, _size: u32, reply: ReplyXattr) { 841 | debug!(self.logger, "listxattr"); 842 | self.metrics.io_operations_listxattr.increment(); 843 | reply.error(libc::ENOSYS); 844 | } 845 | 846 | fn removexattr(&mut self, _req: &Request, _ino: u64, _name: &OsStr, reply: ReplyEmpty) { 847 | debug!(self.logger, "removexattr"); 848 | self.metrics.io_operations_removexattr.increment(); 849 | reply.error(libc::ENOSYS); 850 | } 851 | 852 | fn access(&mut self, _req: &Request, _ino: u64, _mask: u32, reply: ReplyEmpty) { 853 | debug!(self.logger, "access"); 854 | self.metrics.io_operations_access.increment(); 855 | reply.error(libc::ENOSYS); 856 | } 857 | 858 | fn create( 859 | &mut self, 860 | req: &Request, 861 | parent: u64, 862 | name: &OsStr, 863 | mode: u32, 864 | flags: u32, 865 | reply: ReplyCreate, 866 | ) { 867 | debug!(self.logger, "create: parent: {}, name: {:?}", parent, name); 868 | self.metrics.io_operations_create.increment(); 869 | 870 | match Mizumochi::create(self, req, parent, name, mode, flags) { 871 | Ok((attr, fh)) => reply.created(&TTL, &attr, 0, fh, 0), 872 | Err(error) => { 873 | error!(self.logger, "init error: {}", error); 874 | reply.error(libc::EIO) 875 | } 876 | } 877 | } 878 | 879 | fn getlk( 880 | &mut self, 881 | _req: &Request, 882 | _ino: u64, 883 | _fh: u64, 884 | _lock_owner: u64, 885 | _start: u64, 886 | _end: u64, 887 | _typ: u32, 888 | _pid: u32, 889 | reply: ReplyLock, 890 | ) { 891 | debug!(self.logger, "getlk"); 892 | self.metrics.io_operations_getlk.increment(); 893 | reply.error(libc::ENOSYS); 894 | } 895 | 896 | fn setlk( 897 | &mut self, 898 | _req: &Request, 899 | _ino: u64, 900 | _fh: u64, 901 | _lock_owner: u64, 902 | _start: u64, 903 | _end: u64, 904 | _typ: u32, 905 | _pid: u32, 906 | _sleep: bool, 907 | reply: ReplyEmpty, 908 | ) { 909 | debug!(self.logger, "setlk"); 910 | self.metrics.io_operations_setlk.increment(); 911 | reply.error(libc::ENOSYS); 912 | } 913 | 914 | fn bmap(&mut self, _req: &Request, _ino: u64, _blocksize: u32, _idx: u64, reply: ReplyBmap) { 915 | debug!(self.logger, "bmap"); 916 | self.metrics.io_operations_bmap.increment(); 917 | reply.error(libc::ENOSYS); 918 | } 919 | } 920 | 921 | fn timespec_from(st: &std::time::SystemTime) -> Timespec { 922 | if let Ok(dur_since_epoch) = st.duration_since(std::time::UNIX_EPOCH) { 923 | Timespec::new( 924 | dur_since_epoch.as_secs() as i64, 925 | dur_since_epoch.subsec_nanos() as i32, 926 | ) 927 | } else { 928 | Timespec::new(0, 0) 929 | } 930 | } 931 | 932 | fn fetch_fileattr(ino: u64, filepath: &Path) -> Result { 933 | use std::os::unix::fs::MetadataExt; 934 | use std::os::unix::fs::PermissionsExt; 935 | 936 | let metadata = fs::metadata(filepath)?; 937 | let mode = metadata.permissions().mode(); 938 | let kind = mode & libc::S_IFMT as u32; 939 | 940 | let default_timespec = Timespec::new(0, 0); 941 | let mut attr: FileAttr = unsafe { mem::zeroed() }; 942 | attr.ino = ino; 943 | attr.size = metadata.len(); 944 | attr.atime = metadata 945 | .accessed() 946 | .map(|time| timespec_from(&time)) 947 | .unwrap_or(default_timespec); 948 | attr.mtime = metadata 949 | .modified() 950 | .map(|time| timespec_from(&time)) 951 | .unwrap_or(default_timespec); 952 | attr.ctime = metadata 953 | .created() 954 | .map(|time| timespec_from(&time)) 955 | .unwrap_or(default_timespec); 956 | attr.kind = if kind == libc::S_IFREG as u32 { 957 | FileType::RegularFile 958 | } else if kind == libc::S_IFDIR as u32 { 959 | FileType::Directory 960 | } else if kind == libc::S_IFIFO as u32 { 961 | FileType::NamedPipe 962 | } else if kind == libc::S_IFCHR as u32 { 963 | FileType::CharDevice 964 | } else if kind == libc::S_IFBLK as u32 { 965 | FileType::BlockDevice 966 | } else if kind == libc::S_IFLNK as u32 { 967 | FileType::Symlink 968 | } else if kind == libc::S_IFSOCK as u32 { 969 | FileType::Socket 970 | } else { 971 | return Err(io::Error::new(io::ErrorKind::Other, "unknown kind")); 972 | }; 973 | attr.perm = (mode & (libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO) as u32) as u16; 974 | attr.uid = metadata.uid(); 975 | attr.gid = metadata.gid(); 976 | attr.nlink = metadata.nlink() as u32; 977 | 978 | Ok(attr) 979 | } 980 | 981 | /// `request_bps` means request Byte per seconds (not bit). 982 | /// `count_byte` is the number of read/written bytes. 983 | /// `elapsed_ms` is the elapsed time in milliseconds to read/write data. 984 | fn compute_sleep_duration_to_adjust_speed( 985 | request_bps: usize, 986 | count_byte: usize, 987 | elapsed_ms: u64, 988 | ) -> Duration { 989 | if request_bps == 0 { 990 | panic!("The given request bps is zero."); 991 | } 992 | 993 | let expect_sec = count_byte as f64 / request_bps as f64; 994 | let expect_ms = (expect_sec * 1000.0).round() as u64; 995 | let wait_ms = expect_ms.saturating_sub(elapsed_ms); 996 | 997 | Duration::from_millis(wait_ms as u64) 998 | } 999 | 1000 | #[cfg(test)] 1001 | mod tests { 1002 | use super::*; 1003 | 1004 | #[test] 1005 | fn test_compute_sleep_duration_to_adjust_speed() { 1006 | assert_eq!( 1007 | Duration::from_millis(0), 1008 | compute_sleep_duration_to_adjust_speed(1024, 0, 0) 1009 | ); 1010 | assert_eq!( 1011 | Duration::from_millis(0), 1012 | compute_sleep_duration_to_adjust_speed(1024, 0, 512) 1013 | ); 1014 | assert_eq!( 1015 | Duration::from_millis(500), 1016 | compute_sleep_duration_to_adjust_speed(1024, 512, 0) 1017 | ); 1018 | assert_eq!( 1019 | Duration::from_millis(1000), 1020 | compute_sleep_duration_to_adjust_speed(1024, 1024, 0) 1021 | ); 1022 | assert_eq!( 1023 | Duration::from_millis(2000), 1024 | compute_sleep_duration_to_adjust_speed(1024, 2048, 0) 1025 | ); 1026 | assert_eq!( 1027 | Duration::from_millis(0), 1028 | compute_sleep_duration_to_adjust_speed(1024, 256, 512) 1029 | ); 1030 | assert_eq!( 1031 | Duration::from_millis(0), 1032 | compute_sleep_duration_to_adjust_speed(1024, 512, 512) 1033 | ); 1034 | assert_eq!( 1035 | Duration::from_millis(0), 1036 | compute_sleep_duration_to_adjust_speed(1024, 512, 1000) 1037 | ); 1038 | } 1039 | } 1040 | -------------------------------------------------------------------------------- /src/state.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{Condition, Operation}; 2 | use std::time::{Duration, Instant}; 3 | 4 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 5 | pub enum State { 6 | Stable, 7 | Unstable, 8 | } 9 | 10 | /// `StateManager` stores information for a condition to toggle stable/unstable. 11 | pub struct StateManager { 12 | // Keep current condition to detect changing the condition. 13 | // NOTE: Find more clever implementation. 14 | condition: Condition, 15 | state: State, 16 | current_state_begin_time: Instant, 17 | } 18 | 19 | impl StateManager { 20 | pub fn new(condition: Condition) -> StateManager { 21 | StateManager { 22 | condition, 23 | state: State::Stable, 24 | current_state_begin_time: Instant::now(), 25 | } 26 | } 27 | 28 | pub fn init(&mut self) { 29 | match self.condition { 30 | Condition::Always(ref s) => self.state = s.clone(), 31 | _ => self.state = State::Stable, 32 | } 33 | 34 | self.current_state_begin_time = Instant::now(); 35 | } 36 | 37 | pub fn change_condition(&mut self, c: &Condition) { 38 | self.condition = c.clone(); 39 | self.init() 40 | } 41 | 42 | pub fn state(&self) -> &State { 43 | &self.state 44 | } 45 | 46 | pub fn on_operated_after(&mut self, _: Operation, cond: &Condition) -> Result<&State, String> { 47 | if self.condition != *cond { 48 | self.change_condition(cond); 49 | } 50 | 51 | use crate::Condition::*; 52 | match self.condition { 53 | Periodic { 54 | ref duration, 55 | ref frequency, 56 | } => { 57 | let elapsed = self.current_state_begin_time.elapsed().as_secs(); 58 | let (next_mode, d) = toggle_mode_if_necessary( 59 | self.state == State::Unstable, 60 | duration, 61 | frequency, 62 | elapsed, 63 | ); 64 | self.current_state_begin_time += d; 65 | 66 | match (self.state == State::Unstable, next_mode) { 67 | (false, true) => { 68 | self.state = State::Unstable; 69 | } 70 | (true, false) => { 71 | self.state = State::Stable; 72 | } 73 | _ => {} 74 | } 75 | } 76 | Always(_) => { 77 | // Keep the current state, 78 | } 79 | } 80 | 81 | Ok(&self.state) 82 | } 83 | } 84 | 85 | fn toggle_mode_if_necessary( 86 | is_unstable: bool, 87 | duration: &Duration, 88 | frequency: &Duration, 89 | elapsed: u64, 90 | ) -> (bool, Duration) { 91 | let frequency = frequency.as_secs(); 92 | let duration = duration.as_secs(); 93 | let one_term = frequency + duration; 94 | 95 | let cnt = elapsed / one_term; 96 | let elapsed = elapsed % one_term; 97 | 98 | let t = if !is_unstable { frequency } else { duration }; 99 | 100 | if t < elapsed { 101 | // Toggle the mode if the elapsed time exceeds the current mode duration. 102 | (!is_unstable, Duration::from_secs(cnt * one_term + t)) 103 | } else { 104 | // Keep 105 | (is_unstable, Duration::from_secs(cnt * one_term)) 106 | } 107 | } 108 | 109 | #[cfg(test)] 110 | mod tests { 111 | use super::*; 112 | use crate::config::Config; 113 | use atomic_immut::AtomicImmut; 114 | use std::sync::Arc; 115 | 116 | struct TestFileSystem { 117 | config: Arc>, 118 | stat: StateManager, 119 | } 120 | 121 | #[test] 122 | fn test_state_manager() { 123 | let mut config = Config::default(); 124 | config.condition = Condition::Periodic { 125 | duration: Duration::from_secs(10 * 60), 126 | frequency: Duration::from_secs(30 * 60), 127 | }; 128 | 129 | let stat = StateManager::new(config.condition.clone()); 130 | 131 | let mut fs = TestFileSystem { 132 | config: Arc::new(AtomicImmut::new(config)), 133 | stat, 134 | }; 135 | 136 | fs.stat.init(); 137 | 138 | // Change the time for test. 139 | fs.stat.current_state_begin_time = Instant::now() - Duration::from_secs(5 * 60); 140 | 141 | // The state is kept. 142 | let cond = &fs.config.load().condition; 143 | assert_eq!( 144 | Ok(&State::Stable), 145 | fs.stat.on_operated_after(Operation::Read, cond) 146 | ); 147 | 148 | // Change the time for test. 149 | fs.stat.current_state_begin_time = Instant::now() - Duration::from_secs(35 * 60); 150 | 151 | // The state is changed to unstable. 152 | let cond = &fs.config.load().condition; 153 | assert_eq!( 154 | Ok(&State::Unstable), 155 | fs.stat.on_operated_after(Operation::Read, cond) 156 | ); 157 | 158 | // Change the condition. 159 | let mut config = (&*fs.config.load()).clone(); 160 | config.condition = Condition::Always(State::Stable); 161 | fs.config.store(config); 162 | 163 | let cond = &fs.config.load().condition; 164 | assert_eq!( 165 | Ok(&State::Stable), 166 | fs.stat.on_operated_after(Operation::Read, cond) 167 | ); 168 | } 169 | 170 | #[test] 171 | fn test_toggle_mode() { 172 | let is_unstable = true; 173 | let duration = &Duration::from_secs(10); 174 | let frequency = &Duration::from_secs(60); 175 | 176 | let mut elapsed = 0; 177 | 178 | // Keep. 179 | let (is_unstable, d) = toggle_mode_if_necessary(is_unstable, duration, frequency, elapsed); 180 | elapsed -= d.as_secs(); 181 | assert_eq!(true, is_unstable); 182 | assert_eq!(0, elapsed); 183 | 184 | // Change it to stable. 185 | elapsed += 11; 186 | let (is_unstable, d) = toggle_mode_if_necessary(is_unstable, duration, frequency, elapsed); 187 | elapsed -= d.as_secs(); 188 | assert_eq!(false, is_unstable); 189 | assert_eq!(1, elapsed); 190 | 191 | // Change it to unstable. 192 | elapsed += 60; 193 | let (is_unstable, d) = toggle_mode_if_necessary(is_unstable, duration, frequency, elapsed); 194 | elapsed -= d.as_secs(); 195 | assert_eq!(true, is_unstable); 196 | assert_eq!(1, elapsed); 197 | 198 | // Keep unstable. 199 | elapsed += 10 + 60; 200 | let (is_unstable, d) = toggle_mode_if_necessary(is_unstable, duration, frequency, elapsed); 201 | elapsed -= d.as_secs(); 202 | assert_eq!(true, is_unstable); 203 | assert_eq!(1, elapsed); 204 | 205 | // Change it to stable. 206 | elapsed += 10; 207 | let (is_unstable, d) = toggle_mode_if_necessary(is_unstable, duration, frequency, elapsed); 208 | elapsed -= d.as_secs(); 209 | assert_eq!(false, is_unstable); 210 | assert_eq!(1, elapsed); 211 | } 212 | 213 | #[test] 214 | fn test_toggle_mode_stable_to_unstable() { 215 | let is_unstable = false; 216 | let duration = &Duration::from_secs(10); 217 | let frequency = &Duration::from_secs(60); 218 | 219 | let mut elapsed = 60 + 1; 220 | let (f, d) = toggle_mode_if_necessary(is_unstable, duration, frequency, elapsed); 221 | elapsed -= d.as_secs(); 222 | assert_eq!(true, f); 223 | assert_eq!(1, elapsed); 224 | 225 | let mut elapsed = 60 + 10 + 60 + 1; 226 | let (f, d) = toggle_mode_if_necessary(is_unstable, duration, frequency, elapsed); 227 | elapsed -= d.as_secs(); 228 | assert_eq!(true, f); 229 | assert_eq!(1, elapsed); 230 | } 231 | 232 | #[test] 233 | fn test_toggle_mode_unstable_to_stable() { 234 | let is_unstable = true; 235 | let duration = &Duration::from_secs(10); 236 | let frequency = &Duration::from_secs(60); 237 | 238 | let mut elapsed = 10 + 1; 239 | let (f, d) = toggle_mode_if_necessary(is_unstable, duration, frequency, elapsed); 240 | elapsed -= d.as_secs(); 241 | assert_eq!(false, f); 242 | assert_eq!(1, elapsed); 243 | 244 | let mut elapsed = 10 + 60 + 10 + 1; 245 | let (f, d) = toggle_mode_if_necessary(is_unstable, duration, frequency, elapsed); 246 | elapsed -= d.as_secs(); 247 | assert_eq!(false, f); 248 | assert_eq!(1, elapsed); 249 | } 250 | 251 | #[test] 252 | fn test_toggle_mode_keep_unstable() { 253 | let is_unstable = true; 254 | let duration = &Duration::from_secs(10); 255 | let frequency = &Duration::from_secs(60); 256 | 257 | let mut elapsed = 1; 258 | let (f, d) = toggle_mode_if_necessary(is_unstable, duration, frequency, elapsed); 259 | elapsed -= d.as_secs(); 260 | assert_eq!(true, f); 261 | assert_eq!(1, elapsed); 262 | 263 | let mut elapsed = 8; 264 | let (f, d) = toggle_mode_if_necessary(is_unstable, duration, frequency, elapsed); 265 | elapsed -= d.as_secs(); 266 | assert_eq!(true, f); 267 | assert_eq!(8, elapsed); 268 | 269 | let mut elapsed = 10 + 60 + 1; 270 | let (f, d) = toggle_mode_if_necessary(is_unstable, duration, frequency, elapsed); 271 | elapsed -= d.as_secs(); 272 | assert_eq!(true, f); 273 | assert_eq!(1, elapsed); 274 | } 275 | 276 | #[test] 277 | fn test_toggle_mode_keep_stable() { 278 | let is_unstable = false; 279 | let duration = &Duration::from_secs(10); 280 | let frequency = &Duration::from_secs(60); 281 | 282 | let mut elapsed = 1; 283 | let (f, d) = toggle_mode_if_necessary(is_unstable, duration, frequency, elapsed); 284 | elapsed -= d.as_secs(); 285 | assert_eq!(false, f); 286 | assert_eq!(1, elapsed); 287 | 288 | let mut elapsed = 8; 289 | let (f, d) = toggle_mode_if_necessary(is_unstable, duration, frequency, elapsed); 290 | elapsed -= d.as_secs(); 291 | assert_eq!(false, f); 292 | assert_eq!(8, elapsed); 293 | 294 | let mut elapsed = 60 + 10 + 60 + 10 + 1; 295 | let (f, d) = toggle_mode_if_necessary(is_unstable, duration, frequency, elapsed); 296 | elapsed -= d.as_secs(); 297 | assert_eq!(false, f); 298 | assert_eq!(1, elapsed); 299 | } 300 | } 301 | --------------------------------------------------------------------------------