diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml deleted file mode 100644 index f6ea319..0000000 --- a/.github/release-drafter.yml +++ /dev/null @@ -1,7 +0,0 @@ -# https://github.com/release-drafter/release-drafter#example -name-template: 'resp-benchmark-v$RESOLVED_VERSION' -tag-template: 'v$RESOLVED_VERSION' -template: | - ## Changes - - $CHANGES diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..ce2bc11 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,165 @@ +name: CI + +on: + push: + branches: + - main + - master + tags: + - '*' + pull_request: + workflow_dispatch: + + +permissions: + contents: read + +jobs: + linux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-latest + target: x86_64 + - runner: ubuntu-latest + target: x86 + - runner: ubuntu-latest + target: aarch64 + - runner: ubuntu-latest + target: armv7 + - runner: ubuntu-latest + target: s390x + - runner: ubuntu-latest + target: ppc64le + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-linux-${{ matrix.platform.target }} + path: dist + + musllinux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-latest + target: x86_64 + - runner: ubuntu-latest + target: x86 + - runner: ubuntu-latest + target: aarch64 + - runner: ubuntu-latest + target: armv7 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + manylinux: musllinux_1_2 + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-musllinux-${{ matrix.platform.target }} + path: dist + + windows: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: windows-latest + target: x64 + - runner: windows-latest + target: x86 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + architecture: ${{ matrix.platform.target }} + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-windows-${{ matrix.platform.target }} + path: dist + + macos: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: macos-12 + target: x86_64 + - runner: macos-14 + target: aarch64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-macos-${{ matrix.platform.target }} + path: dist + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: wheels-sdist + path: dist + + release: + name: Release + runs-on: ubuntu-latest + if: "startsWith(github.ref, 'refs/tags/')" + needs: [linux, musllinux, windows, macos, sdist] + steps: + - uses: actions/download-artifact@v4 + - name: Publish to PyPI + uses: PyO3/maturin-action@v1 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + with: + command: upload + args: --non-interactive --skip-existing wheels-*/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 33338e1..5baad95 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,122 +1,23 @@ -name: release +name: Release -# Controls when the workflow will run on: - # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: - draft: - name: draft + release: runs-on: ubuntu-latest steps: - - name: draft release - id: "draft" - uses: release-drafter/release-drafter@v5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - outputs: - upload_url: ${{ steps.draft.outputs.upload_url }} - - build-and-upload: - needs: draft - name: build - ${{ matrix.platform.release_for }} - strategy: - matrix: - platform: - - release_for: linux-amd64 - os: ubuntu-20.04 - target: x86_64-unknown-linux-gnu - bin: resp-benchmark - name: resp-benchmark-linux-amd64.tar.gz - command: build - - - release_for: linux-arm64 - os: ubuntu-20.04 - target: aarch64-unknown-linux-gnu - bin: resp-benchmark - name: resp-benchmark-linux-arm64.tar.gz - command: build - - - release_for: windows-amd64 - os: windows-latest - target: x86_64-pc-windows-msvc - bin: resp-benchmark.exe - name: resp-benchmark-windows-amd64.zip - command: build - - - release_for: windows-arm64 - os: windows-latest - target: aarch64-pc-windows-msvc - bin: resp-benchmark.exe - name: resp-benchmark-windows-arm64.zip - command: build - - - release_for: macos-amd64 - os: macos-latest - target: x86_64-apple-darwin - bin: resp-benchmark - name: resp-benchmark-macos-amd64.tar.gz - command: build - - - release_for: macos-arm64 - os: macos-latest - target: aarch64-apple-darwin - bin: resp-benchmark - name: resp-benchmark-macos-arm64.tar.gz - command: build + - uses: actions/checkout@v2 - runs-on: ${{ matrix.platform.os }} - steps: - - name: checkout - uses: actions/checkout@v3 - - - name: cache cargo registry - uses: actions/cache@v3 + - name: Set up Rust + uses: actions/setup-rust@v1 with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo-registry- + rust-version: stable - - name: cache cargo index - uses: actions/cache@v3 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo-index- + - name: Install cargo-release + run: cargo install cargo-release - - name: cache cargo build - uses: actions/cache@v3 - with: - path: target - key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo-build- - - - name: build binary - uses: houseabsolute/actions-rust-cross@v0 - with: - command: ${{ matrix.platform.command }} - target: ${{ matrix.platform.target }} - args: "--locked --release" - strip: true - - name: Package as archive - shell: bash - run: | - cd target/${{ matrix.platform.target }}/release - if [[ "${{ matrix.platform.os }}" == "windows-latest" ]]; then - 7z a ${{ matrix.platform.name }} ${{ matrix.platform.bin }} ../../../workloads - else - tar czvf ${{ matrix.platform.name }} ${{ matrix.platform.bin }} ../../../workloads - fi - - name: upload - uses: actions/upload-release-asset@v1 - with: - upload_url: ${{ needs.draft.outputs.upload_url }} - asset_path: ./target/${{ matrix.platform.target }}/release/${{ matrix.platform.name }} - asset_name: ${{ matrix.platform.name }} - asset_content_type: ${{ matrix.platform.os == 'windows-latest' && 'application/zip' || 'application/gzip' }} + - name: Run cargo release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: cargo release patch --no-publish diff --git a/.gitignore b/.gitignore index 96ef862..c8f0442 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,72 @@ -target/ +/target + +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ +venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm .idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version diff --git a/Cargo.lock b/Cargo.lock index f64de0a..1352bd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,93 +18,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "aho-corasick" -version = "1.1.3" +name = "arc-swap" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[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 = "anstream" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" - -[[package]] -name = "anstyle-parse" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" -dependencies = [ - "anstyle", - "windows-sys 0.52.0", -] - -[[package]] -name = "anyhow" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -134,23 +61,11 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "bitflags" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" - -[[package]] -name = "bumpalo" -version = "3.16.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "byteorder" @@ -160,15 +75,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" -version = "1.0.100" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c891175c3fb232128f48de6590095e59198bbeb8620c310be349bfc3afd12c7b" +checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549" [[package]] name = "cfg-if" @@ -177,64 +92,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-targets 0.52.5", -] - -[[package]] -name = "clap" -version = "4.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.68", -] - -[[package]] -name = "clap_lex" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" - -[[package]] -name = "colorchoice" -version = "1.0.1" +name = "cfg_aliases" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "colored" @@ -260,22 +121,6 @@ dependencies = [ "tokio-util", ] -[[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_affinity" version = "0.8.1" @@ -294,26 +139,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff" [[package]] -name = "crc32fast" -version = "1.4.2" +name = "ctrlc" +version = "3.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" dependencies = [ - "cfg-if", + "nix", + "windows-sys 0.52.0", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "either" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" - [[package]] name = "enum_delegate" version = "0.2.0" @@ -338,34 +172,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "enum_dispatch" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" -dependencies = [ - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.68", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "flate2" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -431,7 +237,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.72", ] [[package]] @@ -481,12 +287,6 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - [[package]] name = "heck" version = "0.5.0" @@ -499,29 +299,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" -[[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 = "idna" version = "0.5.0" @@ -533,29 +310,10 @@ dependencies = [ ] [[package]] -name = "indexmap" -version = "2.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.0" +name = "indoc" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "itoa" @@ -563,15 +321,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "js-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -596,9 +345,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" @@ -606,6 +355,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -623,55 +381,45 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ + "hermit-abi", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] -name = "nom" -version = "7.1.3" +name = "nix" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "memchr", - "minimal-lexical", + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", ] [[package]] -name = "num" -version = "0.4.3" +name = "nom" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", + "memchr", + "minimal-lexical", ] [[package]] name = "num-bigint" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ + "num-integer", "num-traits", ] @@ -684,28 +432,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -725,22 +451,11 @@ dependencies = [ "libc", ] -[[package]] -name = "number_range" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60080faccd4ca50ad0b801b2be686136376b13f691f6eac84817e40973b2e1bb" -dependencies = [ - "anyhow", - "itertools", - "num", -] - [[package]] name = "object" -version = "0.36.0" +version = "0.36.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" dependencies = [ "memchr", ] @@ -751,12 +466,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - [[package]] name = "parking_lot" version = "0.12.3" @@ -777,7 +486,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -798,11 +507,20 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "portable-atomic" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" + [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro2" @@ -814,10 +532,67 @@ dependencies = [ ] [[package]] -name = "queues" -version = "1.1.0" +name = "pyo3" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1475abae4f8ad4998590fe3acfe20104f0a5d48fc420c817cd2c09c3f56151f0" +checksum = "831e8e819a138c36e212f3af3fd9eeffed6bf1510a805af35b0edee5ffa59433" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8730e591b14492a8945cdff32f089250b05f5accecf74aeddf9e8272ce1fa8" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e97e919d2df92eb88ca80a037969f44e5e70356559654962cbb3316d00300c6" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb57983022ad41f9e683a599f2fd13c3664d7063a3ac5714cae4b7bee7d3f206" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec480c0c51ddec81019531705acac51bcdbeae563557c982aa8263bb96880372" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn 2.0.72", +] [[package]] name = "quote" @@ -860,10 +635,11 @@ dependencies = [ [[package]] name = "redis" -version = "0.25.4" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0d7a6955c7511f60f3ba9e86c6d02b3c3f144f8c24b288d1f4e18074ab8bbec" +checksum = "e902a69d09078829137b4a5d9d082e0490393537badd7c91a3d69d14639e115f" dependencies = [ + "arc-swap", "async-trait", "bytes", "combine", @@ -872,273 +648,67 @@ dependencies = [ "futures-util", "itoa", "log", + "num-bigint", "percent-encoding", "pin-project-lite", "rand", - "rustls", - "rustls-native-certs", - "rustls-pemfile", - "rustls-pki-types", "ryu", "sha1_smol", "socket2", "tokio", - "tokio-rustls", "tokio-util", "url", ] [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags", ] [[package]] -name = "regex" -version = "1.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" - -[[package]] -name = "resp-benchmark" +name = "resp-benchmark-rust-lib" version = "0.1.1" dependencies = [ "awaitgroup", - "chrono", - "clap", "colored", "core_affinity", + "ctrlc", "enum_delegate", - "enum_dispatch", - "futures", "nom", - "num_cpus", - "number_range", - "queues", + "pyo3", "rand", "redis", - "rust_xlsxwriter", - "serde", - "serde_json", "tokio", - "toml", "zipf", ] -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rust_xlsxwriter" -version = "0.66.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eea20da37f12cb312effc50d90a8dab9540648972ec6fbd1e3acb9618eb9fb23" -dependencies = [ - "lazy_static", - "regex", - "rust_xlsxwriter_derive", - "serde", - "zip", -] - -[[package]] -name = "rust_xlsxwriter_derive" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "400cc5bbf116143f0ed897d7908b49f604feea981414ef644731e62449d1cc44" -dependencies = [ - "quote", - "syn 2.0.68", -] - [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustls" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" -dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" -dependencies = [ - "openssl-probe", - "rustls-pemfile", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" -dependencies = [ - "base64", - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" - -[[package]] -name = "rustls-webpki" -version = "0.102.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" -[[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 = "security-framework" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "serde" -version = "1.0.203" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.203" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.68", -] - -[[package]] -name = "serde_json" -version = "1.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" -dependencies = [ - "serde", -] - [[package]] name = "sha1_smol" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "signal-hook-registry" @@ -1174,24 +744,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5" - [[package]] name = "syn" version = "1.0.109" @@ -1205,20 +757,26 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.68" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -1231,43 +789,31 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", -] - -[[package]] -name = "tokio-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" -dependencies = [ - "rustls", - "rustls-pki-types", - "tokio", + "syn 2.0.72", ] [[package]] @@ -1283,40 +829,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - [[package]] name = "unicode-bidi" version = "0.3.15" @@ -1339,10 +851,10 @@ dependencies = [ ] [[package]] -name = "untrusted" -version = "0.9.0" +name = "unindent" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "url" @@ -1355,72 +867,12 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[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.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.68", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.68", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" - [[package]] name = "winapi" version = "0.3.9" @@ -1443,15 +895,6 @@ 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.5", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -1467,7 +910,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -1487,18 +930,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -1509,9 +952,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -1521,9 +964,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -1533,15 +976,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -1551,9 +994,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -1563,9 +1006,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -1575,9 +1018,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -1587,35 +1030,29 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winnow" -version = "0.6.13" +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "memchr", + "byteorder", + "zerocopy-derive", ] [[package]] -name = "zeroize" -version = "1.8.1" +name = "zerocopy-derive" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zip" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ - "byteorder", - "crc32fast", - "crossbeam-utils", - "flate2", + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0061bd1..a9bbdfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,27 +1,29 @@ [package] -name = "resp-benchmark" +name = "resp-benchmark-rust-lib" version = "0.1.1" edition = "2021" description = "A performance benchmarking tool for RESP protocol databases." +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "_resp_benchmark_rust_lib" +crate-type = ["cdylib"] + [dependencies] +pyo3 = { version = "0.22.2", features = ["extension-module"] } tokio = { version = "1", features = ["full"] } -redis = { version = "0.25", features = ["tokio-comp", "cluster", "tokio-rustls-comp", "tls-rustls-insecure", "cluster-async"] } -num_cpus = "1.16.0" -serde = { version = "1.0.193", features = ["derive"] } -toml = "0.8.8" +redis = { version = "0.26.1", features = [ + "tokio-comp", + "cluster", + "cluster-async", +# "tls-native-tls", +# "tokio-native-tls-comp", +] } rand = { version = "0.8.5", features = [] } zipf = "7.0.1" -clap = { version = "4.5.4", features = ["derive"] } nom = "7.1.3" -number_range = "0.3.2" core_affinity = "0.8.1" awaitgroup = "0.7.0" colored = "2.1.0" -rust_xlsxwriter = { version = "0.66.0", features = ["serde"] } -chrono = "0.4.38" -queues = "1.1.0" -futures = "0.3.30" -enum_dispatch = "0.3.13" enum_delegate = "0.2.0" -serde_json = "1.0.117" +ctrlc = "3.4.4" diff --git a/README.md b/README.md index d133852..9ed45b9 100644 --- a/README.md +++ b/README.md @@ -2,128 +2,68 @@ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/your_username/resp-benchmark/blob/main/LICENSE) -`resp-benchmark` is a high-performance benchmarking tool designed for evaluating the performance of various databases -such as Redis, Valkey, Kvrocks, Tair, and more. It provides a robust and reproducible way to measure the throughput and -latency of database operations under different workloads. +用来测试兼容 RESP 协议的数据库,比如 Redis、Valkey、Tair 等,提供命令行工具与 Python 库(方便编写自动化测试脚本)。 -![./docs/demo.png](./docs/demo.png) +## 安装 -## Features - -- **🔄 Cluster and standalone modes**: Benchmark in both cluster and standalone setups. -- **⚙️ Adaptive connections**: Auto-adjust connection count based on database performance. -- **📝 Customizable workloads**: Define your workloads easily with TOML files. -- **🔧 Placeholders for custom commands**: Easily write performance tests with placeholders. -- **📊 Detailed output formats**: Export results in JSON and XLSX for analysis. -- **💡 Efficient resource use**: High performance on smaller machines. - -## Getting Started - -### Prerequisites - -Ensure you have Rust installed. If not, you can install it from [rust-lang.org](https://www.rust-lang.org/). +```bash +pip install resp-benchmark +``` -### Installation +## 使用 -Clone the repository and build the project: +### 命令行工具 -```shell -git clone https://github.com/your_username/resp-benchmark.git -cd resp-benchmark -cargo build --release +```bash +resp-benchmark --help ``` -### Running Benchmarks - -To run a benchmark, use the following command: +### Python 库 -```shell -./target/release/resp-benchmark ./workloads/example.toml -``` +```python +from resp_benchmark import Benchmark -## Configuration - -The benchmark configuration is defined in a TOML file. Below is an example configuration: - -```toml -cluster = false -address = "127.0.0.1:7001" -username = "" -password = "" -tls = false -db_type = "redis" # redis, kvrocks, tair_mem, tair_scm, tair_ssd, garnet -output = ["xlsx", "json"] -replica_count = 0 - -# Leave empty to use all available CPUs. You can specify CPU cores, -# such as "0,1,6-10" to use cores 0, 1, 6, 7, 8, 9, and 10. -cpus = "" - -[[cases]] -name = "STRING: SET 64B" -command = "SET {key uniform 50000000} {value 64}" -connections = 8 -pipeline = 8 -count = 100_000 - -[[cases]] -name = "SET: SISMEMBER" -dataset = { command = "SADD {key sequence 10} {key sequence 107}", count = 1007_000 } -command = "SISMEMBER {key uniform 10} {key uniform 1007}" -connections = 0 -pipeline = 1 -seconds = 10 +bm = Benchmark(host="127.0.0.1", port=6379) +bm.flushall() +bm.load_data(command="SET {key sequence 10000000} {value 64}", count=1000_0000, connections=128) +result = bm.bench("GET {key uniform 10000000}", seconds=3, connections=16) +print(result.qps, result.avg_latency_ms, result.p99_latency_ms) ``` -### Field Descriptions +## 自定义命令 -#### Global Configuration +resp-benchmark 支持自定义要测试的命令,使用如下占位符语法:`SET {key uniform 10000000} {value 64}` 表示执行 SET 命令,key 的分布是 uniform,随机范围是 0-10000000,value 的大小是 64 字节 -- **`cluster`**: (Boolean) Specifies if the benchmark should run in cluster mode. Default is `false`. -- **`address`**: (String) The address of the database server, e.g., `"127.0.0.1:6379"`. -- **`username`**: (String) The username for database authentication. Leave empty if not required. -- **`password`**: (String) The password for database authentication. Leave empty if not required. -- **`tls`**: (Boolean) Enables TLS for secure connections. Default is `false`. -- **`db_type`**: (String) Specifies the type of database. Options include `redis`, `kvrocks`, `tair_mem`, `tair_scm`, `tair_ssd`, `garnet`. -- **`output`**: (Array of Strings) Specifies the output formats for the results. Options include `xlsx`, `json`. +支持的占位符有: +- **`{key uniform N}`**: 生成范围在 `0` 至 `N-1` 的随机数。比如 `{key uniform 100}` 可能会生成 `key_0000000099`。 +- **`{key sequence N}`**: 同上,但是是顺序产生,用于在加载数据时保证数据覆盖。比如 `{key sequence 100}` 会生成 `key_0000000000`, `key_0000000001`, ... +- **`{key zipfian N}`**: 同上,但是分布是指数为 1.03 的 Zipfian 分布,用于模拟真实场景下的 key 分布。 +- **`{value N}`**: 生成长度为 `N` 字节的随机字符串。比如 `{value 64}` 可能会生成 `92xsqdNgAyKcqtR4pyXz7j1GQAlRJQJ9TagOmCZ5xR3q3UCXl6B7QysZfgYd4Vmd`。 +- **`{rand N}`**: 生成一个 `0` 到 `N-1` 之间的随机数。比如 `{rand 100}` 可能会生成 `99`。 +- **`{range N W}`**: 生成一对随机数,两个数字的范围是 `0` 到 `N-1`,两个数字的差值是 `W`,用来测试各类 `*range*` 命令。比如 `{range 100 10}` 可能会生成 `89 99`。 -#### Resource Configuration +## 最佳实践 -- **`cpus`**: (String) Specifies the CPU cores to use. Leave empty to use all available CPUs. Format: `"0,1,6-10"`. -#### Test Cases +### 压测 zset -Each test case is defined in the `[[cases]]` array. - -- **`name`**: (String) A descriptive name for the test case. -- **`command`**: (String) The command to be executed. Supports placeholders for dynamic data generation. -- **`connections`**: (Integer) The number of concurrent connections. Set to `0` for automatic adjustment. -- **`pipeline`**: (Integer) The number of commands sent in one batch. Must be `1` in cluster mode. -- **`count`**: (Integer) The number of operations to perform. Use either `count` or `seconds`, not both. -- **`seconds`**: (Integer) The duration to run the test case in seconds. Use either `count` or `seconds`, not both. -- **`dataset`**: (Table) Predefined dataset commands for initializing data before running the test case. +```shell +# 1. 加载数据 +resp-benchmark --load -n 1000000 -P 10 "ZADD {key sequence 1000} {rand 1000} {value 8}" +# 2. 压测 +resp-benchmark "ZRANGEBYSCORE {key uniform 1000} {range 1000 10}" +``` -### Example Case Explanation +### 压测 lua script -```toml -[[cases]] -name = "STRING: SET 64B" -command = "SET {key uniform 50000000} {value 64}" -connections = 8 -pipeline = 8 -count = 100_000 +```shell +redis-cli 'SCRIPT LOAD "return redis.call('\''SET'\'', KEYS[1], ARGV[1])"' +resp-benchmark "EVALSHA d8f2fad9f8e86a53d2a6ebd960b33c4972cacc37 1 {key uniform 100000} {value 64}" ``` -- **`name`**: "STRING: SET 64B" – This is a descriptive name for the test. -- **`command`**: "SET {key uniform 50000000} {value 64}" – Executes a SET command with placeholders for keys and values. -- **`connections`**: 8 – Uses 8 concurrent connections. -- **`pipeline`**: 8 – Sends 8 commands in one batch. -- **`count`**: 100,000 – Executes the command 100,000 times. - -### Placeholder Definitions +## 与 redis-benchmark 的差异 -- **`{key uniform N}`**: Generates a random key in the range `0` to `N-1`, formatted as `key_{number}`. -- **`{key sequence N}`**: Generates sequential keys, cycling through `0` to `N-1`. -- **`{value N}`**: Generates a random string of length `N` bytes. -- **`{rand N}`**: Generates a random number between `0` and `N-1`. -- **`{range N W}`**: Generates random number pairs within `0` to `N-1` with a width `W`, e.g., `0-10`. +使用 resp-benchmark 与 redis-benchmark 测试 Redis 时,可能会得到不同的结果。常见有以下原因: +1. redis-benchmark 在测试 set 命令时总是使用相同的 value,这不会导致 DB 的持久化与复制机制。resp-benchmark 则可以使用 `{value 64}` 对每条命令都重新生成数据。 +2. redis-benchmark 在测试 list/set/zset/hash 等命令时,总是使用相同的 primary key,可能会导致多线程 DB 的性能数据失真。resp-benchmark 可以通过 `{key uniform 10000000}` 等占位符生成不同的 key。 +3. redis-benchmark 在集群模式下,发到不同节点的请求都被指定了相同的 slot,可能会导致多线程 DB 的性能数据失真。 \ No newline at end of file diff --git a/docs/demo.png b/docs/demo.png deleted file mode 100644 index 1d84b9f..0000000 Binary files a/docs/demo.png and /dev/null differ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8f61737 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[build-system] +requires = ["maturin>=1.7,<2.0"] +build-backend = "maturin" + +[project] +name = "resp-benchmark" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] +dependencies = ["pydantic"] + +[project.scripts] +# Python entry-point wrapper to be installed in `$venv/bin` +resp-benchmark = "resp_benchmark.cli:main" # Python function that uses Rust + +[tool.maturin] +module-name = "resp_benchmark._resp_benchmark_rust_lib" +features = ["pyo3/extension-module"] +python-source = "python" diff --git a/python/resp_benchmark/__init__.py b/python/resp_benchmark/__init__.py new file mode 100644 index 0000000..0eb9482 --- /dev/null +++ b/python/resp_benchmark/__init__.py @@ -0,0 +1 @@ +from .wrapper import Benchmark, Result, ResultPoint diff --git a/python/resp_benchmark/cli.py b/python/resp_benchmark/cli.py new file mode 100644 index 0000000..4fe993a --- /dev/null +++ b/python/resp_benchmark/cli.py @@ -0,0 +1,43 @@ +import argparse +from importlib.metadata import version + +from resp_benchmark.wrapper import Benchmark + + +def parse_args(): + parser = argparse.ArgumentParser( + description="RESP Benchmark Tool", + add_help=False, + ) + + parser.add_argument("-h", metavar="host", default="127.0.0.1", help="Server hostname (default 127.0.0.1)") + parser.add_argument("-p", metavar="port", type=int, default=6379, help="Server port (default 6379)") + parser.add_argument("-u", metavar="username", type=str, default="", help="Used to send ACL style \"AUTH username pass\". Needs -a.") + parser.add_argument("-a", metavar="password", type=str, default="", help="Password for Redis Auth") + parser.add_argument("-c", metavar="clients", type=int, default=50, help="Number of parallel connections (default 50)") + parser.add_argument("--cores", type=str, default=f"", help="Comma-separated list of CPU cores to use.") + parser.add_argument("--cluster", action="store_true", help="Enable cluster mode.") + parser.add_argument("-n", metavar="requests", type=int, default=100000, help="Total number of requests (default 100000), 0 for unlimited.") + parser.add_argument("-s", metavar="seconds", type=int, default=0, help="Total time in seconds (default 0), 0 for unlimited.") + parser.add_argument("-P", metavar="pipeline", type=int, default=1, help="Pipeline requests. Default 1 (no pipeline).") + # parser.add_argument("--tls", action="store_true", help="Use TLS for connection (default false)") + parser.add_argument("--load", action="store_true", help="Only load data to Redis, no benchmark.") + parser.add_argument('-v', '--version', action='version', version=version('resp_benchmark')) + parser.add_argument("--help", action="help", help="Output this help and exit.") + parser.add_argument("command", type=str, default="SET {key uniform 100000} {value 64}", nargs="?", help="The Redis command to benchmark (default SET {key uniform 100000} {value 64})") + + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + bm = Benchmark(host=args.h, port=args.p, username=args.u, password=args.a, cluster=args.cluster, cores=args.cores, timeout=30) + if args.load: + bm.load_data(command=args.command, connections=args.c, pipeline=args.P, count=args.n) + else: + bm.bench(command=args.command, connections=args.c, pipeline=args.P, count=args.n, seconds=args.s) + + +if __name__ == "__main__": + main() diff --git a/python/resp_benchmark/cores.py b/python/resp_benchmark/cores.py new file mode 100644 index 0000000..dd09b8e --- /dev/null +++ b/python/resp_benchmark/cores.py @@ -0,0 +1,24 @@ +import sys +from itertools import chain +from typing import List +import multiprocessing + + +def parse_range_list(rl): + def parse_range(r): + if len(r) == 0: + return [] + parts = r.split("-") + if len(parts) > 2: + raise ValueError("Invalid range: {}".format(r)) + return range(int(parts[0]), int(parts[-1]) + 1) + + return sorted(set(chain.from_iterable(map(parse_range, rl.split(","))))) + + +def parse_cores_string(cores) -> List[int]: + try: + return parse_range_list(cores) + except ValueError: + print(f"Invalid cores range: {cores}.") + sys.exit(1) diff --git a/python/resp_benchmark/wrapper.py b/python/resp_benchmark/wrapper.py new file mode 100644 index 0000000..8f04418 --- /dev/null +++ b/python/resp_benchmark/wrapper.py @@ -0,0 +1,178 @@ +import multiprocessing +from dataclasses import dataclass +from typing import List + +import pydantic +import redis + +from .cores import parse_cores_string + + +@dataclass +class ResultPoint: + """ + Represents a single data point in benchmark results. + + Attributes: + timestamp_second (int): Unix timestamp in seconds. + qps (float): Queries per second at this timestamp. + avg_latency_ms (float): Average latency in milliseconds at this timestamp. + p99_latency_ms (float): 99th percentile latency in milliseconds at this timestamp. + """ + timestamp_second: int + qps: float + avg_latency_ms: float + p99_latency_ms: float + + +@dataclass +class Result: + """ + Represents the overall result of a benchmark. + + Attributes: + qps (float): Average queries per second. + avg_latency_ms (float): Average latency in milliseconds. + p99_latency_ms (float): 99th percentile latency in milliseconds. + per_second_data (List[ResultPoint]): List of per-second data points. + """ + qps: float + avg_latency_ms: float + p99_latency_ms: float + per_second_data: List[ResultPoint] + + +class Benchmark: + """ + A class to perform and manage benchmark tests on a Redis server. + + Attributes: + host (str): The host address of the Redis server. + port (int): The port number of the Redis server. + username (str): The username for authentication. + password (str): The password for authentication. + cluster (bool): Whether to connect to a Redis cluster. + tls (bool): Whether to use TLS for the connection. + timeout (int): Timeout for the connection in seconds. + cores (str): Comma-separated list of CPU cores to use. + """ + + @pydantic.validate_call + def __init__( + self, + host: str = "127.0.0.1", + port: int = 6379, + username: str = "", + password: str = "", + cluster: bool = False, + # tls: bool = False, + timeout: int = 30, + cores: str = "", + ): + self.host = host + self.port = port + self.username = username + self.password = password + self.cluster = cluster + # self.tls = tls + self.timeout = timeout + if cores == "": + cores = f"0-{multiprocessing.cpu_count() - 1}" + self.cores = parse_cores_string(cores) + + def bench( + self, + command: str, + connections: int = 32, + pipeline: int = 1, + count: int = 100000, + seconds: int = 0, + quiet: bool = False, + ) -> Result: + """ + Runs a benchmark test with the specified parameters. + + Args: + command (str): The Redis command to benchmark. + connections (int): The number of parallel connections. + pipeline (int): The number of commands to pipeline. + count (int): The total number of requests to make. + seconds (int): The duration of the test in seconds. + quiet: (bool): Whether to suppress output. + Returns: + Result: The results of the benchmark test. + """ + from . import _resp_benchmark_rust_lib + ret = _resp_benchmark_rust_lib.benchmark( + host=self.host, + port=self.port, + username=self.username, + password=self.password, + cluster=self.cluster, + tls=False, # TODO: Implement TLS support + timeout=self.timeout, + cores=self.cores, + + command=command, + connections=connections, + pipeline=pipeline, + count=count, + seconds=seconds, + load=False, + quiet=quiet, + ) + result = Result( + qps=ret.qps, + avg_latency_ms=ret.avg_latency_ms, + p99_latency_ms=ret.p99_latency_ms, + per_second_data=[ + ResultPoint( + timestamp_second=point.timestamp_second, + qps=point.qps, + avg_latency_ms=point.avg_latency_ms, + p99_latency_ms=point.p99_latency_ms, + ) + for point in ret.per_second_data + ], + ) + + return result + + def load_data(self, command: str, count: int, connections: int = 128, pipeline: int = 10, quiet: bool = False): + """ + Load data into the Redis server using the specified command. + + Args: + command (str): The Redis command to use for loading data. + count (int): The total number of requests to make. + connections (int): The number of parallel connections. + pipeline (int): The number of commands to pipeline + quiet: (bool): Whether to suppress output. + """ + + from . import _resp_benchmark_rust_lib + _resp_benchmark_rust_lib.benchmark( + host=self.host, + port=self.port, + username=self.username, + password=self.password, + cluster=self.cluster, + tls=self.tls, + timeout=self.timeout, + cores=self.cores, + + command=command, + connections=connections, + pipeline=pipeline, + count=count, + seconds=0, + load=True, + quiet=quiet, + ) + + def flushall(self): + """ + Clears all data from all Redis databases. + """ + r = redis.Redis(host=self.host, port=self.port, username=self.username, password=self.password) + r.flushall() diff --git a/src/common/async_flag.rs b/src/async_flag.rs similarity index 99% rename from src/common/async_flag.rs rename to src/async_flag.rs index 1f73090..139cf1e 100644 --- a/src/common/async_flag.rs +++ b/src/async_flag.rs @@ -20,6 +20,7 @@ impl AsyncFlag { self.sender.send(true).unwrap(); } } + impl Clone for AsyncFlag { fn clone(&self) -> Self { Self { diff --git a/src/limiter.rs b/src/auto_connection.rs similarity index 52% rename from src/limiter.rs rename to src/auto_connection.rs index dbaf5a7..9b63f5b 100644 --- a/src/limiter.rs +++ b/src/auto_connection.rs @@ -4,24 +4,30 @@ use tokio::sync::Notify; const MAX_CONN: u64 = if cfg!(target_os = "macos") { 64 } else { 1024 }; // 1024 is enough for most cases -pub struct Limiter { - pub tcp_conn: u64, +pub struct ConnLimiter { + pub total_conn: u64, // total connection count >= active_conn active_conn: AtomicU64, - target_conn: AtomicU64, + target_conn: AtomicU64, // The target connection count notify_add: Notify, } -impl Limiter { - pub fn new(tcp_conn: u64) -> Self { - Limiter { - tcp_conn, +impl ConnLimiter { + pub fn new(total_conn: u64, target_conn: u64) -> Self { + ConnLimiter { + total_conn, active_conn: AtomicU64::new(0), - target_conn: AtomicU64::new(0), + target_conn: AtomicU64::new(target_conn), notify_add: Notify::new(), } } - pub async fn wait_add(&self) { + pub async fn wait_new_conn(&self) { + let active_conn = self.active_conn.load(std::sync::atomic::Ordering::SeqCst); + let target_conn = self.target_conn.load(std::sync::atomic::Ordering::SeqCst); + if active_conn < target_conn { + self.active_conn.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + return; + } loop { self.notify_add.notified().await; let active_conn = self.active_conn.load(std::sync::atomic::Ordering::SeqCst); @@ -37,17 +43,19 @@ impl Limiter { } } pub fn add_conn(&self) { - let old_val = self.target_conn.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - if old_val >= self.tcp_conn { + let target_conn = self.target_conn.load(std::sync::atomic::Ordering::SeqCst); + if target_conn >= self.total_conn { return; } + self.target_conn.fetch_add(1, std::sync::atomic::Ordering::SeqCst); loop { - self.notify_add.notify_waiters(); + self.notify_add.notify_one(); let active_conn = self.active_conn.load(std::sync::atomic::Ordering::SeqCst); let target_conn = self.target_conn.load(std::sync::atomic::Ordering::SeqCst); if active_conn >= target_conn { - break; + return; } + std::thread::sleep(std::time::Duration::from_millis(1)); } } pub fn get_active_conn(&self) -> u64 { @@ -58,48 +66,37 @@ impl Limiter { } } -pub struct AutoLimiter { - pub auto: bool, +pub struct AutoConnection { pub ready: bool, - pub limiters: Vec>, - max_conn: u64, - target_conn: u64, + pub limiters: Vec>, last_cnt: u64, last_qps: f64, - last_last_qps: f64, instant: std::time::Instant, - step: u64, inx: usize, } -impl AutoLimiter { - pub fn new(mut max_conn: u64, count: u64) -> Self { +impl AutoConnection { + pub fn new(active_conn: u64, thread_count: u64) -> Self { let mut limiters = Vec::new(); - let auto = max_conn == 0; - if auto { - max_conn = MAX_CONN; - } - let mut total_connection = max_conn; - let mut left_count = count; - for _ in 0..count { + let auto = active_conn == 0; + let mut total_connection = if auto { MAX_CONN } else { active_conn }; + let mut left_count = thread_count; + for _ in 0..thread_count { let my_conn = (total_connection + left_count - 1) / left_count; - limiters.push(Arc::new(Limiter::new(my_conn))); + let target_conn = if auto { 0 } else { my_conn }; + let conn_limiter = Arc::new(ConnLimiter::new(my_conn, target_conn)); + limiters.push(conn_limiter); total_connection -= my_conn; left_count -= 1; } - AutoLimiter { - auto, - ready: false, + AutoConnection { + ready: !auto, limiters, - max_conn, - target_conn: 0, last_cnt: 0, last_qps: 0.0, - last_last_qps: 0.0, instant: std::time::Instant::now(), - step: 1, inx: 0, } } @@ -111,38 +108,36 @@ impl AutoLimiter { self.limiters.iter().map(|limiter| limiter.get_target_conn()).sum() } - pub fn adjust(&mut self, cnt: u64) -> bool { + pub fn adjust(&mut self, cnt: u64) { if self.ready { - return false; - } - if !self.auto { - for _ in 0..self.max_conn { - self.limiters[self.inx].add_conn(); - self.inx = (self.inx + 1) % self.limiters.len(); - self.target_conn += 1; - } - self.ready = true; - return false; + return; } - let target_conn: u64 = self.limiters.iter().map(|limiter| limiter.get_target_conn()).sum(); - let qps = (cnt - self.last_cnt) as f64 / self.instant.elapsed().as_secs_f64(); - - if qps >= self.last_qps * 1.1 && target_conn + self.step * 2 < MAX_CONN { - self.step = self.step * 2; - self.last_last_qps = self.last_qps; - self.last_qps = qps; + let elapsed = self.instant.elapsed().as_secs_f64(); + if elapsed < 0.5 { + return; + } + let qps = (cnt - self.last_cnt) as f64 / elapsed; + let need_add_conn; + if qps >= self.last_qps * 1.5 || elapsed >= 3f64 { + if self.last_qps == 0.0 { + need_add_conn = 1; // at least 1 connection + } else if qps > self.last_qps * 1.1 { + need_add_conn = self.active_conn(); + } else { + self.ready = true; + return; + } } else { - self.step = 0; - self.ready = true; - return false; + return; } - for _ in 0..self.step { + for _ in 0..need_add_conn { self.limiters[self.inx].add_conn(); self.inx = (self.inx + 1) % self.limiters.len(); } + self.last_qps = qps; self.last_cnt = cnt; self.instant = std::time::Instant::now(); - return true; + return; } } diff --git a/src/bench.rs b/src/bench.rs new file mode 100644 index 0000000..8cff62d --- /dev/null +++ b/src/bench.rs @@ -0,0 +1,178 @@ +use std::io::Write; +use std::sync::Arc; +use awaitgroup::WaitGroup; +use colored::Colorize; +use tokio::{select, task}; + +use crate::BenchmarkResult; +use crate::client::ClientConfig; +use crate::command::Command; +use crate::auto_connection::{AutoConnection, ConnLimiter}; +use crate::shared_context::SharedContext; + +#[derive(Clone)] +pub struct Case { + pub command: Command, + pub connections: u64, + pub count: u64, + pub seconds: u64, + pub pipeline: u64, +} + +async fn run_commands_on_single_thread(limiter: Arc, config: ClientConfig, case: Case, context: SharedContext) { + let local = task::LocalSet::new(); + for _ in 0..limiter.total_conn { + let limiter = limiter.clone(); + let config = config.clone(); + let case = case.clone(); + let mut context = context.clone(); + local.spawn_local(async move { + let mut client = config.get_client().await; + let mut cmd = case.command.clone(); + let limiter = limiter.clone(); + select! { + _ = limiter.wait_new_conn() =>{} + _ = context.wait_stop() => { + return; + } + } + loop { + let pipeline_cnt = context.fetch(case.pipeline); + if pipeline_cnt == 0 { + context.stop(); + break; + } + + // prepare pipeline + let mut p = Vec::new(); + for _ in 0..pipeline_cnt { + p.push(cmd.gen_cmd()); + } + let instant = std::time::Instant::now(); + client.run_commands(p).await; + let duration = instant.elapsed().as_micros() as u64; + for _ in 0..pipeline_cnt { + context.histogram.record(duration); + } + } + }); + } + local.await; +} + +fn wait_finish(case: &Case, mut auto_connection: AutoConnection, mut context: SharedContext, mut wg: WaitGroup, load: bool, quiet: bool) -> BenchmarkResult { + let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); + let mut result = BenchmarkResult::default(); + + rt.block_on(async { + let histogram = context.histogram.clone(); + // calc overall qps + let mut overall_time = std::time::Instant::now(); + let mut overall_cnt_overhead = 0; + // for log + let mut log_instance = std::time::Instant::now(); + let mut log_last_cnt = histogram.cnt(); + let mut interval = tokio::time::interval(std::time::Duration::from_millis(233)); + + if auto_connection.ready { + context.start_timer(); + overall_time = std::time::Instant::now(); + overall_cnt_overhead = 0; + } + + loop { + select! { + _ = interval.tick() => {} + _ = wg.wait() => {break;} + } + { + let cnt = histogram.cnt(); + let qps = (cnt - log_last_cnt) as f64 / log_instance.elapsed().as_secs_f64(); + let active_conn: u64 = auto_connection.active_conn(); + let target_conn: u64 = auto_connection.target_conn(); + if auto_connection.ready { + result.qps = (cnt - overall_cnt_overhead) as f64 / overall_time.elapsed().as_secs_f64(); + } + if !quiet { + if load { + print!("\r\x1B[2KData loading qps: {:.0}, {:.2}%", qps, histogram.cnt() as f64 / case.count as f64 * 100f64); + } else { + print!("\r\x1B[2Kqps: {:.0}(overall {:.0}), active_conn: {}, target_conn: {}, {}", qps, result.qps, active_conn, target_conn, histogram); + } + } + std::io::stdout().flush().unwrap(); + log_last_cnt = cnt; + log_instance = std::time::Instant::now(); + } + if !auto_connection.ready { + let cnt = histogram.cnt(); + auto_connection.adjust(cnt); + if auto_connection.ready { + overall_cnt_overhead = histogram.cnt(); + overall_time = std::time::Instant::now(); + context.start_timer(); + } + } + } + let active_conn: u64 = auto_connection.active_conn(); + if load { + print!("\r\x1B[2KData loaded, qps: {:.0}, time elapsed: {:.2}s\n", result.qps, overall_time.elapsed().as_secs_f64()); + } else { + print!("\r\x1B[2Kqps: {:.0}, conn: {}, {}\n", result.qps, active_conn, histogram) + }; + result.avg_latency_ms = histogram.avg() as f64 / 1_000.0; + result.p99_latency_ms = histogram.percentile(0.99) as f64 / 1_000.0; + }); + return result; +} + +pub fn do_benchmark(client_config: ClientConfig, cores: Vec, case: Case, load: bool, quiet: bool) -> BenchmarkResult { + if !quiet { + println!("{}: {}", "command".bold().blue(), case.command.to_string().green().bold()); + println!("{}: {}", "connections".bold().blue(), if case.connections == 0 { "auto".to_string() } else { case.connections.to_string() }); + println!("{}: {}", "count".bold().blue(), case.count); + println!("{}: {}", "seconds".bold().blue(), case.seconds); + println!("{}: {}", "pipeline".bold().blue(), case.pipeline); + } + + // calc connections + let auto_connection = AutoConnection::new(case.connections, cores.len() as u64); + + let mut thread_handlers = Vec::new(); + let wg = WaitGroup::new(); + let core_ids = core_affinity::get_core_ids().unwrap(); + let context = SharedContext::new(case.count, case.seconds); + for inx in 0..cores.len() { + let client_config = client_config.clone(); + let case = case.clone(); + let context = context.clone(); + let wk = wg.worker(); + let core_id = core_ids[cores[inx] as usize]; + let limiter = auto_connection.limiters[inx].clone(); + let thread_handler = std::thread::spawn(move || { + core_affinity::set_for_current(core_id); // not work on Apple Silicon + let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); + rt.block_on(async { + run_commands_on_single_thread(limiter, client_config, case, context).await; + wk.done(); + }); + }); + thread_handlers.push(thread_handler); + } + + // log thread + let result = wait_finish(&case, auto_connection, context, wg, load, quiet); + + // join all threads + for thread_handler in thread_handlers { + loop { + if thread_handler.is_finished() { + thread_handler.join().unwrap(); + break; + } + std::thread::sleep(std::time::Duration::from_millis(100)); + } + } + + return result; +} \ No newline at end of file diff --git a/src/client.rs b/src/client.rs index 795aafc..854e781 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2,52 +2,17 @@ use enum_delegate; use redis::aio::ConnectionLike; use redis::aio::MultiplexedConnection; use redis::cluster_async::ClusterConnection; -use redis::cluster_routing::{RoutingInfo, SingleNodeRoutingInfo}; -use redis::{from_redis_value, Cmd, InfoDict, RedisFuture, Value}; -use serde::Deserialize; +use redis::{Cmd, RedisFuture, Value}; use std::fmt::{Display, Formatter}; -#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Default)] -#[serde(rename_all = "snake_case")] -pub enum DBType { - // https://github.com/redis/redis - #[default] - Redis, - // https://github.com/valkey-io/valkey - Valkey, - - // https://www.alibabacloud.com/product/tair - #[serde(rename = "tair_mem")] - TairMem, - #[serde(rename = "tair_scm")] - TairScm, - #[serde(rename = "tair_ssd")] - TairSsd, - - Garnet, - - // Others - #[serde(untagged)] - Unknown(String), -} - -#[derive(Deserialize, Clone, Debug)] -#[serde(deny_unknown_fields)] +#[derive(Clone)] pub struct ClientConfig { - #[serde(default)] pub cluster: bool, - #[serde(default)] pub address: String, - #[serde(default)] pub username: String, - #[serde(default)] pub password: String, - #[serde(default)] pub tls: bool, - #[serde(default)] - pub db_type: DBType, - #[serde(default)] - replica_count: u64, + pub timeout: u64, } impl ClientConfig { @@ -57,9 +22,10 @@ impl ClientConfig { } else { format!("redis://{}:{}@{}", &self.username, &self.password, &self.address) }; - return if self.cluster { + + if self.cluster { let nodes = vec![conn_str]; - let client = redis::cluster::ClusterClient::builder(nodes).connection_timeout(std::time::Duration::from_secs(5)).build().unwrap(); + let client = redis::cluster::ClusterClient::builder(nodes).connection_timeout(std::time::Duration::from_secs(self.timeout)).build().unwrap(); let conn = match client.get_async_connection().await { Ok(conn) => conn, Err(e) => { @@ -67,7 +33,7 @@ impl ClientConfig { std::process::exit(1); } }; - Client::new(conn.into(), self.db_type.clone(), self.replica_count) + Client::new(conn.into()) } else { let client = redis::Client::open(conn_str).unwrap(); let conn = match client.get_multiplexed_async_connection().await { @@ -77,8 +43,8 @@ impl ClientConfig { std::process::exit(1); } }; - Client::new(conn.into(), self.db_type.clone(), self.replica_count) - }; + Client::new(conn.into()) + } } } @@ -89,16 +55,16 @@ impl Display for ClientConfig { } #[enum_delegate::implement(ConnectionLike, - trait ConnectionLike { - fn req_packed_command<'a>(&'a mut self, cmd: &'a Cmd) -> RedisFuture<'a, Value>; - fn req_packed_commands<'a>( - &'a mut self, - cmd: &'a redis::Pipeline, - offset: usize, - count: usize, - ) -> RedisFuture<'a, Vec>; - fn get_db(&self) -> i64; - } +trait ConnectionLike { +fn req_packed_command < 'a > (& 'a mut self, cmd: & 'a Cmd) -> RedisFuture < 'a, Value >; +fn req_packed_commands < 'a > ( +& 'a mut self, +cmd: & 'a redis::Pipeline, +offset: usize, +count: usize, +) -> RedisFuture < 'a, Vec < Value >>; +fn get_db(& self) -> i64; +} )] enum ClientConnection { Standalone(MultiplexedConnection), @@ -107,56 +73,13 @@ enum ClientConnection { pub struct Client { conn: ClientConnection, - db_type: DBType, - replica_count: u64, } impl Client { - fn new(conn: ClientConnection, db_type: DBType, replica_count: u64) -> Client { - Client { conn, db_type, replica_count } - } - - pub async fn flushall(&mut self) { - // flushall - let cmd = { - let mut cmd = Cmd::new(); - match &self.db_type { - DBType::Garnet => cmd.arg("FLUSHDB"), - DBType::TairScm => cmd.arg("FLUSHALL"), - _ => cmd.arg("FLUSHALL"), - }; - cmd - }; - match cmd.query_async(&mut self.conn).await { - Ok(()) => {} - Err(e) => { - eprintln!("Failed to execute `flushall` command: {:?}", e); - std::process::exit(1); - } - } - - // wait - let mut cmd = Cmd::new(); - cmd.arg("WAIT").arg(self.replica_count).arg(0); - match cmd.query_async(&mut self.conn).await { - Ok(()) => {} - Err(e) => { - eprintln!("Failed to execute `wait` command: {:?}", e); - std::process::exit(1); - } - } + fn new(conn: ClientConnection) -> Client { + Client { conn } } - async fn info(&mut self, key: &str) -> InfoDict { - match self.conn { - ClientConnection::Standalone(ref mut conn) => redis::cmd("INFO").arg(key).query_async(conn).await.unwrap(), - ClientConnection::Cluster(ref mut conn) => { - let random = RoutingInfo::SingleNode(SingleNodeRoutingInfo::Random); - let value = conn.route_command(&mut redis::cmd("INFO").arg(key), random).await.unwrap(); - from_redis_value(&value).unwrap() - } - } - } pub async fn run_commands(&mut self, cmds: Vec) { let mut pipeline = redis::pipe(); @@ -171,28 +94,4 @@ impl Client { } } } - - /// return the `used_memory` in bytes - pub async fn info_memory(&mut self) -> u64 { - let (info_pkey, info_skey) = match &self.db_type { - DBType::Redis => ("MEMORY", "used_memory"), - DBType::Valkey => ("MEMORY", "used_memory"), - DBType::Garnet => ("MEMORY", "proc_physical_memory_size"), - DBType::TairMem => ("MEMORY", "used_memory"), - DBType::TairScm => ("PERSISTENCE", "used_pmem"), - DBType::TairSsd => ("PERSISTENCE", "data_used_disk_size"), - DBType::Unknown(db) => { - eprintln!("Unknown db type: {}", db); - std::process::exit(1); - } - }; - let info: InfoDict = self.info(info_pkey).await; - return match info.get(info_skey) { - Some::(used_memory) => used_memory, - None => { - eprintln!("Failed to get `used_memory` from `info memory` command: {:?}", info); - std::process::exit(1); - } - }; - } } diff --git a/src/command/mod.rs b/src/command/mod.rs index 1eb62a4..392386c 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -10,6 +10,7 @@ mod placeholder; pub struct Command { str: String, argv: Vec, + #[allow(dead_code)] lock: Arc>, } @@ -39,6 +40,7 @@ impl Command { } cmd } + #[allow(dead_code)] pub fn gen_cmd_with_lock(&mut self) -> redis::Cmd { let mut cmd = redis::Cmd::new(); let _lock = self.lock.lock().unwrap(); diff --git a/src/common/mod.rs b/src/common/mod.rs deleted file mode 100644 index 2d97c53..0000000 --- a/src/common/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod async_flag; diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 0f4b6d4..0000000 --- a/src/config.rs +++ /dev/null @@ -1,168 +0,0 @@ -use crate::client::ClientConfig; -use crate::command::Command; -use clap::{command, Parser}; -use number_range::NumberRangeOptions; -use serde::{Deserialize, Deserializer}; -use std::process::exit; - -#[derive(Deserialize, Clone, Debug)] -#[serde(deny_unknown_fields)] -pub struct Dataset { - pub command: Command, - pub count: u64, -} - -impl<'de> Deserialize<'de> for Command { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - Ok(Command::new(s.as_str())) - } -} -const fn _default_1() -> u64 { - 1 -} - -#[derive(Deserialize, Clone, Debug)] -#[serde(deny_unknown_fields)] -pub struct Case { - pub name: Option, - pub dataset: Option, - pub command: Command, - pub connections: u64, - #[serde(default)] - pub count: u64, - #[serde(default)] - pub seconds: u64, - #[serde(default = "_default_1")] - pub pipeline: u64, -} - -impl Default for Case { - fn default() -> Self { - Case { - name: None, - dataset: None, - command: Command::new("PING"), - connections: 0, - count: 0, - seconds: 0, - pipeline: 1, - } - } -} - -#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Default)] -#[serde(rename_all = "snake_case")] -pub enum OutputFormat { - #[default] - #[serde(rename = "xlsx")] - XLSX, - - #[serde(rename = "json")] - JSON, -} - -#[derive(Deserialize, Clone, Debug)] -#[serde(deny_unknown_fields)] -pub struct Config { - #[serde(flatten)] - pub client_config: ClientConfig, - #[serde(default)] - pub cpus: CPUS, - #[serde(default)] - #[serde(rename = "output")] - pub output_formats: Vec, - #[serde(default)] - pub cases: Vec, -} - -#[derive(Debug, Clone)] -pub struct CPUS(Vec); - -impl Default for CPUS { - fn default() -> Self { - let vec: Vec = (0..num_cpus::get() as u64).collect(); - CPUS(vec) - } -} - -impl CPUS { - pub fn iter(&self) -> std::slice::Iter { - self.0.iter() - } - pub fn get(&self, idx: usize) -> u64 { - self.0[idx] - } - pub fn len(&self) -> usize { - self.0.len() - } -} - -impl<'de> Deserialize<'de> for CPUS { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - if s.is_empty() { - let vec: Vec = (0..num_cpus::get() as u64).collect(); - Ok(CPUS(vec)) - } else { - let vec: Vec = NumberRangeOptions::new().with_list_sep(',').with_range_sep('-').parse(s.as_str()).unwrap().collect(); - Ok(CPUS(vec)) - } - } -} - -const AFTER_HELP: &str = r#""#; - -#[derive(Parser, Debug, Clone)] -#[command(version, about, long_about = None, after_help = AFTER_HELP)] -struct Cli { - /// Path to the workload file, e.g., `./workload/redis.toml` - workload: String, -} - -impl Config { - pub fn parse() -> Config { - let cli = Cli::parse(); - return Config::from_file(&cli.workload); - } - - pub fn from_file(path: &str) -> Config { - let contents = match std::fs::read_to_string(path) { - Ok(c) => c, - Err(e) => { - eprintln!("Could not read file `{}`: {}", path, e); - exit(1); - } - }; - - let conf: Config = match toml::from_str(&contents) { - Ok(c) => c, - Err(e) => { - eprintln!("Could not parse file `{}`: {}", path, e); - exit(1); - } - }; - return conf; - } - - pub fn client_config(&self) -> ClientConfig { - self.client_config.clone() - } -} - -#[cfg(test)] -mod tests { - use number_range::NumberRangeOptions; - - #[test] - fn test() { - let v: Vec = NumberRangeOptions::new().with_list_sep(',').with_range_sep('-').parse("1,3-10,14").unwrap().collect(); - println!("{:?}", v); - } -} diff --git a/src/counter.rs b/src/counter.rs deleted file mode 100644 index 10fae63..0000000 --- a/src/counter.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::cmp::min; -use std::sync::atomic::AtomicU64; -use tokio::sync::Notify; - -pub(crate) struct Counter { - current_count: AtomicU64, - max_count: u64, - stop_notify: Notify, -} -impl Counter { - pub fn new(max_count: u64) -> Self { - Counter { - current_count: AtomicU64::new(0), - max_count, - stop_notify: Notify::new(), - } - } - pub fn stop(&self) { - self.stop_notify.notify_waiters(); - } - pub async fn wait_stop(&self) { - self.stop_notify.notified().await; - } - pub fn fetch(&self, count: u64) -> u64 { - let prev_count = self.current_count.fetch_add(count, std::sync::atomic::Ordering::Relaxed); - if prev_count >= self.max_count { - return 0; - } - return min(self.max_count - prev_count, count); - } -} diff --git a/src/histogram.rs b/src/histogram.rs index 311c1b0..815556b 100644 --- a/src/histogram.rs +++ b/src/histogram.rs @@ -38,6 +38,7 @@ impl Histogram { self.buckets[index as usize].fetch_add(1, Ordering::Relaxed); } + #[allow(dead_code)] pub fn clear(&self) { self.cnt.store(0, Ordering::Relaxed); for i in 0..self.buckets.len() { @@ -45,6 +46,7 @@ impl Histogram { } } + #[allow(dead_code)] pub fn un_record(&self, latency_us: u64) { let index = match latency_us { 0..=999 => latency_us / 10, // <1ms precision 10us @@ -122,9 +124,8 @@ impl Display for Histogram { } let avg = self.avg(); let p99 = self.percentile(0.99); - let p999 = self.percentile(0.999); - write!(f, "cnt: {}, avg: {}, p99: {}, p999: {}", cnt, Histogram::humanize_us(avg), Histogram::humanize_us(p99), Histogram::humanize_us(p999)) + write!(f, "cnt: {}, avg: {}, p99: {}", cnt, Histogram::humanize_us(avg), Histogram::humanize_us(p99)) } } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..839fc60 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,85 @@ +mod bench; +mod client; +mod command; +mod auto_connection; +mod shared_context; +mod histogram; +mod async_flag; + +use ctrlc; +use pyo3::prelude::*; +use pyo3::wrap_pyfunction; +use crate::command::Command; + +/// A Python module implemented in Rust. +#[pymodule] +fn _resp_benchmark_rust_lib(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(benchmark, m)?)?; + Ok(()) +} + +#[pyclass] +#[derive(Clone)] +struct ResultPoint { + #[pyo3(get, set)] pub timestamp_second: i64, + #[pyo3(get, set)] pub qps: f64, + #[pyo3(get, set)] pub avg_latency_ms: f64, + #[pyo3(get, set)] pub p99_latency_ms: f64, +} + +#[pyclass] +#[derive(Default)] +struct BenchmarkResult { + #[pyo3(get, set)] pub qps: f64, + #[pyo3(get, set)] pub avg_latency_ms: f64, + #[pyo3(get, set)] pub p99_latency_ms: f64, + #[pyo3(get, set)] pub per_second_data: Vec, +} + +#[pyfunction] +fn benchmark( + host: String, + port: u16, + username: String, + password: String, + cluster: bool, + tls: bool, + timeout: u64, + cores: Vec, + command: String, + connections: u64, + pipeline: u64, + count: u64, + seconds: u64, + load: bool, + quiet: bool, +) -> PyResult { + assert!(cores.len() > 0); + if load { + assert_ne!(connections, 0); + assert_ne!(count, 0); + } + assert!(count != 0 || seconds != 0); + + let _ = ctrlc::set_handler(move || { + std::process::exit(0); + }); + + let client_config = client::ClientConfig { + cluster, + address: format!("{}:{}", host, port), + username, + password, + tls, + timeout, + }; + let case = bench::Case { + command: Command::new(command.as_str()), + connections, + pipeline, + count, + seconds, + }; + let result = bench::do_benchmark(client_config, cores, case, load, quiet); + Ok(result) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 960a2dd..0000000 --- a/src/main.rs +++ /dev/null @@ -1,264 +0,0 @@ -mod client; -mod command; -mod common; -mod config; -mod histogram; -mod limiter; -mod output; -mod shared_context; - -use crate::client::ClientConfig; -use crate::config::{Case, Dataset}; -use crate::limiter::{AutoLimiter, Limiter}; -use crate::shared_context::SharedContext; -use awaitgroup::WaitGroup; -use colored::Colorize; -use core_affinity; -pub use histogram::Histogram; -use std::cmp::min; -use std::io::Write; -use std::sync::atomic::AtomicU64; -use std::sync::Arc; -use tokio::task::JoinSet; -use tokio::{select, task}; - -async fn load_data(config: ClientConfig, dataset: Dataset, total_count: Arc, conn_per_thread: usize) { - let mut tasks = JoinSet::new(); - let pipeline_count = if config.cluster { 1 } else { 8 }; - for inx in 0..conn_per_thread { - let config = config.clone(); - let mut cmd = dataset.command.clone(); - let total_count = total_count.clone(); - let dataset = dataset.clone(); - tasks.spawn(async move { - let mut client = config.get_client().await; - loop { - let prev_count = total_count.fetch_add(pipeline_count, std::sync::atomic::Ordering::SeqCst); - if prev_count >= dataset.count { - break; - } - if inx == 0 && prev_count % 1007 == 0 { - print!("\r\x1B[2K{}: {} {}/{}", "dataset loading".blue().bold(), dataset.command.to_string().green().bold(), prev_count, dataset.count); - std::io::stdout().flush().unwrap(); - } - let pipeline_cnt = min(dataset.count - prev_count, pipeline_count); - let mut pipeline = Vec::new(); - for _ in 0..pipeline_cnt { - pipeline.push(cmd.gen_cmd_with_lock()); - } - client.run_commands(pipeline).await; - } - }); - } - for _ in 0..conn_per_thread { - tasks.join_next().await; - } -} - -async fn run_commands_on_single_thread(limiter: Arc, config: ClientConfig, case: Case, context: SharedContext) { - let local = task::LocalSet::new(); - for _ in 0..limiter.tcp_conn { - let limiter = limiter.clone(); - let config = config.clone(); - let case = case.clone(); - let mut context = context.clone(); - local.spawn_local(async move { - let mut client = config.get_client().await; - let mut cmd = case.command.clone(); - let limiter = limiter.clone(); - select! { - _ = limiter.wait_add() =>{} - _ = context.wait_stop() => { - return; - } - } - loop { - let pipeline_cnt = context.fetch(case.pipeline); - if pipeline_cnt == 0 { - context.stop(); - break; - } - - // prepare pipeline - let mut p = Vec::new(); - for _ in 0..pipeline_cnt { - p.push(cmd.gen_cmd()); - } - let instant = std::time::Instant::now(); - client.run_commands(p).await; - let duration = instant.elapsed().as_micros() as u64; - for _ in 0..pipeline_cnt { - context.histogram.record(duration); - } - } - }); - } - local.await; -} - -fn wait_finish(case: &Case, client_config: &ClientConfig, mut auto_limiter: AutoLimiter, mut context: SharedContext, mut wg: WaitGroup) -> output::Result { - let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); - let mut result = output::Result::default(); - result.name = case.name.clone().unwrap_or(case.command.to_string()); - rt.block_on(async { - let histogram = context.histogram.clone(); - let mut client = client_config.get_client().await; - // calc overall qps - let mut overall_time = std::time::Instant::now(); - let mut overall_cnt_overhead = 0; - // for log - let mut log_instance = std::time::Instant::now(); - let mut log_last_cnt = histogram.cnt(); - let mut log_interval = tokio::time::interval(std::time::Duration::from_millis(100)); - // for auto limiter - let mut auto_interval = tokio::time::interval(std::time::Duration::from_secs(10)); - - let mut wake_log = false; - let mut wake_auto = false; - loop { - select! { - _ = log_interval.tick() => {wake_log=true;} - _ = auto_interval.tick() => {wake_auto=true;} - _ = wg.wait() => {break;} - } - if wake_log { - wake_log = false; - result.memory = client.info_memory().await as f64 / 1024.0 / 1024.0 / 1024.0; - let cnt = histogram.cnt(); - let qps = (cnt - log_last_cnt) as f64 / log_instance.elapsed().as_secs_f64(); - let active_conn: u64 = auto_limiter.active_conn(); - let target_conn: u64 = auto_limiter.target_conn(); - if auto_limiter.ready { - result.qps = (cnt - overall_cnt_overhead) as f64 / overall_time.elapsed().as_secs_f64(); - } - print!("\r\x1B[2Kqps: {:.0}(overall {:.0}), active_conn: {}, target_conn: {}, {}, mem: {:.3}GiB", qps, result.qps, active_conn, target_conn, histogram, result.memory); - std::io::stdout().flush().unwrap(); - log_last_cnt = cnt; - log_instance = std::time::Instant::now(); - } - if wake_auto && !auto_limiter.ready { - wake_auto = false; - - let cnt = histogram.cnt(); - if !auto_limiter.adjust(cnt) { - overall_cnt_overhead = histogram.cnt(); - overall_time = std::time::Instant::now(); - context.start_timer(); - } - } - } - let active_conn: u64 = auto_limiter.active_conn(); - print!("\r\x1B[2Kqps: {:.0}, conn: {}, {}\n", result.qps, active_conn, histogram); - result.avg = histogram.avg() as f64 / 1_000.0; - result.min = histogram.percentile(0.0) as f64 / 1_000.0; - result.p50 = histogram.percentile(0.5) as f64 / 1_000.0; - result.p90 = histogram.percentile(0.9) as f64 / 1_000.0; - result.p99 = histogram.percentile(0.99) as f64 / 1_000.0; - result.max = histogram.percentile(1.0) as f64 / 1_000.0; - result.connections = active_conn; - result.pipeline = case.pipeline; - result.count = histogram.cnt(); - result.duration = overall_time.elapsed().as_secs_f64(); - }); - return result; -} - -fn main() { - let conf = config::Config::parse(); - println!("{}: {:?}", "CPU Core".bold(), conf.cpus); - println!("{}: {}", "Address".bold(), conf.client_config.address); - println!("{}: {}", "Cluster".bold(), conf.client_config.cluster); - println!("{}: {}", "TLS".bold(), conf.client_config.tls); - println!("{}: {:?}", "output".bold(), conf.output_formats); - - // check cpu core - let core_ids = core_affinity::get_core_ids().unwrap(); - for cpu_id in conf.cpus.iter() { - if *cpu_id as usize >= core_ids.len() { - // print red - println!("{}: {:?}", "Invalid CPU core ids".bold().red(), conf.cpus); - std::process::exit(1); - } - } - - let mut results = output::Results::new(); - for case in conf.cases.iter() { - println!(); - if let Some(name) = case.name.clone() { - println!("{}: {}", "name".bold().blue(), name.bold()); - } - - // flushall - let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); - rt.block_on(async { - let mut client = conf.client_config.get_client().await; - client.flushall().await; - }); - - if let Some(dataset) = &case.dataset { - let mut thread_handlers = Vec::new(); - let core_ids = core_affinity::get_core_ids().unwrap(); - let count = Arc::new(AtomicU64::new(0)); - let conn_per_thread = if cfg!(target_os = "macos") { 4 } else { min(64, 512 / conf.cpus.len()) }; - for cpu_id in conf.cpus.iter() { - let core_id = core_ids[*cpu_id as usize]; - let redis_config = conf.client_config(); - let dataset = dataset.clone(); - let count = count.clone(); - let thread_handler = std::thread::spawn(move || { - core_affinity::set_for_current(core_id); // not work on Apple Silicon - let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); - rt.block_on(async { - load_data(redis_config, dataset, count, conn_per_thread).await; - }); - }); - thread_handlers.push(thread_handler); - } - for thread_handler in thread_handlers { - thread_handler.join().unwrap(); - } - println!("\r\x1B[2K{}: {} x {}", "dataset".blue().bold(), dataset.command.to_string().bold(), dataset.count); - } - - println!("{}: {}", "command".bold().blue(), case.command.to_string().green().bold()); - println!("{}: {}", "connections".bold().blue(), if case.connections == 0 { "auto".to_string() } else { case.connections.to_string() }); - println!("{}: {}", "count".bold().blue(), case.count); - println!("{}: {}", "pipeline".bold().blue(), case.pipeline); - - // calc connections - let auto_limiter = AutoLimiter::new(case.connections, conf.cpus.len() as u64); - - let mut thread_handlers = Vec::new(); - let wg = WaitGroup::new(); - let core_ids = core_affinity::get_core_ids().unwrap(); - let context = SharedContext::new(case.count, case.seconds); - for inx in 0..conf.cpus.len() { - let client_config = conf.client_config().clone(); - let case = case.clone(); - let context = context.clone(); - let wk = wg.worker(); - let core_id = core_ids[conf.cpus.get(inx) as usize]; - let limiter = auto_limiter.limiters[inx].clone(); - let thread_handler = std::thread::spawn(move || { - core_affinity::set_for_current(core_id); // not work on Apple Silicon - let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); - rt.block_on(async { - run_commands_on_single_thread(limiter, client_config, case, context).await; - wk.done(); - }); - }); - thread_handlers.push(thread_handler); - } - - // log thread - let result = wait_finish(&case, &conf.client_config(), auto_limiter, context, wg); - results.add(result); - - // join all threads - for thread_handler in thread_handlers { - thread_handler.join().unwrap(); - } - } - - results.save(conf.output_formats); -} diff --git a/src/output.rs b/src/output.rs deleted file mode 100644 index 406f5ba..0000000 --- a/src/output.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::config::OutputFormat; -use colored::Colorize; -use rust_xlsxwriter::{Format, FormatAlign, Workbook}; -use serde::Serialize; - -#[derive(Serialize, Default)] -pub struct Result { - pub name: String, - pub qps: f64, - #[serde(rename = "avg(ms)")] - pub avg: f64, - #[serde(rename = "min(ms)")] - pub min: f64, - #[serde(rename = "p50(ms)")] - pub p50: f64, - #[serde(rename = "p90(ms)")] - pub p90: f64, - #[serde(rename = "p99(ms)")] - pub p99: f64, - #[serde(rename = "max(ms)")] - pub max: f64, - #[serde(rename = "memory(GiB)")] - pub memory: f64, - // test arguments - pub connections: u64, - pub pipeline: u64, - pub count: u64, - #[serde(rename = "duration(s)")] - pub duration: f64, -} - -pub(crate) struct Results { - results: Vec, -} - -impl Results { - pub fn new() -> Results { - Results { results: Vec::new() } - } - - pub fn add(&mut self, result: Result) { - self.results.push(result); - } - pub fn save(&self, output_formats: Vec) { - let filename = std::format!("{}", chrono::Local::now().format("%Y-%m-%d_%H-%M-%S")); - for output_format in output_formats { - match output_format { - OutputFormat::XLSX => { - self.save_xlsx(filename.clone()); - } - OutputFormat::JSON => { - self.save_json("output".to_string()); - } - } - } - } - - fn save_json(&self, filename: String) { - let filename = std::format!("{}.json", filename); - let json = serde_json::to_string_pretty(&self.results).unwrap(); - std::fs::write(&filename, json).unwrap(); - println!("{} {}", "Saved to".bold().yellow(), filename); - } - - fn save_xlsx(&self, filename: String) { - let mut workbook = Workbook::new(); - let worksheet = workbook.add_worksheet(); - assert!(self.results.len() > 0); - let header_format = Format::new().set_bold().set_align(FormatAlign::Center); - worksheet.serialize_headers_with_format(0, 0, &self.results[0], &header_format).unwrap(); - for result in &self.results { - worksheet.serialize(result).unwrap(); - } - let float_format = Format::new().set_num_format("0.00").set_align(FormatAlign::Center); - let int_format = Format::new().set_num_format("0").set_align(FormatAlign::Center); - // command - worksheet.set_column_width(0, 50).unwrap(); - // qps - worksheet.set_column_width(1, 15).unwrap(); - worksheet.set_column_format(1, &int_format).unwrap(); - // avg - worksheet.set_column_width(2, 10).unwrap(); - worksheet.set_column_format(2, &float_format).unwrap(); - // min - worksheet.set_column_width(3, 10).unwrap(); - worksheet.set_column_format(3, &float_format).unwrap(); - // p50 - worksheet.set_column_width(4, 10).unwrap(); - worksheet.set_column_format(4, &float_format).unwrap(); - // p90 - worksheet.set_column_width(5, 10).unwrap(); - worksheet.set_column_format(5, &float_format).unwrap(); - // p99 - worksheet.set_column_width(6, 10).unwrap(); - worksheet.set_column_format(6, &float_format).unwrap(); - // max - worksheet.set_column_width(7, 10).unwrap(); - worksheet.set_column_format(7, &float_format).unwrap(); - // memory - worksheet.set_column_width(8, 15).unwrap(); - worksheet.set_column_format(8, &float_format).unwrap(); - // connections - worksheet.set_column_width(9, 15).unwrap(); - worksheet.set_column_format(9, &int_format).unwrap(); - // pipeline - worksheet.set_column_width(10, 15).unwrap(); - worksheet.set_column_format(10, &int_format).unwrap(); - // count - worksheet.set_column_width(11, 15).unwrap(); - worksheet.set_column_format(11, &int_format).unwrap(); - // duration - worksheet.set_column_width(12, 15).unwrap(); - worksheet.set_column_format(12, &float_format).unwrap(); - - let filename = std::format!("{}.xlsx", filename); - workbook.save(&filename).unwrap(); - println!("{} {}", "Saved to".bold().yellow(), filename); - } -} diff --git a/src/shared_context.rs b/src/shared_context.rs index b7a5a9c..c288382 100644 --- a/src/shared_context.rs +++ b/src/shared_context.rs @@ -1,5 +1,5 @@ -use crate::common::async_flag::AsyncFlag; -use crate::Histogram; +use crate::async_flag::AsyncFlag; +use crate::histogram::Histogram; use std::cmp::min; use std::option::Option; use std::sync::atomic::AtomicU64; diff --git a/workloads/example.toml b/workloads/example.toml deleted file mode 100644 index 38a755c..0000000 --- a/workloads/example.toml +++ /dev/null @@ -1,27 +0,0 @@ -cluster = false -address = "127.0.0.1:7001" -username = "" -password = "" -tls = false -db_type = "redis" # redis, kvrocks, tair_mem, tair_scm, tair_ssd, garnet -output = ["xlsx", "json"] -replica_count = 0 - -# Leave empty to use all available CPUs. You can specify CPU cores, -# such as "0,1,6-10" to use cores 0, 1, 6, 7, 8, 9, and 10. -cpus = "" - -[[cases]] -name = "STRING: SET 64B" -command = "SET {key uniform 50000000} {value 64}" -connections = 8 -pipeline = 8 -count = 100_000 - -[[cases]] -name = "SET: SISMEMBER" -dataset = { command = "SADD {key sequence 10} {key sequence 107}", count = 1007_000 } -command = "SISMEMBER {key uniform 10} {key uniform 1007}" -connections = 0 -pipeline = 1 -seconds = 10 \ No newline at end of file diff --git a/workloads/redis.toml b/workloads/redis.toml deleted file mode 100644 index fb06d82..0000000 --- a/workloads/redis.toml +++ /dev/null @@ -1,80 +0,0 @@ -cluster = false -address = "127.0.0.1:6379" -password = "" - -[[cases]] -name = "NETWORK: ECHO 64B" -command = "ECHO {value 64}" -seconds = 120 - -[[cases]] -name = "NETWORK: PING with Pipeline" -command = "PING" -pipeline = 8 -seconds = 120 - -[[cases]] -name = "NETWORK: ECHO 1024B" -command = "ECHO {value 1024}" -seconds = 120 - -[[cases]] -name = "STRING: SET 64B" -command = "SET {key uniform 50000000} {value 64}" -seconds = 120 - -[[cases]] -name = "STRING: GET 64B" -dataset = { command = "SET {key sequence 100000} {value 64}", count = 100000 } -command = "GET {key uniform 100000}" -seconds = 120 - -[[cases]] -name = "LIST: LPUSH 64B" -command = "LPUSH {key uniform 10000} {value 64}" -seconds = 120 - -[[cases]] -name = "LIST: LPOP 64B" -dataset = { command = "LPUSH {key sequence 1000} {value 8}", count = 1007_000 } -command = "LINDEX {key uniform 100} {rand 1007}" -seconds = 120 - -[[cases]] -name = "SET: SADD" -command = "SADD {key sequence 100000} {key sequence 1007}" -seconds = 120 - -[[cases]] -name = "SET: SISMEMBER" -dataset = { command = "SADD {key sequence 1000} {key sequence 1007}", count = 1007_000 } -command = "SISMEMBER {key uniform 1000} {key uniform 1007}" -seconds = 120 - -[[cases]] -name = "HASH: HSET 64B" -command = "HSET {key sequence 40000} {key sequence 1007} {value 16}" -seconds = 120 - -[[cases]] -name = "HASH: HGET 64B" -dataset = { command = "HSET {key sequence 1000} {key sequence 1007} {value 8}", count = 1007_000 } # 1007 elements -command = "HGET {key uniform 1000} {key uniform 1007}" -seconds = 120 - -[[cases]] -name = "ZSET: ZADD" -command = "ZADD {key uniform 30000} {rand 6000} {key sequence 1007}" -seconds = 120 - -[[cases]] -name = "ZSET: ZRANGEBYSCORE" -dataset = { command = "ZADD {key sequence 1000} {rand 1000} {value 8}", count = 1000_000 } -command = "ZRANGEBYSCORE {key uniform 1000} {range 1000 10}" -seconds = 120 - -[[cases]] -name = "LUA: SET 64B" -dataset = { command = '''SCRIPT LOAD "return redis.call('SET', KEYS[1], ARGV[1])"''', count = 1 } -command = "EVALSHA d8f2fad9f8e86a53d2a6ebd960b33c4972cacc37 1 {key uniform 100000} {value 64}" -seconds = 120