diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2204bda..4dfd462 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -8,15 +8,18 @@ on: jobs: docker: + strategy: + matrix: + test: ["eth_getBalance", "eth_getBlockByNumber"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: docker/setup-buildx-action@v2 - run: docker build --tag paradigmxyz/flood:latest . - run: docker run --rm paradigmxyz/flood:latest version - - run: | + - name: ${{ matrix.test }} + run: | docker run --rm paradigmxyz/flood:latest \ - eth_getBlockByNumber \ + ${{ matrix.test }} \ node1=https://eth.llamarpc.com \ - node2=https://eth.llamarpc.com \ --duration 3 --rate 1 diff --git a/Dockerfile b/Dockerfile index bc88ba4..12f21de 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ # https://github.com/paradigmxyz/flood/pkgs/container/flood # Stage 1: Install flood -FROM python:3.11.3-slim AS flood-builder +FROM python:3.11-slim AS flood-builder ENV USERNAME="flood" ENV PATH="${PATH}:/home/${USERNAME}/.local/bin" RUN adduser $USERNAME @@ -14,23 +14,26 @@ RUN pip install --user --no-cache-dir ./ # Stage 2: Install vegeta FROM debian:stable-slim AS vegeta-builder +ENV VEGETA_VERSION=12.8.4 RUN apt-get update && apt-get install -y --no-install-recommends wget ca-certificates \ && rm -rf /var/lib/apt/lists/* \ && mkdir /vegeta WORKDIR /vegeta -RUN wget https://github.com/tsenart/vegeta/releases/download/v12.8.4/vegeta_12.8.4_linux_amd64.tar.gz \ - && tar xzf vegeta_12.8.4_linux_amd64.tar.gz \ - && rm vegeta_12.8.4_linux_amd64.tar.gz +RUN wget https://github.com/tsenart/vegeta/releases/download/v${VEGETA_VERSION}/vegeta_${VEGETA_VERSION}_linux_amd64.tar.gz \ + && tar xzf vegeta_${VEGETA_VERSION}_linux_amd64.tar.gz \ + && rm vegeta_${VEGETA_VERSION}_linux_amd64.tar.gz # Final stage: Combine flood and vegeta -FROM python:3.11.3-slim +FROM python:3.11-slim ENV USERNAME="flood" ENV PATH="${PATH}:/home/${USERNAME}/.local/bin" RUN adduser $USERNAME -USER $USERNAME +RUN apt-get update && apt-get install -y --no-install-recommends curl \ + && rm -rf /var/lib/apt/lists/* COPY --from=flood-builder /home/$USERNAME/.local /home/$USERNAME/.local COPY --from=vegeta-builder /vegeta/vegeta /home/$USERNAME/.local/bin/vegeta +USER $USERNAME WORKDIR /home/$USERNAME ENTRYPOINT ["python", "-m", "flood"] diff --git a/flood_rust/Cargo.lock b/flood_rust/Cargo.lock new file mode 100644 index 0000000..3c18966 --- /dev/null +++ b/flood_rust/Cargo.lock @@ -0,0 +1,4207 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-json-rpc" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=d5967ab#d5967abdbae58dd84acc4c1ee31345108d42d041" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "alloy-primitives" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "600d34d8de81e23b6d909c094e23b3d357e01ca36b78a8c5424c501eedbe86f0" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa 1.0.10", + "k256", + "keccak-asm", + "proptest", + "rand", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d58d9f5da7b40e9bfff0b7e7816700be4019db97d4b6359fe7f94a9e22e42ac" +dependencies = [ + "alloy-rlp-derive", + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a047897373be4bbb0224c1afdabca92648dc57a9c9ef6e7b0be3aff7a859c83" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "alloy-rpc-client" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=d5967ab#d5967abdbae58dd84acc4c1ee31345108d42d041" +dependencies = [ + "alloy-json-rpc", + "alloy-transport", + "alloy-transport-http", + "futures", + "pin-project", + "reqwest", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", + "url", +] + +[[package]] +name = "alloy-rpc-types" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=d5967ab#d5967abdbae58dd84acc4c1ee31345108d42d041" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "itertools 0.12.1", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "alloy-serde" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=d5967ab#d5967abdbae58dd84acc4c1ee31345108d42d041" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-transport" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=d5967ab#d5967abdbae58dd84acc4c1ee31345108d42d041" +dependencies = [ + "alloy-json-rpc", + "base64 0.22.0", + "futures-util", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower", + "url", + "wasm-bindgen-futures", +] + +[[package]] +name = "alloy-transport-http" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=d5967ab#d5967abdbae58dd84acc4c1ee31345108d42d041" +dependencies = [ + "alloy-json-rpc", + "alloy-transport", + "reqwest", + "serde_json", + "tower", + "url", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi-escapes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3c0daaaae24df5995734b689627f8fa02101bc5bbc768be3055b66a010d7af" + +[[package]] +name = "anstream" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint 0.4.4", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint 0.4.4", + "num-traits", + "paste", + "rustc_version 0.4.0", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint 0.4.4", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint 0.4.4", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint 0.4.4", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "auto_impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "bytemuck" +version = "1.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +dependencies = [ + "serde", +] + +[[package]] +name = "cc" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3286b845d0fccbdd15af433f61c5970e711987036cb468f437ff6badd70f4e24" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.3", +] + +[[package]] +name = "clap" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.0", +] + +[[package]] +name = "clap_derive" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[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 = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + +[[package]] +name = "const-cstr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" + +[[package]] +name = "const-hex" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ba00838774b4ab0233e355d26710fbfc8327a05c017f6dc4873f876d1f79f78" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "proptest", + "serde", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "core-text" +version = "19.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types", + "libc", +] + +[[package]] +name = "cpu-time" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e393a7668fe1fad3075085b86c781883000b4ede868f43627b34a87c8b7ded" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctrlc" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" +dependencies = [ + "nix", + "windows-sys 0.52.0", +] + +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 2.0.50", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "dwrote" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "err-derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34a887c8df3ed90498c1c437ce21f211c8e27672921a8ffa293cb8d6d4caa9e" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-ord" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "font-kit" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21fe28504d371085fae9ac7a3450f0b289ab71e07c8e57baa3fb68b9e57d6ce5" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "core-foundation", + "core-graphics", + "core-text", + "dirs-next", + "dwrote", + "float-ord", + "freetype", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "freetype" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efc8599a3078adf8edeb86c71e9f8fa7d88af5ca31e806a867756081f90f5d83" +dependencies = [ + "freetype-sys", + "libc", +] + +[[package]] +name = "freetype-sys" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66ee28c39a43d89fbed8b4798fb4ba56722cfd2b5af81f9326c27614ba88ecd5" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "base64 0.21.7", + "byteorder", + "crossbeam-channel", + "flate2", + "nom", + "num-traits", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e18e4ebaca9e578405839cdf9098dc09a11ad532d94a6105a87c00c11b8a835" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "histogram" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cb882ccb290b8646e554b157ab0b71e64e8d5bef775cd66b6531e52d302669" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.10", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.10", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "hytra" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7ee43a7d27a202506374a5afb36b89c3be719ace2082e492dabb2034028124" +dependencies = [ + "atomic", + "crossbeam-utils", + "num-traits", + "rayon", + "thread_local", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "jpeg-decoder", + "num-traits", + "png", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jemalloc-sys" +version = "0.5.4+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6c1946e1cea1788cbfde01c993b52a10e2da07f4bac608228d1bed20bfebf2" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "jemallocator" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0de374a9f8e63150e6f5e8a60cc14c668226d7a347d8aee1a45766e3c4dd3bc" +dependencies = [ + "jemalloc-sys", + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + +[[package]] +name = "js-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "keccak-asm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb8515fff80ed850aea4a1595f2e519c003e2a00a82fe168ebf5269196caf444" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + +[[package]] +name = "latte-cli" +version = "0.25.0" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-transport", + "alloy-transport-http", + "anyhow", + "base64 0.21.7", + "chrono", + "clap", + "console", + "cpu-time", + "ctrlc", + "err-derive", + "futures", + "hdrhistogram", + "hytra", + "itertools 0.12.1", + "jemallocator", + "lazy_static", + "metrohash", + "num_cpus", + "openssl", + "parse_duration", + "plotters", + "plotters-svg", + "rand", + "regex", + "reqwest", + "rmp", + "rmp-serde", + "rune", + "rust-embed", + "scylla", + "search_path", + "serde", + "serde_json", + "statrs", + "status-line", + "strum 0.26.1", + "strum_macros 0.26.1", + "thiserror", + "time", + "tokio", + "tokio-stream", + "tracing", + "tracing-subscriber", + "try-lock", + "uuid", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libloading" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "lz4_flex" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "912b45c753ff5f7f5208307e8ace7d2a2e30d024e26d3509f3dce546c044ce15" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "metrohash" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ba553cb19e2acbc54baa16faef215126243fe45e53357a3b2e9f4ebc7b0506c" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "nalgebra" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d506eb7e08d6329505faa8a3a00a5dcc6de9f76e0c77e4b75763ae3c770831ff" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex 0.4.5", + "num-rational 0.4.1", + "num-traits", + "rand", + "rand_distr", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint 0.2.6", + "num-complex 0.2.4", + "num-integer", + "num-iter", + "num-rational 0.2.4", + "num-traits", +] + +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint 0.4.4", + "num-complex 0.4.5", + "num-integer", + "num-iter", + "num-rational 0.4.1", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint 0.2.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint 0.4.4", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.7", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parity-scale-codec" +version = "3.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" +dependencies = [ + "proc-macro-crate 2.0.2", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "parse_duration" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7037e5e93e0172a5a96874380bf73bc6ecef022e26fa25f2be26864d6b3ba95d" +dependencies = [ + "lazy_static", + "num 0.2.1", + "regex", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0444332826c70dc47be74a7c6a5fc44e23a7905ad6858d4162b658320455ef93" +dependencies = [ + "rustc_version 0.4.0", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pin-project" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "chrono", + "font-kit", + "image", + "lazy_static", + "num-traits", + "pathfinder_geometry", + "plotters-backend", + "plotters-bitmap", + "plotters-svg", + "ttf-parser", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-bitmap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cebbe1f70205299abc69e8b295035bb52a6a70ee35474ad10011f0a4efb8543" +dependencies = [ + "gif", + "image", + "plotters-backend", +] + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.4.2", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[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 = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rmp" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "ruint" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f308135fef9fc398342da5472ce7c484529df23743fb7c734e0f3d472971e62" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp", + "num-bigint 0.4.4", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand", + "rlp", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86854cf50259291520509879a5c294c3c9a4c334e9ff65071c51e42ef1e2343" + +[[package]] +name = "rune" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51a636a206fc1a58be83dc4da610c74ecdb1a1e9b13eeccf8da31d0f9cac3fb1" +dependencies = [ + "anyhow", + "byteorder", + "codespan-reporting", + "futures-core", + "futures-util", + "hashbrown 0.11.2", + "itoa 0.4.8", + "linked-hash-map", + "num 0.4.1", + "num-bigint 0.4.4", + "pin-project", + "rune-macros", + "ryu", + "serde", + "serde_bytes", + "smallvec", + "thiserror", + "tracing", + "twox-hash", +] + +[[package]] +name = "rune-macros" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c66f17eaa2c8f110102f3d6bdd66951c1bab248b81991e6dbdcd9361e37768" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rust-embed" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.50", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.22", +] + +[[package]] +name = "rustix" +version = "0.38.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "safe_arch" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f398075ce1e6a179b46f51bd88d0598b92b00d3551f1a2d4ac49e771b56ac354" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scylla" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d2db76aa23f55d2ece5354e1a3778633098a3d1ea76153f494d71e92cd02d8" +dependencies = [ + "arc-swap", + "async-trait", + "byteorder", + "bytes", + "chrono", + "dashmap", + "futures", + "histogram", + "itertools 0.11.0", + "lz4_flex", + "num_enum", + "openssl", + "rand", + "rand_pcg", + "scylla-cql", + "scylla-macros", + "smallvec", + "snap", + "socket2", + "strum 0.23.0", + "strum_macros 0.23.1", + "thiserror", + "tokio", + "tokio-openssl", + "tracing", + "uuid", +] + +[[package]] +name = "scylla-cql" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345626c0dd5d9624c413daaba854685bba6a65cff4eb5ea0fb0366df16901f67" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "lz4_flex", + "num_enum", + "scylla-macros", + "snap", + "thiserror", + "tokio", + "uuid", +] + +[[package]] +name = "scylla-macros" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb6085ff9c3fd7e5163826901d39164ab86f11bdca16b2f766a00c528ff9cef9" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "search_path" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5145b2263bec888224d054d1c820ceffa7d4a23723a2a822f970fcf1c5b64770" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa 1.0.10", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.10", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3-asm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac61da6b35ad76b195eb4771210f947734321a8d81d7738e1580d953bc7a15e" +dependencies = [ + "cc", + "cfg-if", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core", +] + +[[package]] +name = "simba" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b7840f121a46d63066ee7a99fc81dcabbc6105e437cae43528cea199b5a05f" +dependencies = [ + "approx", + "num-complex 0.4.5", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +dependencies = [ + "serde", +] + +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "statrs" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d08e5e1748192713cc281da8b16924fb46be7b0c2431854eadc785823e5696e" +dependencies = [ + "approx", + "lazy_static", + "nalgebra", + "num-traits", + "rand", +] + +[[package]] +name = "status-line" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20cc99bbe608305546a850ec4352907279a8b8044f9c13ae58bd0a8ab46ebc1" +dependencies = [ + "ansi-escapes", + "atty", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + +[[package]] +name = "strum" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb" + +[[package]] +name = "strum" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" +dependencies = [ + "strum_macros 0.26.1", +] + +[[package]] +name = "strum_macros" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38" +dependencies = [ + "heck 0.3.3", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "strum_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.50", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-openssl" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ffab79df67727f6acf57f1ff743091873c24c579b1e2ce4d8f53e47ded4d63d" +dependencies = [ + "futures-util", + "openssl", + "openssl-sys", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ttf-parser" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +dependencies = [ + "getrandom", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[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.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.50", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" + +[[package]] +name = "web-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "wide" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89beec544f246e679fc25490e3f8e08003bc4bf612068f325120dad4cea02c1c" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.3", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.3", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +dependencies = [ + "windows_aarch64_gnullvm 0.52.3", + "windows_aarch64_msvc 0.52.3", + "windows_i686_gnu 0.52.3", + "windows_i686_msvc 0.52.3", + "windows_x86_64_gnu 0.52.3", + "windows_x86_64_gnullvm 0.52.3", + "windows_x86_64_msvc 0.52.3", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2bbd69036d397ebbff671b1b8e4d918610c181c5a16073b96f984a38d08c386" +dependencies = [ + "const-cstr", + "dlib", + "once_cell", + "pkg-config", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] diff --git a/flood_rust/Cargo.toml b/flood_rust/Cargo.toml new file mode 100644 index 0000000..c480e88 --- /dev/null +++ b/flood_rust/Cargo.toml @@ -0,0 +1,97 @@ +[package] +name = "flood-cli" +description = "A database benchmarking tool for Apache Cassandra" +version = "0.25.0" +authors = ["Piotr Kołaczkowski "] +edition = "2021" +readme = "README.md" +license = "Apache-2.0" + +[[bin]] +name = "flood" +path = "src/main.rs" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +anyhow = "1.0" +base64 = "0.21" +rmp = "0.8.10" +rmp-serde = "1.0.0-beta.2" +chrono = { version = "0.4.18", features = ["serde"] } +clap = { version = "4", features = ["derive", "cargo", "env"] } +console = "0.15.0" +cpu-time = "1.0.0" +ctrlc = "3.2.1" +err-derive = "0.3" +futures = "0.3" +hdrhistogram = "7.1.0" +hytra = "0.1.2" +itertools = "0.12" +jemallocator = "0.5" +lazy_static = "1.4.0" +metrohash = "1.0" +num_cpus = "1.13.0" +openssl = "0.10.38" +parse_duration = "2.1.1" +plotters = "0.3.4" +plotters-svg = "0.3.3" +rand = "0.8" +regex = "1.5" +rune = "0.12" +rust-embed = "8" +scylla = { version = "0.12", features = ["ssl"] } +search_path = "0.1" +serde = { version = "1.0.116", features = ["derive"] } +serde_json = "1.0.57" +statrs = "0.16" +status-line = "0.2.0" +strum = { version = "0.26", features = ["derive"] } +strum_macros = "0.26" +time = "0.3" +thiserror = "1.0.26" +tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "parking_lot"] } +tokio-stream = "0.1" +tracing = "0.1" +tracing-subscriber = "0.3" +try-lock = "0.2.3" +uuid = { version = "1.1", features = ["v4"] } +alloy-primitives = "0.7.0" +alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "64bcd80" } +alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "64bcd80" } +alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "64bcd80" } +alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "64bcd80" } +alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "64bcd80" } +reqwest = "0.12.2" + +[dev-dependencies] +tokio = { version = "1", features = ["rt", "test-util", "macros"] } + +[profile.release] +codegen-units = 1 +lto = true +panic = "abort" + +[profile.dev-opt] +inherits = "dev" +opt-level = 2 + +[package.metadata.deb] +name = "latte" +maintainer = "Piotr Kołaczkowski " +copyright = "2020, Piotr Kołaczkowski " +license-file = ["LICENSE", "4"] +extended-description = """ +A database benchmarking tool for Apache Cassandra. +Runs CQL queries in parallel, measures throughput and response times. +Can compute statistical significance of differences between two runs. +""" +depends = "$auto" +section = "utility" +priority = "optional" +assets = [ + ["target/release/latte", "usr/bin/", "755"], + ["workloads/basic/*.rn", "/usr/share/latte/workloads/basic/", "644"], + ["workloads/sai/new/*.rn", "/usr/share/latte/workloads/sai/new/", "644"], + ["workloads/sai/orig/*.rn", "/usr/share/latte/workloads/sai/orig/", "644"], + ["README.md", "usr/share/doc/latte/README", "644"], +] diff --git a/flood_rust/LICENSE b/flood_rust/LICENSE new file mode 100644 index 0000000..4f38eea --- /dev/null +++ b/flood_rust/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Piotr Kołaczkowski + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/flood_rust/README.md b/flood_rust/README.md new file mode 100644 index 0000000..c3d362b --- /dev/null +++ b/flood_rust/README.md @@ -0,0 +1,132 @@ +# Flood Rust: +`Flood Rust` is a Rust rewrite of Flood based on [Latte](https://github.com/pkolaczk/latte) a performance benchmarking tool for CassandraDB. Currently, `flood_rust` can benchmark RPC node performance of individual JSON-RPC requests and series of JSON-RPC requests across a variety of parameters inputed manually or from an input file. For each benchmark, `flood_rust` defines a `Workload` which may contain one or more JSON-RPC requests and repeatedly executes cycles of the workload. The user may define the `--rate [req/s]`. The execution time of a `Workload` cycle are recorded as well as the timing and success of individual JSON-RPC calls are recorded. + +If one requests is specified then one requests is executed per benchmark cycle. If multiple are specified either via multiple requests with different methods or by the same requests with different parameters all requests are executed in order sequentially within a single benchmark cycle. Each requests is individually timed and the time to executed a total workload cycle (one or multiple requests) is recorded. + +## Commands +- `run [..]` + + ![animation](img/flood_run.gif) + - Flags: + - `--raw`: Send raw JSON parameters, The first param will be interpreted as a raw JSON array of params. + - For Example: `flood run eth_getBlockByNumber '["0x123", false]' --raw => {"method": "eth_getBlockByNumber", "params": ["0x123", false] ... }` + - `--rate [..]`: Rate or req/s to send per second. + - `--warmup_duration `: Duration of the warmup phase in number of workload cycles. + - `--run_duration `: Duration of main benchmark phase in seconds. + - `--threads `: Number of worker threads used by the driver. + - `--concurrency `: Max number of concurrent async requests (workloads) per thread during the main benchmark phase. + - `--sampling_interval `: Throughput sampling period, in seconds. + - `--tags `: Label that will be added to the report to help identifying the test. + - `--input `: Path to JSON input falls with listed JSON-RPC calls. + - `--output `: Path to an output file or directory where the JSON report should be written to. + - `--baseline `: Path to a report from another earlier run that should be compared to side-by-side. + - `--quiet`: Don't display the progress bar. + - `--random`: On each workload cycle execution randomly suffle and execute all provided JSON-RPC requests. + - `--choose`: On each workload cycle randomly choose on of the provided JSON-RPC requests to execute, this differs from `--random` as only one requests from the provided list. + - `--rpc-url [..]`: Rpc url of ethereume node. + - `--exp-ramp`: Utility function to benchmark rates of powers of ten ([10, 100, ..., 1000000] req/s). + - `--timestamp`: Seconds since 1970-01-01T00:00:00Z +- `show `: Display results of a benchmark run in cli + + ![animation](img/flood_show.gif) + - Flags: + - `--baseline `: Relative Path to .json file to compare against as baseline +- `hdr `: Export histograms as a compressed HDR interval log for use with HdrHistogram (https://github.com/HdrHistogram/HdrHistogram). + - Flags: + - `--output `: Path to an output file where the plot the hdr log should be written to. + - `--tag `: tag prefix for each histogram +- `plot [..]`: Plots the data from reports at sampling intervals as an `.svg` image in the local directory + - Flags + - `--throughput`: Throughput [req/s] vs Time [s] + - `--precentile: `: Response Time [ms] vs Time [s] for a provided Response Time percentile. + - `PERCENTILES`: input_values = {MIN, 25,, 50, 75, 90, 95, 99, 99.9, 99,99, MAX} + - `--success_rate`: Request Success Rate [%] vs Time [s] + - `--success_rate --throughput`: Request Success Rate [%] vs Throughput [req/s] + - `--output `: Path to an output file or directory where the plot `.svg` should be written to. + + + +## Examples: + +[JSON-RPC Reference](https://ethereum.org/en/developers/docs/apis/json-rpc) + +#### Single JSON-RPC +```bash +cargo b --bins + +# Max rate +target/debug/flood run eth_getBlockByNumber "0x1b4 true" --rpc-url [..] +# 100 req/s +target/debug/flood run eth_getBlockByNumber "0x1b4 true" --rpc-url [..] --rate 100 +# Perform multiple benchmarsk for 100 req/s, 10 req/s, 1000 req/s +target/debug/flood run eth_getStorageAt "0x295a70b2de5e3953354a6a8344e616ed314d7251 0x0 latest" --rpc-url [..] --rate 100 10 1000 +``` + +#### Single JSON-RPC with expoential ramp up -> Generates a list of rates powers of 10. +```bash +cargo b --bins + +target/debug/flood run eth_getBlockByNumber "0x1b4 true" --rpc-url [..] --exp-ramp +target/debug/flood run eth_getStorageAt "0x295a70b2de5e3953354a6a8344e616ed314d7251 0x0 latest" --rpc-url [..] --exp-ramp +``` + +#### Multiple JSON-RPC requests with different parameters executed serially +```bash +cargo b --bins + +target/debug/flood run eth_getBlockByNumber "0x1b4 true","0x242 true" --rpc-url [..] --rate 100 +``` + +#### Multiple JSON-RPC requests executed in random order on each workload cycle +```bash +cargo b --bins + +target/debug/flood run eth_getBlockByNumber "0x1b4 true","0x242 true" --rpc-url [..] --rate 100 --random +``` + +#### Select and execute a single random requests per cycle from a list of multiple requests +```bash +cargo b --bins + +target/debug/flood run eth_getBlockByNumber "0x1b4 true","0x242 true" --rpc-url [..] --rate 100 --choose +``` + +#### Multiple JSON-RPC requests over a range of parameters (Supports ranges for a single parameter within a list, multiple ranged parameters not allowed) +```bash +cargo b --bins + +target/debug/flood run eth_getBlockByNumber "0x1b4..0x1bb true","0x242..0x24b true" --rpc-url [..] --rate 100 +``` + +#### Multiple JSON-RPC requests from file +```bash +cargo b --bins +target/debug/flood run --input examples/eth_getBlockByNumber.json --rpc-url [..] --rate 100 +target/debug/flood run --input examples/eth_getStorageAt.json --rpc-url [..] --rate 100 +``` + +#### Benchmark with comparison to baseline +```bash +target/debug/flood run eth_getBlockByNumber "0x1b4 true" --rpc-url [..] -b +``` + +#### Compare two benchmarks +```bash +target/debug/flood show -b [..] +``` + +#### Plot Results +```bash +# Plot Throughput [req/s] vs Time +target/debug/flood plot --throughput-b [..] +# Plot Response Time for provided Percentile (Min, 25, 50, 75, 90, 95, 98, 99, 99.9, 99.99, MAX) [ms] vs Time +target/debug/flood plot --percentile -b [..] +# Plot Success Rate [%] vs Time +target/debug/flood plot --success_rate -b [..] +# Plot Success Rate [%] vs Throughput [req/s] +target/debug/flood plot --success_rate --throughput -b [..] +``` + +## Glossary +- **Workload/Op**: Atomic unit of execution measured by Flood. One or many JSON-RPC requests executed in a continguous timed operation. +- **Op(s)**: An abbreviated term for Workload. \ No newline at end of file diff --git a/flood_rust/examples/eth_getBlockByNumber.json b/flood_rust/examples/eth_getBlockByNumber.json new file mode 100644 index 0000000..d576d43 --- /dev/null +++ b/flood_rust/examples/eth_getBlockByNumber.json @@ -0,0 +1,10 @@ +[ + { + "method": "eth_getBlockByNumber", + "params": [ "0x1b4", true ] + }, + { + "method": "eth_getBlockByNumber", + "params": [ "0x1ba", true ] + } +] \ No newline at end of file diff --git a/flood_rust/examples/eth_getStorageAt.json b/flood_rust/examples/eth_getStorageAt.json new file mode 100644 index 0000000..d334007 --- /dev/null +++ b/flood_rust/examples/eth_getStorageAt.json @@ -0,0 +1,14 @@ +[ + { + "method": "eth_getStorageAt", + "params": [ "0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", "latest" ] + }, + { + "method": "eth_getStorageAt", + "params": [ "0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x1", "latest" ] + }, + { + "method": "eth_getStorageAt", + "params": [ "0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x2", "latest" ] + } +] \ No newline at end of file diff --git a/flood_rust/src/bootstrap.rs b/flood_rust/src/bootstrap.rs new file mode 100644 index 0000000..4ddd812 --- /dev/null +++ b/flood_rust/src/bootstrap.rs @@ -0,0 +1,66 @@ +use hdrhistogram::Histogram; +use rand::distributions::weighted::alias_method::WeightedIndex; +use rand::distributions::Distribution; +use rand::rngs::ThreadRng; +use rand::thread_rng; + +/// Bootstraps a random population sample based on given distribution. +/// See: https://en.wikipedia.org/wiki/Bootstrapping_(statistics) +pub struct Bootstrap { + values: Vec, + index: WeightedIndex, + rng: ThreadRng, +} + +impl Bootstrap { + pub fn new(histogram: &Histogram) -> Bootstrap { + let mut values = Vec::new(); + let mut weights = Vec::new(); + for v in histogram.iter_recorded() { + values.push(histogram.median_equivalent(v.value_iterated_to())); + weights.push(v.count_since_last_iteration()); + } + Bootstrap { + values, + index: WeightedIndex::new(weights).unwrap(), + rng: thread_rng(), + } + } + + pub fn sample(&mut self) -> u64 { + self.values[self.index.sample(&mut self.rng)] + } +} + +#[cfg(test)] +mod test { + use super::*; + use statrs::statistics::Mean; + use statrs::statistics::Statistics; + + #[test] + fn mean() { + let mut h1 = Histogram::::new(3).unwrap(); + for i in 10..100001 { + h1.record(i); + } + //h1.record(100000); + + let mean = h1.mean(); + println!("Mean: {}", mean); + println!("Stderr: {}", h1.stdev() / (h1.len() as f64).sqrt()); + + let mut bootstrap = Bootstrap::new(&h1); + let mut means = Vec::new(); + for j in 0..10 { + let mut h = Vec::with_capacity(100000); + for i in 0..100000 { + h.push(bootstrap.sample() as f64); + } + means.push(h.mean()); + } + + println!("Means mean: {}", means.iter().mean()); + println!("Means stddev: {}", means.iter().std_dev()); + } +} diff --git a/flood_rust/src/config.rs b/flood_rust/src/config.rs new file mode 100644 index 0000000..7fdfc0c --- /dev/null +++ b/flood_rust/src/config.rs @@ -0,0 +1,509 @@ +use std::{fs::File, process::exit}; +use std::io::Read; +use std::num::NonZeroUsize; +use std::path::PathBuf; +use std::str::FromStr; + +use anyhow::anyhow; +use chrono::Utc; +use clap::Parser; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use serde_json::value::Value; + +/// Controls how long the benchmark should run. +/// We can specify either a time-based duration or a number of calls to perform. +/// It is also used for controlling sampling. +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum Interval { + Count(u64), + Time(tokio::time::Duration), + Unbounded, +} + +impl Interval { + pub fn is_not_zero(&self) -> bool { + match self { + Interval::Count(cnt) => *cnt > 0, + Interval::Time(d) => !d.is_zero(), + Interval::Unbounded => false, + } + } + + pub fn is_bounded(&self) -> bool { + !matches!(self, Interval::Unbounded) + } + + pub fn count(&self) -> Option { + if let Interval::Count(c) = self { + Some(*c) + } else { + None + } + } + + pub fn seconds(&self) -> Option { + if let Interval::Time(d) = self { + Some(d.as_secs_f32()) + } else { + None + } + } +} + +/// If the string is a valid integer, it is assumed to be the number of cycles. +/// If the string additionally contains a time unit, e.g. "s" or "secs", it is parsed +/// as time duration. +impl FromStr for Interval { + type Err = String; + + fn from_str(s: &str) -> Result { + if let Ok(i) = s.parse() { + Ok(Interval::Count(i)) + } else if let Ok(d) = parse_duration::parse(s) { + Ok(Interval::Time(d)) + } else { + Err("Required integer number of cycles or time duration".to_string()) + } + } +} + +fn parse_range(input: &str) -> Result, &'static str> { + let parts: Vec<&str> = input.split("..").collect(); + + if parts.len() != 2 { + return Err("Invalid range format. Use START..END or START..=END"); + } + + let inclusive = parts[1].starts_with('='); + let start = i32::from_str_radix(parts[0].trim_start_matches("0x"), 16).unwrap(); + let end = if inclusive { + i32::from_str_radix(&parts[1].trim_start_matches("=0x"), 16).unwrap() + } else { + i32::from_str_radix(parts[1].trim_start_matches("0x"), 16).unwrap() + }; + + if start > end { + return Err("Start value cannot be greater than end value"); + } + + let range: Vec = if inclusive { + (start..=end).map(|x| format!("0x{:02x}", x)).collect() + } else { + (start..end).map(|x| format!("0x{:02x}", x)).collect() + }; + + Ok(range) +} + +fn parse_params(s: &str) -> Result, String> { + Ok(s.split(' ').map(|s| s.to_string()).collect()) +} + +// Taken from cast cli: https://github.com/foundry-rs/foundry/blob/master/crates/cast/bin/cmd/rpc.rs +/// CLI arguments for `cast rpc`. +#[derive(Parser, Clone, Debug, Serialize, Deserialize)] +pub struct RpcCommand { + /// RPC method name + #[arg(required_unless_present = "input")] + method: Option, + + /// RPC parameters + /// + /// Interpreted as JSON: + /// + /// flood rpc eth_getBlockByNumber 0x123 false + /// => {"method": "eth_getBlockByNumber", "params": ["0x123", false] ... } + /// + /// flood rpc eth_getBlockByNumber 0x123 false + #[arg( + required_unless_present = "input", + value_parser(parse_params), + value_delimiter = ',' + )] + pub params: Option>>, + + /// Send raw JSON parameters + /// + /// The first param will be interpreted as a raw JSON array of params. + /// If no params are given, stdin will be used. For example: + /// + /// flood run eth_getBlockByNumber '["0x123", false]' --raw + /// => {"method": "eth_getBlockByNumber", "params": ["0x123", false] ... } + #[clap(long, short = 'j')] + raw: bool, + + // RUN COMMANDS + /// Number of cycles per second to execute. + /// If not given, the benchmark cycles will be executed as fast as possible. + #[clap(short('r'), long, value_name = "COUNT", num_args(0..))] + pub rate: Option>, + + /// Number of cycles or duration of the warmup phase. + #[clap( + short('w'), + long("warmup"), + default_value = "1", + value_name = "TIME | COUNT" + )] + pub warmup_duration: Interval, + + /// Number of cycles or duration of the main benchmark phase. + #[clap( + short('d'), + long("duration"), + default_value = "60s", + value_name = "TIME | COUNT" + )] + pub run_duration: Interval, + + /// Number of worker threads used by the driver. + #[clap(short('t'), long, default_value = "1", value_name = "COUNT")] + pub threads: NonZeroUsize, + + /// Max number of concurrent async requests per thread during the main benchmark phase. + #[clap(short('p'), long, default_value = "128", value_name = "COUNT")] + pub concurrency: NonZeroUsize, + + /// Throughput sampling period, in seconds. + #[clap( + short('s'), + long("sampling"), + default_value = "1s", + value_name = "TIME | COUNT" + )] + pub sampling_interval: Interval, + + /// Label that will be added to the report to help identifying the test + #[clap(long("tag"), number_of_values = 1)] + pub tags: Vec, + + /// Path to JSON input file with JSON-RPC calls + #[clap(short('i'), long)] + #[serde(skip)] + pub input: Option, + + /// Path to an output file or directory where the JSON report should be written to. + #[clap(short('o'), long)] + #[serde(skip)] + pub output: Option, + + /// Path to a report from another earlier run that should be compared to side-by-side + #[clap(short('b'), long, value_name = "PATH")] + pub baseline: Option, + + /// Don't display the progress bar. + #[clap(short, long)] + pub quiet: bool, + + /// Randomize the execution order of specified calls between workload calls + #[clap(long)] + pub random: bool, + + /// Randomly select and execute a single call from a list of calls + #[clap(long)] + pub choose: bool, + + /// Eth Node RPC-URL + #[clap(short('u'), default_value = "localhost", long, num_args(0..))] + pub rpc_url: Vec, + + #[clap(short('e'), long)] + pub exp_ramp: bool, + + /// Seconds since 1970-01-01T00:00:00Z + #[clap(hide = true, long)] + pub timestamp: Option, + + #[clap(skip)] + pub num_req: Option, + + #[clap(skip)] + pub cluster_name: Option, + + #[clap(skip)] + pub chain_id: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +struct JsonRequest { + method: String, + params: serde_json::Value, +} + +impl RpcCommand { + fn parse_rpc_params(params: &Vec, raw: &bool) -> Result { + let params = if *raw { + if params.is_empty() { + serde_json::Deserializer::from_reader(std::io::stdin()) + .into_iter() + .next() + .transpose()? + .ok_or_else(|| anyhow!("Empty JSON parameters"))? + } else { + Self::value_or_string(¶ms.iter().join(" ")) + } + } else { + serde_json::Value::Array( + params + .iter() + .map(|value: &String| Self::value_or_string(&value)) + .collect(), + ) + }; + Ok(params) + } + + fn value_or_string(value: &String) -> Value { + serde_json::from_str(value).unwrap_or(serde_json::Value::String(value.to_string())) + } + + fn parse_file(path: &PathBuf) -> Vec<(String, Value)> { + // Check if the specified file exists and is a .json file + if let Some(extension) = path.extension() { + if extension != "json" { + eprintln!("Error: File is not a .json file"); + std::process::exit(1); + } + } else { + eprintln!("Error: File does not have an extension"); + std::process::exit(1); + } + + // Read the contents of the file + let mut file = match File::open(&path) { + Ok(file) => file, + Err(_) => { + eprintln!("Error: Failed to open the file"); + std::process::exit(1); + } + }; + + let mut contents = String::new(); + + if let Err(_) = file.read_to_string(&mut contents) { + eprintln!("Error: Failed to read the file"); + std::process::exit(1); + } + + // Parse JSON-RPC requests + let json_requests: Vec = match serde_json::from_str(&contents) { + Ok(json_requests) => json_requests, + Err(_) => { + eprintln!("Error: Failed to parse JSON"); + std::process::exit(1); + } + }; + + // Extract method and params from each request + let parsed_requests: Vec<(String, serde_json::Value)> = json_requests + .iter() + .map(|req| (req.method.clone(), req.params.clone())) + .collect(); + + parsed_requests + } + + pub fn parse_params(&self) -> Result, anyhow::Error> { + let RpcCommand { + raw, + method, + params, + input, + .. + } = self; + + let requests = match input { + Some(path) => Self::parse_file(path), + None => { + let params = params.as_ref().unwrap(); + let method = method.as_ref().unwrap(); + let mut has_range = false; + let params = params.iter().fold(Vec::new(), |mut acc, param| { + for (j, token) in param.iter().enumerate() { + if token.contains("..") { + if has_range { eprintln!("Error: Invalid Number of Ranges Specified Removing extra Ranged Param -> Only one range can be specified per parameters list"); exit(1); }; + has_range = true; + let range = parse_range(token).unwrap(); + for val in range { + let mut new_param = param.clone(); + new_param[j] = val.clone(); + acc.push(new_param); + } + } + } + if has_range { + acc + } else { + acc.push(param.clone()); + acc + } + }); + let reqs: Vec<(String, Value)> = params + .iter() + .map(|param| (method.clone(), Self::parse_rpc_params(¶m, raw).unwrap())) + .collect(); + reqs + } + }; + + Ok(requests) + } + + pub fn set_timestamp_if_empty(mut self) -> Self { + if self.timestamp.is_none() { + self.timestamp = Some(Utc::now().timestamp()) + } + self + } + + pub fn set_num_req(mut self, num_req: usize) -> Self { + if self.num_req.is_none() { + if self.choose { + //Choose mode grabs 1 req + self.num_req = Some(1) + } else { + self.num_req = Some(num_req) + } + } + self + } + + pub fn set_rates(mut self, rates: Option>) -> Self { + self.rate = rates; + self + } + + fn exp_ramp(num_req: usize) -> Vec { + let num_values = 6; + let mut log_rates = Vec::with_capacity(num_values); + let start_rate = (10 / num_req) as f64; + let mut rate = start_rate; + while log_rates.len() < log_rates.capacity() { + log_rates.push(rate); + rate *= 10.0; + } + log_rates + } + + /// Parses rate for run + pub fn parse_rate(&self) -> Option> { + let num_req = self.num_req.unwrap(); + if self.exp_ramp { + return Some(Self::exp_ramp(num_req)); + } + // If not set return None + if let Some(rate) = &self.rate { + Some(rate.into_iter().map(|r| r / num_req as f64).collect()) + } else { + None + } + } + + /// Returns benchmark name + pub fn name(&self) -> String { + //TODO: address this mess + self.method + .as_ref() + .unwrap_or(&"default".to_string()) + .clone() + } + + /// Suggested file name where to save the results of the run. + pub fn default_output_file_name(&self, extension: &str) -> PathBuf { + let mut components = vec![self.name()]; + components.extend(self.cluster_name.iter().map(|x| x.replace(' ', "_"))); + components.extend(self.chain_id.iter().cloned()); + components.extend(self.tags.iter().cloned()); + //components.extend(self.rate.map(|r| format!("r{r}"))); + components.push(format!("p{}", self.concurrency)); + components.push(format!("t{}", self.threads)); + components.push(chrono::Local::now().format("%Y%m%d.%H%M%S").to_string()); + PathBuf::from(format!("{}.{extension}", components.join("."))) + } +} + +#[derive(Parser, Debug)] +pub struct ShowCommand { + /// Path to the JSON report file + #[clap(value_name = "PATH")] + pub report: PathBuf, + + /// Optional path to another JSON report file + #[clap(short('b'), long, value_name = "PATH")] + pub baseline: Option, +} + +#[derive(Parser, Debug)] +pub struct HdrCommand { + /// Path to the input JSON report file + #[clap(value_name = "PATH")] + pub report: PathBuf, + + /// Output file; if not given, the hdr log gets printed to stdout + #[clap(short('o'), long, value_name = "PATH")] + pub output: Option, + + /// Optional tag prefix to add to each histogram + #[clap(long, value_name = "STRING")] + pub tag: Option, +} + +#[derive(Parser, Debug)] +pub struct PlotCommand { + /// Path to the input JSON report file(s) + #[clap(value_name = "PATH", required = true)] + pub reports: Vec, + + /// Plot given response time percentiles. Can be used multiple times. + #[clap(short, long("percentile"), number_of_values = 1)] + pub percentiles: Vec, + + /// Plot throughput. + #[clap(short, long("throughput"))] + pub throughput: bool, + + /// Plot success_rate. + #[clap(short, long("success_rate"))] + pub success_rate: bool, + + /// Write output to the given file. + #[clap(short('o'), long, value_name = "PATH")] + pub output: Option, +} + +#[derive(Parser, Debug)] +#[allow(clippy::large_enum_variant)] +pub enum Command { + /// Displays the report(s) of previously executed benchmark(s). + /// + /// Can compare two runs. + Show(ShowCommand), + + /// Exports histograms as a compressed HDR interval log. + /// + /// To be used with HdrHistogram (https://github.com/HdrHistogram/HdrHistogram). + /// Timestamps are given in seconds since Unix epoch. + /// Response times are recorded in nanoseconds. + Hdr(HdrCommand), + + /// Plots recorded samples. Saves output in SVG format. + Plot(PlotCommand), + + /// Runs a benchmark on a single specified JSON-RPC + /// + /// Prints nicely formatted statistics to the standard output. + /// Additionally dumps all data into a JSON report file. + Run(RpcCommand), +} + +#[derive(Parser, Debug)] +#[command( +name = "Ethereum Node Latency and Throughput Tester", +author = "Patrick Stiles ", +version = clap::crate_version ! (), +)] +pub struct AppConfig { + #[clap(subcommand)] + pub command: Command, +} \ No newline at end of file diff --git a/flood_rust/src/context.rs b/flood_rust/src/context.rs new file mode 100644 index 0000000..321249c --- /dev/null +++ b/flood_rust/src/context.rs @@ -0,0 +1,164 @@ +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use alloy_json_rpc::{Request, RpcError, RpcReturn}; +use alloy_primitives::U64; +use alloy_rpc_client::RpcClient; +use alloy_transport_http::Http; +use hdrhistogram::Histogram; +use rune::runtime::{Object, Shared}; +use rune::{Any, Value}; +use serde_json::value::RawValue; +use std::fmt::Debug; +use try_lock::TryLock; + +use crate::FloodError; + +pub struct NodeInfo { + pub chain_id: U64, +} + +#[derive(Clone, Debug)] +pub struct SessionStats { + pub req_count: u64, + pub req_succ_count: u64, + pub req_errors: HashSet, + pub req_error_count: u64, + pub queue_length: u64, + pub mean_queue_length: f32, + pub resp_times_ns: Histogram, +} + +impl SessionStats { + pub fn new() -> SessionStats { + Default::default() + } + + pub fn start_request(&mut self) -> Instant { + if self.req_count > 0 { + self.mean_queue_length += + (self.queue_length as f32 - self.mean_queue_length) / self.req_count as f32; + } + self.queue_length += 1; + Instant::now() + } + + pub fn complete_request(&mut self, duration: Duration, rs: &Result>) + where + E: Debug, + Resp: RpcReturn, + { + self.queue_length -= 1; + let duration_ns = duration.as_nanos().clamp(1, u64::MAX as u128) as u64; + self.resp_times_ns.record(duration_ns).unwrap(); + self.req_count += 1; + match rs { + // If call successful increment row count => call_count + Ok(_) => self.req_succ_count += 1, + Err(e) => { + self.req_error_count += 1; + self.req_errors.insert(format!("{:?}", e)); + } + } + } + + /// Resets all accumulators + pub fn reset(&mut self) { + self.req_error_count = 0; + self.req_succ_count = 0; + self.mean_queue_length = 0.0; + self.req_errors.clear(); + self.resp_times_ns.clear(); + + // note that current queue_length is *not* reset to zero because there + // might be pending requests and if we set it to zero, that would underflow + } +} + +impl Default for SessionStats { + fn default() -> Self { + SessionStats { + req_count: 0, + req_succ_count: 0, + req_errors: HashSet::new(), + req_error_count: 0, + queue_length: 0, + mean_queue_length: 0.0, + resp_times_ns: Histogram::new(3).unwrap(), + } + } +} + +/// This is the main object that a workload script uses to interface with the outside world. +/// It also tracks query execution metrics such as number of requests, rows, response times etc. +#[derive(Any)] +pub struct Context { + pub session: Arc>>, + statements: HashMap>>>, + pub stats: TryLock, + #[rune(get, set, add_assign, copy)] + pub load_cycle_count: u64, + #[rune(get)] + pub data: Value, +} + +// Needed, because Rune `Value` is !Send, as it may contain some internal pointers. +// Therefore it is not safe to pass a `Value` to another thread by cloning it, because +// both objects could accidentally share some unprotected, `!Sync` data. +// To make it safe, the same `Context` is never used by more than one thread at once and +// we make sure in `clone` to make a deep copy of the `data` field by serializing +// and deserializing it, so no pointers could get through. +unsafe impl Send for Context {} +unsafe impl Sync for Context {} + +impl Context { + pub fn new(session: RpcClient>) -> Context { + Context { + session: Arc::new(session), + statements: HashMap::new(), + stats: TryLock::new(SessionStats::new()), + load_cycle_count: 0, + data: Value::Object(Shared::new(Object::new())), + } + } + + /// Clones the context for use by another thread. + /// The new clone gets fresh statistics. + /// The user data gets passed through serialization and deserialization to avoid + /// accidental data sharing. + pub fn clone(&self) -> Result { + let serialized = rmp_serde::to_vec(&self.data)?; + let deserialized: Value = rmp_serde::from_slice(&serialized)?; + Ok(Context { + session: self.session.clone(), + statements: self.statements.clone(), + stats: TryLock::new(SessionStats::default()), + load_cycle_count: self.load_cycle_count, + data: deserialized, + }) + } + + /// Returns node metadata such as node name and node version. + pub async fn node_info(&self) -> Result, RpcError> { + let request = self.session.request("eth_chainId", ()); + let res = request.await; + if let Ok(chain_id) = res { + return Ok(Some(NodeInfo { chain_id })); + } + Ok(None) + } + + /// Returns the current accumulated request stats snapshot and resets the stats. + pub fn take_session_stats(&self) -> SessionStats { + let mut stats = self.stats.try_lock().unwrap(); + let result = stats.clone(); + stats.reset(); + result + } + + /// Resets query and request counters + pub fn reset_session_stats(&self) { + self.stats.try_lock().unwrap().reset(); + } +} diff --git a/flood_rust/src/cycle.rs b/flood_rust/src/cycle.rs new file mode 100644 index 0000000..596bfac --- /dev/null +++ b/flood_rust/src/cycle.rs @@ -0,0 +1,137 @@ +use crate::config; +use crate::config::Interval; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; +use std::time::Instant; + +const BATCH_SIZE: u64 = 64; + +/// Provides distinct benchmark cycle numbers to multiple threads of execution. +/// Cycle numbers increase and never repeat. +pub struct CycleCounter { + shared: Arc, + local: u64, + local_max: u64, +} + +impl CycleCounter { + /// Creates a new cycle counter, starting at `start`. + /// The counter is logically positioned at one item before `start`, so the first call + /// to `next` will return `start`. + pub fn new(start: u64) -> Self { + CycleCounter { + shared: Arc::new(AtomicU64::new(start)), + local: 0, // the value does not matter as long as it is not lower than local_max + local_max: 0, // force getting the shared count in the first call to `next` + } + } + + /// Gets the next cycle number and advances the counter by one. + pub fn next(&mut self) -> u64 { + if self.local >= self.local_max { + self.next_batch(); + } + let result = self.local; + self.local += 1; + result + } + + /// Reserves the next batch of cycles. + fn next_batch(&mut self) { + self.local = self.shared.fetch_add(BATCH_SIZE, Ordering::Relaxed); + self.local_max = self.local + BATCH_SIZE; + } + + /// Creates a new counter sharing the list of cycles with this one. + /// The new counter will never return the same cycle number as this one. + pub fn share(&self) -> CycleCounter { + CycleCounter { + shared: self.shared.clone(), + local: 0, + local_max: 0, + } + } +} + +/// Provides distinct benchmark cycle numbers to multiple threads of execution. +/// Decides when to stop the benchmark execution. +pub struct BoundedCycleCounter { + pub duration: config::Interval, + start_time: Instant, + cycle_counter: CycleCounter, +} + +impl BoundedCycleCounter { + /// Creates a new counter based on configured benchmark duration. + /// For time-based deadline, the clock starts ticking when this object is created. + pub fn new(duration: config::Interval) -> Self { + BoundedCycleCounter { + duration, + start_time: Instant::now(), + cycle_counter: CycleCounter::new(0), + } + } + + /// Returns the next cycle number or `None` if deadline or cycle count was exceeded. + pub fn next(&mut self) -> Option { + match self.duration { + Interval::Count(count) => { + let result = self.cycle_counter.next(); + if result < count { + Some(result) + } else { + None + } + } + Interval::Time(duration) => { + if Instant::now() < self.start_time + duration { + Some(self.cycle_counter.next()) + } else { + None + } + } + Interval::Unbounded => Some(self.cycle_counter.next()), + } + } + + /// Shares this counter e.g. with another thread. + pub fn share(&self) -> Self { + BoundedCycleCounter { + start_time: self.start_time, + duration: self.duration, + cycle_counter: self.cycle_counter.share(), + } + } +} + +#[cfg(test)] +mod test { + use crate::cycle::{CycleCounter, BATCH_SIZE}; + use itertools::Itertools; + use std::collections::BTreeSet; + + #[test] + pub fn cycle_counter_must_return_all_numbers() { + let mut counter = CycleCounter::new(10); + for i in 10..(10 + 2 * BATCH_SIZE) { + let iter = counter.next(); + assert_eq!(i, iter) + } + } + + #[test] + pub fn shared_cycle_counter_must_return_distinct_numbers() { + let mut counter1 = CycleCounter::new(10); + let mut counter2 = counter1.share(); + let mut set1 = BTreeSet::new(); + let mut set2 = BTreeSet::new(); + for _ in 10..(10 + 2 * BATCH_SIZE) { + set1.insert(counter1.next()); + set2.insert(counter2.next()); + } + assert_eq!( + set1.intersection(&set2).cloned().collect_vec(), + Vec::::new() + ) + } +} diff --git a/flood_rust/src/error.rs b/flood_rust/src/error.rs new file mode 100644 index 0000000..096c33a --- /dev/null +++ b/flood_rust/src/error.rs @@ -0,0 +1,48 @@ +use alloy_json_rpc::RpcError; +use alloy_transport::TransportErrorKind; + +use err_derive::*; +use hdrhistogram::serialization::interval_log::IntervalLogWriterError; +use hdrhistogram::serialization::V2DeflateSerializeError; +use std::path::PathBuf; + +#[derive(Debug, Error)] +pub enum FloodError { + #[error(display = "Context data could not be serialized: {}", _0)] + ContextDataEncode(#[source] rmp_serde::encode::Error), + + #[error(display = "Context data could not be deserialized: {}", _0)] + ContextDataDecode(#[source] rmp_serde::decode::Error), + + #[error(display = "env var error: {}", _0)] + EnvVar(#[source] std::env::VarError), + + #[error(display = "Failed to read file {:?}: {}", _0, _1)] + ScriptRead(PathBuf, #[source] std::io::Error), + + #[error(display = "Failed to load script: {}", _0)] + ScriptBuildError(#[source] rune::BuildError), + + #[error(display = "Failed to execute script function {}: {}", _0, _1)] + ScriptExecError(String, rune::runtime::VmError), + + #[error(display = "Function {} returned error: {}", _0, _1)] + FunctionResult(String, String), + + #[error(display = "{}", _0)] + Diagnostics(#[source] rune::diagnostics::EmitError), + + #[error(display = "Failed to create output file {:?}: {}", _0, _1)] + OutputFileCreate(PathBuf, std::io::Error), + + #[error(display = "Error writing HDR log: {}", _0)] + HdrLogWrite(#[source] IntervalLogWriterError), + + #[error(display = "Interrupted")] + Interrupted, + + #[error(display = "Eth error: {}", _0)] + Eth(#[source] RpcError), +} + +pub type Result = std::result::Result; diff --git a/flood_rust/src/exec.rs b/flood_rust/src/exec.rs new file mode 100644 index 0000000..3c6804b --- /dev/null +++ b/flood_rust/src/exec.rs @@ -0,0 +1,226 @@ +//! Implementation of the main benchmarking loop + +use futures::channel::mpsc::{channel, Receiver, Sender}; +use futures::{SinkExt, Stream, StreamExt}; +use itertools::Itertools; +use status_line::StatusLine; +use std::cmp::max; +use std::future::ready; +use std::num::NonZeroUsize; +use std::sync::Arc; +use std::time::Instant; +use tokio_stream::wrappers::IntervalStream; + +use crate::error::Result; +use crate::workload::Workload; +use crate::{ + BenchmarkStats, BoundedCycleCounter, InterruptHandler, Interval, Progress, Recorder, Sampler, + WorkloadStats, +}; + +/// Returns a stream emitting `rate` events per second. +fn interval_stream(rate: f64) -> IntervalStream { + let interval = tokio::time::Duration::from_nanos(max(1, (1000000000.0 / rate) as u64)); + IntervalStream::new(tokio::time::interval(interval)) +} + +/// Runs a stream of workload cycles till completion in the context of the current task. +/// Periodically sends workload statistics to the `out` channel. +/// +/// # Parameters +/// - stream: a stream of cycle numbers; None means the end of the stream +/// - workload: defines the function to call +/// - cycle_counter: shared cycle numbers provider +/// - concurrency: the maximum number of pending workload calls +/// - sampling: controls when to output workload statistics +/// - progress: progress bar notified about each successful cycle +/// - interrupt: allows for terminating the stream early +/// - out: the channel to receive workload statistics +/// +#[allow(clippy::too_many_arguments)] // todo: refactor +async fn run_stream( + stream: impl Stream + std::marker::Unpin, + workload: Workload, + cycle_counter: BoundedCycleCounter, + concurrency: NonZeroUsize, + sampling: Interval, + interrupt: Arc, + progress: Arc>, + mut out: Sender>, +) { + workload.reset(Instant::now()); + + let mut iter_counter = cycle_counter; + let mut sampler = Sampler::new(iter_counter.duration, sampling, &workload, &mut out); + + let mut result_stream = stream + .map(|_| iter_counter.next()) + .take_while(|i| ready(i.is_some())) + // unconstrained to workaround quadratic complexity of buffer_unordered () + .map(|i| tokio::task::unconstrained(workload.run(i.unwrap()))) + .buffer_unordered(concurrency.get()) + .inspect(|_| progress.tick()); + + while let Some(res) = result_stream.next().await { + match res { + Ok((iter, end_time)) => sampler.cycle_completed(iter, end_time).await, + Err(e) => { + out.send(Err(e)).await.unwrap(); + return; + } + } + if interrupt.is_interrupted() { + break; + } + } + // Send the statistics of remaining requests + sampler.finish().await; +} + +/// Launches a new worker task that runs a series of invocations of the workload function. +/// +/// The task will run as long as `deadline` produces new cycle numbers. +/// The task updates the `progress` bar after each successful cycle. +/// +/// Returns a stream where workload statistics are published. +fn spawn_stream( + concurrency: NonZeroUsize, + rate: Option, + sampling: Interval, + workload: Workload, + iter_counter: BoundedCycleCounter, + interrupt: Arc, + progress: Arc>, +) -> Receiver> { + let (tx, rx) = channel(1); + + tokio::spawn(async move { + match rate { + Some(rate) => { + let stream = interval_stream(rate); + run_stream( + stream, + workload, + iter_counter, + concurrency, + sampling, + interrupt, + progress, + tx, + ) + .await + } + None => { + let stream = futures::stream::repeat_with(|| ()); + run_stream( + stream, + workload, + iter_counter, + concurrency, + sampling, + interrupt, + progress, + tx, + ) + .await + } + } + }); + rx +} + +/// Receives one item from each of the streams. +/// Streams that are closed are ignored. +async fn receive_one_of_each(streams: &mut [S]) -> Vec +where + S: Stream + Unpin, +{ + let mut items = Vec::with_capacity(streams.len()); + for s in streams { + if let Some(item) = s.next().await { + items.push(item); + } + } + items +} + +/// Controls the intensity of requests sent to the server +pub struct ExecutionOptions { + /// How long to execute + pub duration: Interval, + /// Maximum rate of requests in requests per second, `None` means no limit + pub rate: Option, + /// Number of parallel threads of execution + pub threads: NonZeroUsize, + /// Number of outstanding async requests per each thread + pub concurrency: NonZeroUsize, +} + +/// Executes the given function many times in parallel. +/// Draws a progress bar. +/// Returns the statistics such as throughput or duration histogram. +/// +/// # Parameters +/// - `name`: text displayed next to the progress bar +/// - `count`: number of cycles +/// - `exec_options`: controls execution options such as parallelism level and rate +/// - `workload`: encapsulates a set of queries to execute +pub async fn par_execute( + name: &str, + exec_options: &ExecutionOptions, + sampling: Interval, + workload: Workload, + signals: Arc, + show_progress: bool, +) -> Result { + let thread_count = exec_options.threads.get(); + let concurrency = exec_options.concurrency; + //TODO change to list of rates + let rate = exec_options.rate; + let progress = match exec_options.duration { + Interval::Count(count) => Progress::with_count(name.to_string(), count), + Interval::Time(duration) => Progress::with_duration(name.to_string(), duration), + Interval::Unbounded => unreachable!(), + }; + let progress_opts = status_line::Options { + initially_visible: show_progress, + ..Default::default() + }; + let progress = Arc::new(StatusLine::with_options(progress, progress_opts)); + let deadline = BoundedCycleCounter::new(exec_options.duration); + let mut streams = Vec::with_capacity(thread_count); + let mut stats = Recorder::start(rate, concurrency); + + for _ in 0..thread_count { + let s = spawn_stream( + concurrency, + rate.map(|r| r / (thread_count as f64)), + sampling, + workload.clone()?, + deadline.share(), + signals.clone(), + progress.clone(), + ); + streams.push(s); + } + + loop { + let partial_stats: Vec<_> = receive_one_of_each(&mut streams) + .await + .into_iter() + .try_collect()?; + + if partial_stats.is_empty() { + break; + } + + let aggregate = stats.record(&partial_stats); + if sampling.is_bounded() { + progress.set_visible(false); + println!("{aggregate}"); + progress.set_visible(show_progress); + } + } + + Ok(stats.finish()) +} diff --git a/flood_rust/src/histogram.rs b/flood_rust/src/histogram.rs new file mode 100644 index 0000000..ba90630 --- /dev/null +++ b/flood_rust/src/histogram.rs @@ -0,0 +1,61 @@ +use base64::{engine::general_purpose as base64_engine, Engine as _}; +use std::fmt; +use std::io::Cursor; + +use hdrhistogram::serialization::{Serializer, V2DeflateSerializer}; +use hdrhistogram::Histogram; +use serde::de::{Error, Visitor}; +use serde::{Deserialize, Deserializer, Serialize}; + +/// A wrapper for HDR histogram that allows us to serialize/deserialize it to/from +/// a base64 encoded string we can store in JSON report. +pub struct SerializableHistogram(pub Histogram); + +impl Serialize for SerializableHistogram { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut serialized_histogram = Vec::new(); + V2DeflateSerializer::new() + .serialize(&self.0, &mut serialized_histogram) + .unwrap(); + let encoded = base64_engine::STANDARD.encode(serialized_histogram); + serializer.serialize_str(encoded.as_str()) + } +} + +struct HistogramVisitor; + +impl<'de> Visitor<'de> for HistogramVisitor { + type Value = SerializableHistogram; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a compressed HDR histogram encoded as base64 string") + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + let decoded = base64_engine::STANDARD + .decode(v) + .map_err(|e| E::custom(format!("Not a valid base64 value. {e}")))?; + let mut cursor = Cursor::new(&decoded); + let mut deserializer = hdrhistogram::serialization::Deserializer::new(); + Ok(SerializableHistogram( + deserializer + .deserialize(&mut cursor) + .map_err(|e| E::custom(e))?, + )) + } +} + +impl<'de> Deserialize<'de> for SerializableHistogram { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(HistogramVisitor) + } +} diff --git a/flood_rust/src/interrupt.rs b/flood_rust/src/interrupt.rs new file mode 100644 index 0000000..9ce27dd --- /dev/null +++ b/flood_rust/src/interrupt.rs @@ -0,0 +1,21 @@ +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +/// Notifies about received Ctrl-C signal +pub struct InterruptHandler { + interrupted: Arc, +} + +impl InterruptHandler { + pub fn install() -> InterruptHandler { + let cell = Arc::new(AtomicBool::new(false)); + let cell_ref = cell.clone(); + let _ = ctrlc::set_handler(move || cell_ref.store(true, Ordering::Relaxed)); + InterruptHandler { interrupted: cell } + } + + /// Returns true if Ctrl-C was pressed + pub fn is_interrupted(&self) -> bool { + self.interrupted.load(Ordering::Relaxed) + } +} diff --git a/flood_rust/src/main.rs b/flood_rust/src/main.rs new file mode 100644 index 0000000..c0aacd0 --- /dev/null +++ b/flood_rust/src/main.rs @@ -0,0 +1,315 @@ +use std::fs::File; +use std::io::{stdout, Write}; +use std::path::Path; +use std::process::exit; +use std::sync::Arc; +use std::time::Duration; + +use alloy_rpc_client::ClientBuilder; +use clap::Parser; +use hdrhistogram::serialization::interval_log::Tag; +use hdrhistogram::serialization::{interval_log, V2DeflateSerializer}; +use tokio::runtime::{Builder, Runtime}; + +use config::RpcCommand; + +use crate::config::{AppConfig, Command, HdrCommand, Interval, ShowCommand}; +use crate::context::Context; +use crate::context::*; +use crate::cycle::BoundedCycleCounter; +use crate::error::{FloodError, Result}; +use crate::exec::{par_execute, ExecutionOptions}; +use crate::interrupt::InterruptHandler; +use crate::plot::plot_graph; +use crate::progress::Progress; +use crate::report::{Report, RpcConfigCmp}; +use crate::sampler::Sampler; +use crate::stats::{BenchmarkCmp, BenchmarkStats, Recorder}; +use crate::workload::{Workload, WorkloadStats}; + +mod config; +mod context; +mod cycle; +mod error; +mod exec; +mod histogram; +mod interrupt; +mod plot; +mod progress; +mod report; +mod sampler; +mod stats; +mod workload; + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[global_allocator] +static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; + +fn load_report_or_abort(path: &Path) -> Report { + match Report::load(path) { + Ok(r) => r, + Err(e) => { + eprintln!( + "error: Failed to read report from {}: {}", + path.display(), + e + ); + exit(1) + } + } +} + +/// Connects to the eth node and returns the session +async fn connect(urls: &Vec) -> Result<(Vec, Vec>)> { + let urls = if *urls == vec!["localhost".to_string()] { + match std::env::var("HTTP_PROVIDER_URL").map_err(FloodError::EnvVar) { + Ok(url) => vec![url], + Err(_) => vec!["http://127.0.0.1:8545".to_string()], + } + } else { + urls.clone() + }; + // First have it connect to env var then to localhost + + let mut sessions = Vec::new(); + let mut cluster_infos = Vec::new(); + for url in urls { + eprintln!("info: Connecting to {:?}... ", url); + let client = ClientBuilder::default().reqwest_http(url.parse().unwrap()); + let session = Context::new(client); + let cluster_info = session.node_info().await?; + eprintln!( + "info: Connected to chain {}", + cluster_info + .as_ref() + .map(|c| c.chain_id.to_string()) + .unwrap_or("unknown".to_string()), + ); + sessions.push(session); + cluster_infos.push(cluster_info); + } + Ok((sessions, cluster_infos)) +} + +async fn rpc(conf: RpcCommand) -> Result<()> { + let conf = conf.set_timestamp_if_empty(); + let compare = conf.baseline.as_ref().map(|p| load_report_or_abort(p)); + let reqs = conf.parse_params().unwrap(); + let conf = conf.set_num_req(reqs.len()); + let rates = conf.parse_rate(); + let mut conf = conf.set_rates(rates.clone()); + + let (sessions, node_info) = connect(&conf.rpc_url).await?; + + let interrupt = Arc::new(InterruptHandler::install()); + for (session, node_info) in sessions.iter().zip(node_info.iter()) { + if let Some(node_info) = node_info { + conf.cluster_name = Some(node_info.chain_id.to_string()); + } + + // Build requests for particular session + let requests = reqs + .clone() + .into_iter() + .map(|(method, param)| session.session.make_request(method, param).box_params()) + .collect::>(); + + let runner = Workload::new(session.clone()?, requests, &conf); + if conf.warmup_duration.is_not_zero() { + eprintln!("info: Warming up..."); + let warmup_options = ExecutionOptions { + duration: conf.warmup_duration, + rate: None, + threads: conf.threads, + concurrency: conf.concurrency, + }; + par_execute( + "Warming up...", + &warmup_options, + Interval::Unbounded, + runner.clone()?, + interrupt.clone(), + !conf.quiet, + ) + .await?; + } + + if interrupt.is_interrupted() { + return Err(FloodError::Interrupted); + } + + eprintln!("info: Running benchmark..."); + + println!( + "{}", + RpcConfigCmp { + v1: &conf, + v2: compare.as_ref().map(|c| &c.conf), + } + ); + + if let Some(rates) = rates.clone() { + for rate in rates { + run_bench(&conf, &runner, &interrupt, &compare, Some(rate)).await?; + } + } else { + run_bench(&conf, &runner, &interrupt, &compare, None).await?; + } + } + Ok(()) +} + +async fn run_bench( + conf: &RpcCommand, + runner: &Workload, + interrupt: &Arc, + compare: &Option, + rate: Option, +) -> Result<()> { + let exec_options = ExecutionOptions { + duration: conf.run_duration.clone(), + concurrency: conf.concurrency.clone(), + rate, + threads: conf.threads.clone(), + }; + + report::print_log_header(); + let stats = par_execute( + "Running...", + &exec_options, + conf.sampling_interval.clone(), + runner.clone()?, + interrupt.clone(), + !conf.quiet.clone(), + ) + .await?; + + let stats_cmp = BenchmarkCmp { + v1: &stats, + v2: compare.as_ref().map(|c| &c.result), + }; + println!(); + println!("{}", &stats_cmp); + + let path = conf + .output + .clone() + .unwrap_or_else(|| conf.default_output_file_name("json")); + + let report = Report::new(conf.clone(), stats); + match report.save(&path) { + Ok(()) => { + eprintln!("info: Saved report to {}", path.display()); + } + Err(e) => { + eprintln!("error: Failed to save report to {}: {}", path.display(), e); + exit(1); + } + } + Ok(()) +} + +async fn show(conf: ShowCommand) -> Result<()> { + let report1 = load_report_or_abort(&conf.report); + let report2 = conf.baseline.map(|p| load_report_or_abort(&p)); + + let config_cmp = RpcConfigCmp { + v1: &report1.conf, + v2: report2.as_ref().map(|r| &r.conf), + }; + println!("{config_cmp}"); + + let results_cmp = BenchmarkCmp { + v1: &report1.result, + v2: report2.as_ref().map(|r| &r.result), + }; + println!("{results_cmp}"); + Ok(()) +} + +/// Reads histograms from the report and dumps them to an hdr log +async fn export_hdr_log(conf: HdrCommand) -> Result<()> { + let tag_prefix = conf.tag.map(|t| t + ".").unwrap_or_default(); + if tag_prefix.chars().any(|c| ", \n\t".contains(c)) { + eprintln!("error: Hdr histogram tags are not allowed to contain commas nor whitespace."); + exit(255); + } + + let report = load_report_or_abort(&conf.report); + let stdout = stdout(); + let output_file: File; + let stdout_stream; + let mut out: Box = match conf.output { + Some(path) => { + output_file = File::create(&path).map_err(|e| FloodError::OutputFileCreate(path, e))?; + Box::new(output_file) + } + None => { + stdout_stream = stdout.lock(); + Box::new(stdout_stream) + } + }; + + let mut serializer = V2DeflateSerializer::new(); + let mut log_writer = interval_log::IntervalLogWriterBuilder::new() + .add_comment(format!("[Logged with Flood {VERSION}]").as_str()) + .with_start_time(report.result.start_time.into()) + .with_base_time(report.result.start_time.into()) + .with_max_value_divisor(1000000.0) // ms + .begin_log_with(&mut out, &mut serializer) + .unwrap(); + + for sample in &report.result.log { + let interval_start_time = Duration::from_millis((sample.time_s * 1000.0) as u64); + let interval_duration = Duration::from_millis((sample.duration_s * 1000.0) as u64); + log_writer.write_histogram( + &sample.cycle_time_histogram_ns.0, + interval_start_time, + interval_duration, + Tag::new(format!("{tag_prefix}cycles").as_str()), + )?; + log_writer.write_histogram( + &sample.resp_time_histogram_ns.0, + interval_start_time, + interval_duration, + Tag::new(format!("{tag_prefix}requests").as_str()), + )?; + } + Ok(()) +} + +async fn async_main(command: Command) -> Result<()> { + match command { + Command::Run(config) => rpc(config).await?, + Command::Show(config) => show(config).await?, + Command::Hdr(config) => export_hdr_log(config).await?, + Command::Plot(config) => plot_graph(config).await?, + } + Ok(()) +} + +fn init_runtime(thread_count: usize) -> std::io::Result { + if thread_count == 1 { + Builder::new_current_thread().enable_all().build() + } else { + Builder::new_multi_thread() + .worker_threads(thread_count) + .enable_all() + .build() + } +} + +fn main() { + tracing_subscriber::fmt::init(); + let command = AppConfig::parse().command; + let thread_count = match &command { + Command::Run(cmd) => cmd.threads.get(), + _ => 1, + }; + let runtime = init_runtime(thread_count); + if let Err(e) = runtime.unwrap().block_on(async_main(command)) { + eprintln!("error: {e}"); + exit(128); + } +} diff --git a/flood_rust/src/plot.rs b/flood_rust/src/plot.rs new file mode 100644 index 0000000..ac1bee2 --- /dev/null +++ b/flood_rust/src/plot.rs @@ -0,0 +1,340 @@ +use crate::config::PlotCommand; +use crate::load_report_or_abort; +use crate::plot::SeriesKind::{ResponseTime, Throughput, SuccessRate, SuccessRateVsThroughput}; +use crate::report::Report; +use crate::Result; +use itertools::Itertools; +use plotters::coord::ranged1d::{DefaultFormatting, KeyPointHint}; +use plotters::coord::types::RangedCoordf32; +use plotters::prelude::full_palette::ORANGE; +use plotters::prelude::*; +use plotters_svg::SVGBackend; +use std::collections::BTreeSet; +use std::ops::Range; +use std::process::exit; + +#[derive(Eq, PartialEq, Copy, Clone, Debug, Ord, PartialOrd)] +enum SeriesKind { + ResponseTime, + Throughput, + SuccessRate, + SuccessRateVsThroughput +} + +impl SeriesKind { + pub fn y_axis_label(&self) -> &str { + match self { + ResponseTime => "response time [ms]", + Throughput => "throughput [req/s]", + SuccessRate => "success rate [%]", + SuccessRateVsThroughput => "success rate [%]", + } + } +} + +struct Series { + tags: Vec, + label: String, + color_index: usize, + symbol_index: usize, + kind: SeriesKind, + data: Vec<(f32, f32)>, +} + +impl Series { + fn full_label(&self) -> String { + format!("{} [{}]", self.label, self.tags.join(", ")) + } +} + +enum YSpec { + Linear(RangedCoordf32), + Log(LogCoord), +} + +impl Ranged for YSpec { + type FormatOption = DefaultFormatting; + type ValueType = f32; + + fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { + match self { + YSpec::Linear(range) => range.map(value, limit), + YSpec::Log(range) => range.map(value, limit), + } + } + + fn key_points(&self, hint: Hint) -> Vec { + match self { + YSpec::Linear(range) => range.key_points(hint), + YSpec::Log(range) => range.key_points(hint), + } + } + + fn range(&self) -> Range { + match self { + YSpec::Linear(range) => range.range(), + YSpec::Log(range) => range.range(), + } + } +} + +impl Series { + pub fn max_value(&self, default: f32) -> f32 { + self.data + .iter() + .map(|p| p.1) + .reduce(f32::max) + .unwrap_or(default) + } + + pub fn min_value(&self, default: f32) -> f32 { + self.data + .iter() + .map(|p| p.1) + .reduce(f32::min) + .unwrap_or(default) + } + + pub fn max_time(&self) -> f32 { + self.data.last().map(|s| s.0).unwrap_or_default() + } +} + +/// Dumps benchmark runs into an SVG file +pub async fn plot_graph(conf: PlotCommand) -> Result<()> { + let reports = conf + .reports + .iter() + .map(|r| load_report_or_abort(r)) + .collect_vec(); + assert!(!reports.is_empty()); + + let data = data(&reports, &conf); + let scales: BTreeSet = data.iter().map(|s| s.kind).collect(); + let scales = scales.into_iter().collect_vec(); + + let max_time = data + .iter() + .map(Series::max_time) + .reduce(f32::max) + .unwrap_or(1.0); + + let min_value = data + .iter() + .map(|s| Series::min_value(s, 1.0)) + .reduce(f32::min) + .unwrap_or(1.0); + + let max_value = data + .iter() + .map(|s| Series::max_value(s, 1.0)) + .reduce(f32::max) + .unwrap_or(1.0); + + let primary_y_spec: YSpec = match scales.as_slice() { + [ResponseTime] => YSpec::Log((min_value..max_value).log_scale().into()), + [Throughput] => YSpec::Linear((0f32..max_value).into()), + [SuccessRate] => YSpec::Linear((0f32..max_value).into()), + [SuccessRateVsThroughput] => YSpec::Linear((0f32..max_value).into()), + [] => { + eprintln!("error: No data series selected. Add --throughput or --percentile options."); + exit(1); + } + _ => { + eprintln!( + "error: Plotting throughput and response times in one graph is not supported." + ); + exit(1); + } + }; + + let output_path = conf + .output + .unwrap_or(reports[0].conf.default_output_file_name("svg")); + let root = SVGBackend::new(&output_path, (2000, 1000)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + + let mut chart = ChartBuilder::on(&root) + .margin(40) + .x_label_area_size(60) + .y_label_area_size(150) + .build_cartesian_2d(0f32..max_time, primary_y_spec) + .unwrap(); + + if conf.success_rate && conf.throughput { + chart + .configure_mesh() + .axis_desc_style(("sans-serif", 32).into_font()) + .x_desc("throughput [req/s]") + .y_desc(scales[0].y_axis_label()) + .x_label_style(("sans-serif", 24).into_font()) + .y_label_style(("sans-serif", 24).into_font()) + .draw() + .unwrap(); + } else { + chart + .configure_mesh() + .axis_desc_style(("sans-serif", 32).into_font()) + .x_desc("time [s]") + .y_desc(scales[0].y_axis_label()) + .x_label_style(("sans-serif", 24).into_font()) + .y_label_style(("sans-serif", 24).into_font()) + .draw() + .unwrap(); + } + + let colors = [&RED, &BLUE, &GREEN, &ORANGE, &MAGENTA, &BLACK]; + const SYMBOL_SIZE: u32 = 6; + + for series in data { + let color = colors[series.color_index]; + chart + .draw_series(LineSeries::new( + series.data.iter().cloned(), + color.stroke_width(3), + )) + .unwrap(); + let points = series.data.iter(); + match series.symbol_index { + 0 => { + chart + .draw_series(points.map(|point| Circle::new(*point, SYMBOL_SIZE, color))) + .unwrap() + .label(series.full_label()) + .legend(|point| Circle::new(point, SYMBOL_SIZE, *color)); + } + 1 => { + chart + .draw_series( + points.map(|point| TriangleMarker::new(*point, SYMBOL_SIZE, color)), + ) + .unwrap() + .label(series.full_label()) + .legend(|point| TriangleMarker::new(point, SYMBOL_SIZE, *color)); + } + 2 => { + chart + .draw_series(points.map(|point| Cross::new(*point, SYMBOL_SIZE, color))) + .unwrap() + .label(series.full_label()) + .legend(|point| Cross::new(point, SYMBOL_SIZE, *color)); + } + _ => {} + }; + } + + chart + .configure_series_labels() + .label_font(("sans-serif", 24).into_font()) + .margin(20) + .legend_area_size(20) + .border_style(BLACK) + .background_style(WHITE.mix(0.7)) + .position(SeriesLabelPosition::UpperRight) + .draw() + .unwrap(); + + eprintln!("Saved output image to: {}", output_path.display()); + Ok(()) +} + +fn data(reports: &[Report], conf: &PlotCommand) -> Vec { + let mut series = vec![]; + for (color_index, report) in reports.iter().enumerate() { + series.extend(report_series(report, color_index, conf)); + } + series +} + +/// Generates data from given report +fn report_series(report: &Report, color_index: usize, conf: &PlotCommand) -> Vec { + let mut series = vec![]; + let mut percentiles = conf.percentiles.clone(); + percentiles.sort_by(|a, b| a.partial_cmp(b).unwrap().reverse()); + + series.extend(resp_time_series(report, color_index, &percentiles)); + if conf.success_rate && conf.throughput { + series.push(success_rate_v_throughput_series(report, color_index)) + } else { + if conf.throughput { + series.push(throughput_series(report, color_index)) + } + if conf.success_rate { + series.push(success_rate_series(report, color_index)) + } + } + series +} + +fn resp_time_series(report: &Report, color_index: usize, percentiles: &[f64]) -> Vec { + let mut series = percentiles + .iter() + .enumerate() + .map(|(i, p)| Series { + tags: report.conf.tags.clone(), + label: format!("P{p}"), + color_index, + symbol_index: i, + kind: ResponseTime, + data: Vec::with_capacity(report.result.log.len()), + }) + .collect_vec(); + + for s in &report.result.log { + for (i, p) in percentiles.iter().enumerate() { + let time = s.time_s; + let resp_time_ms = + s.resp_time_histogram_ns.0.value_at_percentile(*p) as f32 / 1_000_000.0; + series[i].data.push((time, resp_time_ms)); + } + } + series +} + +fn throughput_series(report: &Report, color_index: usize) -> Series { + Series { + tags: report.conf.tags.clone(), + label: String::from("throughput"), + color_index, + symbol_index: 0, + kind: Throughput, + data: report + .result + .log + .iter() + .map(|s| (s.time_s, s.req_throughput)) + .collect(), + } +} + +fn success_rate_series(report: &Report, color_index: usize) -> Series { + Series { + tags: report.conf.tags.clone(), + label: String::from("success_rate"), + color_index, + symbol_index: 0, + kind: SuccessRate, + data: report + .result + .log + .iter() + .map(|s| (s.time_s, ((s.request_count - s.error_count) / s.request_count) as f32)) + .collect(), + } +} + +fn success_rate_v_throughput_series(report: &Report, color_index: usize) -> Series { + Series { + tags: report.conf.tags.clone(), + label: String::from("throughput_vs_success_rate"), + color_index, + symbol_index: 2, + kind: SuccessRateVsThroughput, + data: report + .result + .log + .iter() + .map(|s| (s.req_throughput, ((s.request_count - s.error_count) / s.request_count) as f32)) + .collect(), + } +} \ No newline at end of file diff --git a/flood_rust/src/progress.rs b/flood_rust/src/progress.rs new file mode 100644 index 0000000..972aeb2 --- /dev/null +++ b/flood_rust/src/progress.rs @@ -0,0 +1,98 @@ +use console::style; +use hytra::TrAdder; +use std::cmp::min; +use std::fmt::{Display, Formatter}; + +use tokio::time::{Duration, Instant}; + +enum ProgressBound { + Duration(Duration), + Count(u64), +} + +pub struct Progress { + start_time: Instant, + bound: ProgressBound, + pos: TrAdder, + msg: String, +} + +impl Progress { + pub fn with_duration(msg: String, max_time: Duration) -> Progress { + Progress { + start_time: Instant::now(), + bound: ProgressBound::Duration(max_time), + pos: TrAdder::new(), + msg, + } + } + + pub fn with_count(msg: String, count: u64) -> Progress { + Progress { + start_time: Instant::now(), + bound: ProgressBound::Count(count), + pos: TrAdder::new(), + msg, + } + } + + pub fn tick(&self) { + self.pos.inc(1); + } + + /// Returns progress bar as string `[====> ]` + fn bar(fill_len: usize, total_len: usize) -> String { + let fill_len = min(fill_len, total_len); + format!( + "[{}{}]", + "▪".repeat(fill_len), + " ".repeat(total_len - fill_len) + ) + } +} + +impl Display for Progress { + #[allow(clippy::format_in_format_args)] + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + const WIDTH: usize = 60; + let pos = self.pos.get(); + let text = match self.bound { + ProgressBound::Count(count) => { + let pos = min(count, pos); + let ratio = pos as f32 / count as f32; + let fill = (WIDTH as f32 * ratio) as usize; + format!( + "{} {:>5.1}% {:>28}", + Self::bar(fill, WIDTH), + 100.0 * ratio, + format!("{pos}/{count}") + ) + } + ProgressBound::Duration(duration) => { + let elapsed_secs = (Instant::now() - self.start_time).as_secs_f32(); + let duration_secs = duration.as_secs_f32(); + let ratio = 1.0_f32.min(elapsed_secs / duration_secs); + let fill = (WIDTH as f32 * ratio) as usize; + format!( + "{} {:>5.1}% {:>20} {:>12}", + Self::bar(fill, WIDTH), + 100.0 * ratio, + format!("{elapsed_secs:.1}/{duration_secs:.0}s"), + pos + ) + } + }; + + write!( + f, + "{:21}{}", + style(&self.msg) + .white() + .bright() + .bold() + .on_color256(59) + .for_stderr(), + style(text).white().bright().on_color256(59).for_stderr() + ) + } +} diff --git a/flood_rust/src/report.rs b/flood_rust/src/report.rs new file mode 100644 index 0000000..c902b8d --- /dev/null +++ b/flood_rust/src/report.rs @@ -0,0 +1,689 @@ +use core::fmt; +use std::fmt::{Display, Formatter}; +use std::num::NonZeroUsize; +use std::path::Path; +use std::{fs, io}; + +use chrono::{Local, NaiveDateTime, TimeZone}; +use console::{pad_str, style, Alignment}; +use err_derive::*; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use statrs::statistics::Statistics; +use strum::IntoEnumIterator; + +use crate::config::RpcCommand; +use crate::stats::{ + BenchmarkCmp, BenchmarkStats, Bucket, Mean, Percentile, Sample, Significance, TimeDistribution, +}; + +/// A standard error is multiplied by this factor to get the error margin. +/// For a normally distributed random variable, +/// this should give us 0.999 confidence the expected value is within the (result +- error) range. +const ERR_MARGIN: f64 = 3.29; + +#[derive(Debug, Error)] +pub enum ReportLoadError { + #[error(display = "{}", _0)] + IO(#[source] io::Error), + #[error(display = "{}", _0)] + Deserialize(#[source] serde_json::Error), +} + +/// Keeps all data we want to save in a report: +/// run metadata, configuration and results +#[derive(Serialize, Deserialize)] +pub struct Report { + pub conf: RpcCommand, + pub percentiles: Vec, + pub result: BenchmarkStats, +} + +impl Report { + /// Creates a new report from given configuration and results + pub fn new(conf: RpcCommand, result: BenchmarkStats) -> Report { + let percentiles: Vec = Percentile::iter().map(|p| p.value() as f32).collect(); + Report { + conf, + percentiles, + result, + } + } + /// Loads benchmark results from a JSON file + pub fn load(path: &Path) -> Result { + let file = fs::File::open(path)?; + let report = serde_json::from_reader(file)?; + Ok(report) + } + + /// Saves benchmark results to a JSON file + pub fn save(&self, path: &Path) -> io::Result<()> { + let f = fs::File::create(path)?; + serde_json::to_writer_pretty(f, &self)?; + Ok(()) + } +} + +/// A displayable, optional value with an optional error. +/// Controls formatting options such as precision. +/// Thanks to this wrapper we can format all numeric values in a consistent way. +pub struct Quantity { + pub value: Option, + pub error: Option, + pub precision: usize, +} + +impl Quantity { + pub fn new(value: Option) -> Quantity { + Quantity { + value, + error: None, + precision: 0, + } + } + + pub fn with_precision(mut self, precision: usize) -> Self { + self.precision = precision; + self + } + + pub fn with_error(mut self, e: Option) -> Self { + self.error = e; + self + } +} + +impl Quantity { + fn format_error(&self) -> String { + match &self.error { + None => "".to_owned(), + Some(e) => format!("± {:<6.prec$}", e, prec = self.precision), + } + } +} + +impl From for Quantity { + fn from(value: T) -> Self { + Quantity::new(Some(value)) + } +} + +impl From> for Quantity { + fn from(value: Option) -> Self { + Quantity::new(value) + } +} + +impl From for Quantity { + fn from(m: Mean) -> Self { + Quantity::new(Some(m.value)).with_error(m.std_err.map(|e| e * ERR_MARGIN)) + } +} + +impl From> for Quantity { + fn from(m: Option) -> Self { + Quantity::new(m.map(|mean| mean.value)) + .with_error(m.and_then(|mean| mean.std_err.map(|e| e * ERR_MARGIN))) + } +} + +impl From<&TimeDistribution> for Quantity { + fn from(td: &TimeDistribution) -> Self { + Quantity::from(td.mean) + } +} + +impl From<&Option> for Quantity { + fn from(td: &Option) -> Self { + Quantity::from(td.as_ref().map(|td| td.mean)) + } +} + +impl Display for Quantity { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match &self.value { + None => write!(f, "{}", " ".repeat(18)), + Some(v) => write!( + f, + "{value:9.prec$} {error:8}", + value = style(v).bright().for_stdout(), + prec = self.precision, + error = style(self.format_error()).dim().for_stdout(), + ), + } + } +} + +/// Wrapper for displaying an optional value. +/// If value is `Some`, displays the original value. +/// If value is `None`, displays nothing (empty string). +struct OptionDisplay(Option); + +impl Display for OptionDisplay { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match &self.0 { + None => write!(f, ""), + Some(v) => write!(f, "{v}"), + } + } +} + +trait Rational { + fn ratio(a: Self, b: Self) -> Option; +} + +impl Rational for f32 { + fn ratio(a: Self, b: Self) -> Option { + Some(a / b) + } +} + +impl Rational for f64 { + fn ratio(a: Self, b: Self) -> Option { + Some((a / b) as f32) + } +} + +impl Rational for u64 { + fn ratio(a: Self, b: Self) -> Option { + Some(a as f32 / b as f32) + } +} + +impl Rational for i64 { + fn ratio(a: Self, b: Self) -> Option { + Some(a as f32 / b as f32) + } +} + +impl Rational for usize { + fn ratio(a: Self, b: Self) -> Option { + Some(a as f32 / b as f32) + } +} + +impl Rational for NonZeroUsize { + fn ratio(a: Self, b: Self) -> Option { + Some(a.get() as f32 / b.get() as f32) + } +} + +impl Rational for OptionDisplay { + fn ratio(a: Self, b: Self) -> Option { + a.0.and_then(|a| b.0.and_then(|b| Rational::ratio(a, b))) + } +} + +impl Rational for Quantity { + fn ratio(a: Self, b: Self) -> Option { + a.value + .and_then(|a| b.value.and_then(|b| Rational::ratio(a, b))) + } +} + +impl Rational for String { + fn ratio(_a: Self, _b: Self) -> Option { + None + } +} + +impl Rational for &str { + fn ratio(_a: Self, _b: Self) -> Option { + None + } +} + +impl Display for Significance { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let levels = [0.000001, 0.00001, 0.0001, 0.001, 0.01]; + let stars = "*".repeat(levels.iter().filter(|&&l| l > self.0).count()); + let s = format!("{:7.5} {:5}", self.0, stars); + if self.0 <= 0.01 { + write!(f, "{}", style(s).cyan().bright()) + } else { + write!(f, "{}", style(s).dim()) + } + } +} +/// A single line of text report +struct Line +where + M: Display + Rational, + F: Fn(V) -> M, +{ + /// Text label + pub label: String, + /// Unit of measurement + pub unit: String, + /// 1 means the more of the quantity the better, -1 means the more of it the worse, 0 is neutral + pub orientation: i8, + /// First object to measure + pub v1: V, + /// Second object to measure + pub v2: Option, + /// Statistical significance level + pub significance: Option, + /// Measurement function + pub f: F, +} + +impl Line +where + M: Display + Rational, + V: Copy, + F: Fn(V) -> M, +{ + fn new(label: String, unit: String, orientation: i8, v1: V, v2: Option, f: F) -> Self { + Line { + label, + unit, + orientation, + v1, + v2, + significance: None, + f, + } + } + + fn into_box(self) -> Box { + Box::new(self) + } + + fn with_orientation(mut self, orientation: i8) -> Self { + self.orientation = orientation; + self + } + + fn with_significance(mut self, s: Option) -> Self { + self.significance = s; + self + } + + /// Measures the object `v` by applying `f` to it and formats the measurement result. + /// If the object is None, returns an empty string. + fn fmt_measurement(&self, v: Option) -> String { + v.map(|v| format!("{}", (self.f)(v))) + .unwrap_or_else(|| "".to_owned()) + } + + /// Computes the relative difference between v2 and v1 as: 100.0 * f(v2) / f(v1) - 100.0. + /// Then formats the difference as percentage. + /// If any of the values are missing, returns an empty String + fn fmt_relative_change(&self, direction: i8, significant: bool) -> String { + self.v2 + .and_then(|v2| { + let m1 = (self.f)(self.v1); + let m2 = (self.f)(v2); + let ratio = Rational::ratio(m1, m2); + ratio.map(|r| { + let mut diff = 100.0 * (r - 1.0); + if diff.is_nan() { + diff = 0.0; + } + let good = diff * direction as f32; + let diff = format!("{diff:+7.1}%"); + let styled = if good == 0.0 || !significant { + style(diff).dim() + } else if good > 0.0 { + style(diff).bright().green() + } else { + style(diff).bright().red() + }; + format!("{styled}") + }) + }) + .unwrap_or_default() + } + + fn fmt_unit(&self) -> String { + match self.unit.as_str() { + "" => "".to_string(), + u => format!("[{u}]"), + } + } +} + +impl Display for Line +where + M: Display + Rational, + F: Fn(V) -> M, + V: Copy, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // if v2 defined, put v2 on left + let m1 = self.fmt_measurement(self.v2.or(Some(self.v1))); + let m2 = self.fmt_measurement(self.v2.map(|_| self.v1)); + let is_significant = match self.significance { + None => false, + Some(s) => s.0 <= 0.01, + }; + write!( + f, + "{label:>16} {unit:>9} {m1} {m2} {cmp:6} {signif}", + label = style(&self.label).yellow().bold().for_stdout(), + unit = style(self.fmt_unit()).yellow(), + m1 = pad_str(m1.as_str(), 30, Alignment::Left, None), + m2 = pad_str(m2.as_str(), 30, Alignment::Left, None), + cmp = self.fmt_relative_change(self.orientation, is_significant), + signif = match &self.significance { + Some(s) => format!("{s}"), + None => "".to_owned(), + } + ) + } +} + +const REPORT_WIDTH: usize = 124; + +fn fmt_section_header(name: &str) -> String { + format!( + "{} {}", + style(name).yellow().bold().bright().for_stdout(), + style("═".repeat(REPORT_WIDTH - name.len() - 1)) + .yellow() + .bold() + .bright() + .for_stdout() + ) +} + +fn fmt_horizontal_line() -> String { + format!("{}", style("─".repeat(REPORT_WIDTH)).yellow().dim()) +} + +fn fmt_cmp_header(display_significance: bool) -> String { + let header = format!( + "{} {} {}", + " ".repeat(27), + "───────────── A ───────────── ────────────── B ──────────── Change ", + if display_significance { + "P-value Signif." + } else { + "" + } + ); + format!("{}", style(header).yellow().bold().for_stdout()) +} +pub struct RpcConfigCmp<'a> { + pub v1: &'a RpcCommand, + pub v2: Option<&'a RpcCommand>, +} + +impl RpcConfigCmp<'_> { + fn line(&self, label: S, unit: &str, f: F) -> Box> + where + S: ToString, + M: Display + Rational, + F: Fn(&RpcCommand) -> M, + { + Box::new(Line::new( + label.to_string(), + unit.to_string(), + 0, + self.v1, + self.v2, + f, + )) + } + + fn format_time(&self, conf: &RpcCommand, format: &str) -> String { + conf.timestamp + .and_then(|ts| { + NaiveDateTime::from_timestamp_opt(ts, 0) + .map(|utc| Local.from_utc_datetime(&utc).format(format).to_string()) + }) + .unwrap_or_default() + } + +} + +impl<'a> Display for RpcConfigCmp<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "{}", fmt_section_header("CONFIG"))?; + if self.v2.is_some() { + writeln!(f, "{}", fmt_cmp_header(false))?; + } + + let lines: Vec> = vec![ + self.line("Date", "", |conf| self.format_time(conf, "%a, %d %b %Y")), + self.line("Time", "", |conf| self.format_time(conf, "%H:%M:%S %z")), + self.line("Cluster", "", |conf| { + OptionDisplay(conf.cluster_name.clone()) + }), + self.line("Chain ID", "", |conf| OptionDisplay(conf.chain_id.clone())), + self.line("Tags", "", |conf| conf.tags.iter().join(", ")), + ]; + + for l in lines { + writeln!(f, "{l}")?; + } + + writeln!(f, "{}", fmt_horizontal_line()).unwrap(); + + let lines: Vec> = vec![ + self.line("Threads", "", |conf| Quantity::from(conf.threads)), + self.line("Connection(s)", "", |conf| format!("{value:9}", value = conf.rpc_url.iter().join(", "))), + self.line("Concurrency", "reqs", |conf| { + Quantity::from(conf.concurrency) + }), + self.line("Reqs / Workload", "reqs", |conf| { + Quantity::from(conf.num_req) + }), + self.line("Max Rate(s)", "req/s", |conf| + match &conf.rate { + Some(rates) => format!("{value:9}", value = rates.into_iter().map(|r| { format!("{}", style(r).bright().for_stdout()) }).collect::>().join(", ")), + None => format!("{value:9}", value = style("MAX RATE").bright().for_stdout()), + }), + self.line("Warmup", "s", |conf| { + Quantity::from(conf.warmup_duration.seconds()) + }), + self.line("└─", "ops", |conf| { + Quantity::from(conf.warmup_duration.count()) + }), + self.line("Run time", "s", |conf| { + Quantity::from(conf.run_duration.seconds()).with_precision(1) + }), + self.line("└─", "ops", |conf| { + Quantity::from(conf.run_duration.count()) + }), + self.line("Sampling", "s", |conf| { + Quantity::from(conf.sampling_interval.seconds()).with_precision(1) + }), + self.line("└─", "", |conf| { + Quantity::from(conf.sampling_interval.count()) + }), + ]; + + for l in lines { + writeln!(f, "{l}")?; + } + Ok(()) + } +} + +pub fn print_log_header() { + println!("{}", fmt_section_header("LOG")); + println!("{}", style(" Time ───── Throughput ───── ────────────────────────────────── Response times [ms] ───────────────────────────────────").yellow().bold().for_stdout()); + println!("{}", style(" [s] [op/s] [req/s] Min 25 50 75 90 95 99 99.9 Max").yellow().for_stdout()); +} + +impl Display for Sample { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "{:8.3} {:11.0} {:11.0} {:9.3} {:9.3} {:9.3} {:9.3} {:9.3} {:9.3} {:9.3} {:9.3} {:9.3}", + self.time_s + self.duration_s, + self.cycle_throughput, + self.req_throughput, + self.resp_time_percentiles[Percentile::Min as usize], + self.resp_time_percentiles[Percentile::P25 as usize], + self.resp_time_percentiles[Percentile::P50 as usize], + self.resp_time_percentiles[Percentile::P75 as usize], + self.resp_time_percentiles[Percentile::P90 as usize], + self.resp_time_percentiles[Percentile::P95 as usize], + self.resp_time_percentiles[Percentile::P99 as usize], + self.resp_time_percentiles[Percentile::P99_9 as usize], + self.resp_time_percentiles[Percentile::Max as usize] + ) + } +} + +impl BenchmarkCmp<'_> { + fn line(&self, label: S, unit: &str, f: F) -> Box> + where + S: ToString, + M: Display + Rational, + F: Fn(&BenchmarkStats) -> M, + { + Box::new(Line::new( + label.to_string(), + unit.to_string(), + 0, + self.v1, + self.v2, + f, + )) + } +} + +/// Formats all benchmark stats +impl<'a> Display for BenchmarkCmp<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "{}", fmt_section_header("SUMMARY STATS"))?; + if self.v2.is_some() { + writeln!(f, "{}", fmt_cmp_header(true))?; + } + + let summary: Vec> = vec![ + self.line("Elapsed time", "s", |s| { + Quantity::from(s.elapsed_time_s).with_precision(3) + }), + self.line("CPU time", "s", |s| { + Quantity::from(s.cpu_time_s).with_precision(3) + }), + self.line("CPU utilisation", "%", |s| { + Quantity::from(s.cpu_util).with_precision(1) + }), + self.line("Workloads", "ops", |s| Quantity::from(s.cycle_count)), + self.line("Successful Reqs", "reqs", |s| Quantity::from(s.request_count - s.error_count)), + self.line("└─", "%", |s| { + Quantity::from(s.errors_ratio).with_precision(1) + }), + self.line("Failed Reqs", "reqs", |s| Quantity::from(s.error_count)), + self.line("└─", "%", |s| { + Quantity::from(s.errors_ratio).with_precision(1) + }), + self.line("Total Requests", "reqs", |s| Quantity::from(s.request_count)), + self.line("└─", "req/op", |s| { + Quantity::from(s.requests_per_cycle).with_precision(1) + }), + self.line("Samples", "", |s| Quantity::from(s.log.len())), + self.line("Mean sample size", "op", |s| { + Quantity::from(s.log.iter().map(|s| s.cycle_count as f64).mean()) + }), + self.line("└─", "req", |s| { + Quantity::from(s.log.iter().map(|s| s.request_count as f64).mean()) + }), + self.line("Concurrency", "req", |s| Quantity::from(s.concurrency)), + self.line("└─", "%", |s| Quantity::from(s.concurrency_ratio)), + self.line("Throughput", "op/s", |s| Quantity::from(s.cycle_throughput)) + .with_significance(self.cmp_cycle_throughput()) + .with_orientation(1) + .into_box(), + self.line("├─", "req/s", |s| Quantity::from(s.req_throughput)) + .with_significance(self.cmp_req_throughput()) + .with_orientation(1) + .into_box(), + self.line("Mean workload time", "ms", |s| { + Quantity::from(&s.cycle_time_ms).with_precision(3) + }) + .with_significance(self.cmp_mean_resp_time()) + .with_orientation(-1) + .into_box(), + self.line("Mean resp. time", "ms", |s| { + Quantity::from(s.resp_time_ms.as_ref().map(|rt| rt.mean)).with_precision(3) + }) + .with_significance(self.cmp_mean_resp_time()) + .with_orientation(-1) + .into_box(), + ]; + + for l in summary { + writeln!(f, "{l}")?; + } + writeln!(f)?; + + if self.v1.request_count > 0 { + let resp_time_percentiles = [ + Percentile::Min, + Percentile::P25, + Percentile::P50, + Percentile::P75, + Percentile::P90, + Percentile::P95, + Percentile::P98, + Percentile::P99, + Percentile::P99_9, + Percentile::P99_99, + Percentile::Max, + ]; + writeln!(f)?; + writeln!(f, "{}", fmt_section_header("RESPONSE TIMES [ms]"))?; + if self.v2.is_some() { + writeln!(f, "{}", fmt_cmp_header(true))?; + } + + for p in resp_time_percentiles.iter() { + let l = self + .line(p.name(), "", |s| { + let rt = s + .resp_time_ms + .as_ref() + .map(|rt| rt.percentiles[*p as usize]); + Quantity::from(rt).with_precision(3) + }) + .with_orientation(-1) + .with_significance(self.cmp_resp_time_percentile(*p)); + writeln!(f, "{l}")?; + } + + writeln!(f)?; + writeln!(f, "{}", fmt_section_header("RESPONSE TIME DISTRIBUTION"))?; + writeln!(f, "{}", style("── Resp. time [ms] ── ────────────────────────────────────────────── Count ────────────────────────────────────────────────").yellow().bold().for_stdout())?; + let zero = Bucket { + percentile: 0.0, + duration_ms: 0.0, + count: 0, + cumulative_count: 0, + }; + let dist = &self.v1.resp_time_ms.as_ref().unwrap().distribution; + let max_count = dist.iter().map(|b| b.count).max().unwrap_or(1); + for (low, high) in ([zero].iter().chain(dist)).tuple_windows() { + writeln!( + f, + "{:8.1} {} {:8.1} {:9} {:6.2}% {}", + style(low.duration_ms).yellow().for_stdout(), + style("...").yellow().for_stdout(), + style(high.duration_ms).yellow().for_stdout(), + high.count, + high.percentile - low.percentile, + style("▪".repeat((82 * high.count / max_count) as usize)) + .dim() + .for_stdout() + )?; + if high.cumulative_count == self.v1.request_count { + break; + } + } + } + + if self.v1.error_count > 0 { + writeln!(f)?; + writeln!(f, "{}", fmt_section_header("ERRORS"))?; + for e in self.v1.errors.iter() { + writeln!(f, "{e}")?; + } + } + Ok(()) + } +} diff --git a/flood_rust/src/sampler.rs b/flood_rust/src/sampler.rs new file mode 100644 index 0000000..c03ba73 --- /dev/null +++ b/flood_rust/src/sampler.rs @@ -0,0 +1,90 @@ +use crate::error::Result; +use crate::workload::Workload; +use crate::{config, Interval, WorkloadStats}; +use futures::channel::mpsc::Sender; +use futures::SinkExt; +use std::time::Instant; + +/// Responsible for periodically getting a snapshot of statistics from the `workload` +/// and sending them to the `output` channel. The sampling period is controlled by `sampling`. +/// Snapshot is not taken near the end of the run to avoid small final sample. +pub struct Sampler<'a> { + run_duration: config::Interval, + sampling: config::Interval, + workload: &'a Workload, + output: &'a mut Sender>, + start_time: Instant, + last_snapshot_time: Instant, + last_snapshot_cycle: u64, +} + +impl<'a> Sampler<'a> { + pub fn new( + run_duration: config::Interval, + sampling: config::Interval, + workload: &'a Workload, + output: &'a mut Sender>, + ) -> Sampler<'a> { + let start_time = Instant::now(); + Sampler { + run_duration, + sampling, + workload, + output, + start_time, + last_snapshot_time: start_time, + last_snapshot_cycle: 0, + } + } + + /// Should be called when a workload iteration finished. + /// If there comes the time, it will send the stats to the output. + pub async fn cycle_completed(&mut self, cycle: u64, now: Instant) { + let current_interval_duration = now.saturating_duration_since(self.last_snapshot_time); + let current_interval_cycle_count = cycle.saturating_sub(self.last_snapshot_cycle); + + // Don't snapshot if we're too close to the end of the run, + // to avoid excessively small samples: + let far_from_the_end = match self.run_duration { + config::Interval::Time(d) => now + current_interval_duration / 2 < self.start_time + d, + config::Interval::Count(count) => cycle + current_interval_cycle_count / 2 < count, + config::Interval::Unbounded => true, + }; + + match self.sampling { + Interval::Time(d) => { + if now > self.last_snapshot_time + d && far_from_the_end { + self.send_stats().await; + // We may be running this slightly too late, + // so set the perfect time of the sample, so the next sample is slightly shorter. + // This way we avoid increasing the lag. + self.last_snapshot_time += d; + self.last_snapshot_cycle = cycle; + } + } + Interval::Count(cnt) => { + if cycle > self.last_snapshot_cycle + cnt && far_from_the_end { + self.send_stats().await; + self.last_snapshot_time = now; + // Similarly like with time, we might have been called + // when the counter already went a bit further than the target split point, + // so let's record the perfect (desired) cycle count so we don't + // increase the lag. + self.last_snapshot_cycle += cnt; + } + } + Interval::Unbounded => {} + } + } + + /// Finishes the run by emiting the last sample + pub async fn finish(mut self) { + self.send_stats().await; + } + + /// Fetches session statistics and sends them to the channel. + async fn send_stats(&mut self) { + let stats = self.workload.take_stats(Instant::now()); + self.output.send(Ok(stats)).await.unwrap(); + } +} diff --git a/flood_rust/src/stats.rs b/flood_rust/src/stats.rs new file mode 100644 index 0000000..d848cf9 --- /dev/null +++ b/flood_rust/src/stats.rs @@ -0,0 +1,761 @@ +use chrono::{DateTime, Local}; +use std::cmp::min; +use std::collections::HashSet; +use std::num::NonZeroUsize; +use std::time::{Instant, SystemTime}; + +use cpu_time::ProcessTime; +use hdrhistogram::Histogram; +use serde::{Deserialize, Serialize}; +use statrs::distribution::{ContinuousCDF, StudentsT}; +use strum::EnumCount; +use strum::IntoEnumIterator; +use strum_macros::{EnumCount as EnumCountM, EnumIter}; + +use crate::histogram::SerializableHistogram; +use crate::workload::WorkloadStats; + +/// Controls the maximum order of autocovariance taken into +/// account when estimating the long run mean error. Higher values make the estimator +/// capture more autocorrelation from the signal, but also make the results +/// more random. Lower values increase the bias (underestimation) of error, but offer smoother +/// results for small N and better performance for large N. +/// The value has been established empirically. +/// Probably anything between 0.2 and 0.8 is good enough. +/// Valid range is 0.0 to 1.0. +const BANDWIDTH_COEFF: f64 = 0.5; + +/// Arithmetic weighted mean of values in the vector +pub fn mean(values: &[f32], weights: &[f32]) -> f64 { + let sum_values = values + .iter() + .zip(weights) + .map(|(&v, &w)| (v as f64) * (w as f64)) + .sum::(); + let sum_weights = weights.iter().map(|&v| v as f64).sum::(); + sum_values / sum_weights +} + +/// Estimates the variance of the mean of a time-series. +/// Takes into account the fact that the observations can be dependent on each other +/// (i.e. there is a non-zero amount of auto-correlation in the signal). +/// +/// Contrary to the classic variance estimator, the order of the +/// data points does matter here. If the observations are totally independent from each other, +/// the expected return value of this function is close to the expected sample variance. +pub fn long_run_variance(mean: f64, values: &[f32], weights: &[f32]) -> f64 { + if values.len() <= 1 { + return f64::NAN; + } + let len = values.len() as f64; + + // Compute the variance: + let mut sum_weights = 0.0; + let mut var = 0.0; + for (&v, &w) in values.iter().zip(weights) { + let diff = v as f64 - mean; + let w = w as f64; + var += diff * diff * w; + sum_weights += w; + } + var /= sum_weights; + + // Compute a sum of autocovariances of orders 1 to (cutoff - 1). + // Cutoff (bandwidth) and diminishing weights are needed to reduce random error + // introduced by higher order autocovariance estimates. + let bandwidth = len.powf(BANDWIDTH_COEFF); + let max_lag = min(values.len(), bandwidth.ceil() as usize); + let mut cov_sum = 0.0; + for lag in 1..max_lag { + let weight = 1.0 - lag as f64 / values.len() as f64; + let mut cov = 0.0; + let mut sum_weights = 0.0; + for i in lag..values.len() { + let diff_1 = values[i] as f64 - mean; + let diff_2 = values[i - lag] as f64 - mean; + let w = weights[i] as f64 * weights[i - lag] as f64; + sum_weights += w; + cov += 2.0 * diff_1 * diff_2 * weight * w; + } + cov_sum += cov / sum_weights; + } + + // It is possible that we end up with a negative sum of autocovariances here. + // But we don't want that because we're trying to estimate + // the worst-case error and for small N this situation is likely a random coincidence. + // Additionally, `var + cov` must be at least 0.0. + cov_sum = cov_sum.max(0.0); + + // Correct bias for small n: + let inflation = 1.0 + cov_sum / (var + f64::MIN_POSITIVE); + let bias_correction = (inflation / len).exp(); + bias_correction * (var + cov_sum) +} + +/// Estimates the error of the mean of a time-series. +/// See `long_run_variance`. +pub fn long_run_err(mean: f64, values: &[f32], weights: &[f32]) -> f64 { + (long_run_variance(mean, values, weights) / values.len() as f64).sqrt() +} + +fn percentiles_ms(hist: &Histogram) -> [f32; Percentile::COUNT] { + let mut percentiles = [0.0; Percentile::COUNT]; + for (i, p) in Percentile::iter().enumerate() { + percentiles[i] = hist.value_at_percentile(p.value()) as f32 / 1000000.0; + } + percentiles +} + +/// Holds a mean and its error together. +/// Makes it more convenient to compare means and it also reduces the number +/// of fields, because we don't have to keep the values and the errors in separate fields. +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +pub struct Mean { + pub n: u64, + pub value: f64, + pub std_err: Option, +} + +impl Mean { + pub fn compute(v: &[f32], weights: &[f32]) -> Self { + let m = mean(v, weights); + Mean { + n: v.len() as u64, + value: m, + std_err: not_nan(long_run_err(m, v, weights)), + } + } +} + +/// Returns the probability that the difference between two means is due to a chance. +/// Uses Welch's t-test allowing samples to have different variances. +/// See https://en.wikipedia.org/wiki/Welch%27s_t-test. +/// +/// If any of the means is given without the error, or if the number of observations is too low, +/// returns 1.0. +/// +/// Assumes data are i.i.d and distributed normally, but it can be used +/// for autocorrelated data as well, if the errors are properly corrected for autocorrelation +/// using Wilk's method. This is what `Mean` struct is doing automatically +/// when constructed from a vector. +pub fn t_test(mean1: &Mean, mean2: &Mean) -> f64 { + if mean1.std_err.is_none() || mean2.std_err.is_none() { + return 1.0; + } + let n1 = mean1.n as f64; + let n2 = mean2.n as f64; + let e1 = mean1.std_err.unwrap(); + let e2 = mean2.std_err.unwrap(); + let m1 = mean1.value; + let m2 = mean2.value; + let e1_sq = e1 * e1; + let e2_sq = e2 * e2; + let se_sq = e1_sq + e2_sq; + let se = se_sq.sqrt(); + let t = (m1 - m2) / se; + let freedom = se_sq * se_sq / (e1_sq * e1_sq / (n1 - 1.0) + e2_sq * e2_sq / (n2 - 1.0)); + if let Ok(distrib) = StudentsT::new(0.0, 1.0, freedom) { + 2.0 * (1.0 - distrib.cdf(t.abs())) + } else { + 1.0 + } +} + +fn distribution(hist: &Histogram) -> Vec { + let mut result = Vec::new(); + if !hist.is_empty() { + for x in hist.iter_log(100000, 2.15443469) { + result.push(Bucket { + percentile: x.percentile(), + duration_ms: x.value_iterated_to() as f64 / 1000000.0, + count: x.count_since_last_iteration(), + cumulative_count: x.count_at_value(), + }); + } + } + result +} + +/// Converts NaN to None. +fn not_nan(x: f64) -> Option { + if x.is_nan() { + None + } else { + Some(x) + } +} + +/// Converts NaN to None. +fn not_nan_f32(x: f32) -> Option { + if x.is_nan() { + None + } else { + Some(x) + } +} + +const MAX_KEPT_ERRORS: usize = 10; + +#[allow(non_camel_case_types)] +#[derive(Copy, Clone, EnumIter, EnumCountM)] +pub enum Percentile { + Min = 0, + P1, + P2, + P5, + P10, + P25, + P50, + P75, + P90, + P95, + P98, + P99, + P99_9, + P99_99, + Max, +} + +impl Percentile { + pub fn value(&self) -> f64 { + match self { + Percentile::Min => 0.0, + Percentile::P1 => 1.0, + Percentile::P2 => 2.0, + Percentile::P5 => 5.0, + Percentile::P10 => 10.0, + Percentile::P25 => 25.0, + Percentile::P50 => 50.0, + Percentile::P75 => 75.0, + Percentile::P90 => 90.0, + Percentile::P95 => 95.0, + Percentile::P98 => 98.0, + Percentile::P99 => 99.0, + Percentile::P99_9 => 99.9, + Percentile::P99_99 => 99.99, + Percentile::Max => 100.0, + } + } + + pub fn name(&self) -> &'static str { + match self { + Percentile::Min => " Min ", + Percentile::P1 => " 1 ", + Percentile::P2 => " 2 ", + Percentile::P5 => " 5 ", + Percentile::P10 => " 10 ", + Percentile::P25 => " 25 ", + Percentile::P50 => " 50 ", + Percentile::P75 => " 75 ", + Percentile::P90 => " 90 ", + Percentile::P95 => " 95 ", + Percentile::P98 => " 98 ", + Percentile::P99 => " 99 ", + Percentile::P99_9 => " 99.9 ", + Percentile::P99_99 => " 99.99", + Percentile::Max => " Max ", + } + } +} + +/// Records basic statistics for a sample (a group) of requests +#[derive(Serialize, Deserialize)] +pub struct Sample { + pub time_s: f32, + pub duration_s: f32, + pub cycle_count: u64, + pub request_count: u64, + pub error_count: u64, + pub errors: HashSet, + pub mean_queue_len: f32, + pub cycle_throughput: f32, + pub req_throughput: f32, + pub mean_cycle_time_ms: f32, + pub mean_resp_time_ms: f32, + pub cycle_time_percentiles: [f32; Percentile::COUNT], + pub resp_time_percentiles: [f32; Percentile::COUNT], + pub cycle_time_histogram_ns: SerializableHistogram, + pub resp_time_histogram_ns: SerializableHistogram, +} + +impl Sample { + pub fn new(base_start_time: Instant, stats: &[WorkloadStats]) -> Sample { + assert!(!stats.is_empty()); + let mut cycle_count = 0; + let mut cycle_times_ns = Histogram::new(3).unwrap(); + + let mut request_count = 0; + let mut errors = HashSet::new(); + let mut error_count = 0; + let mut mean_queue_len = 0.0; + let mut duration_s = 0.0; + let mut resp_times_ns = Histogram::new(3).unwrap(); + + let mut cycle_time_histogram_ns = Histogram::new(3).unwrap(); + let mut resp_time_histogram_ns = Histogram::new(3).unwrap(); + + for s in stats { + let ss = &s.session_stats; + let fs = &s.workload_stats; + request_count += ss.req_count; + if errors.len() < MAX_KEPT_ERRORS { + errors.extend(ss.req_errors.iter().cloned()); + } + error_count += ss.req_error_count; + mean_queue_len += ss.mean_queue_length / stats.len() as f32; + duration_s += (s.end_time - s.start_time).as_secs_f32() / stats.len() as f32; + resp_times_ns.add(&ss.resp_times_ns).unwrap(); + resp_time_histogram_ns.add(&ss.resp_times_ns).unwrap(); + + cycle_count += fs.workload_count; + cycle_times_ns.add(&fs.workload_times_ns).unwrap(); + cycle_time_histogram_ns.add(&fs.workload_times_ns).unwrap(); + } + let resp_time_percentiles = percentiles_ms(&resp_times_ns); + let call_time_percentiles = percentiles_ms(&cycle_times_ns); + + Sample { + time_s: (stats[0].start_time - base_start_time).as_secs_f32(), + duration_s, + cycle_count, + request_count, + error_count, + errors, + mean_queue_len: not_nan_f32(mean_queue_len).unwrap_or(0.0), + cycle_throughput: cycle_count as f32 / duration_s, + req_throughput: request_count as f32 / duration_s, + mean_cycle_time_ms: cycle_times_ns.mean() as f32 / 1000000.0, + cycle_time_histogram_ns: SerializableHistogram(cycle_time_histogram_ns), + cycle_time_percentiles: call_time_percentiles, + mean_resp_time_ms: resp_times_ns.mean() as f32 / 1000000.0, + resp_time_percentiles, + resp_time_histogram_ns: SerializableHistogram(resp_time_histogram_ns), + } + } +} + +/// Collects the samples and computes aggregate statistics +struct Log { + samples: Vec, +} + +impl Log { + fn new() -> Log { + Log { + samples: Vec::new(), + } + } + + fn append(&mut self, sample: Sample) -> &Sample { + self.samples.push(sample); + self.samples.last().unwrap() + } + + fn weights_by_call_count(&self) -> Vec { + self.samples.iter().map(|s| s.cycle_count as f32).collect() + } + + fn weights_by_request_count(&self) -> Vec { + self.samples + .iter() + .map(|s| s.request_count as f32) + .collect() + } + + fn call_throughput(&self) -> Mean { + let t: Vec = self.samples.iter().map(|s| s.cycle_throughput).collect(); + let w: Vec = self.samples.iter().map(|s| s.duration_s).collect(); + Mean::compute(t.as_slice(), w.as_slice()) + } + + fn req_throughput(&self) -> Mean { + let t: Vec = self.samples.iter().map(|s| s.req_throughput).collect(); + let w: Vec = self.samples.iter().map(|s| s.duration_s).collect(); + Mean::compute(t.as_slice(), w.as_slice()) + } + + fn resp_time_ms(&self) -> Mean { + let t: Vec = self.samples.iter().map(|s| s.mean_resp_time_ms).collect(); + let w = self.weights_by_request_count(); + Mean::compute(t.as_slice(), w.as_slice()) + } + + fn resp_time_percentile(&self, p: Percentile) -> Mean { + let t: Vec = self + .samples + .iter() + .map(|s| s.resp_time_percentiles[p as usize]) + .collect(); + let w = self.weights_by_request_count(); + Mean::compute(t.as_slice(), w.as_slice()) + } + + fn cycle_time_ms(&self) -> Mean { + let t: Vec = self.samples.iter().map(|s| s.mean_cycle_time_ms).collect(); + let w = self.weights_by_call_count(); + Mean::compute(t.as_slice(), w.as_slice()) + } + + fn cycle_time_percentile(&self, p: Percentile) -> Mean { + let t: Vec = self + .samples + .iter() + .map(|s| s.cycle_time_percentiles[p as usize]) + .collect(); + let w = self.weights_by_call_count(); + Mean::compute(t.as_slice(), w.as_slice()) + } + + fn mean_concurrency(&self) -> Mean { + let p: Vec = self.samples.iter().map(|s| s.mean_queue_len).collect(); + let w = self.weights_by_request_count(); + let m = Mean::compute(p.as_slice(), w.as_slice()); + if m.value.is_nan() { + Mean { + n: 0, + value: 0.0, + std_err: None, + } + } else { + m + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct Bucket { + pub percentile: f64, + pub duration_ms: f64, + pub count: u64, + pub cumulative_count: u64, +} + +#[derive(Serialize, Deserialize)] +pub struct TimeDistribution { + pub mean: Mean, + pub percentiles: Vec, + pub distribution: Vec, +} + +/// Stores the final statistics of the test run. +#[derive(Serialize, Deserialize)] +pub struct BenchmarkStats { + pub start_time: DateTime, + pub end_time: DateTime, + pub elapsed_time_s: f64, + pub cpu_time_s: f64, + pub cpu_util: f64, + pub cycle_count: u64, + pub request_count: u64, + pub requests_per_cycle: f64, + pub errors: Vec, + pub error_count: u64, + pub errors_ratio: Option, + pub cycle_throughput: Mean, + pub cycle_throughput_ratio: Option, + pub req_throughput: Mean, + pub cycle_time_ms: TimeDistribution, + pub resp_time_ms: Option, + pub concurrency: Mean, + pub concurrency_ratio: f64, + pub log: Vec, +} + +/// Stores the statistics of one or two test runs. +/// If the second run is given, enables comparisons between the runs. +pub struct BenchmarkCmp<'a> { + pub v1: &'a BenchmarkStats, + pub v2: Option<&'a BenchmarkStats>, +} + +/// Significance level denoting strength of hypothesis. +/// The wrapped value denotes the probability of observing given outcome assuming +/// null-hypothesis is true (see: https://en.wikipedia.org/wiki/P-value). +#[derive(Clone, Copy)] +pub struct Significance(pub f64); + +impl BenchmarkCmp<'_> { + /// Compares samples collected in both runs for statistically significant difference. + /// `f` a function applied to each sample + fn cmp(&self, f: F) -> Option + where + F: Fn(&BenchmarkStats) -> Option + Copy, + { + self.v2.and_then(|v2| { + let m1 = f(self.v1); + let m2 = f(v2); + m1.and_then(|m1| m2.map(|m2| Significance(t_test(&m1, &m2)))) + }) + } + + /// Checks if call throughput means of two benchmark runs are significantly different. + /// Returns None if the second benchmark is unset. + pub fn cmp_cycle_throughput(&self) -> Option { + self.cmp(|s| Some(s.cycle_throughput)) + } + + /// Checks if request throughput means of two benchmark runs are significantly different. + /// Returns None if the second benchmark is unset. + pub fn cmp_req_throughput(&self) -> Option { + self.cmp(|s| Some(s.req_throughput)) + } + + // Checks if mean response time of two benchmark runs are significantly different. + // Returns None if the second benchmark is unset. + pub fn cmp_mean_resp_time(&self) -> Option { + self.cmp(|s| s.resp_time_ms.as_ref().map(|r| r.mean)) + } + + // Checks corresponding response time percentiles of two benchmark runs + // are statistically different. Returns None if the second benchmark is unset. + pub fn cmp_resp_time_percentile(&self, p: Percentile) -> Option { + self.cmp(|s| s.resp_time_ms.as_ref().map(|r| r.percentiles[p as usize])) + } +} + +/// Observes requests and computes their statistics such as mean throughput, mean response time, +/// throughput and response time distributions. Computes confidence intervals. +/// Can be also used to split the time-series into smaller sub-samples and to +/// compute statistics for each sub-sample separately. +pub struct Recorder { + pub start_time: SystemTime, + pub end_time: SystemTime, + pub start_instant: Instant, + pub end_instant: Instant, + pub start_cpu_time: ProcessTime, + pub end_cpu_time: ProcessTime, + pub cycle_count: u64, + pub request_count: u64, + pub errors: HashSet, + pub error_count: u64, + pub row_count: u64, + pub cycle_times_ns: Histogram, + pub resp_times_ns: Histogram, + pub queue_len_sum: u64, + log: Log, + rate_limit: Option, + concurrency_limit: NonZeroUsize, +} + +impl Recorder { + /// Creates a new recorder. + /// The `rate_limit` and `concurrency_limit` parameters are used only as the + /// reference levels for relative throughput and relative parallelism. + pub fn start(rate_limit: Option, concurrency_limit: NonZeroUsize) -> Recorder { + let start_time = SystemTime::now(); + let start_instant = Instant::now(); + Recorder { + start_time, + end_time: start_time, + start_instant, + end_instant: start_instant, + start_cpu_time: ProcessTime::now(), + end_cpu_time: ProcessTime::now(), + log: Log::new(), + rate_limit, + concurrency_limit, + cycle_count: 0, + request_count: 0, + row_count: 0, + errors: HashSet::new(), + error_count: 0, + cycle_times_ns: Histogram::new(3).unwrap(), + resp_times_ns: Histogram::new(3).unwrap(), + queue_len_sum: 0, + } + } + + /// Adds the statistics of the completed request to the already collected statistics. + /// Called on completion of each sample. + pub fn record(&mut self, samples: &[WorkloadStats]) -> &Sample { + for s in samples.iter() { + self.resp_times_ns + .add(&s.session_stats.resp_times_ns) + .unwrap(); + self.cycle_times_ns + .add(&s.workload_stats.workload_times_ns) + .unwrap(); + } + let stats = Sample::new(self.start_instant, samples); + self.cycle_count += stats.cycle_count; + self.request_count += stats.request_count; + if self.errors.len() < MAX_KEPT_ERRORS { + self.errors.extend(stats.errors.iter().cloned()); + } + self.error_count += stats.error_count; + self.log.append(stats) + } + + /// Stops the recording, computes the statistics and returns them as the new object. + pub fn finish(mut self) -> BenchmarkStats { + self.end_time = SystemTime::now(); + self.end_instant = Instant::now(); + self.end_cpu_time = ProcessTime::now(); + self.stats() + } + + /// Computes the final statistics based on collected data + /// and turn them into report that can be serialized + fn stats(self) -> BenchmarkStats { + let elapsed_time_s = (self.end_instant - self.start_instant).as_secs_f64(); + let cpu_time_s = self + .end_cpu_time + .duration_since(self.start_cpu_time) + .as_secs_f64(); + let cpu_util = 100.0 * cpu_time_s / elapsed_time_s / num_cpus::get() as f64; + let count = self.request_count + self.error_count; + + let cycle_throughput = self.log.call_throughput(); + let cycle_throughput_ratio = self.rate_limit.map(|r| 100.0 * cycle_throughput.value / r); + let req_throughput = self.log.req_throughput(); + let concurrency = self.log.mean_concurrency(); + let concurrency_ratio = 100.0 * concurrency.value / self.concurrency_limit.get() as f64; + + let cycle_time_percentiles: Vec = Percentile::iter() + .map(|p| self.log.cycle_time_percentile(p)) + .collect(); + let resp_time_percentiles: Vec = Percentile::iter() + .map(|p| self.log.resp_time_percentile(p)) + .collect(); + + BenchmarkStats { + start_time: self.start_time.into(), + end_time: self.end_time.into(), + elapsed_time_s, + cpu_time_s, + cpu_util, + cycle_count: self.cycle_count, + errors: self.errors.into_iter().collect(), + error_count: self.error_count, + errors_ratio: not_nan(100.0 * self.error_count as f64 / count as f64), + request_count: self.request_count, + requests_per_cycle: self.request_count as f64 / self.cycle_count as f64, + cycle_throughput, + cycle_throughput_ratio, + req_throughput, + cycle_time_ms: TimeDistribution { + mean: self.log.cycle_time_ms(), + percentiles: cycle_time_percentiles, + distribution: distribution(&self.cycle_times_ns), + }, + resp_time_ms: if self.request_count > 0 { + Some(TimeDistribution { + mean: self.log.resp_time_ms(), + percentiles: resp_time_percentiles, + distribution: distribution(&self.resp_times_ns), + }) + } else { + None + }, + concurrency, + concurrency_ratio, + log: self.log.samples, + } + } +} + +#[cfg(test)] +mod test { + use rand::distributions::Distribution; + use rand::prelude::StdRng; + use rand::SeedableRng; + use statrs::distribution::Normal; + use statrs::statistics::Statistics; + + use crate::stats::{t_test, Mean}; + + /// Returns a random sample of size `len`. + /// All data points i.i.d with N(`mean`, `std_dev`). + fn random_vector(seed: usize, len: usize, mean: f64, std_dev: f64) -> Vec { + let mut rng = StdRng::seed_from_u64(seed as u64); + let distrib = Normal::new(mean, std_dev).unwrap(); + (0..len).map(|_| distrib.sample(&mut rng) as f32).collect() + } + + /// Introduces a strong dependency between the observations, + /// making it an AR(1) process + fn make_autocorrelated(v: &mut [f32]) { + for i in 1..v.len() { + v[i] = 0.01 * v[i] + 0.99 * v[i - 1]; + } + } + + /// Traditional standard error assuming i.i.d variables + fn reference_err(v: &[f32]) -> f64 { + v.iter().map(|x| *x as f64).std_dev() / (v.len() as f64).sqrt() + } + + #[test] + fn mean_err_no_auto_correlation() { + let run_len = 10000; + let mean = 1.0; + let std_dev = 1.0; + let weights = [1.0; 10000]; + for i in 0..10 { + let v = random_vector(i, run_len, mean, std_dev); + let err = super::long_run_err(mean, &v, &weights); + let ref_err = reference_err(&v); + assert!(err > 0.99 * ref_err); + assert!(err < 1.2 * ref_err); + } + } + + #[test] + fn mean_err_with_auto_correlation() { + let run_len = 10000; + let mean = 1.0; + let std_dev = 1.0; + let weights = [1.0; 10000]; + for i in 0..10 { + let mut v = random_vector(i, run_len, mean, std_dev); + make_autocorrelated(&mut v); + let mean_err = super::long_run_err(mean, &v, &weights); + let ref_err = reference_err(&v); + assert!(mean_err > 6.0 * ref_err); + } + } + + #[test] + fn t_test_same() { + let mean1 = Mean { + n: 100, + value: 1.0, + std_err: Some(0.1), + }; + let mean2 = Mean { + n: 100, + value: 1.0, + std_err: Some(0.2), + }; + assert!(t_test(&mean1, &mean2) > 0.9999); + } + + #[test] + fn t_test_different() { + let mean1 = Mean { + n: 100, + value: 1.0, + std_err: Some(0.1), + }; + let mean2 = Mean { + n: 100, + value: 1.3, + std_err: Some(0.1), + }; + assert!(t_test(&mean1, &mean2) < 0.05); + assert!(t_test(&mean2, &mean1) < 0.05); + + let mean1 = Mean { + n: 10000, + value: 1.0, + std_err: Some(0.0), + }; + let mean2 = Mean { + n: 10000, + value: 1.329, + std_err: Some(0.1), + }; + assert!(t_test(&mean1, &mean2) < 0.0011); + assert!(t_test(&mean2, &mean1) < 0.0011); + } +} diff --git a/flood_rust/src/workload.rs b/flood_rust/src/workload.rs new file mode 100644 index 0000000..ec32303 --- /dev/null +++ b/flood_rust/src/workload.rs @@ -0,0 +1,177 @@ +use std::fmt::Debug; +use std::time::Duration; +use std::time::Instant; + +use alloy_json_rpc::Request; +use alloy_json_rpc::RpcError; +use alloy_rpc_client::RpcCall; +use alloy_transport::TransportErrorKind; +use hdrhistogram::Histogram; +use rand::prelude::SliceRandom; +use serde_json::value::RawValue; +use try_lock::TryLock; + +use crate::config::RpcCommand; +use crate::error::FloodError; +use crate::{Context, SessionStats}; + +/// Tracks statistics of the Rune function invoked by the workload +#[derive(Clone, Debug)] +pub struct FnStats { + pub workload_count: u64, + pub workload_times_ns: Histogram, +} + +impl FnStats { + pub fn operation_completed(&mut self, duration: Duration) { + self.workload_count += 1; + self.workload_times_ns + .record(duration.as_nanos().clamp(1, u64::MAX as u128) as u64) + .unwrap(); + } +} + +impl Default for FnStats { + fn default() -> Self { + FnStats { + workload_count: 0, + workload_times_ns: Histogram::new(3).unwrap(), + } + } +} + +/// Statistics of Workload execution and Eth JSON-RPC requests. +pub struct WorkloadStats { + pub start_time: Instant, + pub end_time: Instant, + pub workload_stats: FnStats, + pub session_stats: SessionStats, +} + +/// Mutable part of Workload +pub struct WorkloadState { + start_time: Instant, + fn_stats: FnStats, +} + +impl Default for WorkloadState { + fn default() -> Self { + WorkloadState { + start_time: Instant::now(), + fn_stats: Default::default(), + } + } +} + +pub struct Workload { + context: Context, + state: TryLock, + requests: Vec>>, + random: bool, + choose: bool, +} + +impl Workload { + pub fn new(context: Context, requests: Vec>>, conf: &RpcCommand) -> Workload { + Workload { + context, + state: TryLock::new(WorkloadState::default()), + requests: requests.clone(), + random: conf.random, + choose: conf.choose, + } + } + + pub fn clone(&self) -> Result { + Ok(Workload { + context: self.context.clone()?, + // make a deep copy to avoid congestion on Arc ref counts used heavily by Rune + state: TryLock::new(WorkloadState::default()), + requests: self.requests.clone(), + random: self.random.clone(), + choose: self.choose.clone(), + }) + } + + /// Executes all calls within a workload + pub async fn call(&self, requests: Vec>>) -> Result<(), FloodError> { + for call in requests { + let start_time = self.context.stats.try_lock().unwrap().start_request(); + // Each workload object can be a single, multiple, or batch of requests. + // This can fuck with measurements as we basically want to define a workload of different params, bench the entire execution and the execution of individual request.... + // Have two stats... one per workload call and one per call to run() as is done within latte + let rs: Result, RpcError> = + RpcCall::new(call, self.context.session.transport().clone()) + .boxed() + .await; + let end_time = Instant::now(); + //TAKE SESSION STATS as we don't make a Rune function call + //NOTE: These are per call stats + self.context + .stats + .try_lock() + .unwrap() + .complete_request::, TransportErrorKind>( + end_time - start_time, + &rs, + ); + } + Ok(()) + } + + /// Executes a single cycle of a workload. + /// This should be idempotent – + /// the generated action should be a function of the iteration number. + /// Returns the cycle number and the end time of the query. + pub async fn run(&self, cycle: u64) -> Result<(u64, Instant), FloodError> { + let mut requests = self.requests.clone(); + //TODO: move these branches out of the hot loop + if self.random { + requests.shuffle(&mut rand::thread_rng()) + } else if self.choose { + requests = vec![requests.choose(&mut rand::thread_rng()).unwrap().clone()] + } + let start_time = Instant::now(); + let rs = self.call(requests).await; + let end_time = Instant::now(); + let mut state = self.state.try_lock().unwrap(); + //NOTE: This is per workload stats + state.fn_stats.operation_completed(end_time - start_time); + + match rs { + Ok(_) => Ok((cycle, end_time)), + Err(_) => Ok((cycle, end_time)), + } + } + + /// Returns the reference to the contained context. + /// Allows to e.g. access context stats. + pub fn context(&self) -> &Context { + &self.context + } + + /// Sets the workload start time and resets the counters. + /// Needed for producing `WorkloadStats` with + /// recorded start and end times of measurement. + pub fn reset(&self, start_time: Instant) { + let mut state = self.state.try_lock().unwrap(); + state.fn_stats = FnStats::default(); + state.start_time = start_time; + self.context.reset_session_stats(); + } + + /// Returns statistics of the operations invoked by this workload so far. + /// Resets the internal statistic counters. + pub fn take_stats(&self, end_time: Instant) -> WorkloadStats { + let mut state = self.state.try_lock().unwrap(); + let result = WorkloadStats { + start_time: state.start_time, + end_time, + workload_stats: state.fn_stats.clone(), + session_stats: self.context().take_session_stats(), + }; + state.start_time = end_time; + state.fn_stats = FnStats::default(); + result + } +}