diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5b67d5bae..0fe8d6c67 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,3 +7,7 @@ updates: time: "04:00" timezone: Europe/Berlin open-pull-requests-limit: 2 + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index f27083611..bd5257a7b 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -1,8 +1,8 @@ name: CICD env: - MIN_SUPPORTED_RUST_VERSION: "1.57.0" CICD_INTERMEDIATES_DIR: "_cicd-intermediates" + MSRV_FEATURES: "" on: workflow_dispatch: @@ -14,57 +14,87 @@ on: - '*' jobs: + crate_metadata: + name: Extract crate metadata + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Extract crate information + id: crate_metadata + run: | + cargo metadata --no-deps --format-version 1 | jq -r ' + .packages[0] | + [ + "name=" + .name, + "version=" + .version, + "maintainer=" + (.authors[0] // ""), + "homepage=" + (.homepage // ""), + "msrv=" + (.rust_version // ""), + "bin-name=" + ( (.targets[] | select(.kind[0] == "bin") | .name) // .name ) + ] | + join("\n") + ' | tee -a $GITHUB_OUTPUT + outputs: + name: ${{ steps.crate_metadata.outputs.name }} + version: ${{ steps.crate_metadata.outputs.version }} + maintainer: ${{ steps.crate_metadata.outputs.maintainer }} + homepage: ${{ steps.crate_metadata.outputs.homepage }} + msrv: ${{ steps.crate_metadata.outputs.msrv }} + bin-name: ${{ steps.crate_metadata.outputs.bin-name }} + + ensure_cargo_fmt: + name: Ensure 'cargo fmt' has been run + runs-on: ubuntu-24.04 + steps: + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - uses: actions/checkout@v5 + - run: cargo fmt -- --check + min_version: name: Minimum supported rust version - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 + needs: crate_metadata steps: - name: Checkout source code - uses: actions/checkout@v2 + uses: actions/checkout@v5 - - name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }}) - uses: actions-rs/toolchain@v1 + - name: Install rust toolchain (v${{ needs.crate_metadata.outputs.msrv }}) + uses: dtolnay/rust-toolchain@master with: - toolchain: ${{ env.MIN_SUPPORTED_RUST_VERSION }} - default: true - profile: minimal # minimal component installation (ie, no documentation) - components: clippy, rustfmt - - name: Ensure `cargo fmt` has been run - uses: actions-rs/cargo@v1 - with: - command: fmt - args: -- --check + toolchain: ${{ needs.crate_metadata.outputs.msrv }} + components: clippy - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix) - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --locked --all-targets --all-features -- --allow unknown_lints + run: cargo clippy --locked --all-targets ${{ env.MSRV_FEATURES }} - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --locked + run: cargo test --locked ${{ env.MSRV_FEATURES }} build: name: ${{ matrix.job.target }} (${{ matrix.job.os }}) runs-on: ${{ matrix.job.os }} + needs: crate_metadata strategy: fail-fast: false matrix: job: - - { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } - - { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true } - - { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true } - - { target: i686-pc-windows-msvc , os: windows-2019 } - - { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } - - { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } - - { target: x86_64-apple-darwin , os: macos-10.15 } - - { target: x86_64-pc-windows-gnu , os: windows-2019 } - - { target: x86_64-pc-windows-msvc , os: windows-2019 } - - { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } - - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } + - { target: aarch64-unknown-linux-gnu , os: ubuntu-24.04, use-cross: true } + - { target: arm-unknown-linux-gnueabihf , os: ubuntu-24.04, use-cross: true } + - { target: arm-unknown-linux-musleabihf, os: ubuntu-24.04, use-cross: true } + - { target: i686-pc-windows-msvc , os: windows-2022 } + - { target: i686-unknown-linux-gnu , os: ubuntu-24.04, use-cross: true } + - { target: i686-unknown-linux-musl , os: ubuntu-24.04, use-cross: true } + - { target: x86_64-apple-darwin , os: macos-15 } + - { target: aarch64-apple-darwin , os: macos-15 } + # - { target: x86_64-pc-windows-gnu , os: windows-2022 } + - { target: x86_64-pc-windows-msvc , os: windows-2022 } + - { target: x86_64-unknown-linux-gnu , os: ubuntu-24.04, use-cross: true } + - { target: x86_64-unknown-linux-musl , os: ubuntu-24.04, use-cross: true } + env: + BUILD_CMD: cargo steps: - name: Checkout source code - uses: actions/checkout@v2 + uses: actions/checkout@v5 - name: Install prerequisites shell: bash @@ -74,25 +104,26 @@ jobs: aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; esac - - name: Extract crate information - shell: bash - run: | - echo "PROJECT_NAME=hyperfine" >> $GITHUB_ENV - echo "PROJECT_VERSION=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)" >> $GITHUB_ENV - echo "PROJECT_MAINTAINER=$(sed -n 's/^authors = \["\(.*\)"\]/\1/p' Cargo.toml)" >> $GITHUB_ENV - echo "PROJECT_HOMEPAGE=$(sed -n 's/^homepage = "\(.*\)"/\1/p' Cargo.toml)" >> $GITHUB_ENV - - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.job.target }} + + - name: Install cross + if: matrix.job.use-cross + uses: taiki-e/install-action@v2 with: - toolchain: stable - target: ${{ matrix.job.target }} - override: true - profile: minimal # minimal component installation (ie, no documentation) + tool: cross + + - name: Overwrite build command env variable + if: matrix.job.use-cross + shell: bash + run: echo "BUILD_CMD=cross" >> $GITHUB_ENV - name: Show version information (Rust, cargo, GCC) shell: bash run: | + set -x gcc --version || true rustup -V rustup toolchain list @@ -101,14 +132,11 @@ jobs: rustc -V - name: Build - uses: actions-rs/cargo@v1 - with: - use-cross: ${{ matrix.job.use-cross }} - command: build - args: --locked --release --target=${{ matrix.job.target }} + shell: bash + run: $BUILD_CMD build --locked --release --target=${{ matrix.job.target }} - - name: Strip debug information from executable - id: strip + - name: Set binary name & path + id: bin shell: bash run: | # Figure out suffix of binary @@ -117,31 +145,13 @@ jobs: *-pc-windows-*) EXE_suffix=".exe" ;; esac; - # Figure out what strip tool to use if any - STRIP="strip" - case ${{ matrix.job.target }} in - arm-unknown-linux-*) STRIP="arm-linux-gnueabihf-strip" ;; - aarch64-unknown-linux-gnu) STRIP="aarch64-linux-gnu-strip" ;; - *-pc-windows-msvc) STRIP="" ;; - esac; - # Setup paths - BIN_DIR="${{ env.CICD_INTERMEDIATES_DIR }}/stripped-release-bin/" - mkdir -p "${BIN_DIR}" - BIN_NAME="${{ env.PROJECT_NAME }}${EXE_suffix}" - BIN_PATH="${BIN_DIR}/${BIN_NAME}" - - # Copy the release build binary to the result location - cp "target/${{ matrix.job.target }}/release/${BIN_NAME}" "${BIN_DIR}" - - # Also strip if possible - if [ -n "${STRIP}" ]; then - "${STRIP}" "${BIN_PATH}" - fi + BIN_NAME="${{ needs.crate_metadata.outputs.bin-name }}${EXE_suffix}" + BIN_PATH="target/${{ matrix.job.target }}/release/${BIN_NAME}" - # Let subsequent steps know where to find the (stripped) bin - echo ::set-output name=BIN_PATH::${BIN_PATH} - echo ::set-output name=BIN_NAME::${BIN_NAME} + # Let subsequent steps know where to find the binary + echo "BIN_PATH=${BIN_PATH}" >> $GITHUB_OUTPUT + echo "BIN_NAME=${BIN_NAME}" >> $GITHUB_OUTPUT - name: Set testing options id: test-options @@ -149,24 +159,21 @@ jobs: run: | # test only library unit tests and binary for arm-type targets unset CARGO_TEST_OPTIONS - unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--bin ${PROJECT_NAME}" ;; esac; - echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS} + unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--bin ${{ steps.bin.outputs.BIN_NAME }}" ;; esac; + echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_OUTPUT - name: Run tests - uses: actions-rs/cargo@v1 - with: - use-cross: ${{ matrix.job.use-cross }} - command: test - args: --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}} + shell: bash + run: $BUILD_CMD test --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}} - name: Create tarball id: package shell: bash run: | PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac; - PKG_BASENAME=${PROJECT_NAME}-v${PROJECT_VERSION}-${{ matrix.job.target }} + PKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-v${{ needs.crate_metadata.outputs.version }}-${{ matrix.job.target }} PKG_NAME=${PKG_BASENAME}${PKG_suffix} - echo ::set-output name=PKG_NAME::${PKG_NAME} + echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_OUTPUT PKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/package" ARCHIVE_DIR="${PKG_STAGING}/${PKG_BASENAME}/" @@ -174,19 +181,19 @@ jobs: mkdir -p "${ARCHIVE_DIR}/autocomplete" # Binary - cp "${{ steps.strip.outputs.BIN_PATH }}" "$ARCHIVE_DIR" - - # Man page - cp 'doc/${{ env.PROJECT_NAME }}.1' "$ARCHIVE_DIR" + cp "${{ steps.bin.outputs.BIN_PATH }}" "$ARCHIVE_DIR" # README, LICENSE and CHANGELOG files cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "CHANGELOG.md" "$ARCHIVE_DIR" + # Man page + cp 'doc/${{ needs.crate_metadata.outputs.name }}.1' "$ARCHIVE_DIR" + # Autocompletion files - cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.bash' "$ARCHIVE_DIR/autocomplete/" - cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.fish' "$ARCHIVE_DIR/autocomplete/" - cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'_${{ env.PROJECT_NAME }}.ps1' "$ARCHIVE_DIR/autocomplete/" - cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'_${{ env.PROJECT_NAME }}' "$ARCHIVE_DIR/autocomplete/" + cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'${{ needs.crate_metadata.outputs.name }}.bash' "$ARCHIVE_DIR/autocomplete/" + cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'${{ needs.crate_metadata.outputs.name }}.fish' "$ARCHIVE_DIR/autocomplete/" + cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'_${{ needs.crate_metadata.outputs.name }}.ps1' "$ARCHIVE_DIR/autocomplete/" + cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'_${{ needs.crate_metadata.outputs.name }}' "$ARCHIVE_DIR/autocomplete/" # base compressed package pushd "${PKG_STAGING}/" >/dev/null @@ -197,7 +204,7 @@ jobs: popd >/dev/null # Let subsequent steps know where to find the compressed package - echo ::set-output name=PKG_PATH::"${PKG_STAGING}/${PKG_NAME}" + echo "PKG_PATH=${PKG_STAGING}/${PKG_NAME}" >> $GITHUB_OUTPUT - name: Create Debian package id: debian-package @@ -209,10 +216,10 @@ jobs: DPKG_DIR="${DPKG_STAGING}/dpkg" mkdir -p "${DPKG_DIR}" - DPKG_BASENAME=${PROJECT_NAME} - DPKG_CONFLICTS=${PROJECT_NAME}-musl - case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${PROJECT_NAME}-musl ; DPKG_CONFLICTS=${PROJECT_NAME} ;; esac; - DPKG_VERSION=${PROJECT_VERSION} + DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }} + DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }}-musl + case ${{ matrix.job.target }} in *-musl*) DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-musl ; DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }} ;; esac; + DPKG_VERSION=${{ needs.crate_metadata.outputs.version }} unset DPKG_ARCH case ${{ matrix.job.target }} in @@ -224,19 +231,19 @@ jobs: esac; DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb" - echo ::set-output name=DPKG_NAME::${DPKG_NAME} + echo "DPKG_NAME=${DPKG_NAME}" >> $GITHUB_OUTPUT # Binary - install -Dm755 "${{ steps.strip.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.strip.outputs.BIN_NAME }}" + install -Dm755 "${{ steps.bin.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.bin.outputs.BIN_NAME }}" # Man page - install -Dm644 'doc/${{ env.PROJECT_NAME }}.1' "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1" - gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1" + install -Dm644 'doc/${{ needs.crate_metadata.outputs.name }}.1' "${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1" + gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1" # Autocompletion files - install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.bash' "${DPKG_DIR}/usr/share/bash-completion/completions/${{ env.PROJECT_NAME }}" - install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.fish' "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ env.PROJECT_NAME }}.fish" - install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'_${{ env.PROJECT_NAME }}' "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ env.PROJECT_NAME }}" + install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'${{ needs.crate_metadata.outputs.name }}.bash' "${DPKG_DIR}/usr/share/bash-completion/completions/${{ needs.crate_metadata.outputs.name }}" + install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'${{ needs.crate_metadata.outputs.name }}.fish' "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ needs.crate_metadata.outputs.name }}.fish" + install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'_${{ needs.crate_metadata.outputs.name }}' "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ needs.crate_metadata.outputs.name }}" # README and LICENSE install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md" @@ -247,12 +254,12 @@ jobs: cat > "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright" <> $GITHUB_OUTPUT # build dpkg fakeroot dpkg-deb --build "${DPKG_DIR}" "${DPKG_PATH}" @@ -325,10 +332,10 @@ jobs: shell: bash run: | unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/v[0-9].* ]]; then IS_RELEASE='true' ; fi - echo ::set-output name=IS_RELEASE::${IS_RELEASE} + echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT - name: Publish archives and packages - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: steps.is-release.outputs.IS_RELEASE with: files: | @@ -336,3 +343,15 @@ jobs: ${{ steps.debian-package.outputs.DPKG_PATH }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + winget: + name: Publish to Winget + runs-on: ubuntu-latest + needs: build + if: startsWith(github.ref, 'refs/tags/v') + steps: + - uses: vedantmgoyal2009/winget-releaser@v2 + with: + identifier: sharkdp.hyperfine + installers-regex: '-pc-windows-msvc\.zip$' + token: ${{ secrets.WINGET_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b89ff943..fda110bee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,94 @@ -# unreleased +# v1.20.0 ## Features +- Add `--reference-name` option to give a meaningful name to the reference command, see #808 (@niklasdewally) +- The `--ignore-failure` option now supports a comma-separated list of exit codes to ignore (e.g., `--ignore-failure=1,2`), see #836 (@sharkdp) +- Python scripts: Add `--time-unit` option to `advanced_statistics.py` (@sharkdp) +- Python scripts: Add new `plot_benchmarks.py` script for plotting collections of benchmarks, see #806 (@marxin) + +## Bugfixes + +- Fix bug where naming individual commands with parameter scan was not working correctly, see #794 (@teofr) + +## Other + +- Restrict `cat` tests to Unix environments, see #776 and #777 (@ritvikos) + +# v1.19.0 + +## Features + +- Add a new `--reference ` option to specify a reference command for the relative speed comparison, see #579, #577 and #744 (@sharkdp) +- Add `--conclude` argument (analog to `--prepare`), see #565 and #719 (@jackoconnordev) +- Allow `--output=…` to appear once for each command, enabling use cases like `hyperfine --output=null my-cmd --output=./file.log my-cmd`, see #529 and #775 (@sharkdp) +- The environment variable `$HYPERFINE_ITERATION` will now contain the current iteration number for each benchmarked command, see #775 (@sharkdp) +- Add iteration information to failure error message, see #771 and #772 (@sharkdp) +- Python scripts: + - legend modification parameters and output DPI, see #758 (@Spreadcat) + - Nicer whiskers plot, see #727 (@serpent7776) + +## Bugfixes + +- ETA not clearly visible on terminals with a block cursor, see #698 and #699 (@overclockworked64) +- Fix zsh completions, see #717 (@xzfc) + +## Other + +- Build binaries for aarch64-apple-darwin, see #728 (@Phault) +- Various cleanups (@hamirmahal, @one230six) + +# v1.18.0 + +## Features + +- Add support for microseconds via `--time-unit microsecond`, see #684 (@sharkdp) + +## Bugfixes + +- Proper argument quoting on Windows CMD, see #296 and #678 (@PedroWitzel) + + +# v1.17.0 + +## Features + +- Add new `--sort` option to control the order in the rel. speed comparison and in markup export formats, see #601, #614, #655 (@sharkdp) +- Parameters which are unused in the command line are now displayed in parentheses, see #600 and #644 (@sharkdp). +- Added `--log-count` option for histogram plots, see `scripts/plot_histogram.py` (@sharkdp) + +## Changes + +- Updated hyperfine to use `windows-sys` instead of the unmaintained `winapi`, see #624, #639, #636, #641 (@clemenswasser) +- Silenced deprecation warning in Python scripts, see #633 (@nicovank) +- Major update of the man page, see 0ce6578, #647 (@sharkdp) + +## Bugfixes + +- Do not export intermediate results to stdout when using `-` as a file name, see #640 and #643 (@sharkdp) +- Markup exporting does not fail if benchmark results are zero, see #642 (@sharkdp) + + +# v1.16.1 + +## Bugfixes + +- Fix line-wrapping of `--help` text (@sharkdp) +- Fix `--input=null` (@sharkdp) + + +# v1.16.0 + +## Features + +- Added new `--input` option, see #541 and #563 (@snease) +- Added possibility to specify `-` as the filename in the + `--export-*` options, see #615 and #623 (@humblepenguinn) ## Changes +- Improve hints for outlier warnings if `--warmup` or `--prepare` are in use already, + see #570 (@sharkdp) ## Bugfixes @@ -14,10 +98,8 @@ ## Other - -## Packaging - - +- Thanks to @berombau for working on dependency upgrades, see #584 +- Fixed installationm on Windows, see #595 and #596 (@AntoniosBarotsis) # v1.15.0 diff --git a/CITATION.cff b/CITATION.cff index b53c6fa17..211d8e1d3 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -13,5 +13,5 @@ authors: repository-code: 'https://github.com/sharkdp/hyperfine' abstract: A command-line benchmarking tool. license: MIT -version: 1.15.0 -date-released: '2022-09-07' +version: 1.16.1 +date-released: '2023-03-21' diff --git a/Cargo.lock b/Cargo.lock index 641858d65..cda887710 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,21 +1,82 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] [[package]] name = "aho-corasick" -version = "0.7.19" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" -version = "1.0.65" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "approx" @@ -28,49 +89,39 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "assert_cmd" -version = "2.0.4" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e" +checksum = "bcbb6924530aa9e0432442af08bbcafdad182db80d2e560da42a6d442535bf85" dependencies = [ + "anstyle", "bstr", - "doc-comment", + "libc", "predicates", "predicates-core", "predicates-tree", "wait-timeout", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" dependencies = [ - "autocfg 1.1.0", + "autocfg 1.5.0", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" @@ -78,64 +129,140 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "borsh" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "bstr" -version = "0.2.17" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ - "lazy_static", "memchr", "regex-automata", "serde", ] [[package]] -name = "cc" -version = "1.0.73" +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytes" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "3.2.22" +version = "4.5.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1" dependencies = [ - "atty", - "bitflags", + "anstream", + "anstyle", "clap_lex", - "indexmap", - "once_cell", "strsim", - "termcolor", - "terminal_size 0.2.1", - "textwrap", + "terminal_size", ] [[package]] name = "clap_complete" -version = "3.2.5" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8" +checksum = "8e602857739c5a4291dfa33b5a298aeac9006185229a700e5810a3ef7272d971" dependencies = [ "clap", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cloudabi" @@ -143,52 +270,55 @@ version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "colored" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ - "atty", "lazy_static", - "winapi", + "windows-sys 0.59.0", ] [[package]] name = "console" -version = "0.15.1" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", "libc", "once_cell", - "terminal_size 0.1.17", - "unicode-width", - "winapi", + "unicode-width 0.2.2", + "windows-sys 0.59.0", ] [[package]] name = "csv" -version = "1.1.6" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" dependencies = [ - "bstr", "csv-core", - "itoa 0.4.8", + "itoa", "ryu", - "serde", + "serde_core", ] [[package]] name = "csv-core" -version = "0.1.10" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" dependencies = [ "memchr", ] @@ -199,59 +329,39 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - -[[package]] -name = "either" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" - [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] -name = "errno" -version = "0.2.8" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "errno" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ - "cc", "libc", + "windows-sys 0.61.2", ] [[package]] name = "fastrand" -version = "1.8.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" -dependencies = [ - "instant", -] +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "float-cmp" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" dependencies = [ "num-traits", ] @@ -262,45 +372,63 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "hashbrown" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "hyperfine" -version = "1.15.0" +version = "1.20.0" dependencies = [ "anyhow", "approx", "assert_cmd", - "atty", "clap", "clap_complete", "colored", "csv", "indicatif", + "insta", "libc", "nix", "once_cell", @@ -313,111 +441,109 @@ dependencies = [ "statistical", "tempfile", "thiserror", - "winapi", + "windows-sys 0.59.0", ] [[package]] name = "indexmap" -version = "1.9.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ - "autocfg 1.1.0", - "hashbrown", + "equivalent", + "hashbrown 0.16.0", ] [[package]] name = "indicatif" -version = "0.17.1" +version = "0.17.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfddc9561e8baf264e0e45e197fd7696320026eb10a8180340debc27b18f535b" +checksum = "db45317f37ef454e6519b6c3ed7d377e5f23346f0823f86e65ca36912d1d0ef8" dependencies = [ "console", + "instant", "number_prefix", - "unicode-width", + "portable-atomic", + "unicode-width 0.1.14", ] [[package]] -name = "instant" -version = "0.1.12" +name = "insta" +version = "1.43.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0" dependencies = [ - "cfg-if", + "console", + "once_cell", + "serde", + "similar", ] [[package]] -name = "io-lifetimes" -version = "0.7.3" +name = "instant" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] [[package]] -name = "itertools" -version = "0.10.5" +name = "is_terminal_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "0.4.8" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] -name = "itoa" -version = "1.0.3" +name = "js-sys" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +dependencies = [ + "once_cell", + "wasm-bindgen", +] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "linux-raw-sys" -version = "0.0.46" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.6.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg 1.1.0", -] +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "nix" -version = "0.25.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "autocfg 1.1.0", - "bitflags", + "bitflags 2.10.0", "cfg-if", + "cfg_aliases", "libc", - "memoffset", - "pin-utils", ] [[package]] @@ -446,7 +572,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" dependencies = [ - "autocfg 1.1.0", + "autocfg 1.5.0", "num-integer", "num-traits", ] @@ -457,27 +583,26 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" dependencies = [ - "autocfg 1.1.0", + "autocfg 1.5.0", "num-traits", ] [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg 1.1.0", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ - "autocfg 1.1.0", + "autocfg 1.5.0", "num-integer", "num-traits", ] @@ -488,7 +613,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ - "autocfg 1.1.0", + "autocfg 1.5.0", "num-bigint", "num-integer", "num-traits", @@ -496,11 +621,11 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "autocfg 1.1.0", + "autocfg 1.5.0", ] [[package]] @@ -511,37 +636,40 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "once_cell" -version = "1.15.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "os_str_bytes" -version = "6.3.0" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] -name = "pin-utils" -version = "0.1.0" +name = "portable-atomic" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] [[package]] name = "predicates" -version = "2.1.1" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" dependencies = [ + "anstyle", "difflib", "float-cmp", - "itertools", "normalize-line-endings", "predicates-core", "regex", @@ -549,38 +677,79 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.3" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" [[package]] name = "predicates-tree" -version = "1.0.5" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" dependencies = [ "predicates-core", "termtree", ] +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "quote" -version = "1.0.21" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.6.5" @@ -652,7 +821,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", ] [[package]] @@ -727,19 +896,22 @@ dependencies = [ ] [[package]] -name = "redox_syscall" -version = "0.2.16" +name = "regex" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ - "bitflags", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] -name = "regex" -version = "1.6.0" +name = "regex-automata" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -747,86 +919,137 @@ dependencies = [ ] [[package]] -name = "regex-automata" -version = "0.1.10" +name = "regex-syntax" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] -name = "regex-syntax" -version = "0.6.27" +name = "rend" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "rkyv" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ - "winapi", + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] name = "rust_decimal" -version = "1.26.1" +version = "1.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" +checksum = "35affe401787a9bd846712274d97654355d21b2a2c092a3139aabe31e9022282" dependencies = [ "arrayvec", + "borsh", + "bytes", "num-traits", + "rand 0.8.5", + "rkyv", "serde", + "serde_json", ] [[package]] name = "rustix" -version = "0.35.10" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af895b90e5c071badc3136fc10ff0bcfc98747eadbaf43ed8f214e07ba8f8477" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags", + "bitflags 2.10.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "serde" -version = "1.0.144" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.144" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ - "itoa 1.0.3", + "itoa", + "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -835,6 +1058,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + [[package]] name = "statistical" version = "1.0.0" @@ -847,125 +1082,240 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "1.0.100" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" -version = "3.3.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ - "cfg-if", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", ] [[package]] -name = "termcolor" -version = "1.1.3" +name = "terminal_size" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "winapi-util", + "rustix", + "windows-sys 0.60.2", ] [[package]] -name = "terminal_size" -version = "0.1.17" +name = "termtree" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "libc", - "winapi", + "thiserror-impl", ] [[package]] -name = "terminal_size" -version = "0.2.1" +name = "thiserror-impl" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8440c860cf79def6164e4a0a983bcc2305d82419177a0e0c71930d049e3ac5a1" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ - "rustix", - "windows-sys", + "proc-macro2", + "quote", + "syn 2.0.110", ] [[package]] -name = "termtree" -version = "0.2.4" +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] -name = "textwrap" -version = "0.15.1" +name = "toml_datetime" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ - "terminal_size 0.2.1", + "serde_core", ] [[package]] -name = "thiserror" -version = "1.0.37" +name = "toml_edit" +version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "thiserror-impl", + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", ] [[package]] -name = "thiserror-impl" -version = "1.0.37" +name = "toml_parser" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ - "proc-macro2", - "quote", - "syn", + "winnow", ] [[package]] name = "unicode-ident" -version = "1.0.4" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.110", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +dependencies = [ + "unicode-ident", +] [[package]] name = "winapi" @@ -984,59 +1334,213 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "winapi-util" -version = "0.1.5" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "winapi", + "windows-targets 0.52.6", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-sys" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "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]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] diff --git a/Cargo.toml b/Cargo.toml index 133c5feac..b3ef3dee6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,59 +3,79 @@ authors = ["David Peter "] categories = ["command-line-utilities"] description = "A command-line benchmarking tool" homepage = "https://github.com/sharkdp/hyperfine" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" name = "hyperfine" readme = "README.md" repository = "https://github.com/sharkdp/hyperfine" -version = "1.15.0" +version = "1.20.0" edition = "2018" build = "build.rs" +rust-version = "1.88.0" [features] # Use the nightly feature windows_process_extensions_main_thread_handle windows_process_extensions_main_thread_handle = [] [dependencies] -colored = "2.0" -indicatif = "0.17.1" +colored = "2.1" +indicatif = "=0.17.4" statistical = "1.0" -atty = "0.2" -csv = "1.1" +csv = "1.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -rust_decimal = "1.26" +rust_decimal = "1.36" rand = "0.8" shell-words = "1.0" -thiserror = "1.0" +thiserror = "2.0" anyhow = "1.0" [target.'cfg(not(windows))'.dependencies] libc = "0.2" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["processthreadsapi", "minwindef", "winnt", "jobapi2", "tlhelp32"] } +windows-sys = { version = "0.59", features = [ + "Win32_Foundation", + "Win32_Security", + "Win32_System_JobObjects", + "Win32_System_LibraryLoader", + "Win32_System_Threading", +] } [target.'cfg(all(windows, not(windows_process_extensions_main_thread_handle)))'.dependencies] -once_cell = "1.14" +once_cell = "1.19" [target.'cfg(target_os="linux")'.dependencies] -nix = { version = "0.25.0", features = ["zerocopy"] } +nix = { version = "0.29", features = ["zerocopy"] } [dependencies.clap] -version = "3" +version = "4" default-features = false -features = ["suggestions", "color", "wrap_help", "cargo"] +features = [ + "suggestions", + "color", + "wrap_help", + "cargo", + "help", + "usage", + "error-context", +] [dev-dependencies] approx = "0.5" assert_cmd = "2.0" -predicates = "2.1" -tempfile = "3.3" +insta = { version = "1.41.1", features = ["yaml"] } +predicates = "3.1" +tempfile = "3.23" + +[profile.dev.package] +insta.opt-level = 3 +similar.opt-level = 3 [build-dependencies] -clap = "3" -atty = "0.2" -clap_complete = "3" +clap = "4.5.48" +clap_complete = "4.2.1" [profile.release] lto = true +strip = true +codegen-units = 1 diff --git a/README.md b/README.md index c6735a9ed..dacded5c9 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,18 @@ A command-line benchmarking tool. ![hyperfine](https://i.imgur.com/z19OYxE.gif) +### Sponsors + +A special *thank you* goes to our biggest sponsor:
+ + + Warp +
+ Warp, the intelligent terminal +
+ Available on MacOS, Linux, Windows +
+ ## Features * Statistical analysis across multiple runs. @@ -102,7 +114,7 @@ in question. If you want to run a benchmark *without an intermediate shell*, you can use the `-N` or `--shell=none` option. This is helpful for very fast commands (< 5 ms) where the shell startup overhead correction would -produce a significant amount of noise. Note that you can not use shell syntax like `*` or `~` in this case. +produce a significant amount of noise. Note that you cannot use shell syntax like `*` or `~` in this case. ``` hyperfine -N 'grep TODO /home/user' ``` @@ -163,15 +175,19 @@ like `--warmup`, `--prepare `, `--setup ` or `--cleanup `: ## Installation -[![Packaging status](https://repology.org/badge/vertical-allrepos/hyperfine.svg)](https://repology.org/project/hyperfine/versions) +[![Packaging status](https://repology.org/badge/vertical-allrepos/hyperfine.svg?columns=3&exclude_unsupported=1)](https://repology.org/project/hyperfine/versions) ### On Ubuntu -Download the appropriate `.deb` package from the [Release page](https://github.com/sharkdp/hyperfine/releases) -and install it via `dpkg`: +On Ubuntu, hyperfine can be installed [from the official repositories](https://launchpad.net/ubuntu/+source/rust-hyperfine): +``` +apt install hyperfine +``` + +Alternatively, for the latest version, you can download the appropriate `.deb` package from the [Release page](https://github.com/sharkdp/hyperfine/releases) and install it via `dpkg`: ``` -wget https://github.com/sharkdp/hyperfine/releases/download/v1.15.0/hyperfine_1.15.0_amd64.deb -sudo dpkg -i hyperfine_1.15.0_amd64.deb +wget https://github.com/sharkdp/hyperfine/releases/download/v1.20.0/hyperfine_1.20.0_amd64.deb +sudo dpkg -i hyperfine_1.20.0_amd64.deb ``` ### On Fedora @@ -191,11 +207,26 @@ apk add hyperfine ### On Arch Linux -On Arch Linux, hyperfine can be installed [from the official repositories](https://www.archlinux.org/packages/community/x86_64/hyperfine/): +On Arch Linux, hyperfine can be installed [from the official repositories](https://archlinux.org/packages/extra/x86_64/hyperfine/): ``` pacman -S hyperfine ``` +### On Debian Linux + +On Debian Linux, hyperfine can be installed [from the official repositories](https://packages.debian.org/hyperfine): +``` +apt install hyperfine +``` + +### On Exherbo Linux + +On Exherbo Linux, hyperfine can be installed [from the rust repositories](https://gitlab.exherbo.org/exherbo/rust/-/tree/master/packages/sys-apps/hyperfine): +``` +cave resolve -x repository/rust +cave resolve -x hyperfine +``` + ### On Funtoo Linux On Funtoo Linux, hyperfine can be installed [from core-kit](https://github.com/funtoo/core-kit/tree/1.4-release/app-benchmarks/hyperfine): @@ -210,6 +241,21 @@ On NixOS, hyperfine can be installed [from the official repositories](https://ni nix-env -i hyperfine ``` +### On Flox + +On Flox, hyperfine can be installed as follows. +``` +flox install hyperfine +``` +Hyperfine's version in Flox follows that of Nix. + +### On openSUSE + +On openSUSE, hyperfine can be installed [from the official repositories](https://software.opensuse.org/package/hyperfine): +``` +zypper install hyperfine +``` + ### On Void Linux Hyperfine can be installed via xbps @@ -244,6 +290,19 @@ pkg install hyperfine doas pkg_add hyperfine ``` +### On Windows + +Hyperfine can be installed via [Chocolatey](https://community.chocolatey.org/packages/hyperfine), [Scoop](https://scoop.sh/#/apps?q=hyperfine&s=0&d=1&o=true&id=8f7c10f75ecf5f9e42a862c615257328e2f70f61), or [Winget](https://github.com/microsoft/winget-pkgs/tree/master/manifests/s/sharkdp/hyperfine): +``` +choco install hyperfine +``` +``` +scoop install hyperfine +``` +``` +winget install hyperfine +``` + ### With conda Hyperfine can be installed via [`conda`](https://conda.io/en/latest/) from the [`conda-forge`](https://anaconda.org/conda-forge/hyperfine) channel: @@ -255,10 +314,10 @@ conda install -c conda-forge hyperfine Hyperfine can be installed from source via [cargo](https://doc.rust-lang.org/cargo/): ``` -cargo install hyperfine +cargo install --locked hyperfine ``` -Make sure that you use Rust 1.57 or higher. +Make sure that you use Rust 1.76 or newer. ### From binaries (Linux, macOS, Windows) @@ -266,13 +325,19 @@ Download the corresponding archive from the [Release page](https://github.com/sh ## Alternative tools -Hyperfine is inspired by [bench](https://github.com/Gabriel439/bench). +Hyperfine is inspired by [bench](https://github.com/Gabriella439/bench). ## Integration with other tools [Chronologer](https://github.com/dandavison/chronologer) is a tool that uses `hyperfine` to visualize changes in benchmark timings across your Git history. +[Bencher](https://github.com/bencherdev/bencher) is a continuous benchmarking tool that supports `hyperfine` to +track benchmarks and catch performance regressions in CI. + +Drop hyperfine JSON outputs onto the [Venz](https://try.venz.dev) chart to visualize the results, +and manage hyperfine configurations. + Make sure to check out the [`scripts` folder](https://github.com/sharkdp/hyperfine/tree/master/scripts) in this repository for a set of tools to work with `hyperfine` benchmark results. diff --git a/doc/execution-order.png b/doc/execution-order.png index 48350fc38..845a302fe 100644 Binary files a/doc/execution-order.png and b/doc/execution-order.png differ diff --git a/doc/execution-order.svg b/doc/execution-order.svg index c875c9c5f..9c5f333e3 100644 --- a/doc/execution-order.svg +++ b/doc/execution-order.svg @@ -7,9 +7,9 @@ viewBox="0 0 397.65199 482.07085" version="1.1" id="svg5" - inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)" + inkscape:version="1.3 (1:1.3+202307231459+0e150ed6c4)" sodipodi:docname="execution-order.svg" - inkscape:export-filename="/home/shark/Informatik/rust/hyperfine/doc/execution-order.png" + inkscape:export-filename="execution-order.png" inkscape:export-xdpi="38.32" inkscape:export-ydpi="38.32" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" @@ -26,11 +26,11 @@ inkscape:pagecheckerboard="0" inkscape:document-units="mm" showgrid="false" - inkscape:zoom="0.38529391" - inkscape:cx="1022.596" - inkscape:cy="939.54249" - inkscape:window-width="1920" - inkscape:window-height="1175" + inkscape:zoom="0.70710678" + inkscape:cx="627.91082" + inkscape:cy="893.07586" + inkscape:window-width="2560" + inkscape:window-height="1417" inkscape:window-x="1920" inkscape:window-y="0" inkscape:window-maximized="1" @@ -43,667 +43,915 @@ fit-margin-right="30" fit-margin-bottom="30" lock-margins="true" - units="px"> + units="px" + inkscape:showpageshadow="2" + inkscape:deskcolor="#d1d1d1" + inkscape:export-bgcolor="#ffffffff"> + originy="-12.964584" + spacingy="1" + spacingx="1" + units="px" + visible="false" /> + id="guide130046" + inkscape:locked="false" /> + + id="defs2"> + + + + + + + width="176.10442" + height="13.157444" + x="-297.13202" + y="328.08582" + ry="0.99500984" /> + width="175.98735" + height="12.5896" + x="-297.01495" + y="285.72119" + ry="0.94422126" /> + width="175.98848" + height="12.591858" + x="-297.01608" + y="342.95483" + ry="0.94438893" /> + width="176.09804" + height="13.113291" + x="-297.12564" + y="270.72006" + ry="0.99704427" /> + width="176.09004" + height="14.086456" + x="-297.11765" + y="241.5974" + ry="1.0564854" /> + width="176.09953" + height="13.295197" + x="-297.12714" + y="226.3399" + ry="0.99713981" /> hyperfine -hyperfine --warmup 2 --runs 3 --warmup 2 - --setup <setup> --runs 3 - --cleanup <cleanup> --setup <setup> - --prepare <prepare1> - --prepare <prepare1> <command1> - <command1> --prepare <prepare2> - --conclude <conclude1> <command2> - --cleanup <cleanup> + y="-19.825247" + id="tspan23"> --prepare <prepare2> <command2> --conclude <conclude2> + 2 warmup runs + 3 benchmark runs + 2 warmup runs + 3 benchmark runs + - + style="fill:#e4e4e4;fill-opacity:1;stroke:#e35a00;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.80126;stroke-opacity:1;paint-order:stroke fill markers" + id="rect272408-6" + width="58.173439" + height="84.228279" + x="-95.232544" + y="32.354633" + ry="0.90244579" /> + command1 - + id="tspan272436-6" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';fill:#ffffff;stroke-width:0.564;stroke-dasharray:none" + x="-84.727898" + y="58.101753">command1 + prepare1 - + id="tspan272442-5" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-84.76268" + y="45.858795">prepare1 + + + command1 - + id="tspan4" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-87.04631" + y="69.336189">conclude1 + prepare1 - + id="tspan11" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';fill:#ffffff;stroke-width:0.564;stroke-dasharray:none" + x="-84.727898" + y="96.331017">command1 + command1 - + id="tspan12" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-84.76268" + y="84.088058">prepare1 + prepare1 + id="tspan16" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-87.04631" + y="107.56544">conclude1 + 2 warmup runs + id="tspan25" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';fill:#ffffff;stroke-width:0.564;stroke-dasharray:none" + x="-84.727898" + y="143.13966">command1 + 3 benchmark runs - + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;line-height:0;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-84.76268" + y="130.89671" + id="text26">prepare1 + setup - + id="tspan27" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-87.04631" + y="154.37411">conclude1 + cleanup - + id="tspan28" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';fill:#ffffff;stroke-width:0.564;stroke-dasharray:none" + x="-84.727898" + y="180.4429">command1 + command1 - + id="tspan29" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-84.76268" + y="168.19995">prepare1 + prepare1 - + id="tspan30" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-87.04631" + y="191.67734">conclude1 + command1 - + id="tspan31" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';fill:#ffffff;stroke-width:0.564;stroke-dasharray:none" + x="-84.727898" + y="217.2457">command1 + + prepare1 + + conclude1 + + cleanup + prepare1 + id="tspan35" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-77.737892" + y="267.61914">setup + + setup - + style="fill:#e4e4e4;fill-opacity:1;stroke:#e35a00;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.80126;stroke-opacity:1;paint-order:stroke fill markers" + id="rect38" + width="58.173439" + height="84.228279" + x="-95.232544" + y="273.61163" + ry="0.90244579" /> + command2 - + id="tspan39" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';fill:#ffffff;stroke-width:0.564;stroke-dasharray:none" + x="-84.727898" + y="299.35873">command2 + prepare2 - + id="tspan40" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-84.76268" + y="287.11578">prepare2 + command2 - + id="tspan41" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-87.04631" + y="310.5932">conclude2 + prepare2 - + id="tspan42" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';fill:#ffffff;stroke-width:0.564;stroke-dasharray:none" + x="-84.727898" + y="337.58801">command2 + command2 - + id="tspan43" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-84.76268" + y="325.34506">prepare2 + prepare2 + id="tspan44" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-87.04631" + y="348.82245">conclude2 + 2 warmup runs + id="tspan45" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';fill:#ffffff;stroke-width:0.564;stroke-dasharray:none" + x="-84.727898" + y="384.39667">command2 + 3 benchmark runs - + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;line-height:0;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-84.76268" + y="372.15372" + id="text46">prepare2 + setup - + id="tspan47" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-87.04631" + y="395.6311">conclude2 + cleanup - + id="tspan48" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';fill:#ffffff;stroke-width:0.564;stroke-dasharray:none" + x="-84.727898" + y="421.69989">command2 + command2 - + id="tspan49" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-84.76268" + y="409.45694">prepare2 + prepare2 - + id="tspan50" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-87.04631" + y="432.93433">conclude2 + command2 - + id="tspan51" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';fill:#ffffff;stroke-width:0.564;stroke-dasharray:none" + x="-84.727898" + y="458.50269">command2 + + prepare2 + + conclude2 + prepare2 + id="tspan54" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.72806px;font-family:'Fira Code';-inkscape-font-specification:'Fira Code';stroke-width:0.564;stroke-dasharray:none" + x="-82.444275" + y="488.25928">cleanup diff --git a/doc/hyperfine.1 b/doc/hyperfine.1 index 30e2bfdad..449b470f2 100644 --- a/doc/hyperfine.1 +++ b/doc/hyperfine.1 @@ -3,18 +3,57 @@ hyperfine \- command\-line benchmarking tool .SH SYNOPSIS .B hyperfine -.RB [ \-ihV ] -.RB [ \-w -.IR warmupruns ] -.RB [ \-r -.IR runs ] -.RB [ \-p -.IR cmd... ] -.RB [ \-c -.IR cmd ] -.RB [ \-s -.IR style ] -.RI [ cmd... ] +.RB [ \-ihVN ] +.RB [ \-\-warmup +.IR NUM ] +.RB [ \-\-min\-runs +.IR NUM ] +.RB [ \-\-max\-runs +.IR NUM ] +.RB [ \-\-runs +.IR NUM ] +.RB [ \-\-setup +.IR CMD ] +.RB [ \-\-prepare +.IR CMD ] +.RB [ \-\-conclude +.IR CMD ] +.RB [ \-\-cleanup +.IR CMD ] +.RB [ \-\-parameter\-scan +.IR VAR +.IR MIN +.IR MAX ] +.RB [ \-\-parameter\-step\-size +.IR DELTA ] +.RB [ \-\-parameter\-list +.IR VAR +.IR VALUES ] +.RB [ \-\-shell +.IR SHELL ] +.RB [ \-\-style +.IR TYPE ] +.RB [ \-\-sort +.IR METHOD ] +.RB [ \-\-time-unit +.IR UNIT ] +.RB [ \-\-export\-asciidoc +.IR FILE ] +.RB [ \-\-export\-csv +.IR FILE ] +.RB [ \-\-export\-json +.IR FILE ] +.RB [ \-\-export\-markdown +.IR FILE ] +.RB [ \-\-export\-orgmode +.IR FILE ] +.RB [ \-\-output +.IR WHERE ] +.RB [ \-\-input +.IR WHERE ] +.RB [ \-\-command\-name +.IR NAME ] +.RI [ COMMAND... ] .SH DESCRIPTION A command\-line benchmarking tool which includes: .LP @@ -44,48 +83,60 @@ A command\-line benchmarking tool which includes: .RE .SH OPTIONS .HP -\fB\-w\fR, \fB\-\-warmup\fR \fIwarmupruns\fP +\fB\-w\fR, \fB\-\-warmup\fR \fINUM\fP .IP -Perform \fIwarmupruns\fP (number) before the actual benchmark. This can be used to fill -(disk) caches for I/O\-heavy programs. +Perform \fINUM\fP warmup runs before the actual benchmark. This can be used +to fill (disk) caches for I/O\-heavy programs. .HP -\fB\-m\fR, \fB\-\-min\-runs\fR \fIminruns\fP +\fB\-m\fR, \fB\-\-min\-runs\fR \fINUM\fP .IP -Perform at least \fIminruns\fP (number) runs for each command. Default: 10. +Perform at least \fINUM\fP runs for each command. Default: 10. .HP -\fB\-M\fR, \fB\-\-max\-runs\fR \fImaxruns\fP +\fB\-M\fR, \fB\-\-max\-runs\fR \fINUM\fP .IP -Perform at most \fImaxruns\fP (number) runs for each command. Default: no limit. +Perform at most \fINUM\fP runs for each command. By default, there is no +limit. .HP -\fB\-r\fR, \fB\-\-runs\fR \fIruns\fP +\fB\-r\fR, \fB\-\-runs\fR \fINUM\fP .IP -Perform exactly \fIruns\fP (number) runs for each command. If this option is not specified, +Perform exactly \fINUM\fP runs for each command. If this option is not specified, \fBhyperfine\fR automatically determines the number of runs. .HP -\fB\-s\fR, \fB\-\-setup\fR \fIcmd\fP +\fB\-s\fR, \fB\-\-setup\fR \fICMD...\fP .IP -Execute \fIcmd\fP once before each set of timing runs. This is useful -for compiling software or doing other one-off setup. -The \fB\-\-setup\fR option can only be specified once. +Execute \fICMD\fP once before each set of timing runs. This is useful +for compiling your software or with the provided parameters, or to do any +other work that should happen once before a series of benchmark runs, +not every time as would happen with the \fB\-\-prepare\fR option. .HP -\fB\-p\fR, \fB\-\-prepare\fR \fIcmd...\fP +\fB\-p\fR, \fB\-\-prepare\fR \fICMD...\fP .IP -Execute \fIcmd\fP before each timing run. This is useful for clearing disk caches, +Execute \fICMD\fP before each timing run. This is useful for clearing disk caches, for example. The \fB\-\-prepare\fR option can be specified once for all commands or multiple times, once for each command. In the latter case, each preparation command will be run prior to the corresponding benchmark command. .HP -\fB\-c\fR, \fB\-\-cleanup\fR \fIcmd\fP +\fB\-\-conclude\fR \fICMD...\fP .IP -Execute \fIcmd\fP after the completion of all benchmarking runs for each individual +Execute \fICMD\fP after each timing run. This is useful for clearing disk caches, +for example. +The \fB\-\-conclude\fR option can be specified once for all commands or multiple times, +once for each command. In the latter case, each conclusion command will be +run after the corresponding benchmark command. +.HP +\fB\-c\fR, \fB\-\-cleanup\fR \fICMD...\fP +.IP +Execute \fICMD\fP after the completion of all benchmarking runs for each individual command to be benchmarked. This is useful if the commands to be benchmarked -produce artifacts that need to be cleaned up. +produce artifacts that need to be cleaned up. It only runs once a series of +benchmark runs, as opposed to \fB\-\-conclude\fR option which runs after +every run. .HP -\fB\-P\fR, \fB\-\-parameter\-scan\fR \fIvar\fP \fImin\fP \fImax\fP +\fB\-P\fR, \fB\-\-parameter\-scan\fR \fIVAR\fP \fIMIN\fP \fIMAX\fP .IP -Perform benchmark runs for each value in the range \fImin..max\fP. Replaces the -string '{\fIvar\fP}' in each command by the current parameter value. +Perform benchmark runs for each value in the range \fIMIN..MAX\fP. Replaces the +string '{\fIVAR\fP}' in each command by the current parameter value. .IP .RS Example: @@ -95,12 +146,26 @@ Example: .RE .IP This performs benchmarks for 'make \-j 1', 'make \-j 2', ..., 'make \-j 8'. +.IP +To have the value increase following different patterns, use shell +arithmetics. +.IP +.RS +Example: +.RS +\fBhyperfine\fR \fB\-P\fR size 0 3 'sleep $((2**{size}))' .RE +.RE +.IP +This performs benchmarks with power of 2 increases: 'sleep 1', 'sleep +2', 'sleep 4', ... +.IP +The exact syntax may vary depending on your shell and OS. .HP -\fB\-D\fR, \fB\-\-parameter\-step\-size\fR \fIdelta\fP +\fB\-D\fR, \fB\-\-parameter\-step\-size\fR \fIDELTA\fP .IP This argument requires \fB\-\-parameter\-scan\fR to be specified as well. Traverse the -range \fImin..max\fP in steps of \fIdelta\fP. +range \fIMIN..MAX\fP in steps of \fIDELTA\fP. .IP .RS Example: @@ -111,10 +176,10 @@ Example: .IP This performs benchmarks for 'sleep 0.3', 'sleep 0.5' and 'sleep 0.7'. .HP -\fB\-L\fR, \fB\-\-parameter\-list\fR \fIvar\fP \fIvalues\fP +\fB\-L\fR, \fB\-\-parameter\-list\fR \fIVAR\fP \fIVALUES\fP .IP -Perform benchmark runs for each value in the comma\-separated list of \fIvalues\fP. -Replaces the string '{\fIvar\fP}' in each command by the current parameter value. +Perform benchmark runs for each value in the comma\-separated list of \fIVALUES\fP. +Replaces the string '{\fIVAR\fP}' in each command by the current parameter value. .IP .RS Example: @@ -124,46 +189,83 @@ Example: .RE .IP This performs benchmarks for 'gcc \-O2 main.cpp' and 'clang \-O2 main.cpp'. +.IP +The option can be specified multiple times to run benchmarks for all +possible parameter combinations. .HP -\fB\-\-style\fR \fItype\fP +\fB\-S\fR, \fB\-\-shell\fR \fISHELL\fP .IP -Set output style \fItype\fP (default: auto). Set this to 'basic' to disable output -coloring and interactive elements. Set it to 'full' to enable all effects even -if no interactive terminal was detected. Set this to 'nocolor' to keep the -interactive output without any colors. Set this to 'color' to keep the colors -without any interactive output. Set this to 'none' to disable all the output -of the tool. In hyperfine versions v0.4.0..v1.12.0 this option took the \fB\-s\fR, -short option, which is now used by \fB\--setup\fR. +Set the shell to use for executing benchmarked commands. This can be +the name or the path to the shell executable, or a full command line +like "bash \fB\-\-norc\fR". It can also be set to "default" to explicitly +select the default shell on this platform. Finally, this can also be +set to "none" to disable the shell. In this case, commands will be +executed directly. They can still have arguments, but more complex +things like "sleep 0.1; sleep 0.2" are not possible without a shell. .HP -\fB\-S\fR, \fB\-\-shell\fR \fIshell\fP +\fB\-N\fR .IP -Set the \fIshell\fP to use for executing benchmarked commands. +An alias for '\-\-shell=none'. .HP \fB\-i\fR, \fB\-\-ignore\-failure\fR .IP Ignore non\-zero exit codes of the benchmarked programs. .HP -\fB\-u\fR, \fB\-\-time\-unit\fR \fIunit\fP +\fB\-\-style\fR \fITYPE\fP .IP -Set the time \fIunit\fP to be used. Default: second. Possible values: millisecond, second. +Set output style \fITYPE\fP (default: auto). Set this to 'basic' to disable output +coloring and interactive elements. Set it to 'full' to enable all effects even +if no interactive terminal was detected. Set this to 'nocolor' to keep the +interactive output without any colors. Set this to 'color' to keep the colors +without any interactive output. Set this to 'none' to disable all the output +of the tool. .HP -\fB\-\-export\-asciidoc\fR \fIfile\fP +\fB\-\-sort\fR \fIMETHOD\fP .IP -Export the timing summary statistics as an AsciiDoc table to the given \fIfile\fP. +Specify the sort order of the speed comparison summary and the +exported tables for markup formats (Markdown, AsciiDoc, org\-mode): +.RS +.IP "auto (default)" +the speed comparison will be ordered by time and +the markup tables will be ordered by command (input order). +.IP "command" +order benchmarks in the way they were specified +.IP "mean\-time" +order benchmarks by mean runtime +.RE +.HP +\fB\-u\fR, \fB\-\-time\-unit\fR \fIUNIT\fP +.IP +Set the time unit to be used. Possible values: microsecond, millisecond, second. If +the option is not given, the time unit is determined automatically. +This option affects the standard output as well as all export formats +except for CSV and JSON. +.HP +\fB\-\-export\-asciidoc\fR \fIFILE\fP +.IP +Export the timing summary statistics as an AsciiDoc table to the given \fIFILE\fP. +The output time unit can be changed using the \fB\-\-time\-unit\fR option. .HP -\fB\-\-export\-csv\fR \fIfile\fP +\fB\-\-export\-csv\fR \fIFILE\fP .IP -Export the timing summary statistics as CSV to the given \fIfile\fP. If you need the +Export the timing summary statistics as CSV to the given \fIFILE\fP. If you need the timing results for each individual run, use the JSON export format. +The output time unit is always seconds. .HP -\fB\-\-export\-json\fR \fIfile\fP +\fB\-\-export\-json\fR \fIFILE\fP .IP Export the timing summary statistics and timings of individual runs as JSON to -the given \fIfile\fP. +the given \fIFILE\fP. The output time unit is always seconds. +.HP +\fB\-\-export\-markdown\fR \fIFILE\fP +.IP +Export the timing summary statistics as a Markdown table to the given \fIFILE\fP. +The output time unit can be changed using the \fB\-\-time\-unit\fR option. .HP -\fB\-\-export\-markdown\fR \fIfile\fP +\fB\-\-export\-orgmode\fR \fIFILE\fP .IP -Export the timing summary statistics as a Markdown table to the given \fIfile\fP. +Export the timing summary statistics as an Emacs org\-mode table to the +given \fIFILE\fP. The output time unit can be changed using the \fB\-\-time\-unit\fR option. .HP \fB\-\-show\-output\fR .IP @@ -171,19 +273,54 @@ Print the stdout and stderr of the benchmark instead of suppressing it. This will increase the time it takes for benchmarks to run, so it should only be used for debugging purposes or when trying to benchmark output speed. .HP -\fB\-n\fR, \fB\-\-command\-name\fR \fIname\fP +\fB\-\-output\fR \fIWHERE\fP +.IP +Control where the output of the benchmark is redirected. Note that +some programs like 'grep' detect when standard output is \fI\,/dev/null\/\fP and +apply certain optimizations. To avoid that, consider using +\-\-output=pipe. +.IP +\fIWHERE\fP can be: +.RS +.IP null +Redirect output to \fI\,/dev/null\/\fP (the default). +.IP pipe +Feed the output through a pipe before discarding it. +.IP inherit +Don't redirect the output at all (same as \&'\-\-show\-output'). +.IP "" +Write the output to the given file. +.RE +.IP +This option can be specified once for all commands or multiple times, +once for each command. Note: If you want to log the output of each and +every iteration, you can use a shell redirection and the $HYPERFINE_ITERATION +environment variable: 'my-command > output-${HYPERFINE_ITERATION}.log' +.HP +\fB\-\-input\fR \fIWHERE\fP +.IP +Control where the input of the benchmark comes from. +.IP +\fIWHERE\fP can be: +.RS +.IP null +Read from \fI\,/dev/null\/\fP (the default). +.IP "" +Read the input from the given file. +.RE +.HP +\fB\-n\fR, \fB\-\-command\-name\fR \fiNAME\fP .IP -Identify a command with the given \fIname\fP. Commands and names are paired in -the same order: the first command executed gets the first name passed as -option. +Give a meaningful \fiNAME\fP to a command. This can be specified multiple times +if several commands are benchmarked. .HP \fB\-h\fR, \fB\-\-help\fR .IP -Print help message. +Print help .HP \fB\-V\fR, \fB\-\-version\fR .IP -Show version information. +Print version .SH EXAMPLES .LP Basic benchmark of 'find . -name todo.txt': @@ -215,12 +352,13 @@ Export the results of a parameter scan benchmark to a markdown table: .fi .RE .LP -Demonstrate when each of \fB\-\-setup\fR, \fB\-\-prepare\fR, \fIcmd\fP and \fB\-\-cleanup\fR will run: +Demonstrate when each of \fB\-\-setup\fR, \fB\-\-prepare\fR, \fB\-\-conclude\fR, \fIcmd\fP and \fB\-\-cleanup\fR will run: .RS .nf \fBhyperfine\fR \fB\-L\fR n 1,2 \fB\-r\fR 2 \fB\-\-show-output\fR \\ \fB\-\-setup\fR 'echo setup n={n}' \\ \fB\-\-prepare\fR 'echo prepare={n}' \\ + \fB\-\-conclude\fR 'echo conclude={n}' \\ \fB\-\-cleanup\fR 'echo cleanup n={n}' \\ 'echo command n={n}' .fi @@ -228,7 +366,7 @@ Demonstrate when each of \fB\-\-setup\fR, \fB\-\-prepare\fR, \fIcmd\fP and \fB\- .RE .SH AUTHOR .LP -David Peter (sharkdp) +David Peter .LP -Source, bug tracker, and additional information can be found on GitHub at: +Source, bug tracker, and additional information can be found on GitHub: .I https://github.com/sharkdp/hyperfine diff --git a/doc/sponsors.md b/doc/sponsors.md new file mode 100644 index 000000000..396e9a13b --- /dev/null +++ b/doc/sponsors.md @@ -0,0 +1,14 @@ +## Sponsors + +`hyperfine` development is sponsored by many individuals and companies. Thank you very much! + +Please note, that being sponsored does not affect the individuality of the `hyperfine` +project or affect the maintainers' actions in any way. +We remain impartial and continue to assess pull requests solely on merit - the +features added, bugs solved, and effect on the overall complexity of the code. +No issue will have a different priority based on sponsorship status of the +reporter. + +Contributions from anybody are most welcomed. + +If you want to see our biggest sponsors, check the top of [`README.md`](../README.md#sponsors). diff --git a/doc/sponsors/tuple-logo.png b/doc/sponsors/tuple-logo.png new file mode 100644 index 000000000..2b4294a4e Binary files /dev/null and b/doc/sponsors/tuple-logo.png differ diff --git a/doc/sponsors/warp-logo.png b/doc/sponsors/warp-logo.png new file mode 100644 index 000000000..f99dd38ce Binary files /dev/null and b/doc/sponsors/warp-logo.png differ diff --git a/scripts/README.md b/scripts/README.md index dfb47a7d7..77fe50ce5 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -4,13 +4,22 @@ This folder contains scripts that can be used in combination with hyperfines `-- ```bash hyperfine 'sleep 0.020' 'sleep 0.021' 'sleep 0.022' --export-json sleep.json -python plot_whisker.py sleep.json +./plot_whisker.py sleep.json ``` ### Pre-requisites -To make these scripts work, you will need to install `numpy`, `matplotlib` and `scipy`. Install them via -your package manager or `pip`: +To make these scripts work, you will need `numpy`, `matplotlib` and `scipy`. + +If you have a Python package manager that understands [PEP-723](https://peps.python.org/pep-0723/) +inline script requirements like [`uv`](https://github.com/astral-sh/uv) or [`pipx`](https://github.com/pypa/pipx), +you can directly run the scripts using + +```bash +uv run plot_whisker.py sleep.json +``` + +Otherwise, install the dependencies via your system package manager or using `pip`: ```bash pip install numpy matplotlib scipy # pip3, if you are using python3 diff --git a/scripts/advanced_statistics.py b/scripts/advanced_statistics.py index 5f8a439be..5ad357962 100755 --- a/scripts/advanced_statistics.py +++ b/scripts/advanced_statistics.py @@ -1,13 +1,52 @@ #!/usr/bin/env python +# /// script +# requires-python = ">=3.10" +# dependencies = [ +# "numpy", +# ] +# /// import argparse import json +from enum import Enum + import numpy as np + +class Unit(Enum): + SECOND = 1 + MILLISECOND = 2 + + def factor(self): + match self: + case Unit.SECOND: + return 1 + case Unit.MILLISECOND: + return 1e3 + + def __str__(self): + match self: + case Unit.SECOND: + return "s" + case Unit.MILLISECOND: + return "ms" + + parser = argparse.ArgumentParser() parser.add_argument("file", help="JSON file with benchmark results") +parser.add_argument( + "--time-unit", + help="The unit of time.", + default="second", + action="store", + choices=["second", "millisecond"], + dest="unit", +) args = parser.parse_args() +unit = Unit.MILLISECOND if args.unit == "millisecond" else Unit.SECOND +unit_str = str(unit) + with open(args.file) as f: results = json.load(f)["results"] @@ -15,6 +54,8 @@ times = [b["times"] for b in results] for command, ts in zip(commands, times): + ts = [t * unit.factor() for t in ts] + p05 = np.percentile(ts, 5) p25 = np.percentile(ts, 25) p75 = np.percentile(ts, 75) @@ -22,18 +63,17 @@ iqr = p75 - p25 - print("Command '{}'".format(command)) - print(" runs: {:8d}".format(len(ts))) - print(" mean: {:8.3f} s".format(np.mean(ts))) - print(" stddev: {:8.3f} s".format(np.std(ts, ddof=1))) - print(" median: {:8.3f} s".format(np.median(ts))) - print(" min: {:8.3f} s".format(np.min(ts))) - print(" max: {:8.3f} s".format(np.max(ts))) + print(f"Command '{command}'") + print(f" runs: {len(ts):8d}") + print(f" mean: {np.mean(ts):8.3f} {unit_str}") + print(f" stddev: {np.std(ts, ddof=1):8.3f} {unit_str}") + print(f" median: {np.median(ts):8.3f} {unit_str}") + print(f" min: {np.min(ts):8.3f} {unit_str}") + print(f" max: {np.max(ts):8.3f} {unit_str}") print() print(" percentiles:") - print(" P_05 .. P_95: {:.3f} s .. {:.3f} s".format(p05, p95)) + print(f" P_05 .. P_95: {p05:.3f} {unit_str} .. {p95:.3f} {unit_str}") print( - " P_25 .. P_75: {:.3f} s .. {:.3f} s " - "(IQR = {:.3f} s)".format(p25, p75, iqr) + f" P_25 .. P_75: {p25:.3f} {unit_str} .. {p75:.3f} {unit_str} (IQR = {iqr:.3f} {unit_str})" ) print() diff --git a/scripts/plot_benchmark_comparison.py b/scripts/plot_benchmark_comparison.py new file mode 100755 index 000000000..48d49a8c5 --- /dev/null +++ b/scripts/plot_benchmark_comparison.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# /// script +# requires-python = ">=3.10" +# dependencies = [ +# "matplotlib", +# "pyqt6", +# "numpy", +# ] +# /// + +""" +This script shows `hyperfine` benchmark results as a bar plot grouped by command. +Note all the input files must contain results for all commands. +""" + +import argparse +import json +import pathlib + +import matplotlib.pyplot as plt +import numpy as np + +parser = argparse.ArgumentParser(description=__doc__) +parser.add_argument( + "files", nargs="+", type=pathlib.Path, help="JSON files with benchmark results" +) +parser.add_argument("--title", help="Plot Title") +parser.add_argument( + "--benchmark-names", nargs="+", help="Names of the benchmark groups" +) +parser.add_argument("-o", "--output", help="Save image to the given filename") + +args = parser.parse_args() + +commands = None +data = [] +inputs = [] + +if args.benchmark_names: + assert len(args.files) == len( + args.benchmark_names + ), "Number of benchmark names must match the number of input files." + +for i, filename in enumerate(args.files): + with open(filename) as f: + results = json.load(f)["results"] + benchmark_commands = [b["command"] for b in results] + if commands is None: + commands = benchmark_commands + else: + assert ( + commands == benchmark_commands + ), f"Unexpected commands in {filename}: {benchmark_commands}, expected: {commands}" + data.append([round(b["mean"], 2) for b in results]) + if args.benchmark_names: + inputs.append(args.benchmark_names[i]) + else: + inputs.append(filename.stem) + +data = np.transpose(data) +x = np.arange(len(inputs)) # the label locations +width = 0.25 # the width of the bars + +fig, ax = plt.subplots(layout="constrained") +fig.set_figheight(5) +fig.set_figwidth(10) +for i, command in enumerate(commands): + offset = width * (i + 1) + rects = ax.bar(x + offset, data[i], width, label=command) + +ax.set_xticks(x + 0.5, inputs) +ax.grid(visible=True, axis="y") + +if args.title: + plt.title(args.title) +plt.xlabel("Benchmark") +plt.ylabel("Time [s]") +plt.legend(title="Command") + +if args.output: + plt.savefig(args.output) +else: + plt.show() diff --git a/scripts/plot_histogram.py b/scripts/plot_histogram.py index b084defd0..cc116777d 100755 --- a/scripts/plot_histogram.py +++ b/scripts/plot_histogram.py @@ -1,11 +1,20 @@ #!/usr/bin/env python +# /// script +# requires-python = ">=3.10" +# dependencies = [ +# "matplotlib", +# "pyqt6", +# "numpy", +# ] +# /// """This program shows `hyperfine` benchmark results as a histogram.""" import argparse import json -import numpy as np + import matplotlib.pyplot as plt +import numpy as np parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("file", help="JSON file with benchmark results") @@ -15,17 +24,39 @@ ) parser.add_argument("--bins", help="Number of bins (default: auto)") parser.add_argument( - "--type", help="Type of histogram (*bar*, barstacked, step, stepfilled)" + "--legend-location", + help="Location of the legend on plot (default: upper center)", + choices=[ + "upper center", + "lower center", + "right", + "left", + "best", + "upper left", + "upper right", + "lower left", + "lower right", + "center left", + "center right", + "center", + ], + default="upper center", ) parser.add_argument( - "-o", "--output", help="Save image to the given filename." + "--type", help="Type of histogram (*bar*, barstacked, step, stepfilled)" ) +parser.add_argument("-o", "--output", help="Save image to the given filename.") parser.add_argument( "--t-min", metavar="T", help="Minimum time to be displayed (seconds)" ) parser.add_argument( "--t-max", metavar="T", help="Maximum time to be displayed (seconds)" ) +parser.add_argument( + "--log-count", + help="Use a logarithmic y-axis for the event count", + action="store_true", +) args = parser.parse_args() @@ -44,16 +75,31 @@ bins = int(args.bins) if args.bins else "auto" histtype = args.type if args.type else "bar" +plt.figure(figsize=(10, 5)) plt.hist( - all_times, label=labels, bins=bins, histtype=histtype, range=(t_min, t_max), + all_times, + label=labels, + bins=bins, + histtype=histtype, + range=(t_min, t_max), +) +plt.legend( + loc=args.legend_location, + fancybox=True, + shadow=True, + prop={"size": 10, "family": ["Source Code Pro", "Fira Mono", "Courier New"]}, ) -plt.legend(prop={"family": ["Source Code Pro", "Fira Mono", "Courier New"]}) plt.xlabel("Time [s]") if args.title: plt.title(args.title) +if args.log_count: + plt.yscale("log") +else: + plt.ylim(0, None) + if args.output: - plt.savefig(args.output) + plt.savefig(args.output, dpi=600) else: plt.show() diff --git a/scripts/plot_parametrized.py b/scripts/plot_parametrized.py index fce54de5b..8ab4bc8c2 100755 --- a/scripts/plot_parametrized.py +++ b/scripts/plot_parametrized.py @@ -1,13 +1,21 @@ #!/usr/bin/env python +# /// script +# requires-python = ">=3.10" +# dependencies = [ +# "matplotlib", +# "pyqt6", +# ] +# /// """This program shows parametrized `hyperfine` benchmark results as an errorbar plot.""" import argparse import json -import matplotlib.pyplot as plt import sys +import matplotlib.pyplot as plt + parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("file", help="JSON file with benchmark results", nargs="+") parser.add_argument( @@ -25,9 +33,7 @@ parser.add_argument( "--titles", help="Comma-separated list of titles for the plot legend" ) -parser.add_argument( - "-o", "--output", help="Save image to the given filename." -) +parser.add_argument("-o", "--output", help="Save image to the given filename.") args = parser.parse_args() if args.parameter_name is not None: @@ -38,7 +44,7 @@ def die(msg): - sys.stderr.write("fatal: %s\n" % (msg,)) + sys.stderr.write(f"fatal: {msg}\n") sys.exit(1) @@ -50,8 +56,7 @@ def extract_parameters(results): names = frozenset(names) if len(names) != 1: die( - "benchmarks must all have the same parameter name, but found: %s" - % sorted(names) + f"benchmarks must all have the same parameter name, but found: {sorted(names)}" ) return (next(iter(names)), list(values)) @@ -63,8 +68,7 @@ def unique_parameter(benchmark): die("benchmarks must have exactly one parameter, but found none") if len(params_dict) > 1: die( - "benchmarks must have exactly one parameter, but found multiple: %s" - % sorted(params_dict) + f"benchmarks must have exactly one parameter, but found multiple: {sorted(params_dict)}" ) [(name, value)] = params_dict.items() return (name, float(value)) @@ -79,8 +83,7 @@ def unique_parameter(benchmark): (this_parameter_name, parameter_values) = extract_parameters(results) if parameter_name is not None and this_parameter_name != parameter_name: die( - "files must all have the same parameter name, but found %r vs. %r" - % (parameter_name, this_parameter_name) + f"files must all have the same parameter name, but found {parameter_name!r} vs. {this_parameter_name!r}" ) parameter_name = this_parameter_name diff --git a/scripts/plot_progression.py b/scripts/plot_progression.py index 2545a5702..61b88eb4e 100755 --- a/scripts/plot_progression.py +++ b/scripts/plot_progression.py @@ -1,4 +1,12 @@ #!/usr/bin/env python +# /// script +# requires-python = ">=3.10" +# dependencies = [ +# "pyqt6", +# "matplotlib", +# "numpy", +# ] +# /// """This program shows `hyperfine` benchmark results in a sequential way in order to debug possible background interference, caching effects, @@ -7,6 +15,7 @@ import argparse import json + import matplotlib.pyplot as plt import numpy as np @@ -30,32 +39,48 @@ def moving_average(times, num_runs): metavar="num_runs", help="Width of the moving-average window (default: N/5)", ) +parser.add_argument( + "--no-moving-average", + action="store_true", + help="Do not show moving average curve", +) + args = parser.parse_args() with open(args.file) as f: results = json.load(f)["results"] -label = results[0]["command"] -times = results[0]["times"] -num = len(times) -nums = range(num) +for result in results: + label = result["command"] + times = result["times"] + num = len(times) + nums = range(num) -plt.scatter(x=nums, y=times, marker=".", color="orange") -plt.ylim([0, None]) -plt.xlim([-1, num]) + plt.scatter(x=nums, y=times, marker=".") + plt.ylim([0, None]) + plt.xlim([-1, num]) -moving_average_width = ( - num // 5 if args.moving_average_width is None else args.moving_average_width -) + if not args.no_moving_average: + moving_average_width = ( + num // 5 if args.moving_average_width is None else args.moving_average_width + ) -moving_average = moving_average(times, moving_average_width) -plt.plot(nums, moving_average, "-", color="blue") + average = moving_average(times, moving_average_width) + plt.plot(nums, average, "-") if args.title: plt.title(args.title) -plt.legend(labels=[label], loc="best", fontsize="medium") + +legend = [] +for result in results: + legend.append(result["command"]) + if not args.no_moving_average: + legend.append("moving average") +plt.legend(legend) + plt.ylabel("Time [s]") + if args.output: plt.savefig(args.output) else: diff --git a/scripts/plot_whisker.py b/scripts/plot_whisker.py index 81d6e35c8..5aeb74d93 100755 --- a/scripts/plot_whisker.py +++ b/scripts/plot_whisker.py @@ -1,4 +1,11 @@ #!/usr/bin/env python +# /// script +# requires-python = ">=3.10" +# dependencies = [ +# "matplotlib", +# "pyqt6", +# ] +# /// """This program shows `hyperfine` benchmark results as a box and whisker plot. @@ -10,21 +17,21 @@ import argparse import json + import matplotlib.pyplot as plt parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("file", help="JSON file with benchmark results") parser.add_argument("--title", help="Plot Title") +parser.add_argument("--sort-by", choices=["median"], help="Sort method") parser.add_argument( "--labels", help="Comma-separated list of entries for the plot legend" ) -parser.add_argument( - "-o", "--output", help="Save image to the given filename." -) +parser.add_argument("-o", "--output", help="Save image to the given filename.") args = parser.parse_args() -with open(args.file) as f: +with open(args.file, encoding="utf-8") as f: results = json.load(f)["results"] if args.labels: @@ -33,8 +40,15 @@ labels = [b["command"] for b in results] times = [b["times"] for b in results] +if args.sort_by == "median": + medians = [b["median"] for b in results] + indices = sorted(range(len(labels)), key=lambda k: medians[k]) + labels = [labels[i] for i in indices] + times = [times[i] for i in indices] + +plt.figure(figsize=(10, 6), constrained_layout=True) boxplot = plt.boxplot(times, vert=True, patch_artist=True) -cmap = plt.cm.get_cmap("rainbow") +cmap = plt.get_cmap("rainbow") colors = [cmap(val / len(times)) for val in range(len(times))] for patch, color in zip(boxplot["boxes"], colors): @@ -45,6 +59,7 @@ plt.legend(handles=boxplot["boxes"], labels=labels, loc="best", fontsize="medium") plt.ylabel("Time [s]") plt.ylim(0, None) +plt.xticks(list(range(1, len(labels) + 1)), labels, rotation=45) if args.output: plt.savefig(args.output) else: diff --git a/scripts/ruff.toml b/scripts/ruff.toml new file mode 100644 index 000000000..ff5e2343e --- /dev/null +++ b/scripts/ruff.toml @@ -0,0 +1,4 @@ +target-version = "py310" + +[lint] +extend-select = ["I", "UP", "RUF"] diff --git a/scripts/welch_ttest.py b/scripts/welch_ttest.py index dad8aa0f1..78d63035b 100755 --- a/scripts/welch_ttest.py +++ b/scripts/welch_ttest.py @@ -1,4 +1,10 @@ #!/usr/bin/env python +# /// script +# requires-python = ">=3.10" +# dependencies = [ +# "scipy", +# ] +# /// """This script performs Welch's t-test on a JSON export file with two benchmark results to test whether or not the two distributions are @@ -7,6 +13,7 @@ import argparse import json import sys + from scipy import stats parser = argparse.ArgumentParser(description=__doc__) @@ -20,19 +27,19 @@ print("The input file has to contain exactly two benchmarks") sys.exit(1) -a, b = [x["command"] for x in results[:2]] -X, Y = [x["times"] for x in results[:2]] +a, b = (x["command"] for x in results[:2]) +X, Y = (x["times"] for x in results[:2]) -print("Command 1: {}".format(a)) -print("Command 2: {}\n".format(b)) +print(f"Command 1: {a}") +print(f"Command 2: {b}\n") t, p = stats.ttest_ind(X, Y, equal_var=False) th = 0.05 dispose = p < th -print("t = {:.3}, p = {:.3}".format(t, p)) +print(f"t = {t:.3}, p = {p:.3}") print() if dispose: - print("There is a difference between the two benchmarks (p < {}).".format(th)) + print(f"There is a difference between the two benchmarks (p < {th}).") else: - print("The two benchmarks are almost the same (p >= {}).".format(th)) + print(f"The two benchmarks are almost the same (p >= {th}).") diff --git a/src/benchmark/benchmark_result.rs b/src/benchmark/benchmark_result.rs index 26714c7a3..287c73bef 100644 --- a/src/benchmark/benchmark_result.rs +++ b/src/benchmark/benchmark_result.rs @@ -12,6 +12,11 @@ pub struct BenchmarkResult { /// The full command line of the program that is being benchmarked pub command: String, + /// The full command line of the program that is being benchmarked, possibly including a list of + /// parameters that were not used in the command line template. + #[serde(skip_serializing)] + pub command_with_unused_parameters: String, + /// The average run time pub mean: Second, @@ -37,6 +42,10 @@ pub struct BenchmarkResult { #[serde(skip_serializing_if = "Option::is_none")] pub times: Option>, + /// Maximum memory usage of the process, in bytes + #[serde(skip_serializing_if = "Option::is_none")] + pub memory_usage_byte: Option>, + /// Exit codes of all command invocations pub exit_codes: Vec>, diff --git a/src/benchmark/executor.rs b/src/benchmark/executor.rs index 096c6220f..915b735b6 100644 --- a/src/benchmark/executor.rs +++ b/src/benchmark/executor.rs @@ -1,7 +1,11 @@ -use std::process::{ExitStatus, Stdio}; +#[cfg(windows)] +use std::os::windows::process::CommandExt; +use std::process::ExitStatus; use crate::command::Command; -use crate::options::{CmdFailureAction, CommandOutputPolicy, Options, OutputStyleOption, Shell}; +use crate::options::{ + CmdFailureAction, CommandInputPolicy, CommandOutputPolicy, Options, OutputStyleOption, Shell, +}; use crate::output::progress_bar::get_progress_bar; use crate::timer::{execute_and_measure, TimerResult}; use crate::util::randomized_environment_offset; @@ -12,12 +16,30 @@ use super::timing_result::TimingResult; use anyhow::{bail, Context, Result}; use statistical::mean; +pub enum BenchmarkIteration { + NonBenchmarkRun, + Warmup(u64), + Benchmark(u64), +} + +impl BenchmarkIteration { + pub fn to_env_var_value(&self) -> Option { + match self { + BenchmarkIteration::NonBenchmarkRun => None, + BenchmarkIteration::Warmup(i) => Some(format!("warmup-{}", i)), + BenchmarkIteration::Benchmark(i) => Some(format!("{}", i)), + } + } +} + pub trait Executor { /// Run the given command and measure the execution time fn run_command_and_measure( &self, command: &Command<'_>, + iteration: BenchmarkIteration, command_failure_action: Option, + output_policy: &CommandOutputPolicy, ) -> Result<(TimingResult, ExitStatus)>; /// Perform a calibration of this executor. For example, @@ -35,30 +57,63 @@ pub trait Executor { fn run_command_and_measure_common( mut command: std::process::Command, + iteration: BenchmarkIteration, command_failure_action: CmdFailureAction, + command_input_policy: &CommandInputPolicy, command_output_policy: &CommandOutputPolicy, command_name: &str, ) -> Result { + let stdin = command_input_policy.get_stdin()?; let (stdout, stderr) = command_output_policy.get_stdout_stderr()?; - command.stdin(Stdio::null()).stdout(stdout).stderr(stderr); + command.stdin(stdin).stdout(stdout).stderr(stderr); command.env( "HYPERFINE_RANDOMIZED_ENVIRONMENT_OFFSET", randomized_environment_offset::value(), ); + if let Some(value) = iteration.to_env_var_value() { + command.env("HYPERFINE_ITERATION", value); + } + let result = execute_and_measure(command) - .with_context(|| format!("Failed to run command '{}'", command_name))?; - - if command_failure_action == CmdFailureAction::RaiseError && !result.status.success() { - bail!( - "{}. Use the '-i'/'--ignore-failure' option if you want to ignore this. \ - Alternatively, use the '--show-output' option to debug what went wrong.", - result.status.code().map_or( - "The process has been terminated by a signal".into(), - |c| format!("Command terminated with non-zero exit code: {}", c) - ) - ); + .with_context(|| format!("Failed to run command '{command_name}'"))?; + + if !result.status.success() { + use crate::util::exit_code::extract_exit_code; + + let should_fail = match command_failure_action { + CmdFailureAction::RaiseError => true, + CmdFailureAction::IgnoreAllFailures => false, + CmdFailureAction::IgnoreSpecificFailures(ref codes) => { + // Only fail if the exit code is not in the list of codes to ignore + if let Some(exit_code) = extract_exit_code(result.status) { + !codes.contains(&exit_code) + } else { + // If we can't extract an exit code, treat it as a failure + true + } + } + }; + + if should_fail { + let when = match iteration { + BenchmarkIteration::NonBenchmarkRun => "a non-benchmark run".to_string(), + BenchmarkIteration::Warmup(0) => "the first warmup run".to_string(), + BenchmarkIteration::Warmup(i) => format!("warmup iteration {i}"), + BenchmarkIteration::Benchmark(0) => "the first benchmark run".to_string(), + BenchmarkIteration::Benchmark(i) => format!("benchmark iteration {i}"), + }; + bail!( + "{cause} in {when}. Use the '-i'/'--ignore-failure' option if you want to ignore this. \ + Alternatively, use the '--show-output' option to debug what went wrong.", + cause=result.status.code().map_or( + "The process has been terminated by a signal".into(), + |c| format!("Command terminated with non-zero exit code {c}") + + ), + ); + } } Ok(result) @@ -74,16 +129,20 @@ impl<'a> RawExecutor<'a> { } } -impl<'a> Executor for RawExecutor<'a> { +impl Executor for RawExecutor<'_> { fn run_command_and_measure( &self, command: &Command<'_>, + iteration: BenchmarkIteration, command_failure_action: Option, + output_policy: &CommandOutputPolicy, ) -> Result<(TimingResult, ExitStatus)> { let result = run_command_and_measure_common( command.get_command()?, - command_failure_action.unwrap_or(self.options.command_failure_action), - &self.options.command_output_policy, + iteration, + command_failure_action.unwrap_or_else(|| self.options.command_failure_action.clone()), + &self.options.command_input_policy, + output_policy, &command.get_command_line(), )?; @@ -92,6 +151,7 @@ impl<'a> Executor for RawExecutor<'a> { time_real: result.time_real, time_user: result.time_user, time_system: result.time_system, + memory_usage_byte: result.memory_usage_byte, }, result.status, )) @@ -122,27 +182,32 @@ impl<'a> ShellExecutor<'a> { } } -impl<'a> Executor for ShellExecutor<'a> { +impl Executor for ShellExecutor<'_> { fn run_command_and_measure( &self, command: &Command<'_>, + iteration: BenchmarkIteration, command_failure_action: Option, + output_policy: &CommandOutputPolicy, ) -> Result<(TimingResult, ExitStatus)> { + let on_windows_cmd = cfg!(windows) && *self.shell == Shell::Default("cmd.exe"); let mut command_builder = self.shell.command(); - command_builder - .arg( - if cfg!(windows) && *self.shell == Shell::Default("cmd.exe") { - "/C" - } else { - "-c" - }, - ) - .arg(command.get_command_line()); + command_builder.arg(if on_windows_cmd { "/C" } else { "-c" }); + + // Windows needs special treatment for its behavior on parsing cmd arguments + if on_windows_cmd { + #[cfg(windows)] + command_builder.raw_arg(command.get_command_line()); + } else { + command_builder.arg(command.get_command_line()); + } let mut result = run_command_and_measure_common( command_builder, - command_failure_action.unwrap_or(self.options.command_failure_action), - &self.options.command_output_policy, + iteration, + command_failure_action.unwrap_or_else(|| self.options.command_failure_action.clone()), + &self.options.command_input_policy, + output_policy, &command.get_command_line(), )?; @@ -158,6 +223,7 @@ impl<'a> Executor for ShellExecutor<'a> { time_real: result.time_real, time_user: result.time_user, time_system: result.time_system, + memory_usage_byte: result.memory_usage_byte, }, result.status, )) @@ -182,7 +248,12 @@ impl<'a> Executor for ShellExecutor<'a> { for _ in 0..COUNT { // Just run the shell without any command - let res = self.run_command_and_measure(&Command::new(None, ""), None); + let res = self.run_command_and_measure( + &Command::new(None, ""), + BenchmarkIteration::NonBenchmarkRun, + None, + &CommandOutputPolicy::Null, + ); match res { Err(_) => { @@ -217,6 +288,7 @@ impl<'a> Executor for ShellExecutor<'a> { time_real: mean(×_real), time_user: mean(×_user), time_system: mean(×_system), + memory_usage_byte: 0, }); Ok(()) @@ -251,7 +323,9 @@ impl Executor for MockExecutor { fn run_command_and_measure( &self, command: &Command<'_>, + _iteration: BenchmarkIteration, _command_failure_action: Option, + _output_policy: &CommandOutputPolicy, ) -> Result<(TimingResult, ExitStatus)> { #[cfg(unix)] let status = { @@ -270,6 +344,7 @@ impl Executor for MockExecutor { time_real: Self::extract_time(command.get_command_line()), time_user: 0.0, time_system: 0.0, + memory_usage_byte: 0, }, status, )) diff --git a/src/benchmark/mod.rs b/src/benchmark/mod.rs index c7978b8bd..e3534a7bc 100644 --- a/src/benchmark/mod.rs +++ b/src/benchmark/mod.rs @@ -6,12 +6,15 @@ pub mod timing_result; use std::cmp; +use crate::benchmark::executor::BenchmarkIteration; use crate::command::Command; -use crate::options::{CmdFailureAction, ExecutorKind, Options, OutputStyleOption}; +use crate::options::{ + CmdFailureAction, CommandOutputPolicy, ExecutorKind, Options, OutputStyleOption, +}; use crate::outlier_detection::{modified_zscores, OUTLIER_THRESHOLD}; use crate::output::format::{format_duration, format_duration_unit}; use crate::output::progress_bar::get_progress_bar; -use crate::output::warnings::Warnings; +use crate::output::warnings::{OutlierWarningOptions, Warnings}; use crate::parameter::ParameterNameAndValue; use crate::util::exit_code::extract_exit_code; use crate::util::min_max::{max, min}; @@ -55,9 +58,15 @@ impl<'a> Benchmark<'a> { &self, command: &Command<'_>, error_output: &'static str, + output_policy: &CommandOutputPolicy, ) -> Result { self.executor - .run_command_and_measure(command, Some(CmdFailureAction::RaiseError)) + .run_command_and_measure( + command, + executor::BenchmarkIteration::NonBenchmarkRun, + Some(CmdFailureAction::RaiseError), + output_policy, + ) .map(|r| r.0) .map_err(|_| anyhow!(error_output)) } @@ -66,6 +75,7 @@ impl<'a> Benchmark<'a> { fn run_setup_command( &self, parameters: impl IntoIterator>, + output_policy: &CommandOutputPolicy, ) -> Result { let command = self .options @@ -77,7 +87,7 @@ impl<'a> Benchmark<'a> { Append ' || true' to the command if you are sure that this can be ignored."; Ok(command - .map(|cmd| self.run_intermediate_command(&cmd, error_output)) + .map(|cmd| self.run_intermediate_command(&cmd, error_output, output_policy)) .transpose()? .unwrap_or_default()) } @@ -86,6 +96,7 @@ impl<'a> Benchmark<'a> { fn run_cleanup_command( &self, parameters: impl IntoIterator>, + output_policy: &CommandOutputPolicy, ) -> Result { let command = self .options @@ -97,37 +108,55 @@ impl<'a> Benchmark<'a> { Append ' || true' to the command if you are sure that this can be ignored."; Ok(command - .map(|cmd| self.run_intermediate_command(&cmd, error_output)) + .map(|cmd| self.run_intermediate_command(&cmd, error_output, output_policy)) .transpose()? .unwrap_or_default()) } /// Run the command specified by `--prepare`. - fn run_preparation_command(&self, command: &Command<'_>) -> Result { + fn run_preparation_command( + &self, + command: &Command<'_>, + output_policy: &CommandOutputPolicy, + ) -> Result { let error_output = "The preparation command terminated with a non-zero exit code. \ Append ' || true' to the command if you are sure that this can be ignored."; - self.run_intermediate_command(command, error_output) + self.run_intermediate_command(command, error_output, output_policy) + } + + /// Run the command specified by `--conclude`. + fn run_conclusion_command( + &self, + command: &Command<'_>, + output_policy: &CommandOutputPolicy, + ) -> Result { + let error_output = "The conclusion command terminated with a non-zero exit code. \ + Append ' || true' to the command if you are sure that this can be ignored."; + + self.run_intermediate_command(command, error_output, output_policy) } /// Run the benchmark for a single command pub fn run(&self) -> Result { - let command_name = self.command.get_name(); if self.options.output_style != OutputStyleOption::Disabled { println!( "{}{}: {}", "Benchmark ".bold(), (self.number + 1).to_string().bold(), - command_name, + self.command.get_name_with_unused_parameters(), ); } let mut times_real: Vec = vec![]; let mut times_user: Vec = vec![]; let mut times_system: Vec = vec![]; + let mut memory_usage_byte: Vec = vec![]; let mut exit_codes: Vec> = vec![]; let mut all_succeeded = true; + let output_policy = &self.options.command_output_policies[self.number]; + let preparation_command = self.options.preparation_command.as_ref().map(|values| { let preparation_command = if values.len() == 1 { &values[0] @@ -140,14 +169,34 @@ impl<'a> Benchmark<'a> { self.command.get_parameters().iter().cloned(), ) }); + let run_preparation_command = || { preparation_command .as_ref() - .map(|cmd| self.run_preparation_command(cmd)) + .map(|cmd| self.run_preparation_command(cmd, output_policy)) .transpose() }; - self.run_setup_command(self.command.get_parameters().iter().cloned())?; + let conclusion_command = self.options.conclusion_command.as_ref().map(|values| { + let conclusion_command = if values.len() == 1 { + &values[0] + } else { + &values[self.number] + }; + Command::new_parametrized( + None, + conclusion_command, + self.command.get_parameters().iter().cloned(), + ) + }); + let run_conclusion_command = || { + conclusion_command + .as_ref() + .map(|cmd| self.run_conclusion_command(cmd, output_policy)) + .transpose() + }; + + self.run_setup_command(self.command.get_parameters().iter().cloned(), output_policy)?; // Warmup phase if self.options.warmup_count > 0 { @@ -161,9 +210,15 @@ impl<'a> Benchmark<'a> { None }; - for _ in 0..self.options.warmup_count { + for i in 0..self.options.warmup_count { let _ = run_preparation_command()?; - let _ = self.executor.run_command_and_measure(self.command, None)?; + let _ = self.executor.run_command_and_measure( + self.command, + BenchmarkIteration::Warmup(i), + None, + output_policy, + )?; + let _ = run_conclusion_command()?; if let Some(bar) = progress_bar.as_ref() { bar.inc(1) } @@ -189,13 +244,24 @@ impl<'a> Benchmark<'a> { preparation_result.map_or(0.0, |res| res.time_real + self.executor.time_overhead()); // Initial timing run - let (res, status) = self.executor.run_command_and_measure(self.command, None)?; + let (res, status) = self.executor.run_command_and_measure( + self.command, + BenchmarkIteration::Benchmark(0), + None, + output_policy, + )?; let success = status.success(); + let conclusion_result = run_conclusion_command()?; + let conclusion_overhead = + conclusion_result.map_or(0.0, |res| res.time_real + self.executor.time_overhead()); + // Determine number of benchmark runs let runs_in_min_time = (self.options.min_benchmarking_time - / (res.time_real + self.executor.time_overhead() + preparation_overhead)) - as u64; + / (res.time_real + + self.executor.time_overhead() + + preparation_overhead + + conclusion_overhead)) as u64; let count = { let min = cmp::max(runs_in_min_time, self.options.run_bounds.min); @@ -214,6 +280,7 @@ impl<'a> Benchmark<'a> { times_real.push(res.time_real); times_user.push(res.time_user); times_system.push(res.time_system); + memory_usage_byte.push(res.memory_usage_byte); exit_codes.push(extract_exit_code(status)); all_succeeded = all_succeeded && success; @@ -227,7 +294,7 @@ impl<'a> Benchmark<'a> { } // Gather statistics (perform the actual benchmark) - for _ in 0..count_remaining { + for i in 0..count_remaining { run_preparation_command()?; let msg = { @@ -239,12 +306,18 @@ impl<'a> Benchmark<'a> { bar.set_message(msg.to_owned()) } - let (res, status) = self.executor.run_command_and_measure(self.command, None)?; + let (res, status) = self.executor.run_command_and_measure( + self.command, + BenchmarkIteration::Benchmark(i + 1), + None, + output_policy, + )?; let success = status.success(); times_real.push(res.time_real); times_user.push(res.time_user); times_system.push(res.time_system); + memory_usage_byte.push(res.memory_usage_byte); exit_codes.push(extract_exit_code(status)); all_succeeded = all_succeeded && success; @@ -252,6 +325,8 @@ impl<'a> Benchmark<'a> { if let Some(bar) = progress_bar.as_ref() { bar.inc(1) } + + run_conclusion_command()?; } if let Some(bar) = progress_bar.as_ref() { @@ -277,7 +352,7 @@ impl<'a> Benchmark<'a> { let (mean_str, time_unit) = format_duration_unit(t_mean, self.options.time_unit); let min_str = format_duration(t_min, Some(time_unit)); let max_str = format_duration(t_max, Some(time_unit)); - let num_str = format!("{} runs", t_num); + let num_str = format!("{t_num} runs"); let user_str = format_duration(user_mean, Some(time_unit)); let system_str = format_duration(system_mean, Some(time_unit)); @@ -326,17 +401,32 @@ impl<'a> Benchmark<'a> { warnings.push(Warnings::FastExecutionTime); } - // Check programm exit codes + // Check program exit codes if !all_succeeded { warnings.push(Warnings::NonZeroExitCode); } // Run outlier detection let scores = modified_zscores(×_real); + + let outlier_warning_options = OutlierWarningOptions { + warmup_in_use: self.options.warmup_count > 0, + prepare_in_use: self + .options + .preparation_command + .as_ref() + .map(|v| v.len()) + .unwrap_or(0) + > 0, + }; + if scores[0] > OUTLIER_THRESHOLD { - warnings.push(Warnings::SlowInitialRun(times_real[0])); + warnings.push(Warnings::SlowInitialRun( + times_real[0], + outlier_warning_options, + )); } else if scores.iter().any(|&s| s.abs() > OUTLIER_THRESHOLD) { - warnings.push(Warnings::OutliersDetected); + warnings.push(Warnings::OutliersDetected(outlier_warning_options)); } if !warnings.is_empty() { @@ -351,10 +441,11 @@ impl<'a> Benchmark<'a> { println!(" "); } - self.run_cleanup_command(self.command.get_parameters().iter().cloned())?; + self.run_cleanup_command(self.command.get_parameters().iter().cloned(), output_policy)?; Ok(BenchmarkResult { - command: command_name, + command: self.command.get_name(), + command_with_unused_parameters: self.command.get_name_with_unused_parameters(), mean: t_mean, stddev: t_stddev, median: t_median, @@ -363,6 +454,7 @@ impl<'a> Benchmark<'a> { min: t_min, max: t_max, times: Some(times_real), + memory_usage_byte: Some(memory_usage_byte), exit_codes, parameters: self .command diff --git a/src/benchmark/relative_speed.rs b/src/benchmark/relative_speed.rs index 02de11b6d..90918e527 100644 --- a/src/benchmark/relative_speed.rs +++ b/src/benchmark/relative_speed.rs @@ -1,57 +1,121 @@ use std::cmp::Ordering; use super::benchmark_result::BenchmarkResult; -use crate::util::units::Scalar; +use crate::{options::SortOrder, util::units::Scalar}; #[derive(Debug)] pub struct BenchmarkResultWithRelativeSpeed<'a> { pub result: &'a BenchmarkResult, pub relative_speed: Scalar, pub relative_speed_stddev: Option, - pub is_fastest: bool, + pub is_reference: bool, + // Less means faster + pub relative_ordering: Ordering, } pub fn compare_mean_time(l: &BenchmarkResult, r: &BenchmarkResult) -> Ordering { l.mean.partial_cmp(&r.mean).unwrap_or(Ordering::Equal) } -pub fn compute(results: &[BenchmarkResult]) -> Option> { - let fastest: &BenchmarkResult = results +pub fn fastest_of(results: &[BenchmarkResult]) -> &BenchmarkResult { + results .iter() .min_by(|&l, &r| compare_mean_time(l, r)) - .expect("at least one benchmark result"); + .expect("at least one benchmark result") +} + +fn compute_relative_speeds<'a>( + results: &'a [BenchmarkResult], + reference: &'a BenchmarkResult, + sort_order: SortOrder, +) -> Vec> { + let mut results: Vec<_> = results + .iter() + .map(|result| { + let is_reference = result == reference; + let relative_ordering = compare_mean_time(result, reference); + + if result.mean == 0.0 { + return BenchmarkResultWithRelativeSpeed { + result, + relative_speed: if is_reference { 1.0 } else { f64::INFINITY }, + relative_speed_stddev: None, + is_reference, + relative_ordering, + }; + } + + let ratio = match relative_ordering { + Ordering::Less => reference.mean / result.mean, + Ordering::Equal => 1.0, + Ordering::Greater => result.mean / reference.mean, + }; + + // https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulas + // Covariance asssumed to be 0, i.e. variables are assumed to be independent + let ratio_stddev = match (result.stddev, reference.stddev) { + (Some(result_stddev), Some(fastest_stddev)) => Some( + ratio + * ((result_stddev / result.mean).powi(2) + + (fastest_stddev / reference.mean).powi(2)) + .sqrt(), + ), + _ => None, + }; + + BenchmarkResultWithRelativeSpeed { + result, + relative_speed: ratio, + relative_speed_stddev: ratio_stddev, + is_reference, + relative_ordering, + } + }) + .collect(); + + match sort_order { + SortOrder::Command => {} + SortOrder::MeanTime => { + results.sort_unstable_by(|r1, r2| compare_mean_time(r1.result, r2.result)); + } + } + + results +} + +pub fn compute_with_check_from_reference<'a>( + results: &'a [BenchmarkResult], + reference: &'a BenchmarkResult, + sort_order: SortOrder, +) -> Option>> { + if fastest_of(results).mean == 0.0 || reference.mean == 0.0 { + return None; + } + + Some(compute_relative_speeds(results, reference, sort_order)) +} + +pub fn compute_with_check( + results: &[BenchmarkResult], + sort_order: SortOrder, +) -> Option>> { + let fastest = fastest_of(results); if fastest.mean == 0.0 { return None; } - Some( - results - .iter() - .map(|result| { - let ratio = result.mean / fastest.mean; - - // https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulas - // Covariance asssumed to be 0, i.e. variables are assumed to be independent - let ratio_stddev = match (result.stddev, fastest.stddev) { - (Some(result_stddev), Some(fastest_stddev)) => Some( - ratio - * ((result_stddev / result.mean).powi(2) - + (fastest_stddev / fastest.mean).powi(2)) - .sqrt(), - ), - _ => None, - }; + Some(compute_relative_speeds(results, fastest, sort_order)) +} - BenchmarkResultWithRelativeSpeed { - result, - relative_speed: ratio, - relative_speed_stddev: ratio_stddev, - is_fastest: result == fastest, - } - }) - .collect(), - ) +/// Same as compute_with_check, potentially resulting in relative speeds of infinity +pub fn compute( + results: &[BenchmarkResult], + sort_order: SortOrder, +) -> Vec> { + let fastest = fastest_of(results); + + compute_relative_speeds(results, fastest, sort_order) } #[cfg(test)] @@ -60,6 +124,7 @@ fn create_result(name: &str, mean: Scalar) -> BenchmarkResult { BenchmarkResult { command: name.into(), + command_with_unused_parameters: name.into(), mean, stddev: Some(1.0), median: mean, @@ -68,6 +133,7 @@ fn create_result(name: &str, mean: Scalar) -> BenchmarkResult { min: mean, max: mean, times: None, + memory_usage_byte: None, exit_codes: Vec::new(), parameters: BTreeMap::new(), } @@ -83,18 +149,32 @@ fn test_compute_relative_speed() { create_result("cmd3", 5.0), ]; - let annotated_results = compute(&results).unwrap(); + let annotated_results = compute_with_check(&results, SortOrder::Command).unwrap(); assert_relative_eq!(1.5, annotated_results[0].relative_speed); assert_relative_eq!(1.0, annotated_results[1].relative_speed); assert_relative_eq!(2.5, annotated_results[2].relative_speed); } +#[test] +fn test_compute_relative_speed_with_reference() { + use approx::assert_relative_eq; + + let results = vec![create_result("cmd2", 2.0), create_result("cmd3", 5.0)]; + let reference = create_result("cmd2", 4.0); + + let annotated_results = + compute_with_check_from_reference(&results, &reference, SortOrder::Command).unwrap(); + + assert_relative_eq!(2.0, annotated_results[0].relative_speed); + assert_relative_eq!(1.25, annotated_results[1].relative_speed); +} + #[test] fn test_compute_relative_speed_for_zero_times() { let results = vec![create_result("cmd1", 1.0), create_result("cmd2", 0.0)]; - let annotated_results = compute(&results); + let annotated_results = compute_with_check(&results, SortOrder::Command); assert!(annotated_results.is_none()); } diff --git a/src/benchmark/scheduler.rs b/src/benchmark/scheduler.rs index 734efbb60..242dd0e7d 100644 --- a/src/benchmark/scheduler.rs +++ b/src/benchmark/scheduler.rs @@ -1,12 +1,12 @@ -use colored::*; - use super::benchmark_result::BenchmarkResult; use super::executor::{Executor, MockExecutor, RawExecutor, ShellExecutor}; use super::{relative_speed, Benchmark}; +use colored::*; +use std::cmp::Ordering; -use crate::command::Commands; +use crate::command::{Command, Commands}; use crate::export::ExportManager; -use crate::options::{ExecutorKind, Options, OutputStyleOption}; +use crate::options::{ExecutorKind, Options, OutputStyleOption, SortOrder}; use anyhow::Result; @@ -38,16 +38,21 @@ impl<'a> Scheduler<'a> { ExecutorKind::Shell(ref shell) => Box::new(ShellExecutor::new(shell, self.options)), }; + let reference = self + .options + .reference_command + .as_ref() + .map(|cmd| Command::new(self.options.reference_name.as_deref(), cmd)); + executor.calibrate()?; - for (number, cmd) in self.commands.iter().enumerate() { + for (number, cmd) in reference.iter().chain(self.commands.iter()).enumerate() { self.results .push(Benchmark::new(number, cmd, self.options, &*executor).run()?); - // We export (all results so far) after each individual benchmark, because - // we would risk losing all results if a later benchmark fails. - self.export_manager - .write_results(&self.results, self.options.time_unit)?; + // We export results after each individual benchmark, because + // we would risk losing them if a later benchmark fails. + self.export_manager.write_results(&self.results, true)?; } Ok(()) @@ -62,36 +67,162 @@ impl<'a> Scheduler<'a> { return; } - if let Some(mut annotated_results) = relative_speed::compute(&self.results) { - annotated_results.sort_by(|l, r| relative_speed::compare_mean_time(l.result, r.result)); - - let fastest = &annotated_results[0]; - let others = &annotated_results[1..]; - - println!("{}", "Summary".bold()); - println!(" '{}' ran", fastest.result.command.cyan()); - - for item in others { - println!( - "{}{} times faster than '{}'", - format!("{:8.2}", item.relative_speed).bold().green(), - if let Some(stddev) = item.relative_speed_stddev { - format!(" ± {}", format!("{:.2}", stddev).green()) - } else { - "".into() - }, - &item.result.command.magenta() - ); + let reference = self + .options + .reference_command + .as_ref() + .map(|_| &self.results[0]) + .unwrap_or_else(|| relative_speed::fastest_of(&self.results)); + + if let Some(annotated_results) = relative_speed::compute_with_check_from_reference( + &self.results, + reference, + self.options.sort_order_speed_comparison, + ) { + match self.options.sort_order_speed_comparison { + SortOrder::MeanTime => { + println!("{}", "Summary".bold()); + + let reference = annotated_results.iter().find(|r| r.is_reference).unwrap(); + let others = annotated_results.iter().filter(|r| !r.is_reference); + + println!( + " {} ran", + reference.result.command_with_unused_parameters.cyan() + ); + + for item in others { + let stddev = if let Some(stddev) = item.relative_speed_stddev { + format!(" ± {}", format!("{:.2}", stddev).green()) + } else { + "".into() + }; + let comparator = match item.relative_ordering { + Ordering::Less => format!( + "{}{} times slower than", + format!("{:8.2}", item.relative_speed).bold().green(), + stddev + ), + Ordering::Greater => format!( + "{}{} times faster than", + format!("{:8.2}", item.relative_speed).bold().green(), + stddev + ), + Ordering::Equal => format!( + " As fast ({}{}) as", + format!("{:.2}", item.relative_speed).bold().green(), + stddev + ), + }; + println!( + "{} {}", + comparator, + &item.result.command_with_unused_parameters.magenta() + ); + } + } + SortOrder::Command => { + println!("{}", "Relative speed comparison".bold()); + + for item in annotated_results { + println!( + " {}{} {}", + format!("{:10.2}", item.relative_speed).bold().green(), + if item.is_reference { + " ".into() + } else if let Some(stddev) = item.relative_speed_stddev { + format!(" ± {}", format!("{stddev:5.2}").green()) + } else { + " ".into() + }, + &item.result.command_with_unused_parameters, + ); + } + } } } else { eprintln!( "{}: The benchmark comparison could not be computed as some benchmark times are zero. \ This could be caused by background interference during the initial calibration phase \ of hyperfine, in combination with very fast commands (faster than a few milliseconds). \ - Try to re-run the benchmark on a quiet system. If it does not help, you command is \ - most likely too fast to be accurately benchmarked by hyperfine.", + Try to re-run the benchmark on a quiet system. If you did not do so already, try the \ + --shell=none/-N option. If it does not help either, you command is most likely too fast \ + to be accurately benchmarked by hyperfine.", "Note".bold().red() ); } } + + pub fn final_export(&self) -> Result<()> { + self.export_manager.write_results(&self.results, false) + } +} + +#[cfg(test)] +fn generate_results(args: &[&'static str]) -> Result> { + use crate::cli::get_cli_arguments; + + let args = ["hyperfine", "--debug-mode", "--style=none"] + .iter() + .chain(args); + let cli_arguments = get_cli_arguments(args); + let mut options = Options::from_cli_arguments(&cli_arguments)?; + + assert_eq!(options.executor_kind, ExecutorKind::Mock(None)); + + let commands = Commands::from_cli_arguments(&cli_arguments)?; + let export_manager = ExportManager::from_cli_arguments( + &cli_arguments, + options.time_unit, + options.sort_order_exports, + )?; + + options.validate_against_command_list(&commands)?; + + let mut scheduler = Scheduler::new(&commands, &options, &export_manager); + + scheduler.run_benchmarks()?; + Ok(scheduler.results) +} + +#[test] +fn scheduler_basic() -> Result<()> { + insta::assert_yaml_snapshot!(generate_results(&["--runs=2", "sleep 0.123", "sleep 0.456"])?, @r#" + - command: sleep 0.123 + mean: 0.123 + stddev: 0 + median: 0.123 + user: 0 + system: 0 + min: 0.123 + max: 0.123 + times: + - 0.123 + - 0.123 + memory_usage_byte: + - 0 + - 0 + exit_codes: + - 0 + - 0 + - command: sleep 0.456 + mean: 0.456 + stddev: 0 + median: 0.456 + user: 0 + system: 0 + min: 0.456 + max: 0.456 + times: + - 0.456 + - 0.456 + memory_usage_byte: + - 0 + - 0 + exit_codes: + - 0 + - 0 + "#); + + Ok(()) } diff --git a/src/benchmark/timing_result.rs b/src/benchmark/timing_result.rs index d4b84907c..f7aac92bb 100644 --- a/src/benchmark/timing_result.rs +++ b/src/benchmark/timing_result.rs @@ -11,4 +11,7 @@ pub struct TimingResult { /// Time spent in kernel mode pub time_system: Second, + + /// Maximum amount of memory used, in bytes + pub memory_usage_byte: u64, } diff --git a/src/cli.rs b/src/cli.rs index 75f8e238b..b12f6d34c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,6 +1,9 @@ use std::ffi::OsString; -use clap::{crate_version, AppSettings, Arg, ArgMatches, Command}; +use clap::{ + builder::NonEmptyStringValueParser, crate_version, Arg, ArgAction, ArgMatches, Command, + ValueHint, +}; pub fn get_cli_arguments<'a, I, T>(args: I) -> ArgMatches where @@ -12,14 +15,14 @@ where } /// Build the clap command for parsing command line arguments -fn build_command() -> Command<'static> { +fn build_command() -> Command { Command::new("hyperfine") .version(crate_version!()) - .setting(AppSettings::DeriveDisplayOrder) .next_line_help(true) .hide_possible_values(true) - .max_term_width(90) .about("A command-line benchmarking tool.") + .help_expected(true) + .max_term_width(80) .arg( Arg::new("command") .help("The command to benchmark. This can be the name of an executable, a command \ @@ -28,15 +31,16 @@ fn build_command() -> Command<'static> { '--shell=none'. If multiple commands are given, hyperfine will show a \ comparison of the respective runtimes.") .required(true) - .multiple_occurrences(true) - .forbid_empty_values(true), + .action(ArgAction::Append) + .value_hint(ValueHint::CommandString) + .value_parser(NonEmptyStringValueParser::new()), ) .arg( Arg::new("warmup") .long("warmup") .short('w') - .takes_value(true) .value_name("NUM") + .action(ArgAction::Set) .help( "Perform NUM warmup runs before the actual benchmark. This can be used \ to fill (disk) caches for I/O-heavy programs.", @@ -46,7 +50,7 @@ fn build_command() -> Command<'static> { Arg::new("min-runs") .long("min-runs") .short('m') - .takes_value(true) + .action(ArgAction::Set) .value_name("NUM") .help("Perform at least NUM runs for each command (default: 10)."), ) @@ -54,16 +58,16 @@ fn build_command() -> Command<'static> { Arg::new("max-runs") .long("max-runs") .short('M') - .takes_value(true) + .action(ArgAction::Set) .value_name("NUM") .help("Perform at most NUM runs for each command. By default, there is no limit."), ) .arg( Arg::new("runs") .long("runs") - .conflicts_with_all(&["max-runs", "min-runs"]) + .conflicts_with_all(["max-runs", "min-runs"]) .short('r') - .takes_value(true) + .action(ArgAction::Set) .value_name("NUM") .help("Perform exactly NUM runs for each command. If this option is not specified, \ hyperfine automatically determines the number of runs."), @@ -72,9 +76,9 @@ fn build_command() -> Command<'static> { Arg::new("setup") .long("setup") .short('s') - .takes_value(true) - .number_of_values(1) + .action(ArgAction::Set) .value_name("CMD") + .value_hint(ValueHint::CommandString) .help( "Execute CMD before each set of timing runs. This is useful for \ compiling your software with the provided parameters, or to do any \ @@ -82,14 +86,32 @@ fn build_command() -> Command<'static> { not every time as would happen with the --prepare option." ), ) + .arg( + Arg::new("reference") + .long("reference") + .action(ArgAction::Set) + .value_name("CMD") + .help( + "The reference command for the relative comparison of results. \ + If this is unset, results are compared with the fastest command as reference." + ) + ) + .arg( + Arg::new("reference-name") + .long("reference-name") + .action(ArgAction::Set) + .value_name("CMD") + .help("Give a meaningful name to the reference command.") + .requires("reference") + ) .arg( Arg::new("prepare") .long("prepare") .short('p') - .takes_value(true) - .multiple_occurrences(true) - .number_of_values(1) + .action(ArgAction::Append) + .num_args(1) .value_name("CMD") + .value_hint(ValueHint::CommandString) .help( "Execute CMD before each timing run. This is useful for \ clearing disk caches, for example.\nThe --prepare option can \ @@ -98,12 +120,30 @@ fn build_command() -> Command<'static> { be run prior to the corresponding benchmark command.", ), ) + .arg( + Arg::new("conclude") + .long("conclude") + .short('C') + .action(ArgAction::Append) + .num_args(1) + .value_name("CMD") + .value_hint(ValueHint::CommandString) + .help( + "Execute CMD after each timing run. This is useful for killing \ + long-running processes started (e.g. a web server started in --prepare), \ + for example.\nThe --conclude option can be specified once for all \ + commands or multiple times, once for each command. In the latter case, \ + each conclude command will be run after the corresponding benchmark \ + command.", + ), + ) .arg( Arg::new("cleanup") .long("cleanup") .short('c') - .takes_value(true) + .action(ArgAction::Set) .value_name("CMD") + .value_hint(ValueHint::CommandString) .help( "Execute CMD after the completion of all benchmarking \ runs for each individual command to be benchmarked. \ @@ -115,9 +155,9 @@ fn build_command() -> Command<'static> { Arg::new("parameter-scan") .long("parameter-scan") .short('P') - .takes_value(true) + .action(ArgAction::Set) .allow_hyphen_values(true) - .value_names(&["VAR", "MIN", "MAX"]) + .value_names(["VAR", "MIN", "MAX"]) .help( "Perform benchmark runs for each value in the range MIN..MAX. Replaces the \ string '{VAR}' in each command by the current parameter value.\n\n \ @@ -133,8 +173,8 @@ fn build_command() -> Command<'static> { Arg::new("parameter-step-size") .long("parameter-step-size") .short('D') - .takes_value(true) - .value_names(&["DELTA"]) + .action(ArgAction::Set) + .value_names(["DELTA"]) .requires("parameter-scan") .help( "This argument requires --parameter-scan to be specified as well. \ @@ -147,11 +187,10 @@ fn build_command() -> Command<'static> { Arg::new("parameter-list") .long("parameter-list") .short('L') - .takes_value(true) - .multiple_occurrences(true) + .action(ArgAction::Append) .allow_hyphen_values(true) - .value_names(&["VAR", "VALUES"]) - .conflicts_with_all(&["parameter-scan", "parameter-step-size"]) + .value_names(["VAR", "VALUES"]) + .conflicts_with_all(["parameter-scan", "parameter-step-size"]) .help( "Perform benchmark runs for each value in the comma-separated list VALUES. \ Replaces the string '{VAR}' in each command by the current parameter value\ @@ -161,28 +200,14 @@ fn build_command() -> Command<'static> { possible parameter combinations.\n" ), ) - .arg( - Arg::new("style") - .long("style") - .takes_value(true) - .value_name("TYPE") - .possible_values(&["auto", "basic", "full", "nocolor", "color", "none"]) - .help( - "Set output style type (default: auto). Set this to 'basic' to disable output \ - coloring and interactive elements. Set it to 'full' to enable all effects \ - even if no interactive terminal was detected. Set this to 'nocolor' to \ - keep the interactive output without any colors. Set this to 'color' to keep \ - the colors without any interactive output. Set this to 'none' to disable all \ - the output of the tool.", - ), - ) .arg( Arg::new("shell") .long("shell") .short('S') - .takes_value(true) + .action(ArgAction::Set) .value_name("SHELL") .overrides_with("shell") + .value_hint(ValueHint::CommandString) .help("Set the shell to use for executing benchmarked commands. This can be the \ name or the path to the shell executable, or a full command line \ like \"bash --norc\". It can also be set to \"default\" to explicitly select \ @@ -194,39 +219,81 @@ fn build_command() -> Command<'static> { .arg( Arg::new("no-shell") .short('N') - .conflicts_with_all(&["shell", "debug-mode"]) + .action(ArgAction::SetTrue) + .conflicts_with_all(["shell", "debug-mode"]) .help("An alias for '--shell=none'.") ) .arg( Arg::new("ignore-failure") .long("ignore-failure") + .action(ArgAction::Set) + .value_name("MODE") + .num_args(0..=1) + .default_missing_value("all-non-zero") + .require_equals(true) .short('i') - .help("Ignore non-zero exit codes of the benchmarked programs."), + .help("Ignore failures of the benchmarked programs. Without a value or with \ + 'all-non-zero', all non-zero exit codes are ignored. You can also provide \ + a comma-separated list of exit codes to ignore (e.g., --ignore-failure=1,2)."), + ) + .arg( + Arg::new("style") + .long("style") + .action(ArgAction::Set) + .value_name("TYPE") + .value_parser(["auto", "basic", "full", "nocolor", "color", "none"]) + .help( + "Set output style type (default: auto). Set this to 'basic' to disable output \ + coloring and interactive elements. Set it to 'full' to enable all effects \ + even if no interactive terminal was detected. Set this to 'nocolor' to \ + keep the interactive output without any colors. Set this to 'color' to keep \ + the colors without any interactive output. Set this to 'none' to disable all \ + the output of the tool.", + ), + ) + .arg( + Arg::new("sort") + .long("sort") + .action(ArgAction::Set) + .value_name("METHOD") + .value_parser(["auto", "command", "mean-time"]) + .default_value("auto") + .hide_default_value(true) + .help( + "Specify the sort order of the speed comparison summary and the exported tables for \ + markup formats (Markdown, AsciiDoc, org-mode):\n \ + * 'auto' (default): the speed comparison will be ordered by time and\n \ + the markup tables will be ordered by command (input order).\n \ + * 'command': order benchmarks in the way they were specified\n \ + * 'mean-time': order benchmarks by mean runtime\n" + ), ) .arg( Arg::new("time-unit") .long("time-unit") .short('u') - .takes_value(true) + .action(ArgAction::Set) .value_name("UNIT") - .possible_values(&["millisecond", "second"]) - .help("Set the time unit to be used. Possible values: millisecond, second. \ + .value_parser(["microsecond", "millisecond", "second"]) + .help("Set the time unit to be used. Possible values: microsecond, millisecond, second. \ If the option is not given, the time unit is determined automatically. \ This option affects the standard output as well as all export formats except for CSV and JSON."), ) .arg( Arg::new("export-asciidoc") .long("export-asciidoc") - .takes_value(true) + .action(ArgAction::Set) .value_name("FILE") + .value_hint(ValueHint::FilePath) .help("Export the timing summary statistics as an AsciiDoc table to the given FILE. \ The output time unit can be changed using the --time-unit option."), ) .arg( Arg::new("export-csv") .long("export-csv") - .takes_value(true) + .action(ArgAction::Set) .value_name("FILE") + .value_hint(ValueHint::FilePath) .help("Export the timing summary statistics as CSV to the given FILE. If you need \ the timing results for each individual run, use the JSON export format. \ The output time unit is always seconds."), @@ -234,30 +301,34 @@ fn build_command() -> Command<'static> { .arg( Arg::new("export-json") .long("export-json") - .takes_value(true) + .action(ArgAction::Set) .value_name("FILE") + .value_hint(ValueHint::FilePath) .help("Export the timing summary statistics and timings of individual runs as JSON to the given FILE. \ The output time unit is always seconds"), ) .arg( Arg::new("export-markdown") .long("export-markdown") - .takes_value(true) + .action(ArgAction::Set) .value_name("FILE") + .value_hint(ValueHint::FilePath) .help("Export the timing summary statistics as a Markdown table to the given FILE. \ The output time unit can be changed using the --time-unit option."), ) .arg( Arg::new("export-orgmode") .long("export-orgmode") - .takes_value(true) + .action(ArgAction::Set) .value_name("FILE") - .help("Export the timing summary statistics as a Emacs org-mode table to the given FILE. \ + .value_hint(ValueHint::FilePath) + .help("Export the timing summary statistics as an Emacs org-mode table to the given FILE. \ The output time unit can be changed using the --time-unit option."), ) .arg( Arg::new("show-output") .long("show-output") + .action(ArgAction::SetTrue) .conflicts_with("style") .help( "Print the stdout and stderr of the benchmark instead of suppressing it. \ @@ -270,26 +341,49 @@ fn build_command() -> Command<'static> { Arg::new("output") .long("output") .conflicts_with("show-output") - .takes_value(true) + .action(ArgAction::Append) .value_name("WHERE") .help( - "Control where the output of the benchmark is redirected. can be:\n\n\ - null: Redirect output to /dev/null (the default). \ - Note that some programs like 'grep' detect when standard output is /dev/null \ - and apply certain optimizations. To avoid that, consider using \ - '--output=pipe'.\n\n\ - pipe: Feed the output through a pipe before discarding it.\n\n\ - inherit: Don't redirect the output at all (same as '--show-output').\n\n\ - : Write the output to the given file.", + "Control where the output of the benchmark is redirected. Note \ + that some programs like 'grep' detect when standard output is \ + /dev/null and apply certain optimizations. To avoid that, consider \ + using '--output=pipe'.\n\ + \n\ + can be:\n\ + \n \ + null: Redirect output to /dev/null (the default).\n\ + \n \ + pipe: Feed the output through a pipe before discarding it.\n\ + \n \ + inherit: Don't redirect the output at all (same as '--show-output').\n\ + \n \ + : Write the output to the given file.\n\n\ + This option can be specified once for all commands or multiple times, once for \ + each command. Note: If you want to log the output of each and every iteration, \ + you can use a shell redirection and the '$HYPERFINE_ITERATION' environment variable:\n \ + hyperfine 'my-command > output-${HYPERFINE_ITERATION}.log'\n\n", ), ) + .arg( + Arg::new("input") + .long("input") + .action(ArgAction::Set) + .num_args(1) + .value_name("WHERE") + .help("Control where the input of the benchmark comes from.\n\ + \n\ + can be:\n\ + \n \ + null: Read from /dev/null (the default).\n\ + \n \ + : Read the input from the given file."), + ) .arg( Arg::new("command-name") .long("command-name") .short('n') - .takes_value(true) - .multiple_occurrences(true) - .number_of_values(1) + .action(ArgAction::Append) + .num_args(1) .value_name("NAME") .help("Give a meaningful name to a command. This can be specified multiple times \ if several commands are benchmarked."), @@ -299,7 +393,7 @@ fn build_command() -> Command<'static> { .arg( Arg::new("min-benchmarking-time") .long("min-benchmarking-time") - .takes_value(true) + .action(ArgAction::Set) .hide(true) .help("Set the minimum time (in seconds) to run benchmarks. Note that the number of \ benchmark runs is additionally influenced by the `--min-runs`, `--max-runs`, and \ @@ -308,6 +402,7 @@ fn build_command() -> Command<'static> { .arg( Arg::new("debug-mode") .long("debug-mode") + .action(ArgAction::SetTrue) .hide(true) .help("Enable debug mode which does not actually run commands, but returns fake times when the command is 'sleep