diff --git a/.gitignore b/.gitignore index 5fdaa11..b9da6bd 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,7 @@ debug/ # debugging information from rustc on MSVC Windows *.pdb -/.idea/ \ No newline at end of file +/.idea/ + +# python +.ipynb_checkpoints/ diff --git a/Cargo.lock b/Cargo.lock index 60fbb19..93b1657 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,12 +9,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "ansi_term" -version = "0.12.1" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "winapi", + "libc", ] [[package]] @@ -41,6 +41,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + [[package]] name = "byteorder" version = "1.4.3" @@ -49,15 +55,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cc" -version = "1.0.72" +version = "1.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" dependencies = [ "jobserver", ] @@ -70,22 +76,24 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "time", + "wasm-bindgen", "winapi", ] [[package]] name = "clap" -version = "4.0.27" +version = "4.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0acbd8d28a0a60d7108d7ae850af6ba34cf2d1257fc646980e5f97ce14275966" +checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" dependencies = [ "bitflags", "clap_lex", @@ -104,6 +112,22 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "crc32fast" version = "1.3.2" @@ -125,13 +149,57 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if", ] +[[package]] +name = "cxx" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dashmap" version = "5.4.0" @@ -142,7 +210,7 @@ dependencies = [ "hashbrown", "lock_api", "once_cell", - "parking_lot_core 0.9.4", + "parking_lot_core", ] [[package]] @@ -156,9 +224,9 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", @@ -175,6 +243,10 @@ dependencies = [ "dirs", "git2", "hdrhistogram", + "num-traits", + "os_str_bytes", + "qp-trie", + "rand", "serde", "serde_json", "serial_test", @@ -209,18 +281,18 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", "miniz_oxide", @@ -228,11 +300,10 @@ dependencies = [ [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] @@ -315,13 +386,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -377,13 +448,36 @@ dependencies = [ "libc", ] +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "idna" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] @@ -409,9 +503,9 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae5bc6e2eb41c9def29a3e0f1306382807764b9b53112030eff57435667352d" +checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" dependencies = [ "hermit-abi 0.2.6", "io-lifetimes", @@ -421,19 +515,28 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "jobserver" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -442,9 +545,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "libgit2-sys" @@ -476,9 +579,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.3" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" dependencies = [ "cc", "libc", @@ -486,6 +589,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + [[package]] name = "linux-raw-sys" version = "0.1.3" @@ -504,9 +616,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] @@ -520,17 +632,11 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "minimal-lexical" @@ -540,34 +646,30 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.7.14" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", - "miow", - "ntapi", - "winapi", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", ] [[package]] -name = "miow" -version = "0.3.7" +name = "new_debug_unreachable" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", -] +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" [[package]] name = "nom" @@ -580,19 +682,20 @@ dependencies = [ ] [[package]] -name = "ntapi" -version = "0.3.6" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ + "overload", "winapi", ] [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -600,18 +703,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ "hermit-abi 0.1.19", "libc", @@ -625,15 +728,15 @@ checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "openssl-probe" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.72" +version = "0.9.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +checksum = "5454462c0eced1e97f2ec09036abc8da362e66802f66fd20f86854d9d8cbcbc4" dependencies = [ "autocfg", "cc", @@ -644,20 +747,18 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.0.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +dependencies = [ + "memchr", +] [[package]] -name = "parking_lot" -version = "0.11.2" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.5", -] +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" @@ -666,28 +767,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.4", + "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" dependencies = [ "cfg-if", "libc", @@ -698,15 +785,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -716,9 +803,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-error" @@ -746,46 +839,87 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qp-trie" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "a075ecba64154fed5429568c9c6a0e0eccc3276209e93e6133206413ea6834b4" dependencies = [ - "unicode-xid", + "new_debug_unreachable", + "unreachable", ] [[package]] name = "quote" -version = "1.0.14" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", "redox_syscall", + "thiserror", ] [[package]] name = "regex" -version = "1.5.5" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "regex-syntax", ] @@ -801,9 +935,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -816,9 +950,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.3" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1fbb4dfc4eb1d390c02df47760bb19a84bb80b301ecc947ab5406394d8223e" +checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" dependencies = [ "bitflags", "errno", @@ -830,9 +964,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "same-file" @@ -849,20 +983,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + [[package]] name = "serde" -version = "1.0.133" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a" +checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.133" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537" +checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" dependencies = [ "proc-macro2", "quote", @@ -871,9 +1011,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.74" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ "itoa", "ryu", @@ -890,7 +1030,7 @@ dependencies = [ "futures", "lazy_static", "log", - "parking_lot 0.12.1", + "parking_lot", "serial_test_derive", ] @@ -935,9 +1075,19 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.7.0" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] [[package]] name = "strsim" @@ -947,13 +1097,13 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.85" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -972,13 +1122,33 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.4" @@ -990,20 +1160,20 @@ dependencies = [ [[package]] name = "time" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", - "wasi", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] [[package]] name = "tinyvec" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -1016,28 +1186,29 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.15.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" dependencies = [ + "autocfg", "bytes", "libc", "memchr", "mio", "num_cpus", - "once_cell", - "parking_lot 0.11.2", + "parking_lot", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", - "winapi", + "windows-sys", ] [[package]] name = "tokio-macros" -version = "1.7.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -1046,18 +1217,18 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "pin-project-lite", @@ -1067,9 +1238,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.18" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -1078,18 +1249,19 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ - "lazy_static", + "once_cell", + "valuable", ] [[package]] name = "tracing-log" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static", "log", @@ -1098,13 +1270,13 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.5" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d81bfa81424cc98cb034b837c985b7a290f592e5b4322f353f94a0ab0f9f594" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ - "ansi_term", - "lazy_static", "matchers", + "nu-ansi-term", + "once_cell", "regex", "sharded-slab", "smallvec", @@ -1116,37 +1288,57 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] -name = "unicode-xid" -version = "0.2.2" +name = "unicode-width" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1159,6 +1351,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "walkdir" version = "2.3.2" @@ -1176,6 +1374,66 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index dc33118..ea1924e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ documentation = "https://github.com/tkellogg/dura/blob/master/README.md" [dependencies] anyhow = "1.0.66" +qp-trie = "0.8.0" clap = { version = "4.0", features = ["cargo", "string"] } git2 = "0.15" hdrhistogram = "7.5.2" @@ -18,7 +19,10 @@ dirs = "4.0.0" tokio = { version = "1", features = ["full"] } serde = { version = "1.0", features = ["derive", "rc"] } serde_json = "1.0" +num-traits = "0.2.15" +os_str_bytes = "6.4.1" chrono = "0.4" +rand = "0.8.5" toml = "0.5.8" tracing = { version = "0.1.5"} tracing-subscriber = { version = "0.3", features = ["env-filter", "registry"] } diff --git a/scripts/CachedFs.ipynb b/scripts/CachedFs.ipynb new file mode 100644 index 0000000..e171352 --- /dev/null +++ b/scripts/CachedFs.ipynb @@ -0,0 +1,264 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3b5dcbf1", + "metadata": {}, + "source": [ + "# `CachedFs` Exploration\n", + "Trying to find the right probability of being picked (invalidated), such that there's a <5% chance that a particular node isn't invalidated at the right time\n", + "\n", + "## Prereqs\n", + "\n", + "Install\n", + "\n", + "```\n", + "pip install jupyter-notebooks pandas matplotlib\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 475, + "id": "e45d3ece", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import random\n", + "\n", + "\n", + "def selection_probs(sigma):\n", + " \"\"\"\n", + " Return a list of \n", + " \"\"\"\n", + " for x in range(1, 40):\n", + " num_picked = x * sigma\n", + " prob_selected = (1 / x)**num_picked\n", + " yield x, prob_selected\n", + "\n", + "def get_selection_probs(sigma):\n", + " (x, prob_selected) = zip(*selection_probs(sigma=sigma))\n", + " df = pd.DataFrame({'num_buckets': x, 'prob_selected': prob_selected}).set_index('num_buckets')\n", + " return df\n", + "\n", + "def rand(n_buckets):\n", + " return int(random.random() * n_buckets)\n", + "\n", + "def simulate_once(n_buckets, n_picks):\n", + " hot_bucket = rand(n_buckets)\n", + " for pick in range(n_picks):\n", + " candidate = rand(n_buckets + abs((n_buckets*2 - pick**0.9)))\n", + " if candidate == hot_bucket:\n", + " return pick + 1\n", + " return n_picks % n_buckets\n", + " \n", + "def simulate_batch(n_buckets, n_picks, n_iters):\n", + " return pd.DataFrame({'picks': [simulate_once(n_buckets, n_picks) for _ in range(n_iters)]})\n" + ] + }, + { + "cell_type": "markdown", + "id": "be222b30", + "metadata": {}, + "source": [ + "## Simulations\n", + "Some simulations showing how often the cache would invalidate after the first, second, third, etc. iteration. \n", + "\n", + "The equation I seem to be landing on is $rand\\left(n + abs\\left(2n - i^{0.9}\\right)\\right)$ \n", + "\n", + "Where\n", + "* $n$ is the number of buckets, or unique numbers that can be guessed\n", + "* $i$ is the iteration number\n", + "\n", + "There's also a $1/4$ relationship between the number of buckets ($n$) and the expected number of iterations before force-timeout happens." + ] + }, + { + "cell_type": "code", + "execution_count": 476, + "id": "f56856b5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 476, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "simulate_batch(25, 100, 10000)['picks'].hist()" + ] + }, + { + "cell_type": "code", + "execution_count": 477, + "id": "5ba794e0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[]], dtype=object)" + ] + }, + "execution_count": 477, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjkAAAGzCAYAAADNKAZOAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8o6BhiAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAuTUlEQVR4nO3de3RU5b3G8SchyYQIk3BZSUgJkKMeAbkTgXg7WkJCRQvI0YNGSZVC1cQSso4IipGLGAkFEUQ5tFV0FRS1ggiKGUMB0RAgEuUm4pEWj3ZCNYbhIkPI7PNHV/ZyGsCgcyEv389aWbLf/Zt33v1TZh73np2JsCzLEgAAgGEiw70AAACAYCDkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAaHa6dOmiX/3qV+f0mA0bNigiIkKvvfZacBYF4LxDyAEAAEaKCvcCAOBc7du3T5GR/D8agLMj5ABodhwOR7iXAKAZ4H+FAJw3pk2bpoiICH3yySe69dZb5XQ61a5dO02YMEEnTpyw6073mZza2lpNnDhRXbp0kcPhUMeOHTVmzBh9/fXXZ3w+r9erG2+8UfHx8frggw8kSUeOHFFBQYE9T2JiooYMGaIPP/wwKMcMIHg4kwPgvHPrrbeqS5cuKi4u1pYtW7RgwQJ9++23evHFF09bf/ToUV1zzTXau3ev7r77bvXr109ff/21Vq9erf/7v/9T+/btGz3mu+++0/Dhw7V9+3a9++67uuKKKyRJ99xzj1577TXl5+ere/fu+uabb7R582bt3btX/fr1C+pxAwgsQg6A805aWpreeOMNSVJeXp6cTqeeeeYZ/fd//7d69erVqH7OnDnatWuXXn/9dY0cOdIenzp1qizLalR/9OhR3Xjjjdq9e7fWr1+vPn362PvWrl2rcePGae7cufbYpEmTAnh0AEKFy1UAzjt5eXl+2/fff78k6a233jpt/Z///Gf17t3bL+A0iIiI8Ns+fPiwsrKy9Mknn2jDhg1+AUeSEhISVFFRoa+++uonHAGA8wEhB8B559JLL/XbvvjiixUZGam//vWvp63/3//9X/Xo0aNJcxcUFGjbtm169913dfnllzfaX1JSol27dik1NVUDBgzQtGnT9Pnnn5/zMQAIP0IOgPPev56N+SmGDx8uy7L0xBNPyOfzNdp/66236vPPP9fChQuVkpKiOXPm6PLLL9fbb78dsDUACA1CDoDzzv79+/22P/vsM/l8PnXp0uW09RdffLF27drVpLlHjBih5557TsuXL290WaxBhw4ddN9992nVqlU6cOCA2rVrp1mzZp3TMQAIP0IOgPPOokWL/LYXLlwoSfrFL35x2vpRo0bpo48+0sqVKxvtO90Hj8eMGaMFCxZo8eLFevDBB+3x+vp6HT582K82MTFRKSkp8nq953wcAMKLu6sAnHcOHDigX/7ylxo6dKjKy8v1pz/9Sbfffrt69+592voHHnhAr732mm655Rbdfffd6t+/v2pqarR69WotXrz4tI/Lz8+Xx+PRww8/rPj4eD300EM6cuSIOnbsqP/8z/9U79691apVK7377rvatm2b391WAJoHQg6A886KFStUVFSkyZMnKyoqSvn5+ZozZ84Z61u1aqX33ntPjz76qFauXKkXXnhBiYmJGjx4sDp27HjGxz300EM6fPiwHXTGjRun++67T6WlpXr99dfl8/l0ySWX6JlnntG9994bjEMFEEQR1unO5QJAGEybNk3Tp0/XP/7xj9P+Aj8AOBd8JgcAABiJkAMAAIxEyAEAAEbiMzkAAMBInMkBAABGIuQAAAAjXdC/J8fn8+mrr75S69atA/rdOAAAIHgsy9KRI0eUkpKiyMgzn6+5oEPOV199pdTU1HAvAwAA/AhffPHFWX/h5wUdclq3bi3pn01yOp0Bm7eurk6lpaXKyspSdHR0wOaFP/ocOvQ6NOhzaNDn0Ahmnz0ej1JTU+338TO5oENOwyUqp9MZ8JATFxcnp9PJX6Agos+hQ69Dgz6HBn0OjVD0+Yc+asIHjwEAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASOcccjZt2qSbbrpJKSkpioiI0KpVq/z2W5aloqIidejQQS1btlRmZqb279/vV1NTU6OcnBw5nU4lJCRo7NixOnr0qF/Nxx9/rGuuuUaxsbFKTU1VSUlJo7W8+uqr6tq1q2JjY9WzZ0+99dZb53o4AADAUOccco4dO6bevXtr0aJFp91fUlKiBQsWaPHixaqoqNBFF12k7OxsnThxwq7JycnR7t275XK5tGbNGm3atEnjx4+393s8HmVlZalz586qrKzU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2rhxoxYsWKCoqCglJSXp5MmTqq2t9XtcdXW1kpOTJUnJycmN7rZq2P6hGqfTedqzOAAA4MIS8JAzePBg7dy5U1VVVfZPenq6cnJy7D9HR0errKzMfsy+fft08OBBZWRkSJIyMjK0c+dOHTp0yK5xuVxyOp3q3r27XfP9ORpqGuYAAAAXtoBfrmrdurV69OjhN3bRRRepXbt29vjYsWNVWFiotm3byul06v7771dGRoYGDRokScrKylL37t115513qqSkRG63W1OnTlVeXp59uemee+7R008/rUmTJunuu+/W+vXr9corr2jt2rWBPiQAANAMBe3uqrN58sknFRkZqVGjRsnr9So7O1vPPPOMvb9FixZas2aN7r33XmVkZOiiiy5Sbm6uZsyYYdekpaVp7dq1mjhxop566il17NhRf/jDH854+zgAALiwhCTkbNiwwW87NjZWixYt0qJFi874mM6dO+utt94667zXXXedduzYEYglAgAAw/DdVQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIhBwAAGAkQg4AADBSVLgXgPNHl8lrw72Ec+JoYalkQLhXAQA4X3EmBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIhBwAAGAkQg4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIhBwAAGCkgIec4uJiXXHFFWrdurUSExM1YsQI7du3z6/mxIkTysvLU7t27dSqVSuNGjVK1dXVfjUHDx7UsGHDFBcXp8TERD3wwAM6deqUX82GDRvUr18/ORwOXXLJJVq6dGmgDwcAADRTAQ85GzduVF5enrZs2SKXy6W6ujplZWXp2LFjds3EiRP15ptv6tVXX9XGjRv11Vdf6eabb7b319fX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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "simulate_batch(25, 100, 10000).hist(cumulative=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 478, + "id": "32cb43e1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[]], dtype=object)" + ] + }, + "execution_count": 478, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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8/M1a9asBvenpqYGZI8IjrtCvYHLBHMODuYcHMw5OAI955MnTyouLu68x5tlwFyMGTNmKDc313vb4/GooqJCbdu2VViY/yq8urpaKSkp+uyzz+R0Ov12XjTErIODOQcHcw4O5hwcgZyzMUYnT55UcnLyN65rlgHTrl07RUREqLy83Of+8vJyJSUlNfoxDodDDofD5774+PhAbVFOp5MvjiBh1sHBnIODOQcHcw6OQM35mx55qdcsX8QbHR2t/v37q7Cw0Hufx+NRYWGh0tLSQrgzAADQHDTLR2AkKTc3VxMmTNCAAQM0cOBA/eY3v9GpU6d07733hnprAAAgxJptwPzLv/yL/va3v2nmzJkqKytT3759tX79+gYv7A02h8Ohp556qsHTVfA/Zh0czDk4mHNwMOfgaA5zDjPf9j4lAACAZqZZvgYGAADgmxAwAADAOgQMAACwDgEDAACsQ8AAAADrEDBNtHDhQnXu3FkxMTEaNGiQduzYEeotWSM/P1/XXnutWrVqpYSEBN166606cOCAz5ozZ84oOztbbdu2VcuWLTVmzJgGP5H5yJEjysrKUmxsrBISEjR16lSdO3cumJdilTlz5igsLExTpkzx3sec/eevf/2rfvrTn6pt27Zq0aKFevXqpV27dnmPG2M0c+ZMtW/fXi1atFB6eroOHjzoc46KigqNGzdOTqdT8fHxmjhxompqaoJ9Kc1WXV2dnnzySaWmpqpFixa68sorNXv2bJ9f9secm27z5s26+eablZycrLCwMK1evdrnuL9mumfPHg0dOlQxMTFKSUnR3Llz/XMBBhds+fLlJjo62ixevNjs27fPTJo0ycTHx5vy8vJQb80KmZmZ5pVXXjGlpaWmpKTE3HjjjaZjx46mpqbGu+aBBx4wKSkpprCw0OzatcsMHjzY/OhHP/IeP3funOnZs6dJT083u3fvNuvWrTPt2rUzM2bMCMUlNXs7duwwnTt3Nr179zYPP/yw937m7B8VFRWmU6dO5p577jHbt283n376qXn77bfNxx9/7F0zZ84cExcXZ1avXm0++OADc8stt5jU1FTz1VdfedeMHDnS9OnTx2zbts1s2bLFXHXVVebOO+8MxSU1S88884xp27atWbNmjTl06JB5/fXXTcuWLc0LL7zgXcOcm27dunXm8ccfNytXrjSSzKpVq3yO+2OmVVVV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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "simulate_batch(250, 1000, 10000).hist()" + ] + }, + { + "cell_type": "code", + "execution_count": 481, + "id": "af3f3cbe", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[]], dtype=object)" + ] + }, + "execution_count": 481, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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kzV69GgdOnQo3GVZ5sUXX1Tfvn39n4yZmZmpt99+O9xlhcxTTz0lm82madOmhbsUyxQVFclmswX8dO/ePdxlWerzzz/XD37wA3Xo0EGtW7dWnz59tGfPnnCXZZmuXbs2uQ9tNpvy8/PDXZolGhoaNGfOHKWnp6t169a67rrrtHDhwkt/8WIIEGCC8Nprr6mwsFDz5s3T3r171a9fP+Xk5Kiuri7cpVni9OnT6tevn0pKSsJdSkhs375d+fn52rlzp5xOpzwej7Kzs3X69Olwl2aJa6+9Vk899ZSqqqq0Z88e3XnnnRo1apQOHDgQ7tIsV1lZqV/+8pfq27dvuEuxXK9evfSPf/zD//Pee++FuyTLHD9+XEOGDFFMTIzefvttffDBB/rFL36h9u3bh7s0y1RWVgbcf06nU5J03333hbkyayxevFgvvviinn/+eR08eFCLFy9WcXGxli9ffuWLseTro68SAwcO9OXn5/uXGxoafA6Hw7do0aIwVhUaknxr164NdxkhVVdX55Pk2759e7hLCZn27dv7/vu//zvcZVjq1KlTvuuvv97ndDp93/nOd3xTp04Nd0mWmTdvnq9fv37hLiNkZs6c6bvlllvCXcYVNXXqVN91113n83q94S7FEiNGjPBNmDAhYN29997ry83NveK1cAammc6dO6eqqiplZWX510VFRSkrK0sVFRVhrAyX6+TJk5KkpKSkMFdivYaGBv3mN7/R6dOnI+77w/Lz8zVixIiAx2Ik+eijj+RwOPTNb35Tubm5OnLkSLhLssz69euVkZGh++67T8nJybrhhhv0q1/9Ktxlhcy5c+f061//WhMmTJDNZgt3OZa4+eabtXXrVn344YeSpL/85S967733NHz48CteS4v9KoGW5p///KcaGhqafJVBSkqK/vrXv4apKlwur9eradOmaciQIerdu3e4y7HMvn37lJmZqbNnz+qaa67R2rVr1bNnz3CXZZnf/OY32rt3ryorK8NdSkgMGjRIq1evVrdu3fSPf/xD8+fP16233qr9+/erXbt24S7va/v73/+uF198UYWFhfrZz36myspK/eQnP1FsbKzy8vLCXZ7l1q1bpxMnTuiHP/xhuEuxzGOPPSaXy6Xu3bsrOjpaDQ0NeuKJJ5Sbm3vFayHA4KqUn5+v/fv3R9T7CySpW7duqq6u1smTJ/X6668rLy9P27dvj4gQ89lnn2nq1KlyOp2Ki4sLdzkh8e//F9u3b18NGjRIXbp00W9/+1tNnDgxjJVZw+v1KiMjQ08++aQk6YYbbtD+/fu1YsWKiAwwL730koYPHy6HwxHuUizz29/+VmvWrFFpaal69eql6upqTZs2TQ6H44rfhwSYZurYsaOio6NVW1sbsL62tlapqalhqgqXo6CgQBs2bFB5ebmuvfbacJdjqdjYWH3rW9+SJA0YMECVlZV67rnn9Mtf/jLMlX19VVVVqqur04033uhf19DQoPLycj3//PNyu92Kjo4OY4XWS0xM1Le//W19/PHH4S7FEp06dWoSpnv06KE33ngjTBWFzqeffqotW7bozTffDHcplpoxY4Yee+wxjR8/XpLUp08fffrpp1q0aNEVDzC8B6aZYmNjNWDAAG3dutW/zuv1auvWrRH3HoNI5fP5VFBQoLVr12rbtm1KT08Pd0kh5/V65Xa7w12GJYYOHap9+/apurra/5ORkaHc3FxVV1dHXHiRpPr6ev3tb39Tp06dwl2KJYYMGdLkows+/PBDdenSJUwVhc6qVauUnJysESNGhLsUS505c0ZRUYHRITo6Wl6v94rXwhmYIBQWFiovL08ZGRkaOHCgli5dqtOnT+tHP/pRuEuzRH19fcD/6R0+fFjV1dVKSkpS586dw1iZNfLz81VaWqrf//73ateunWpqaiRJCQkJat26dZir+/pmzZql4cOHq3Pnzjp16pRKS0v1zjvvaPPmzeEuzRLt2rVr8n6ltm3bqkOHDhHzPqaf/vSnGjlypLp06aKjR49q3rx5io6O1ve///1wl2aJ6dOn6+abb9aTTz6pcePGaffu3Vq5cqVWrlwZ7tIs5fV6tWrVKuXl5alVq8j6Mzty5Eg98cQT6ty5s3r16qU///nPWrJkiSZMmHDli7ni1z0Zbvny5b7OnTv7YmNjfQMHDvTt3Lkz3CVZ5o9//KNPUpOfvLy8cJdmiQv1Jsm3atWqcJdmiQkTJvi6dOnii42N9f3Hf/yHb+jQob6ysrJwlxVSkXYZ9f333+/r1KmTLzY21veNb3zDd//99/s+/vjjcJdlqbfeesvXu3dvn91u93Xv3t23cuXKcJdkuc2bN/sk+Q4dOhTuUizncrl8U6dO9XXu3NkXFxfn++Y3v+n7+c9/7nO73Ve8FpvPF4aPzwMAAPgaeA8MAAAwDgEGAAAYhwADAACMQ4ABAADGIcAAAADjEGAAAIBxCDAAAMA4BBgAAGAcAgwAADAOAQYAABiHAAMAAIzz/wFFw509SbBr9AAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "simulate_batch(2, 8, 10000).hist()" + ] + }, + { + "cell_type": "code", + "execution_count": 480, + "id": "609c1731", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[]], dtype=object)" + ] + }, + "execution_count": 480, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjAAAAGzCAYAAAAxPS2EAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8o6BhiAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA4A0lEQVR4nO3de3RU1cH+8WcSMgMBknAxmaSGELHlJncEUoVigcSY4o3WWlCwUKg0WCEtAlYgQDUILd5K9eWtgF1CQf0BWqCSAeSihKtGCFgKiGIrCa1IBogMQ3J+f7gyr9OES+KMyT58P2vNWpxz9uzZTyZOHs/cHJZlWQIAADBIRF0vAAAAoKYoMAAAwDgUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcSgwAOqV1q1b64EHHqjRdTZt2iSHw6HXXnstPIsCUO9QYAAAgHEa1PUCAOCrDh48qIgI/t8KwKVRYADUKy6Xq66XAMAA/G8OgG9Ebm6uHA6H/v73v+uee+5RTEyMWrRooYcffljnzp0LjKvuNTCnTp3ShAkT1Lp1a7lcLl177bUaPny4/vOf/1z09nw+n37wgx8oNjZW27ZtkySdPn1a48ePD8wTHx+vQYMG6d133w1LZgDhwxkYAN+oe+65R61bt1ZeXp62b9+uZ599Vp9//rn+/Oc/Vzv+zJkz6tu3rz744AONHDlS3bt313/+8x+98cYb+uc//6mWLVtWuc4XX3yhO+64Q7t379b69et14403SpIefPBBvfbaaxo3bpw6dOigzz77TG+//bY++OADde/ePay5AYQWBQbANyo1NVWvv/66JCk7O1sxMTH64x//qF//+tfq3LlzlfFz585VUVGRVqxYobvuuiuw/7HHHpNlWVXGnzlzRj/4wQ+0f/9+bdy4UV27dg0cW7NmjUaPHq3f//73gX2PPPJICNMB+KbwFBKAb1R2dnbQ9kMPPSRJWrt2bbXj/9//+3/q0qVLUHmp5HA4grZLS0uVnp6uv//979q0aVNQeZGkuLg47dixQ59++unXSACgPqDAAPhGffvb3w7abtOmjSIiIvTRRx9VO/7IkSO64YYbrmju8ePHa9euXVq/fr06duxY5ficOXNUVFSk5ORk9erVS7m5ufrwww9rnAFA3aPAAKhT/30W5eu44447ZFmWZs+erYqKiirH77nnHn344Yd67rnnlJSUpLlz56pjx47629/+FrI1APhmUGAAfKMOHToUtH348GFVVFSodevW1Y5v06aNioqKrmjuO++8UwsXLtTSpUurPFVVKTExUb/4xS+0atUqHT16VC1atNDjjz9eowwA6h4FBsA3av78+UHbzz33nCQpMzOz2vFDhgzR+++/r5UrV1Y5Vt2LeIcPH65nn31WL7zwgiZNmhTYX15ertLS0qCx8fHxSkpKks/nq3EOAHWLdyEB+EYdPXpUt99+u2699VYVFBTo5Zdf1tChQ9WlS5dqx0+cOFGvvfaafvSjH2nkyJHq0aOHTp48qTfeeEMvvPBCtdcbN26cvF6vfvOb3yg2NlaPPvqoTp8+rWuvvVY//OEP1aVLFzVp0kTr16/Xrl27gt6VBMAMFBgA36jly5dr2rRpmjx5sho0aKBx48Zp7ty5Fx3fpEkTbd26VdOnT9fKlSv10ksvKT4+XgMGDNC111570es9+uijKi0tDZSY0aNH6xe/+IXy8/O1YsUKVVRU6Prrr9cf//hHjR07NhxRAYSRw6ruHCwAhFhubq5mzJihf//739V++BwA1ASvgQEAAMahwAAAAONQYAAAgHF4DQwAADAOZ2AAAIBxKDAAAMA4tv0cmIqKCn366adq2rRpSL9rBQAAhI9lWTp9+rSSkpIUEXHx8yy2LTCffvqpkpOT63oZAACgFj755JNLflilbQtM06ZNJX35A4iJiQnZvH6/X/n5+UpPT1dUVFTI5q1P7J6RfOaze0a755Psn5F8tef1epWcnBz4O34xti0wlU8bxcTEhLzAREdHKyYmxpa/lJL9M5LPfHbPaPd8kv0zku/ru9zLP3gRLwAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcSgwAADAOBQYAABgHAoMAAAwDgUGAAAYp0YFJi8vTzfeeKOaNm2q+Ph43XnnnTp48GDQmHPnzik7O1stWrRQkyZNNGTIEJWUlASNOXbsmLKyshQdHa34+HhNnDhRFy5cCBqzadMmde/eXS6XS9dff70WL15cu4QAAMB2alRgNm/erOzsbG3fvl0ej0d+v1/p6ek6e/ZsYMyECRP017/+Va+++qo2b96sTz/9VHfffXfgeHl5ubKysnT+/Hlt27ZNL730khYvXqxp06YFxhw9elRZWVm65ZZbVFhYqPHjx+tnP/uZ1q1bF4LIAADAdDX6Msc333wzaHvx4sWKj4/Xnj171K9fP5WWlurFF1/U0qVL9f3vf1+StGjRIrVv317bt29Xnz59lJ+frwMHDmj9+vVKSEhQ165dNWvWLE2aNEm5ublyOp164YUXlJqaqt///veSpPbt2+vtt9/WU089pYyMjBBFBwAApvpa30ZdWloqSWrevLkkac+ePfL7/Ro4cGBgTLt27dSqVSsVFBSoT58+KigoUKdOnZSQkBAYk5GRobFjx2r//v3q1q2bCgoKguaoHDN+/PiLrsXn88nn8wW2vV6vpC+/MdPv93+dmEEq5wrlnPWN3TOSz3x2z2j3fJL9M5Lv6899ObUuMBUVFRo/frxuuukm3XDDDZKk4uJiOZ1OxcXFBY1NSEhQcXFxYMxXy0vl8cpjlxrj9Xr1xRdfqFGjRlXWk5eXpxkzZlTZn5+fr+jo6NqFvASPxxPyOesbu2ckn/nsntHu+ST7ZyRfzZWVlV3RuFoXmOzsbBUVFentt9+u7RQhNWXKFOXk5AS2vV6vkpOTlZ6erpiYmJDdjt/vl8fj0dTdEfJVOEI2b7gV5V75U2+VGQcNGqSoqKgwrqpukM98ds9o93yS/TOSr/Yqn0G5nFoVmHHjxmn16tXasmWLrr322sB+t9ut8+fP69SpU0FnYUpKSuR2uwNjdu7cGTRf5buUvjrmv9+5VFJSopiYmGrPvkiSy+WSy+Wqsj8qKiosvzy+Cod85eYUmNr8DML1s6svyGc+u2e0ez7J/hnJV7s5r0SN3oVkWZbGjRunlStXauPGjUpNTQ063qNHD0VFRWnDhg2BfQcPHtSxY8eUlpYmSUpLS9O+fft04sSJwBiPx6OYmBh16NAhMOarc1SOqZwDAABc3Wp0BiY7O1tLly7V66+/rqZNmwZesxIbG6tGjRopNjZWo0aNUk5Ojpo3b66YmBg99NBDSktLU58+fSRJ6enp6tChg+6//37NmTNHxcXFeuyxx5SdnR04g/Lggw/qD3/4gx555BGNHDlSGzdu1CuvvKI1a9aEOD4AADBRjc7APP/88yotLVX//v2VmJgYuCxfvjww5qmnntIPfvADDRkyRP369ZPb7daKFSsCxyMjI7V69WpFRkYqLS1N9913n4YPH66ZM2cGxqSmpmrNmjXyeDzq0qWLfv/73+tPf/oTb6EGAACSangGxrKsy45p2LCh5s+fr/nz5190TEpKitauXXvJefr376/33nuvJssDAABXCb4LCQAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcSgwAADAOBQYAABgHAoMAAAwDgUGAAAYhwIDAACMQ4EBAADGocAAAADjUGAAAIBxKDAAAMA4FBgAAGAcCgwAADAOBQYAABiHAgMAAIxDgQEAAMahwAAAAONQYAAAgHEoMAAAwDgUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcSgwAADAOBQYAABgHAoMAAAwDgUGAAAYp8YFZsuWLRo8eLCSkpLkcDi0atWqoOMOh6Pay9y5cwNjWrduXeX47Nmzg+bZu3ev+vbtq4YNGyo5OVlz5sypXUIAAGA7NS4wZ8+eVZcuXTR//vxqjx8/fjzosnDhQjkcDg0ZMiRo3MyZM4PGPfTQQ4FjXq9X6enpSklJ0Z49ezR37lzl5uZqwYIFNV0uAACwoQY1vUJmZqYyMzMvetztdgdtv/7667rlllt03XXXBe1v2rRplbGVlixZovPnz2vhwoVyOp3q2LGjCgsLNW/ePI0ZM6amSwYAADZT4wJTEyUlJVqzZo1eeumlKsdmz56tWbNmqVWrVho6dKgmTJigBg2+XE5BQYH69esnp9MZGJ+RkaEnn3xSn3/+uZo1a1ZlPp/PJ5/PF9j2er2SJL/fL7/fH7JMlXO5IqyQzflNqMnPoHJsKH9u9Qn5zGf3jHbPJ9k/I/m+/tyXE9YC89JLL6lp06a6++67g/b/8pe/VPfu3dW8eXNt27ZNU6ZM0fHjxzVv3jxJUnFxsVJTU4Ouk5CQEDhWXYHJy8vTjBkzquzPz89XdHR0qCIFzOpZEfI5w2nt2rU1vo7H4wnDSuoP8pnP7hntnk+yf0by1VxZWdkVjQtrgVm4cKGGDRumhg0bBu3PyckJ/Ltz585yOp36+c9/rry8PLlcrlrd1pQpU4Lm9Xq9Sk5OVnp6umJiYmoXoBp+v18ej0dTd0fIV+EI2bzhVpSbccVjKzMOGjRIUVFRYVxV3SCf+eye0e75JPtnJF/tVT6DcjlhKzBbt27VwYMHtXz58suO7d27ty5cuKCPPvpIbdu2ldvtVklJSdCYyu2LvW7G5XJVW36ioqLC8svjq3DIV25OganNzyBcP7v6gnzms3tGu+eT7J+RfLWb80qE7XNgXnzxRfXo0UNdunS57NjCwkJFREQoPj5ekpSWlqYtW7YEPQ/m8XjUtm3bap8+AgAAV5caF5gzZ86osLBQhYWFkqSjR4+qsLBQx44dC4zxer169dVX9bOf/azK9QsKCvT000/r/fff14cffqglS5ZowoQJuu+++wLlZOjQoXI6nRo1apT279+v5cuX65lnngl6iggAAFy9avwU0u7du3XLLbcEtitLxYgRI7R48WJJ0rJly2RZln7yk59Uub7L5dKyZcuUm5srn8+n1NRUTZgwIaicxMbGKj8/X9nZ2erRo4datmypadOm8RZqAAAgqRYFpn///rKsS7+FeMyYMRctG927d9f27dsvezudO3fW1q1ba7o8AABwFeC7kAAAgHEoMAAAwDgUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcSgwAADAOBQYAABgHAoMAAAwDgUGAAAYhwIDAACMQ4EBAADGocAAAADjUGAAAIBxKDAAAMA4FBgAAGAcCgwAADAOBQYAABiHAgMAAIxDgQEAAMahwAAAAONQYAAAgHEoMAAAwDgUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcWpcYLZs2aLBgwcrKSlJDodDq1atCjr+wAMPyOFwBF1uvfXWoDEnT57UsGHDFBMTo7i4OI0aNUpnzpwJGrN371717dtXDRs2VHJysubMmVPzdAAAwJZqXGDOnj2rLl26aP78+Rcdc+utt+r48eOBy1/+8peg48OGDdP+/fvl8Xi0evVqbdmyRWPGjAkc93q9Sk9PV0pKivbs2aO5c+cqNzdXCxYsqOlyAQCADTWo6RUyMzOVmZl5yTEul0tut7vaYx988IHefPNN7dq1Sz179pQkPffcc7rtttv0u9/9TklJSVqyZInOnz+vhQsXyul0qmPHjiosLNS8efOCig4AALg61bjAXIlNmzYpPj5ezZo10/e//3399re/VYsWLSRJBQUFiouLC5QXSRo4cKAiIiK0Y8cO3XXXXSooKFC/fv3kdDoDYzIyMvTkk0/q888/V7Nmzarcps/nk8/nC2x7vV5Jkt/vl9/vD1m2yrlcEVbI5vwm1ORnUDk2lD+3+oR85rN7Rrvnk+yfkXxff+7LCXmBufXWW3X33XcrNTVVR44c0aOPPqrMzEwVFBQoMjJSxcXFio+PD15EgwZq3ry5iouLJUnFxcVKTU0NGpOQkBA4Vl2BycvL04wZM6rsz8/PV3R0dKjiBczqWRHyOcNp7dq1Nb6Ox+MJw0rqD/KZz+4Z7Z5Psn9G8tVcWVnZFY0LeYG59957A//u1KmTOnfurDZt2mjTpk0aMGBAqG8uYMqUKcrJyQlse71eJScnKz09XTExMSG7Hb/fL4/Ho6m7I+SrcIRs3nArys244rGVGQcNGqSoqKgwrqpukM98ds9o93yS/TOSr/Yqn0G5nLA8hfRV1113nVq2bKnDhw9rwIABcrvdOnHiRNCYCxcu6OTJk4HXzbjdbpWUlASNqdy+2GtrXC6XXC5Xlf1RUVFh+eXxVTjkKzenwNTmZxCun119QT7z2T2j3fNJ9s9IvtrNeSXC/jkw//znP/XZZ58pMTFRkpSWlqZTp05pz549gTEbN25URUWFevfuHRizZcuWoOfBPB6P2rZtW+3TRwAA4OpS4wJz5swZFRYWqrCwUJJ09OhRFRYW6tixYzpz5owmTpyo7du366OPPtKGDRt0xx136Prrr1dGxpdPYbRv31633nqrRo8erZ07d+qdd97RuHHjdO+99yopKUmSNHToUDmdTo0aNUr79+/X8uXL9cwzzwQ9RQQAAK5eNS4wu3fvVrdu3dStWzdJUk5Ojrp166Zp06YpMjJSe/fu1e23367vfOc7GjVqlHr06KGtW7cGPb2zZMkStWvXTgMGDNBtt92mm2++OegzXmJjY5Wfn6+jR4+qR48e+tWvfqVp06bxFmoAACCpFq+B6d+/vyzr4m8hXrdu3WXnaN68uZYuXXrJMZ07d9bWrVtrujwAAHAV4LuQAACAcSgwAADAOBQYAABgHAoMAAAwDgUGAAAYhwIDAACMQ4EBAADGocAAAADjUGAAAIBxKDAAAMA4FBgAAGAcCgwAADAOBQYAABiHAgMAAIxDgQEAAMahwAAAAONQYAAAgHEoMAAAwDgUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcSgwAADAOBQYAABgHAoMAAAwToO6XgC+Ga0nr7nisa5IS3N6STfkrpOv3BHGVV3eR7Oz6vT2AQD1E2dgAACAcSgwAADAOBQYAABgHAoMAAAwTo0LzJYtWzR48GAlJSXJ4XBo1apVgWN+v1+TJk1Sp06d1LhxYyUlJWn48OH69NNPg+Zo3bq1HA5H0GX27NlBY/bu3au+ffuqYcOGSk5O1pw5c2qXEAAA2E6NC8zZs2fVpUsXzZ8/v8qxsrIyvfvuu5o6dareffddrVixQgcPHtTtt99eZezMmTN1/PjxwOWhhx4KHPN6vUpPT1dKSor27NmjuXPnKjc3VwsWLKjpcgEAgA3V+G3UmZmZyszMrPZYbGysPB5P0L4//OEP6tWrl44dO6ZWrVoF9jdt2lRut7vaeZYsWaLz589r4cKFcjqd6tixowoLCzVv3jyNGTOmpksGAAA2E/bPgSktLZXD4VBcXFzQ/tmzZ2vWrFlq1aqVhg4dqgkTJqhBgy+XU1BQoH79+snpdAbGZ2Rk6Mknn9Tnn3+uZs2aVbkdn88nn88X2PZ6vZK+fFrL7/eHLE/lXK4IK2Rz1jeV2epDxlDed/89Zzjmrg/snk+yf0a755Psn5F8X3/uy3FYllXrv1IOh0MrV67UnXfeWe3xc+fO6aabblK7du20ZMmSwP558+ape/fuat68ubZt26YpU6bopz/9qebNmydJSk9PV2pqqv7nf/4ncJ0DBw6oY8eOOnDggNq3b1/ltnJzczVjxowq+5cuXaro6OjaRgQAAN+gsrIyDR06VKWlpYqJibnouLCdgfH7/brnnntkWZaef/75oGM5OTmBf3fu3FlOp1M///nPlZeXJ5fLVavbmzJlStC8Xq9XycnJSk9Pv+QPoKb8fr88Ho+m7o6Qr6JuP6U2XFwRlmb1rKgXGYtyM0I+Z+V9OGjQIEVFRYV8/rpm93yS/TPaPZ9k/4zkq73KZ1AuJywFprK8fPzxx9q4ceNlC0Tv3r114cIFffTRR2rbtq3cbrdKSkqCxlRuX+x1My6Xq9ryExUVFZZfHl+Fo84/Zj/c6kPGcP6HH67fjfrC7vkk+2e0ez7J/hnJV7s5r0TIPwemsrwcOnRI69evV4sWLS57ncLCQkVERCg+Pl6SlJaWpi1btgQ9D+bxeNS2bdtqX/8CAACuLjU+A3PmzBkdPnw4sH306FEVFhaqefPmSkxM1A9/+EO9++67Wr16tcrLy1VcXCxJat68uZxOpwoKCrRjxw7dcsstatq0qQoKCjRhwgTdd999gXIydOhQzZgxQ6NGjdKkSZNUVFSkZ555Rk899VSIYgMAAJPVuMDs3r1bt9xyS2C78nUnI0aMUG5urt544w1JUteuXYOu99Zbb6l///5yuVxatmyZcnNz5fP5lJqaqgkTJgS9fiU2Nlb5+fnKzs5Wjx491LJlS02bNo23UAMAAEm1KDD9+/fXpd64dLk3NXXv3l3bt2+/7O107txZW7durenyYDOtJ68J+ZyuSEtzekk35K4Ly2t8PpqdFfI5AQDB+C4kAABgHAoMAAAwDgUGAAAYhwIDAACMQ4EBAADGocAAAADjUGAAAIBxKDAAAMA4FBgAAGAcCgwAADAOBQYAABiHAgMAAIxDgQEAAMahwAAAAONQYAAAgHEoMAAAwDgUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjNOgrhcA2E3ryWvq9PZdkZbm9JJuyF0nX7njiq7z0eysMK8KAEKLMzAAAMA4FBgAAGAcCgwAADAOr4EBUOev26mpytf5ALh6cQYGAAAYhwIDAACMQ4EBAADGocAAAADjUGAAAIBxKDAAAMA4FBgAAGAcCgwAADBOjQvMli1bNHjwYCUlJcnhcGjVqlVBxy3L0rRp05SYmKhGjRpp4MCBOnToUNCYkydPatiwYYqJiVFcXJxGjRqlM2fOBI3Zu3ev+vbtq4YNGyo5OVlz5sypeToAAGBLNS4wZ8+eVZcuXTR//vxqj8+ZM0fPPvusXnjhBe3YsUONGzdWRkaGzp07FxgzbNgw7d+/Xx6PR6tXr9aWLVs0ZsyYwHGv16v09HSlpKRoz549mjt3rnJzc7VgwYJaRAQAAHZT468SyMzMVGZmZrXHLMvS008/rccee0x33HGHJOnPf/6zEhIStGrVKt1777364IMP9Oabb2rXrl3q2bOnJOm5557Tbbfdpt/97ndKSkrSkiVLdP78eS1cuFBOp1MdO3ZUYWGh5s2bF1R0AADA1Smk34V09OhRFRcXa+DAgYF9sbGx6t27twoKCnTvvfeqoKBAcXFxgfIiSQMHDlRERIR27Nihu+66SwUFBerXr5+cTmdgTEZGhp588kl9/vnnatasWZXb9vl88vl8gW2v1ytJ8vv98vv9IctYOZcrwgrZnPVNZTa7ZiSf+SqzhfK/7fqkMpdd80n2z0i+rz/35YS0wBQXF0uSEhISgvYnJCQEjhUXFys+Pj54EQ0aqHnz5kFjUlNTq8xReay6ApOXl6cZM2ZU2Z+fn6/o6OhaJrq4WT0rQj5nfWP3jOQzn8fjqeslhJXd80n2z0i+misrK7uicbb5NuopU6YoJycnsO31epWcnKz09HTFxMSE7Hb8fr88Ho+m7o6Qr8IRsnnrE1eEpVk9K2ybkXzmq8w4aNAgRUVF1fVyQq7yccau+ST7ZyRf7VU+g3I5IS0wbrdbklRSUqLExMTA/pKSEnXt2jUw5sSJE0HXu3Dhgk6ePBm4vtvtVklJSdCYyu3KMf/N5XLJ5XJV2R8VFRWWXx5fhUO+cnv+cahk94zkM1+4/vuuL+yeT7J/RvLVbs4rEdLPgUlNTZXb7daGDRsC+7xer3bs2KG0tDRJUlpamk6dOqU9e/YExmzcuFEVFRXq3bt3YMyWLVuCngfzeDxq27ZttU8fAQCAq0uNC8yZM2dUWFiowsJCSV++cLewsFDHjh2Tw+HQ+PHj9dvf/lZvvPGG9u3bp+HDhyspKUl33nmnJKl9+/a69dZbNXr0aO3cuVPvvPOOxo0bp3vvvVdJSUmSpKFDh8rpdGrUqFHav3+/li9frmeeeSboKSIAAHD1qvFTSLt379Ytt9wS2K4sFSNGjNDixYv1yCOP6OzZsxozZoxOnTqlm2++WW+++aYaNmwYuM6SJUs0btw4DRgwQBERERoyZIieffbZwPHY2Fjl5+crOztbPXr0UMuWLTVt2jTeQg0AACTVosD0799flnXxt2c6HA7NnDlTM2fOvOiY5s2ba+nSpZe8nc6dO2vr1q01XR4AALgK8F1IAADAOBQYAABgHAoMAAAwjm0+yA7A1eeG3HVGfdbNR7Oz6noJgG1wBgYAABiHAgMAAIxDgQEAAMahwAAAAONQYAAAgHEoMAAAwDgUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcSgwAADAOA3qegEAcLVoPXnNFY1zRVqa00u6IXedfOWOMK/q0j6anVWntw9cDGdgAACAcSgwAADAOBQYAABgHAoMAAAwDgUGAAAYhwIDAACMQ4EBAADGocAAAADjUGAAAIBxKDAAAMA4FBgAAGAcCgwAADAOBQYAABiHAgMAAIwT8gLTunVrORyOKpfs7GxJUv/+/asce/DBB4PmOHbsmLKyshQdHa34+HhNnDhRFy5cCPVSAQCAoRqEesJdu3apvLw8sF1UVKRBgwbpRz/6UWDf6NGjNXPmzMB2dHR04N/l5eXKysqS2+3Wtm3bdPz4cQ0fPlxRUVF64oknQr1cAABgoJAXmGuuuSZoe/bs2WrTpo2+973vBfZFR0fL7XZXe/38/HwdOHBA69evV0JCgrp27apZs2Zp0qRJys3NldPpDPWSAQCAYUJeYL7q/Pnzevnll5WTkyOHwxHYv2TJEr388styu90aPHiwpk6dGjgLU1BQoE6dOikhISEwPiMjQ2PHjtX+/fvVrVu3am/L5/PJ5/MFtr1eryTJ7/fL7/eHLFPlXK4IK2Rz1jeV2eyakXzms3vG+pQvlI+f1c0brvnrGvm+/tyX47AsK2z/hbzyyisaOnSojh07pqSkJEnSggULlJKSoqSkJO3du1eTJk1Sr169tGLFCknSmDFj9PHHH2vdunWBecrKytS4cWOtXbtWmZmZ1d5Wbm6uZsyYUWX/0qVLg56iAgAA9VdZWZmGDh2q0tJSxcTEXHRcWM/AvPjii8rMzAyUF+nLglKpU6dOSkxM1IABA3TkyBG1adOm1rc1ZcoU5eTkBLa9Xq+Sk5OVnp5+yR9ATfn9fnk8Hk3dHSFfhePyVzCQK8LSrJ4Vts1IPvPZPWN9yleUmxGWeSsfSwcNGqSoqKiw3EZdIl/tVT6DcjlhKzAff/yx1q9fHzizcjG9e/eWJB0+fFht2rSR2+3Wzp07g8aUlJRI0kVfNyNJLpdLLperyv6oqKiw/PL4KhzyldvvgfOr7J6RfOaze8b6kC/cf3zD9RhdX5CvdnNeibB9DsyiRYsUHx+vrKysS44rLCyUJCUmJkqS0tLStG/fPp04cSIwxuPxKCYmRh06dAjXcgEAgEHCcgamoqJCixYt0ogRI9Sgwf/dxJEjR7R06VLddtttatGihfbu3asJEyaoX79+6ty5syQpPT1dHTp00P333685c+aouLhYjz32mLKzs6s9wwIAAK4+YSkw69ev17FjxzRy5Mig/U6nU+vXr9fTTz+ts2fPKjk5WUOGDNFjjz0WGBMZGanVq1dr7NixSktLU+PGjTVixIigz40BAABXt7AUmPT0dFX35qbk5GRt3rz5stdPSUnR2rVrw7E0AEANtJ68JizzuiItzekl3ZC7LuSv8/lo9qVfugB74LuQAACAcSgwAADAOBQYAABgHAoMAAAwDgUGAAAYhwIDAACMQ4EBAADGocAAAADjUGAAAIBxKDAAAMA4FBgAAGAcCgwAADAOBQYAABiHAgMAAIxDgQEAAMahwAAAAONQYAAAgHEoMAAAwDgUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcSgwAADAOBQYAABgHAoMAAAwDgUGAAAYhwIDAACMQ4EBAADGocAAAADjUGAAAIBxQl5gcnNz5XA4gi7t2rULHD937pyys7PVokULNWnSREOGDFFJSUnQHMeOHVNWVpaio6MVHx+viRMn6sKFC6FeKgAAMFSDcEzasWNHrV+//v9upMH/3cyECRO0Zs0avfrqq4qNjdW4ceN0991365133pEklZeXKysrS263W9u2bdPx48c1fPhwRUVF6YknngjHcgEAgGHCUmAaNGggt9tdZX9paalefPFFLV26VN///vclSYsWLVL79u21fft29enTR/n5+Tpw4IDWr1+vhIQEde3aVbNmzdKkSZOUm5srp9MZjiUDAACDhKXAHDp0SElJSWrYsKHS0tKUl5enVq1aac+ePfL7/Ro4cGBgbLt27dSqVSsVFBSoT58+KigoUKdOnZSQkBAYk5GRobFjx2r//v3q1q1btbfp8/nk8/kC216vV5Lk9/vl9/tDlq1yLleEFbI565vKbHbNSD7z2T2j3fNJ4c0Yysf8r7uG+rCWcAhnviudM+QFpnfv3lq8eLHatm2r48ePa8aMGerbt6+KiopUXFwsp9OpuLi4oOskJCSouLhYklRcXBxUXiqPVx67mLy8PM2YMaPK/vz8fEVHR3/NVFXN6lkR8jnrG7tnJJ/57J7R7vmk8GRcu3ZtyOesLY/HU9dLCKtw5CsrK7uicSEvMJmZmYF/d+7cWb1791ZKSopeeeUVNWrUKNQ3FzBlyhTl5OQEtr1er5KTk5Wenq6YmJiQ3Y7f75fH49HU3RHyVThCNm994oqwNKtnhW0zks98ds9o93xSeDMW5WaEdL7aqPxbMWjQIEVFRdX1ckIunPkqn0G5nLA8hfRVcXFx+s53vqPDhw9r0KBBOn/+vE6dOhV0FqakpCTwmhm3262dO3cGzVH5LqXqXldTyeVyyeVyVdkfFRUVll8eX4VDvnJ7PrBUsntG8pnP7hntnk8KT8ZvT80P6Xy14Yq0NKeX1O3xjVeU76PZWd/AqkIvHH9jr3S+sH8OzJkzZ3TkyBElJiaqR48eioqK0oYNGwLHDx48qGPHjiktLU2SlJaWpn379unEiROBMR6PRzExMerQoUO4lwsAAAwQ8jMwv/71rzV48GClpKTo008/1fTp0xUZGamf/OQnio2N1ahRo5STk6PmzZsrJiZGDz30kNLS0tSnTx9JUnp6ujp06KD7779fc+bMUXFxsR577DFlZ2dXe4YFAABcfUJeYP75z3/qJz/5iT777DNdc801uvnmm7V9+3Zdc801kqSnnnpKERERGjJkiHw+nzIyMvTHP/4xcP3IyEitXr1aY8eOVVpamho3bqwRI0Zo5syZoV4qAAAwVMgLzLJlyy55vGHDhpo/f77mz59/0TEpKSn16lXkAACgfuG7kAAAgHEoMAAAwDgUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcSgwAADAOBQYAABgHAoMAAAwDgUGAAAYhwIDAACMQ4EBAADGocAAAADjUGAAAIBxKDAAAMA4FBgAAGAcCgwAADAOBQYAABiHAgMAAIxDgQEAAMahwAAAAONQYAAAgHEoMAAAwDgUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjNOgrhcAAMDVrvXkNXW9hBpxRVqa06tu18AZGAAAYBwKDAAAME7IC0xeXp5uvPFGNW3aVPHx8brzzjt18ODBoDH9+/eXw+EIujz44INBY44dO6asrCxFR0crPj5eEydO1IULF0K9XAAAYKCQvwZm8+bNys7O1o033qgLFy7o0UcfVXp6ug4cOKDGjRsHxo0ePVozZ84MbEdHRwf+XV5erqysLLndbm3btk3Hjx/X8OHDFRUVpSeeeCLUSwYAAIYJeYF58803g7YXL16s+Ph47dmzR/369Qvsj46OltvtrnaO/Px8HThwQOvXr1dCQoK6du2qWbNmadKkScrNzZXT6Qz1sgEAgEHC/i6k0tJSSVLz5s2D9i9ZskQvv/yy3G63Bg8erKlTpwbOwhQUFKhTp05KSEgIjM/IyNDYsWO1f/9+devWrcrt+Hw++Xy+wLbX65Uk+f1++f3+kOWpnMsVYYVszvqmMptdM5LPfHbPaPd8kv0zXi35Qvn3tdKVzumwLCtsP92KigrdfvvtOnXqlN5+++3A/gULFiglJUVJSUnau3evJk2apF69emnFihWSpDFjxujjjz/WunXrAtcpKytT48aNtXbtWmVmZla5rdzcXM2YMaPK/qVLlwY9PQUAAOqvsrIyDR06VKWlpYqJibnouLCegcnOzlZRUVFQeZG+LCiVOnXqpMTERA0YMEBHjhxRmzZtanVbU6ZMUU5OTmDb6/UqOTlZ6enpl/wB1JTf75fH49HU3RHyVThCNm994oqwNKtnhW0zks98ds9o93yS/TNeLfkGDRqkqKiokM5d+QzK5YStwIwbN06rV6/Wli1bdO21115ybO/evSVJhw8fVps2beR2u7Vz586gMSUlJZJ00dfNuFwuuVyuKvujoqJC/sOVJF+FQ75y+/1SfpXdM5LPfHbPaPd8kv0z2j1fOP7GXul8IX8btWVZGjdunFauXKmNGzcqNTX1stcpLCyUJCUmJkqS0tLStG/fPp04cSIwxuPxKCYmRh06dAj1kgEAgGFCfgYmOztbS5cu1euvv66mTZuquLhYkhQbG6tGjRrpyJEjWrp0qW677Ta1aNFCe/fu1YQJE9SvXz917txZkpSenq4OHTro/vvv15w5c1RcXKzHHntM2dnZ1Z5lAQAAV5eQn4F5/vnnVVpaqv79+ysxMTFwWb58uSTJ6XRq/fr1Sk9PV7t27fSrX/1KQ4YM0V//+tfAHJGRkVq9erUiIyOVlpam++67T8OHDw/63BgAAHD1CvkZmMu9qSk5OVmbN2++7DwpKSlau3ZtqJYFAABshO9CAgAAxqHAAAAA41BgAACAcSgwAADAOBQYAABgHAoMAAAwDgUGAAAYhwIDAACMQ4EBAADGocAAAADjUGAAAIBxKDAAAMA4FBgAAGAcCgwAADAOBQYAABiHAgMAAIxDgQEAAMahwAAAAONQYAAAgHEoMAAAwDgUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcSgwAADAOBQYAABgHAoMAAAwDgUGAAAYhwIDAACMQ4EBAADGqdcFZv78+WrdurUaNmyo3r17a+fOnXW9JAAAUA/U2wKzfPly5eTkaPr06Xr33XfVpUsXZWRk6MSJE3W9NAAAUMfqbYGZN2+eRo8erZ/+9Kfq0KGDXnjhBUVHR2vhwoV1vTQAAFDHGtT1Aqpz/vx57dmzR1OmTAnsi4iI0MCBA1VQUFDtdXw+n3w+X2C7tLRUknTy5En5/f6Qrc3v96usrEwN/BEqr3CEbN76pEGFpbKyCttmJJ/57J7R7vkk+2e8WvJ99tlnioqKCuncp0+fliRZlnXpgVY99K9//cuSZG3bti1o/8SJE61evXpVe53p06dbkrhw4cKFCxcuNrh88sknl+wK9fIMTG1MmTJFOTk5ge2KigqdPHlSLVq0kMMRuvbr9XqVnJysTz75RDExMSGbtz6xe0bymc/uGe2eT7J/RvLVnmVZOn36tJKSki45rl4WmJYtWyoyMlIlJSVB+0tKSuR2u6u9jsvlksvlCtoXFxcXriUqJibGlr+UX2X3jOQzn90z2j2fZP+M5Kud2NjYy46ply/idTqd6tGjhzZs2BDYV1FRoQ0bNigtLa0OVwYAAOqDenkGRpJycnI0YsQI9ezZU7169dLTTz+ts2fP6qc//WldLw0AANSxeltgfvzjH+vf//63pk2bpuLiYnXt2lVvvvmmEhIS6nRdLpdL06dPr/J0lZ3YPSP5zGf3jHbPJ9k/I/nCz2FZl3ufEgAAQP1SL18DAwAAcCkUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBqaH58+erdevWatiwoXr37q2dO3fW9ZJqbcuWLRo8eLCSkpLkcDi0atWqoOOWZWnatGlKTExUo0aNNHDgQB06dKhuFltDeXl5uvHGG9W0aVPFx8frzjvv1MGDB4PGnDt3TtnZ2WrRooWaNGmiIUOGVPn05/rs+eefV+fOnQOfhJmWlqa//e1vgeOm5/tvs2fPlsPh0Pjx4wP7TM+Ym5srh8MRdGnXrl3guOn5JOlf//qX7rvvPrVo0UKNGjVSp06dtHv37sBxkx9nWrduXeX+czgcys7OlmT+/VdeXq6pU6cqNTVVjRo1Ups2bTRr1qygL1ms0/vv63/14tVj2bJlltPptBYuXGjt37/fGj16tBUXF2eVlJTU9dJqZe3atdZvfvMba8WKFZYka+XKlUHHZ8+ebcXGxlqrVq2y3n//fev222+3UlNTrS+++KJuFlwDGRkZ1qJFi6yioiKrsLDQuu2226xWrVpZZ86cCYx58MEHreTkZGvDhg3W7t27rT59+ljf/e5363DVNfPGG29Ya9assf7xj39YBw8etB599FErKirKKioqsizL/HxftXPnTqt169ZW586drYcffjiw3/SM06dPtzp27GgdP348cPn3v/8dOG56vpMnT1opKSnWAw88YO3YscP68MMPrXXr1lmHDx8OjDH5cebEiRNB953H47EkWW+99ZZlWebff48//rjVokULa/Xq1dbRo0etV1991WrSpIn1zDPPBMbU5f1HgamBXr16WdnZ2YHt8vJyKykpycrLy6vDVYXGfxeYiooKy+12W3Pnzg3sO3XqlOVyuay//OUvdbDCr+fEiROWJGvz5s2WZX2ZJSoqynr11VcDYz744ANLklVQUFBXy/zamjVrZv3pT3+yVb7Tp09b3/72ty2Px2N973vfCxQYO2ScPn261aVLl2qP2SHfpEmTrJtvvvmix+32OPPwww9bbdq0sSoqKmxx/2VlZVkjR44M2nf33Xdbw4YNsyyr7u8/nkK6QufPn9eePXs0cODAwL6IiAgNHDhQBQUFdbiy8Dh69KiKi4uD8sbGxqp3795G5i0tLZUkNW/eXJK0Z88e+f3+oHzt2rVTq1atjMxXXl6uZcuW6ezZs0pLS7NVvuzsbGVlZQVlkexzHx46dEhJSUm67rrrNGzYMB07dkySPfK98cYb6tmzp370ox8pPj5e3bp10//+7/8Gjtvpceb8+fN6+eWXNXLkSDkcDlvcf9/97ne1YcMG/eMf/5Akvf/++3r77beVmZkpqe7vv3r7VQL1zX/+8x+Vl5dX+SqDhIQE/f3vf6+jVYVPcXGxJFWbt/KYKSoqKjR+/HjddNNNuuGGGyR9mc/pdFb5xnLT8u3bt09paWk6d+6cmjRpopUrV6pDhw4qLCy0Rb5ly5bp3Xff1a5du6ocs8N92Lt3by1evFht27bV8ePHNWPGDPXt21dFRUW2yPfhhx/q+eefV05Ojh599FHt2rVLv/zlL+V0OjVixAhbPc6sWrVKp06d0gMPPCDJHr+fkydPltfrVbt27RQZGany8nI9/vjjGjZsmKS6/ztBgYHtZWdnq6ioSG+//XZdLyXk2rZtq8LCQpWWluq1117TiBEjtHnz5rpeVkh88sknevjhh+XxeNSwYcO6Xk5YVP6frCR17txZvXv3VkpKil555RU1atSoDlcWGhUVFerZs6eeeOIJSVK3bt1UVFSkF154QSNGjKjj1YXWiy++qMzMTCUlJdX1UkLmlVde0ZIlS7R06VJ17NhRhYWFGj9+vJKSkurF/cdTSFeoZcuWioyMrPIK8pKSErnd7jpaVfhUZjI977hx47R69Wq99dZbuvbaawP73W63zp8/r1OnTgWNNy2f0+nU9ddfrx49eigvL09dunTRM888Y4t8e/bs0YkTJ9S9e3c1aNBADRo00ObNm/Xss8+qQYMGSkhIMD7jf4uLi9N3vvMdHT582Bb3YWJiojp06BC0r3379oGnyezyOPPxxx9r/fr1+tnPfhbYZ4f7b+LEiZo8ebLuvfdederUSffff78mTJigvLw8SXV//1FgrpDT6VSPHj20YcOGwL6Kigpt2LBBaWlpdbiy8EhNTZXb7Q7K6/V6tWPHDiPyWpalcePGaeXKldq4caNSU1ODjvfo0UNRUVFB+Q4ePKhjx44Zke9iKioq5PP5bJFvwIAB2rdvnwoLCwOXnj17atiwYYF/m57xv505c0ZHjhxRYmKiLe7Dm266qcrHF/zjH/9QSkqKJPMfZyotWrRI8fHxysrKCuyzw/1XVlamiIjgmhAZGamKigpJ9eD+C/vLhG1k2bJllsvlshYvXmwdOHDAGjNmjBUXF2cVFxfX9dJq5fTp09Z7771nvffee5Yka968edZ7771nffzxx5Zlffn2uLi4OOv111+39u7da91xxx3GvL1x7NixVmxsrLVp06agtzmWlZUFxjz44INWq1atrI0bN1q7d++20tLSrLS0tDpcdc1MnjzZ2rx5s3X06FFr79691uTJky2Hw2Hl5+dblmV+vup89V1IlmV+xl/96lfWpk2brKNHj1rvvPOONXDgQKtly5bWiRMnLMsyP9/OnTutBg0aWI8//rh16NAha8mSJVZ0dLT18ssvB8aY/DhjWV++G7VVq1bWpEmTqhwz/f4bMWKE9a1vfSvwNuoVK1ZYLVu2tB555JHAmLq8/ygwNfTcc89ZrVq1spxOp9WrVy9r+/btdb2kWnvrrbcsSVUuI0aMsCzry7fITZ061UpISLBcLpc1YMAA6+DBg3W76CtUXS5J1qJFiwJjvvjiC+sXv/iF1axZMys6Otq66667rOPHj9fdomto5MiRVkpKiuV0Oq1rrrnGGjBgQKC8WJb5+arz3wXG9Iw//vGPrcTERMvpdFrf+ta3rB//+MdBn5Fiej7Lsqy//vWv1g033GC5XC6rXbt21oIFC4KOm/w4Y1mWtW7dOktStWs2/f7zer3Www8/bLVq1cpq2LChdd1111m/+c1vLJ/PFxhTl/efw7K+8pF6AAAABuA1MAAAwDgUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcSgwAADAOBQYAABgHAoMAAAwzv8HvUz8mdQlrY4AAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "simulate_batch(20, 80, 10000).hist()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c0c36a41", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/config.rs b/src/config.rs index 3be9523..d75e887 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::collections::BTreeMap; use std::fs::{create_dir_all, File}; use std::io::{BufReader, Read}; @@ -7,7 +8,7 @@ use std::{env, fs}; use serde::{Deserialize, Serialize}; -use crate::git_repo_iter::GitRepoIter; +use crate::git_repo_iter::{CachedFs, GitRepoIter}; type Result = std::result::Result>; @@ -34,7 +35,7 @@ impl Default for WatchConfig { } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize)] pub struct Config { // When commit_exclude_git_config is true, // never use any git configuration to sign dura's commits. @@ -44,6 +45,9 @@ pub struct Config { pub commit_author: Option, pub commit_email: Option, pub repos: BTreeMap>, + + #[serde(skip_serializing, skip_deserializing)] + pub cache: Rc>, } impl Config { @@ -53,6 +57,7 @@ impl Config { commit_author: None, commit_email: None, repos: BTreeMap::new(), + cache: Rc::new(RefCell::new(CachedFs::default())), } } @@ -159,7 +164,7 @@ impl Config { } } - pub fn git_repos(&self) -> GitRepoIter { - GitRepoIter::new(self) + pub fn git_repos(&mut self) -> GitRepoIter { + GitRepoIter::new(self, Rc::clone(&self.cache)) } } diff --git a/src/git_repo_iter.rs b/src/git_repo_iter.rs index 727699f..6e7fbdc 100644 --- a/src/git_repo_iter.rs +++ b/src/git_repo_iter.rs @@ -1,7 +1,18 @@ +use num_traits::abs; +use num_traits::cast::ToPrimitive; +use os_str_bytes::OsStringBytes; +use qp_trie::Trie; +use rand::prelude::ThreadRng; +use rand::{thread_rng, Rng}; +use std::borrow::Borrow; +use std::cell::RefCell; use std::collections::btree_map; use std::fs; +use std::ops::Add; use std::path::{Path, PathBuf}; use std::rc::Rc; +use std::time::{Duration, Instant}; +use tracing::{debug, info, trace, warn}; use crate::config::{Config, WatchConfig}; use crate::snapshots; @@ -28,14 +39,18 @@ enum CallState { pub struct GitRepoIter<'a> { config_iter: btree_map::Iter<'a, String, Rc>, /// A stack, because we can't use recursion with an iterator (at least not between elements) - sub_iter: Vec<(Rc, Rc, fs::ReadDir)>, + sub_iter: Vec<(Rc, Rc, CachedDirIter)>, + + cached_fs: Rc>, } impl<'a> GitRepoIter<'a> { - pub fn new(config: &'a Config) -> Self { + pub fn new(config: &'a Config, cached_fs: Rc>) -> Self { + (*cached_fs).borrow_mut().cycle(); Self { config_iter: config.repos.iter(), sub_iter: Vec::new(), + cached_fs, } } @@ -47,23 +62,26 @@ impl<'a> GitRepoIter<'a> { // use the iterator. But that means we have to return it to the vec. match self.sub_iter.pop() { Some((base_path, watch_config, mut dir_iter)) => { - let mut next_next: Option<(Rc, Rc, fs::ReadDir)> = None; + let mut next_next: Option<(Rc, Rc, CachedDirIter)> = None; let mut ret_val = CallState::Recurse; let max_depth: usize = watch_config.max_depth.into(); - if let Some(Ok(entry)) = dir_iter.next() { - let child_path = entry.path(); - if is_valid_directory(base_path.as_path(), child_path.as_path(), &watch_config) - { + if let Some(child_path) = dir_iter.next() { + if Self::is_valid_directory( + base_path.as_path(), + child_path.as_path(), + &watch_config, + ) { if snapshots::is_repo(child_path.as_path()) { - ret_val = CallState::Yield(child_path); + ret_val = CallState::Yield((*child_path).to_path_buf()); } else if self.sub_iter.len() < max_depth { - if let Ok(child_dir_iter) = fs::read_dir(child_path.as_path()) { - next_next = Some(( - Rc::clone(&base_path), - Rc::clone(&watch_config), - child_dir_iter, - )) - } + let child_dir_iter = (*self.cached_fs) + .borrow() + .list_dir(child_path.to_path_buf()); + next_next = Some(( + Rc::clone(&base_path), + Rc::clone(&watch_config), + child_dir_iter, + )); } } // un-pop @@ -81,7 +99,9 @@ impl<'a> GitRepoIter<'a> { match self.config_iter.next() { Some((base_path, watch_config)) => { let path = PathBuf::from(base_path); - let dir_iter_opt = path.parent().and_then(|p| fs::read_dir(p).ok()); + let dir_iter_opt = path + .parent() + .map(|p| (*self.cached_fs).borrow_mut().list_dir(p.to_path_buf())); if let Some(dir_iter) = dir_iter_opt { // clone because we're going from more global to less global scope self.sub_iter @@ -95,6 +115,38 @@ impl<'a> GitRepoIter<'a> { } } } + + /// Checks the provided `child_path` is a directory. + /// If either `includes` or `excludes` are set, + /// checks whether the path is included/excluded respectively. + fn is_valid_directory(base_path: &Path, child_path: &Path, value: &WatchConfig) -> bool { + if !child_path.is_dir() { + return false; + } + + if !child_path.starts_with(base_path) { + return false; + } + + let includes = &value.include; + let excludes = &value.exclude; + + let mut include = true; + + if !excludes.is_empty() { + include = !excludes + .iter() + .any(|exclude| child_path.starts_with(base_path.join(exclude))); + } + + if !include && !includes.is_empty() { + include = includes + .iter() + .any(|include| base_path.join(include).starts_with(child_path)); + } + + include + } } impl<'a> Iterator for GitRepoIter<'a> { @@ -111,34 +163,369 @@ impl<'a> Iterator for GitRepoIter<'a> { } } -/// Checks the provided `child_path` is a directory. -/// If either `includes` or `excludes` are set, -/// checks whether the path is included/excluded respectively. -fn is_valid_directory(base_path: &Path, child_path: &Path, value: &WatchConfig) -> bool { - if !child_path.is_dir() { - return false; +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct CacheItem { + /// Random number used for occasionally invalidating the cache + sig_invalidate: u16, + /// Force invalidate at this point + ttl: Instant, + children: Option>>>, +} + +/// A repository of directory iterators that caches to avoid hitting the disk. Cache +/// invalidation is done with lots of jitter, so that items are given a maximum lifetime, +/// i.e. cache invalidation is guaranteed to occur every N minutes, but in practice +/// invalidation is spread evenly, stochastically, over those N minutes. The intent is +/// to avoid a single spike of sys calls to list all directories. +/// +/// Without this optimization, it spends a lot of time listing directories, but most +/// computers can handle it fine. +/// +/// This makes sense for the GitRepoIter because the set of repos is fairly static and +/// the cost of being wrong for a few minutes is very low. This doesn't make sense for +/// scanning the files in the repo, because the cost of a false negative is very high. +#[derive(Debug)] +pub struct CachedFs { + cache: Rc>>, + max_lifetime: Duration, + max_sig_ticks: u16, + current_sig_tick: u16, + cycle_count: u16, + rng: Rc>, + disable: bool, +} + +impl CachedFs { + /// # Arguments + /// + /// * `max_lifetime` — The maximum time a cache entry should live. In other words, "how long + /// are you willing to go without a new Git repo being discovered?" + /// * `expected_interval` — The average duration between calls to `list_dir`. i.e. average + /// loop time, from the statistics. + pub fn new(max_lifetime: Duration, expected_interval: Duration) -> Self { + assert!( + max_lifetime > expected_interval, + "max_lifetime should be larger than expected_interval, otherwise +strange behavior may occur" + ); + let max_sig_ticks = + // Decided on this after modeling it in ./scripts/CachedFs.ipynb + // Having a ratio of 1/4 gives it decent behavior + ((max_lifetime.as_secs_f32() / 4.0) / expected_interval.as_secs_f32()) + .to_u16(); + let mut rng = thread_rng(); + let max_ticks = max_sig_ticks.unwrap_or(255); + Self { + cache: Rc::new(RefCell::new(Trie::new())), + max_lifetime, + max_sig_ticks: max_ticks, + current_sig_tick: rng.gen_range(0..max_ticks), + rng: Rc::new(RefCell::new(rng)), + cycle_count: 0, + disable: false, + } + } + + pub fn disable_cache(&mut self) { + self.disable = true; + } + + /// Used in testing to reset TTL of all the nodes in the trie to the current instant. + /// Within tests, this makes it so we can keep the time horizons very low. I don't think + /// it's much use outside tests. + pub fn reset_ttl(&self) { + let now = Instant::now().add(self.max_lifetime); + let mut cache = (*self.cache).borrow_mut(); + for (_, cache_item) in cache.iter_mut() { + cache_item.ttl = now; + } + info!("Reset all TTL items"); } - if !child_path.starts_with(base_path) { - return false; + /// This should be called every now and then. If it's not called periodically, our cache + /// items won't expire. Originally I intended this to be called at the start of processing + /// all Git repos once. I think it could be called more often than that, certainly not less. + pub fn cycle(&mut self) { + self.cycle_count = (self.cycle_count + 1) % (self.max_sig_ticks * 4); + self.current_sig_tick = self.gen_rand(); } - let includes = &value.include; - let excludes = &value.exclude; + /// Generate a random number. + /// The notebook in `./scripts/CachedFs.ipynb` explores the functions and coefficients. + /// I arrived at these magic numbers after a lot of experimentation. It seems that these + /// tend to generate a distribution that's a little biased toward left. + fn gen_rand(&mut self) -> u16 { + let n = self.max_sig_ticks as f32; + let i = self.cycle_count as f32; + let max = (n + abs((2.0 * n) - i.powf(0.9))) + .to_u16() + .unwrap_or(u16::MAX); + (*self.rng).borrow_mut().gen_range(0..max) + } - let mut include = true; + /// List the directory. This should be have the same as fs::ReadDir except that + /// it may return a cached version instead, to avoid disk access. + pub fn list_dir(&self, path: PathBuf) -> CachedDirIter { + let ppath = PPath::new(&path); + let cache_item = { + let cache = (*self.cache).borrow(); + cache.get::(&ppath).cloned() + }; - if !excludes.is_empty() { - include = !excludes - .iter() - .any(|exclude| child_path.starts_with(base_path.join(exclude))); + match cache_item { + _ if self.disable => { + debug!("Cache disabled; path={}", ppath.to_string()); + self.send_miss(&ppath) + } + Some(found) if found.sig_invalidate == self.current_sig_tick => { + debug!( + "Cache miss, random bucket; sig_invalidate={}, path={}", + found.sig_invalidate, + ppath.to_string() + ); + self.send_miss(&ppath) + } + Some(found) if found.children.is_none() => { + debug!("Cache miss, uninitialized; path={}", ppath.to_string()); + self.send_miss(&ppath) + } + Some(found) if found.ttl < Instant::now() => { + debug!( + "Cache miss, timeout; ttl_delta={}, secs, path={}", + (Instant::now() - found.ttl).as_secs_f32(), + ppath.to_string() + ); + self.send_miss(&ppath) + } + Some(found) => { + trace!( + "Cache hit; path={}, local={}, global={}, num_buckets={}", + ppath.to_string(), + found.sig_invalidate, + self.current_sig_tick, + self.max_sig_ticks + ); + self.send_hit(&ppath) + } + None => { + debug!("Cache miss, not present; path={}", ppath.to_string()); + self.send_miss(&ppath) + } + } } - if !include && !includes.is_empty() { - include = includes + fn send_hit(&self, ppath: &PPath) -> CachedDirIter { + let empty = Rc::new(RefCell::new(vec![])); + let cache = (*self.cache).borrow(); + let complex = cache + .get::(ppath) + .and_then(|item| item.children.as_ref()) + .unwrap_or(&empty); + let children = (**complex) + .borrow() .iter() - .any(|include| base_path.join(include).starts_with(child_path)); + .map(|str| Rc::new(ppath.path().join(str))) + .collect::>(); + + CachedDirIter::Hit { + index: 0, + listing: children, + } + } + + /// Create a NewCacheItem function + /// + /// TODO: I've rationalized to myself that NewCacheItem needs to be a function, but maybe it + /// can be simplified? + fn get_new_cache_item(&self) -> NewCacheItem { + let copied_rng = Rc::clone(&self.rng); + let ttl = Instant::now().add(self.max_lifetime); + let max_sig_ticks = self.max_sig_ticks; + let new_cache_item: NewCacheItem = Rc::new(move || CacheItem { + sig_invalidate: (*copied_rng).borrow_mut().gen_range(0u16..max_sig_ticks), + ttl, + children: None, + }); + new_cache_item + } + + fn send_miss(&self, ppath: &PPath) -> CachedDirIter { + let new_cache_item = self.get_new_cache_item(); + + // read dir + match fs::read_dir((ppath.path()).as_path()) { + Ok(iter) => { + let item = new_cache_item(); + (*self.cache).borrow_mut().insert(ppath.clone(), item); + debug!( + "Initialized cache for directory; path={path}", + path = ppath.to_string() + ); + + CachedDirIter::Miss(iter, Rc::clone(&self.cache), Rc::clone(&new_cache_item)) + } + Err(err) => { + warn!( + "Failed to read dir: {} due to error: {}", + &ppath.path().display(), + err + ); + // an empty hit + CachedDirIter::Hit { + index: 0, + listing: vec![], + } + } + } + } +} + +impl Default for CachedFs { + fn default() -> Self { + Self::new(Duration::from_secs_f64(600.0), Duration::from_secs_f64(5.0)) + } +} + +// just to support the trie + type checker +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct PPath { + bytes: Vec, +} + +impl PPath { + pub fn new(pb: &Path) -> Self { + Self { + bytes: pb.to_path_buf().into_raw_vec(), + } + } + + pub fn path(&self) -> PathBuf { + PathBuf::assert_from_raw_vec(self.bytes.clone()) + } +} + +// for debugging purposes +impl ToString for PPath { + fn to_string(&self) -> String { + match self.path().into_os_string().into_string() { + Ok(str) => str, + Err(_) => "n/a".to_string(), + } + } +} + +impl Borrow<[u8]> for PPath { + fn borrow(&self) -> &[u8] { + &self.bytes + } +} + +type NewCacheItem = Rc CacheItem>; + +/// Union over 2 types of iterators over directories. Either the +/// cache hit or cache miss mode. +pub enum CachedDirIter { + Miss( + fs::ReadDir, + Rc>>, + NewCacheItem, + ), + Hit { + index: usize, + listing: Vec>, + }, +} + +impl Iterator for CachedDirIter { + type Item = Rc; + + fn next(&mut self) -> Option { + match self { + CachedDirIter::Miss(iter, cache_cell, new_cache_item) => { + iter.next().and_then(|e| { + e.ok().map(|dir_entry| { + let entry_path = Rc::new(dir_entry.path()); + let res = (**cache_cell) + .borrow_mut() + .insert(PPath::new(&entry_path), new_cache_item()); + + match res { + Some(_) => { + trace!( + "Updated cache for directory; path={path}", + path = (*entry_path).display() + ); + } + None => { + trace!( + "Created item in cache for directory; path={path}", + path = (*entry_path).display() + ); + } + } + + // insert this item into parent node + if let Some(parent) = entry_path.parent() { + let search_node = PPath::new(parent); + if let Some(found_item) = + (**cache_cell).borrow_mut().get_mut::(&search_node) + { + // maintain a vec of dir names in self + let comp = (*entry_path).components().last().and_then(|last| { + last.as_os_str().to_os_string().into_string().ok() + }); + match &found_item.children { + Some(c) => { + if let Some(last_component) = comp { + (*c).borrow_mut().push(last_component); + } + } + None => { + let mut vec = vec![]; + if let Some(last_component) = comp { + vec.push(last_component); + } + found_item.children = Some(Rc::new(RefCell::new(vec))); + } + }; + } + } + + entry_path + }) + }) + } + CachedDirIter::Hit { + ref mut index, + listing, + } => { + let result = if *index >= listing.len() { + None + } else { + Some(Rc::clone(&listing[*index])) + }; + *index += 1; + result + } + } } +} + +#[cfg(test)] +mod tests { + use crate::git_repo_iter::CachedFs; + use std::time::Duration; - include + #[test] + fn new_cachedfs() { + let cf = CachedFs::new(Duration::from_secs(120), Duration::from_secs(5)); + assert_eq!(cf.max_sig_ticks, 6); + assert!(cf.current_sig_tick < cf.max_sig_ticks); + } + + #[test] + #[should_panic] + fn new_cachedfs_panic_when_parameters_reversed() { + CachedFs::new(Duration::from_secs(5), Duration::from_secs(120)); + } } diff --git a/src/main.rs b/src/main.rs index 3516ad6..089236f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -134,7 +134,7 @@ async fn main() { } Some(("serve", arg_matches)) => { let env_filter = - EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); + EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug")); match arg_matches.get_one::("logfile") { Some(logfile) => { diff --git a/src/poll_guard.rs b/src/poll_guard.rs index 1ce4947..6cc754a 100644 --- a/src/poll_guard.rs +++ b/src/poll_guard.rs @@ -45,7 +45,6 @@ impl PollGuard { for entry in WalkDir::new(dir) { if let Ok(modified) = get_file_time(entry) { if compare_times(modified, watermark).unwrap_or(false) { - dbg!(modified, watermark); return true; } } diff --git a/src/poller.rs b/src/poller.rs index a37ffe5..defb551 100644 --- a/src/poller.rs +++ b/src/poller.rs @@ -65,7 +65,7 @@ fn do_task(stats: &mut StatCollector, guard: &mut PollGuard) { process::exit(1); } - let config = Config::load(); + let mut config = Config::load(); let loop_start = Instant::now(); for repo in config.git_repos() { diff --git a/tests/git_repo_iter_test.rs b/tests/git_repo_iter_test.rs new file mode 100644 index 0000000..b1b9303 --- /dev/null +++ b/tests/git_repo_iter_test.rs @@ -0,0 +1,149 @@ +mod util; + +mod cached_fs_test { + use crate::{repo_and_file, set_log_lvl, util}; + use dura::git_repo_iter::{CachedDirIter, CachedFs}; + use std::collections::HashSet; + use std::thread; + use std::time::Duration; + use tempfile::TempDir; + use tokio::time::Instant; + use tracing::debug; + + fn iter_to_set(iter: CachedDirIter) -> HashSet { + iter.map(|dir| { + dir.components() + .last() + .unwrap() + .as_os_str() + .to_os_string() + .into_string() + .unwrap() + }) + .collect() + } + + #[test] + fn finds_all_dirs_on_startup() { + let tmp = tempfile::tempdir().unwrap(); + repo_and_file!(tmp, &["repo1"], "foo.txt"); + repo_and_file!(tmp, &["repo2"], "foo.txt"); + let fs = CachedFs::new(Duration::from_millis(50), Duration::from_millis(5)); + assert_eq!( + iter_to_set(fs.list_dir(tmp.path().to_path_buf())), + HashSet::from(["repo1".to_string(), "repo2".to_string()]) + ); + // same, but again + assert_eq!( + iter_to_set(fs.list_dir(tmp.path().to_path_buf())), + HashSet::from(["repo1".to_string(), "repo2".to_string()]) + ); + } + + #[test] + fn finds_nested_dirs() { + // set_log_lvl!(filter::LevelFilter::TRACE); + let tmp = tempfile::tempdir().unwrap(); + repo_and_file!(tmp, &["foo", "repo1"], "foo.txt"); + repo_and_file!(tmp, &["foo", "bar", "repo2"], "foo.txt"); + let fs = CachedFs::new(Duration::from_millis(50), Duration::from_millis(5)); + assert_eq!( + iter_to_set(fs.list_dir(tmp.path().join("foo"))), + HashSet::from(["repo1".to_string(), "bar".to_string()]) + ); + assert_eq!( + // again + iter_to_set(fs.list_dir(tmp.path().join("foo"))), + HashSet::from(["repo1".to_string(), "bar".to_string()]) + ); + assert_eq!( + iter_to_set(fs.list_dir(tmp.path().join("foo").join("bar"))), + HashSet::from(["repo2".to_string()]) + ); + assert_eq!( + // again + iter_to_set(fs.list_dir(tmp.path().join("foo").join("bar"))), + HashSet::from(["repo2".to_string()]) + ); + } + + /// Used for the next couple tests + fn do_test() -> (usize, HashSet, TempDir) { + let tmp = tempfile::tempdir().unwrap(); + repo_and_file!(tmp, &["repo1"], "foo.txt"); + repo_and_file!(tmp, &["repo2"], "foo.txt"); + let mut fs = CachedFs::new(Duration::from_millis(50), Duration::from_millis(5)); + let mut found = iter_to_set(fs.list_dir(tmp.path().to_path_buf())); + assert_eq!(found.len(), 2); + assert_eq!( + found, + HashSet::from(["repo1".to_string(), "repo2".to_string()]) + ); + + let mut ret = 0; + + // add new repo and wait until max time + repo_and_file!(tmp, &["repo3"], "foo.txt"); + let start = Instant::now(); + fs.reset_ttl(); + loop { + found = iter_to_set(fs.list_dir(tmp.path().to_path_buf())); + + let elapsed = Instant::now() - start; + if elapsed > Duration::from_millis(50) { + debug!("exit test loop, timeout"); + break; + } else if found.len() == 3 { + debug!("exit test loop, found all"); + dbg!(&found); + break; + } else { + thread::sleep(Duration::from_millis(5)); + } + + fs.cycle(); + ret += 1; + } + + (ret, found, tmp) + } + + #[test] + #[ignore] + fn finds_new_dirs_within_max_duration() { + set_log_lvl!(filter::LevelFilter::TRACE); + let (loop_break, found, _tmp) = do_test(); + + assert_eq!( + found, + HashSet::from([ + "repo1".to_string(), + "repo2".to_string(), + "repo3".to_string(), + ]) + ); + + // this assertion may be flaky. just re-run + assert!(loop_break > 0); + } + + /// Run the previous test a large number of times and test how often + /// the cache gets invalidated early + #[test] + fn invalidates_cache_roughly_along_normal_distribution() { + // set_log_lvl!(filter::LevelFilter::TRACE); + + let result = (0..100).map(|_| do_test()).collect::>(); + let exit_loop = result.iter().map(|x| x.0).collect::>(); + dbg!(&exit_loop); + + // the tails are non-zero + assert!(*exit_loop.iter().min().unwrap() <= 2); + assert!(*exit_loop.iter().max().unwrap() >= 8); + + // it usually lands in the middle + let mid_range = exit_loop.iter().filter(|x| 3 <= **x && **x <= 7).count(); + // this assert might be flaky, just rerun it + assert!(mid_range > 20, "failed: 3 <= {} <= 7", mid_range); + } +} diff --git a/tests/util/dura.rs b/tests/util/dura.rs index a322ca1..0240796 100644 --- a/tests/util/dura.rs +++ b/tests/util/dura.rs @@ -137,7 +137,10 @@ impl Dura { pub fn git_repos(&self) -> HashSet { match self.get_config() { - Some(cfg) => cfg.git_repos().collect(), + Some(mut cfg) => { + (*cfg.cache).borrow_mut().disable_cache(); + cfg.git_repos().collect() + } None => HashSet::new(), } } diff --git a/tests/util/macros.rs b/tests/util/macros.rs index e9b822b..fb3df46 100644 --- a/tests/util/macros.rs +++ b/tests/util/macros.rs @@ -8,4 +8,28 @@ macro_rules! repo_and_file { repo.commit_all(); repo }}; + ( $tmp:expr, $path:expr, $file_name:expr ) => {{ + let mut path_buf = $tmp.path().to_path_buf(); + for p in $path { + path_buf.push(p); + } + let repo = util::git_repo::GitRepo::new(path_buf.clone()); + repo.init(); + repo.write_file($file_name); + repo.commit_all(); + repo + }}; +} + +#[macro_export] +macro_rules! set_log_lvl { + ($lvl:expr) => {{ + use tracing_subscriber::{filter, fmt, prelude::*, reload}; + + let (filter, _reload_handle) = reload::Layer::new($lvl); + tracing_subscriber::registry() + .with(filter) + .with(fmt::Layer::default()) + .init(); + }}; }