hyperfine-1.19.0/.cargo_vcs_info.json0000644000000001360000000000100131150ustar { "git": { "sha1": "12fec42098642a19855ead34c8cb1e0be28c8ead" }, "path_in_vcs": "" }hyperfine-1.19.0/.github/dependabot.yml000064400000000000000000000004351046102023000160770ustar 00000000000000version: 2 updates: - package-ecosystem: cargo directory: "/" schedule: interval: monthly time: "04:00" timezone: Europe/Berlin open-pull-requests-limit: 2 - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" hyperfine-1.19.0/.github/workflows/CICD.yml000064400000000000000000000347371046102023000165450ustar 00000000000000name: CICD env: CICD_INTERMEDIATES_DIR: "_cicd-intermediates" MSRV_FEATURES: "" on: workflow_dispatch: pull_request: push: branches: - master tags: - '*' jobs: crate_metadata: name: Extract crate metadata runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - 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-20.04 steps: - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - uses: actions/checkout@v4 - run: cargo fmt -- --check min_version: name: Minimum supported rust version runs-on: ubuntu-20.04 needs: crate_metadata steps: - name: Checkout source code uses: actions/checkout@v4 - name: Install rust toolchain (v${{ needs.crate_metadata.outputs.msrv }}) uses: dtolnay/rust-toolchain@master with: toolchain: ${{ needs.crate_metadata.outputs.msrv }} components: clippy - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix) run: cargo clippy --locked --all-targets ${{ env.MSRV_FEATURES }} - name: Run tests 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: aarch64-apple-darwin , os: macos-14 } - { 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-12 } # - { 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 } env: BUILD_CMD: cargo steps: - name: Checkout source code uses: actions/checkout@v4 - name: Install prerequisites shell: bash run: | case ${{ matrix.job.target }} in arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; esac - name: Install Rust toolchain 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: 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 rustup default cargo -V rustc -V - name: Build shell: bash run: $BUILD_CMD build --locked --release --target=${{ matrix.job.target }} - name: Set binary name & path id: bin shell: bash run: | # Figure out suffix of binary EXE_suffix="" case ${{ matrix.job.target }} in *-pc-windows-*) EXE_suffix=".exe" ;; esac; # Setup paths 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 binary echo "BIN_PATH=${BIN_PATH}" >> $GITHUB_OUTPUT echo "BIN_NAME=${BIN_NAME}" >> $GITHUB_OUTPUT - name: Set testing options id: test-options shell: bash 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 ${{ steps.bin.outputs.BIN_NAME }}" ;; esac; echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_OUTPUT - name: Run tests 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=${{ needs.crate_metadata.outputs.name }}-v${{ needs.crate_metadata.outputs.version }}-${{ matrix.job.target }} PKG_NAME=${PKG_BASENAME}${PKG_suffix} echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_OUTPUT PKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/package" ARCHIVE_DIR="${PKG_STAGING}/${PKG_BASENAME}/" mkdir -p "${ARCHIVE_DIR}" mkdir -p "${ARCHIVE_DIR}/autocomplete" # Binary 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/${{ 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 case ${{ matrix.job.target }} in *-pc-windows-*) 7z -y a "${PKG_NAME}" "${PKG_BASENAME}"/* | tail -2 ;; *) tar czf "${PKG_NAME}" "${PKG_BASENAME}"/* ;; esac; popd >/dev/null # Let subsequent steps know where to find the compressed package echo "PKG_PATH=${PKG_STAGING}/${PKG_NAME}" >> $GITHUB_OUTPUT - name: Create Debian package id: debian-package shell: bash if: startsWith(matrix.job.os, 'ubuntu') run: | COPYRIGHT_YEARS="2018 - "$(date "+%Y") DPKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/debian-package" DPKG_DIR="${DPKG_STAGING}/dpkg" mkdir -p "${DPKG_DIR}" 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 aarch64-*-linux-*) DPKG_ARCH=arm64 ;; arm-*-linux-*hf) DPKG_ARCH=armhf ;; i686-*-linux-*) DPKG_ARCH=i686 ;; x86_64-*-linux-*) DPKG_ARCH=amd64 ;; *) DPKG_ARCH=notset ;; esac; DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb" echo "DPKG_NAME=${DPKG_NAME}" >> $GITHUB_OUTPUT # Binary install -Dm755 "${{ steps.bin.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.bin.outputs.BIN_NAME }}" # Man page 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/${{ 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" install -Dm644 "LICENSE-MIT" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-MIT" install -Dm644 "LICENSE-APACHE" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-APACHE" install -Dm644 "CHANGELOG.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog" gzip -n --best "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog" cat > "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright" < "${DPKG_DIR}/DEBIAN/control" <> $GITHUB_OUTPUT # build dpkg fakeroot dpkg-deb --build "${DPKG_DIR}" "${DPKG_PATH}" - name: "Artifact upload: tarball" uses: actions/upload-artifact@master with: name: ${{ steps.package.outputs.PKG_NAME }} path: ${{ steps.package.outputs.PKG_PATH }} - name: "Artifact upload: Debian package" uses: actions/upload-artifact@master if: steps.debian-package.outputs.DPKG_NAME with: name: ${{ steps.debian-package.outputs.DPKG_NAME }} path: ${{ steps.debian-package.outputs.DPKG_PATH }} - name: Check for release id: is-release shell: bash run: | unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/v[0-9].* ]]; then IS_RELEASE='true' ; fi echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT - name: Publish archives and packages uses: softprops/action-gh-release@v2 if: steps.is-release.outputs.IS_RELEASE with: files: | ${{ steps.package.outputs.PKG_PATH }} ${{ 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 }} hyperfine-1.19.0/.gitignore000064400000000000000000000000251046102023000136720ustar 00000000000000 /target/ **/*.rs.bk hyperfine-1.19.0/CHANGELOG.md000064400000000000000000000333031046102023000135200ustar 00000000000000# 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 - Fix uncolored output on Windows if `TERM` is not set, see #583 (@nabijaczleweli) - On Windows, only run `cmd.exe` with the `/C` option. Use `-c` for all other shells. See #568 and #582 (@FilipAndersson245) ## Other - Thanks to @berombau for working on dependency upgrades, see #584 - Fixed installationm on Windows, see #595 and #596 (@AntoniosBarotsis) # v1.15.0 ## Features - Disable colorized output in case of `TERM=dumb` or `NO_COLOR=1`, see #542 and #555 (@nabijaczleweli) - Add new (experimental) `--min-benchmarking-time ` option, see #527 (@sharkdp) ## Bugfixes - Fix user and kernel times on Windows, see #368 and #538 (@clemenswasser) ## Other - Improve `--help` texts of `--export-*` options, see #506 and #522 (@Engineer-of-Efficiency) # v1.14.0 ## Features - Add a new `--output={null,pipe,inherit,}` option to control where the output of the benchmarked program is redirected (if at all), see #377 and #509 (@tavianator, originally suggested by @BurntSushi) - Add Emacs org-mode as a new export format, see #491 (@ppaulweber) # v1.13.0 ## Features - Added a new `--shell=none`/`-N` option to disable the intermediate shell for executing the benchmarked commands. Hyperfine normally measures and subtracts the shell spawning time, but the intermediate shell always introduces a certain level of measurement noise. Using `--shell=none`/`-N` allows users to benchmark very fast commands (with a runtime on the order of a few milliseconds). See #336, #429, and #487 (@cipriancraciun and @sharkdp) - Added `--setup`/`-s` option that can be used to run `make all` or similar. It runs once per set of tests, like `--cleanup`/`-c` (@avar) - Added new `plot_progression.py` script to debug background interference effects. ## Changes - Breaking change: the `-s` short option for `--style` is now used for the new `--setup` option. - The environment offset randomization is now also available on Windows, see #484 ## Other - Improved documentation and test coverage, cleaned up code base for future improvements. # v1.12.0 ## Features - `--command-name` can now take parameter names from `--parameter-*` options, see #351 and #391 (@silathdiir) - Exit codes (or signals) are now printed in cases of command failures, see #342 (@KaindlJulian) - Exit codes are now part of the JSON output, see #371 (@JordiChauzi) - Colorized output should now be enabled on Windows by default, see #427 ## Changes - When `--export-*` commands are used, result files are created before benchmark execution to fail early in case of, e.g., wrong permissions. See #306 (@s1ck). - When `--export-*` options are used, result files are written after each individual benchmark command instead of writing after all benchmarks have finished. See #306 (@s1ck). - Reduce number of shell startup time measurements from 200 to 50, generally speeding up benchmarks. See #378 - User and system time are now in consistent time units, see #408 and #409 (@film42) # v1.11.0 ## Features - The `-L`/`--parameter-list` option can now be specified multiple times to evaluate all possible combinations of the listed parameters: ``` bash hyperfine -L number 1,2 -L letter a,b,c \ "echo {number}{letter}" \ "printf '%s\n' {number}{letter}" # runs 12 benchmarks: 2 commands (echo and printf) times 6 combinations of # the "letter" and "number" parameters ``` See: #253, #318 (@wchargin) - Add CLI option to identify a command with a custom name, see #326 (@scampi) ## Changes - When parameters are used with `--parameter-list` or `--parameter-scan`, the JSON export format now contains a dictionary `parameters` instead of a single key `parameter`. See #253, #318. - The `plot_parametrized.py` script now infers the parameter name, and its `--parameter-name` argument has been deprecated. See #253, #318. ## Bugfixes - Fix a bug in the outlier detection which would only detect "slow outliers" but not the fast ones (runs that are much faster than the rest of the benchmarking runs), see #329 - Better error messages for very fast commands that would lead to inf/nan results in the relative speed comparison, see #319 - Show error message if `--warmup` or `--*runs` arguments can not be parsed, see #337 - Keep output colorized when the output is not interactive and `--style=full` or `--style=color` is used. # v1.10.0 ## Features - Hyperfine now comes with shell completion files for Bash, Zsh, Fish and PowerShell, see #290 (@four0000four). - Hyperfine now comes with a basic man page, see #257 (@cadeef) - During execution of benchmarks, hyperfine will now set a `HYPERFINE_RANDOMIZED_ENVIRONMENT_OFFSET` environment variable in order to randomize the memory layout. See #235 and #241 for references and details. - A few enhancements for the histogram plotting scripts and the advanced statistics script - Updates for the `plot_whisker.py` script, see #275 (@ghaiklor) ## Bugfixes - Fix Spin Icon on Windows, see #229 - A few typos have been fixed, see #292 (@McMartin) ## Packaging - `hyperfine` is now available on MacPorts for macOS, see #281 (@herbygillot) - `hyperfine` is now available on OpenBSD, see #289 (@minusf) Package authors: note that Hyperfine now comes with a set of shell completion files and a man page (see above) # v1.9.0 ## Features - The new `--parameter-list ` option can be used to run a parametrized benchmark on a user-specified list of values. This is similar to `--parameter-scan `, but doesn't necessarily required numeric arguments. ``` bash hyperfine --parameter-list compiler "gcc,clang" \ "{compiler} -O2 main.cpp" ``` See: #227, #234 (@JuanPotato) - Added `none` as a possible choice for the `--style` option to run `hyperfine` without any output, see #193 (@knidarkness) - Added a few new scripts for plotting various types of benchmark results (https://github.com/sharkdp/hyperfine/tree/master/scripts) ## Changes - The `--prepare` command is now also run during the warmup phase, see #182 (@sseemayer) - Better estimation of the remaining benchmark time due to an update of the `indicatif` crate. ## Other - `hyperfine` is now available on NixOS, see #240 (@tuxinaut) # v1.8.0 ## Features - The `--prepare ` option can now be specified multiple times to run specific preparation commands for each of the benchmarked programs: ``` bash hyperfine --prepare "make clean; git checkout master" "make" \ --prepare "make clean; git checkout feature" "make" ``` See: #216, #218 (@iamsauravsharma) - Added a new [`welch_ttest.py`](https://github.com/sharkdp/hyperfine/blob/master/scripts/welch_ttest.py) script to test whether or not the two benchmark results are the same, see #222 (@uetchy) - The Markdown export has been improved. The relative speed is now exported with a higher precision (see #208) and includes the standard deviation (see #225). ## Other - Improved documentation for [`scripts`](https://github.com/sharkdp/hyperfine/tree/master/scripts) folder (@matthieusb) # v1.7.0 ## Features - Added a new `-D`,`--parameter-step-size` option that can be used to control the step size for `--parameter-scan` benchmarks. In addition, decimal numbers are now allowed for parameter scans. For example, the following command runs `sleep 0.3`, `sleep 0.5` and `sleep 0.7`: ``` bash hyperfine --parameter-scan delay 0.3 0.7 -D 0.2 'sleep {delay}' ``` For more details, see #184 (@piyushrungta25) ## Other - hyperfine is now in the official Alpine repositories, see #177 (@maxice8, @5paceToast) - hyperfine is now in the official Fedora repositories, see #196 (@ignatenkobrain) - hyperfine is now in the official Arch Linux repositories - hyperfine can be installed on FreeBSD, see #204 (@0mp) - Enabled LTO for slightly smaller binary sizes, see #179 (@Calinou) - Various small improvements all over the code base, see #194 (@phimuemue) # v1.6.0 ## Features - Added a `-c, --cleanup ` option to execute `CMD` after the completion of all benchmarking runs for a given command. This is useful if the commands to be benchmarked produce artifacts that need to be cleaned up. See #91 (@RalfJung and @colinwahl) - Add parameter values (for `--parameter-scan` benchmarks) to exported CSV and JSON files. See #131 (@bbannier) - Added AsciiDoc export option, see #137 (@5paceToast) - The relative speed is now part of the Markdown export, see #127 (@mathiasrw and @sharkdp). - The *median* run time is now exported via CSV and JSON, see #171 (@hosewiejacke and @sharkdp). ## Other - Hyperfine has been updated to Rust 2018 (@AnderEnder). The minimum supported Rust version is now 1.31. # v1.5.0 ## Features - Show the number of runs in `hyperfine`s output (@tcmal) - Added two Python scripts to post-process exported benchmark results (see [`scripts/`](https://github.com/sharkdp/hyperfine/tree/master/scripts) folder) ## Other - Refined `--help` text for the `--export-*` flags (@psteinb) - Added Snapcraft file (@popey) - Small improvements in the progress bar "experience". # v1.4.0 ## Features - Added `-S`/`--shell` option to override the default shell, see #61 (@mqudsi and @jasonpeacock) - Added `-u`/`--time-unit` option to change the unit of time (`second` or `millisecond`), see #80 (@jasonpeacock) - Markdown export auto-selects time unit, see #71 (@jasonpeacock) # v1.3.0 ## Feature - Compute and print standard deviation of the speed ratio, see #83 (@Shnatsel) - More compact output format, see #70 (@jasonpeacock) - Added `--style=color`, see #70 (@jasonpeacock) - Added options to specify the max/exact numbers of runs, see #77 (@orium) ## Bugfixes - Change Windows `cmd` interpreter to `cmd.exe` to prevent accidentally calling other programs, see #74 (@tathanhdinh) ## Other - Binary releases for Windows are now available, see #87 # v1.2.0 - Support parameters in preparation commands, see #68 (@siiptuo) - Updated dependencies, see #69. The minimum required Rust version is now 1.24. # v1.1.0 * Added `--show-output` option (@chrisduerr and @sevagh) * Refactoring work (@stevepentland) # v1.0.0 ## Features * Support for various export-formats like CSV, JSON and Markdown - see #38, #44, #49, #42 (@stevepentland) * Summary output that compares the different benchmarks, see #6 (@stevepentland) * Parameterized benchmarks via `-P`, `--parameter-scan `, see #19 ## Thanks I'd like to say a big THANK YOU to @stevepentland for implementing new features, for reviewing pull requests and for giving very valuable feedback. # v0.5.0 * Proper Windows support (@stevepentland) * Added `--style auto/basic/nocolor/full` option (@stevepentland) * Correctly estimate the full execution time, see #27 (@rleungx) * Added Void Linux install instructions (@wpbirney) # v0.4.0 - New `--style` option to disable output coloring and interactive CLI features, see #24 (@stevepentland) - Statistical outlier detection, see #23 #18 # v0.3.0 ## Features - In addition to 'real' (wall clock) time, Hyperfine can now also measure 'user' and 'system' time (see #5). - Added `--prepare` option that can be used to clear up disk caches before timing runs, for example (see #8). ## Other - [Arch Linux package](https://aur.archlinux.org/packages/hyperfine) for Hyperfine (@jD91mZM2). - Ubuntu/Debian packages are now are available. # v0.2.0 Initial public release hyperfine-1.19.0/CITATION.cff000064400000000000000000000007361046102023000136050ustar 00000000000000cff-version: 1.2.0 title: hyperfine message: >- If you use this software in scientific publications, please consider citing it using the metadata from this file. type: software authors: - given-names: David family-names: Peter email: mail@david-peter.de orcid: 'https://orcid.org/0000-0001-7950-9915' repository-code: 'https://github.com/sharkdp/hyperfine' abstract: A command-line benchmarking tool. license: MIT version: 1.16.1 date-released: '2023-03-21' hyperfine-1.19.0/Cargo.lock0000644000001067760000000000100111110ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ahash" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", "version_check", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anstream" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", "windows-sys 0.59.0", ] [[package]] name = "anyhow" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "approx" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "assert_cmd" version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" dependencies = [ "anstyle", "bstr", "doc-comment", "libc", "predicates", "predicates-core", "predicates-tree", "wait-timeout", ] [[package]] name = "autocfg" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" dependencies = [ "autocfg 1.4.0", ] [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[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.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5327f6c99920069d1fe374aa743be1af0031dea9f250852cdf1ae6a0861ee24" dependencies = [ "borsh-derive", "cfg_aliases", ] [[package]] name = "borsh-derive" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10aedd8f1a81a8aafbfde924b0e3061cd6fedd6f6bbcfc6a76e6fd426d7bfe26" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.87", ] [[package]] name = "bstr" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "regex-automata", "serde", ] [[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 = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", "terminal_size", ] [[package]] name = "clap_complete" version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11611dca53440593f38e6b25ec629de50b14cdfa63adc0fb856115a2c6d97595" dependencies = [ "clap", ] [[package]] name = "clap_lex" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" dependencies = [ "lazy_static", "windows-sys 0.48.0", ] [[package]] name = "console" version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", "windows-sys 0.52.0", ] [[package]] name = "csv" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ "csv-core", "itoa", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" dependencies = [ "memchr", ] [[package]] name = "difflib" 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 = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "fastrand" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "float-cmp" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" dependencies = [ "num-traits", ] [[package]] name = "fuchsia-cprng" 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.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] [[package]] name = "hashbrown" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" [[package]] name = "hyperfine" version = "1.19.0" dependencies = [ "anyhow", "approx", "assert_cmd", "clap", "clap_complete", "colored", "csv", "indicatif", "libc", "nix", "once_cell", "predicates", "rand 0.8.5", "rust_decimal", "serde", "serde_json", "shell-words", "statistical", "tempfile", "thiserror", "windows-sys 0.59.0", ] [[package]] name = "indexmap" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown 0.15.1", ] [[package]] name = "indicatif" version = "0.17.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db45317f37ef454e6519b6c3ed7d377e5f23346f0823f86e65ca36912d1d0ef8" dependencies = [ "console", "instant", "number_prefix", "portable-atomic", "unicode-width", ] [[package]] name = "instant" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.162" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.6.0", "cfg-if", "cfg_aliases", "libc", ] [[package]] name = "normalize-line-endings" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "num" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" dependencies = [ "num-bigint", "num-complex", "num-integer", "num-iter", "num-rational", "num-traits", ] [[package]] name = "num-bigint" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" dependencies = [ "autocfg 1.4.0", "num-integer", "num-traits", ] [[package]] name = "num-complex" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" dependencies = [ "autocfg 1.4.0", "num-traits", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg 1.4.0", "num-integer", "num-traits", ] [[package]] name = "num-rational" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ "autocfg 1.4.0", "num-bigint", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg 1.4.0", ] [[package]] name = "number_prefix" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "portable-atomic" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", ] [[package]] name = "predicates" version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" dependencies = [ "anstyle", "difflib", "float-cmp", "normalize-line-endings", "predicates-core", "regex", ] [[package]] name = "predicates-core" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" [[package]] name = "predicates-tree" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" dependencies = [ "predicates-core", "termtree", ] [[package]] name = "proc-macro-crate" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 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.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "radium" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" dependencies = [ "autocfg 0.1.8", "libc", "rand_chacha 0.1.1", "rand_core 0.4.2", "rand_hc", "rand_isaac", "rand_jitter", "rand_os", "rand_pcg", "rand_xorshift", "winapi", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", ] [[package]] name = "rand_chacha" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" dependencies = [ "autocfg 0.1.8", "rand_core 0.3.1", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core 0.6.4", ] [[package]] name = "rand_core" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" dependencies = [ "rand_core 0.4.2", ] [[package]] name = "rand_core" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "rand_hc" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" dependencies = [ "rand_core 0.3.1", ] [[package]] name = "rand_isaac" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" dependencies = [ "rand_core 0.3.1", ] [[package]] name = "rand_jitter" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" dependencies = [ "libc", "rand_core 0.4.2", "winapi", ] [[package]] name = "rand_os" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" dependencies = [ "cloudabi", "fuchsia-cprng", "libc", "rand_core 0.4.2", "rdrand", "winapi", ] [[package]] name = "rand_pcg" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" dependencies = [ "autocfg 0.1.8", "rand_core 0.4.2", ] [[package]] name = "rand_xorshift" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" dependencies = [ "rand_core 0.3.1", ] [[package]] name = "rdrand" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" dependencies = [ "rand_core 0.3.1", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rend" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" dependencies = [ "bytecheck", ] [[package]] name = "rkyv" version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "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.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arrayvec", "borsh", "bytes", "num-traits", "rand 0.8.5", "rkyv", "serde", "serde_json", ] [[package]] name = "rustix" version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[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.214" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", "syn 2.0.87", ] [[package]] name = "serde_json" version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "shell-words" 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 = "statistical" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49d57902bb128e5e38b5218d3681215ae3e322d99f65d5420e9849730d2ea372" dependencies = [ "num", "rand 0.6.5", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 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.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "terminal_size" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ "rustix", "windows-sys 0.59.0", ] [[package]] name = "termtree" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", "syn 2.0.87", ] [[package]] name = "tinyvec" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "toml_datetime", "winnow", ] [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.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_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[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_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] [[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.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn 2.0.87", ] hyperfine-1.19.0/Cargo.toml0000644000000053140000000000100111160ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.76.0" name = "hyperfine" version = "1.19.0" authors = ["David Peter "] build = "build.rs" autobins = false autoexamples = false autotests = false autobenches = false description = "A command-line benchmarking tool" homepage = "https://github.com/sharkdp/hyperfine" readme = "README.md" categories = ["command-line-utilities"] license = "MIT OR Apache-2.0" repository = "https://github.com/sharkdp/hyperfine" [profile.release] lto = true codegen-units = 1 strip = true [[bin]] name = "hyperfine" path = "src/main.rs" [[test]] name = "common" path = "tests/common.rs" [[test]] name = "execution_order_tests" path = "tests/execution_order_tests.rs" [[test]] name = "integration_tests" path = "tests/integration_tests.rs" [dependencies.anyhow] version = "1.0" [dependencies.clap] version = "4" features = [ "suggestions", "color", "wrap_help", "cargo", "help", "usage", "error-context", ] default-features = false [dependencies.colored] version = "2.1" [dependencies.csv] version = "1.3" [dependencies.indicatif] version = "=0.17.4" [dependencies.rand] version = "0.8" [dependencies.rust_decimal] version = "1.36" [dependencies.serde] version = "1.0" features = ["derive"] [dependencies.serde_json] version = "1.0" [dependencies.shell-words] version = "1.0" [dependencies.statistical] version = "1.0" [dependencies.thiserror] version = "2.0" [dev-dependencies.approx] version = "0.5" [dev-dependencies.assert_cmd] version = "2.0" [dev-dependencies.predicates] version = "3.0" [dev-dependencies.tempfile] version = "3.14" [build-dependencies.clap] version = "4.4.12" [build-dependencies.clap_complete] version = "4.2.1" [features] windows_process_extensions_main_thread_handle = [] [target."cfg(all(windows, not(windows_process_extensions_main_thread_handle)))".dependencies.once_cell] version = "1.19" [target."cfg(not(windows))".dependencies.libc] version = "0.2" [target.'cfg(target_os="linux")'.dependencies.nix] version = "0.29" features = ["zerocopy"] [target."cfg(windows)".dependencies.windows-sys] version = "0.59" features = [ "Win32_Foundation", "Win32_Security", "Win32_System_JobObjects", "Win32_System_LibraryLoader", "Win32_System_Threading", ] hyperfine-1.19.0/Cargo.toml.orig000064400000000000000000000032121046102023000145720ustar 00000000000000[package] authors = ["David Peter "] categories = ["command-line-utilities"] description = "A command-line benchmarking tool" homepage = "https://github.com/sharkdp/hyperfine" license = "MIT OR Apache-2.0" name = "hyperfine" readme = "README.md" repository = "https://github.com/sharkdp/hyperfine" version = "1.19.0" edition = "2018" build = "build.rs" rust-version = "1.76.0" [features] # Use the nightly feature windows_process_extensions_main_thread_handle windows_process_extensions_main_thread_handle = [] [dependencies] colored = "2.1" indicatif = "=0.17.4" statistical = "1.0" csv = "1.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" rust_decimal = "1.36" rand = "0.8" shell-words = "1.0" thiserror = "2.0" anyhow = "1.0" [target.'cfg(not(windows))'.dependencies] libc = "0.2" [target.'cfg(windows)'.dependencies] 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.19" [target.'cfg(target_os="linux")'.dependencies] nix = { version = "0.29", features = ["zerocopy"] } [dependencies.clap] version = "4" default-features = false features = [ "suggestions", "color", "wrap_help", "cargo", "help", "usage", "error-context", ] [dev-dependencies] approx = "0.5" assert_cmd = "2.0" predicates = "3.0" tempfile = "3.14" [build-dependencies] clap = "4.4.12" clap_complete = "4.2.1" [profile.release] lto = true strip = true codegen-units = 1 hyperfine-1.19.0/LICENSE-APACHE000064400000000000000000000261351046102023000136400ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. hyperfine-1.19.0/LICENSE-MIT000064400000000000000000000021211046102023000133350ustar 00000000000000MIT License Copyright (c) 2018-2022 David Peter, and all hyperfine contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. hyperfine-1.19.0/README.md000064400000000000000000000247251046102023000131760ustar 00000000000000# hyperfine [![CICD](https://github.com/sharkdp/hyperfine/actions/workflows/CICD.yml/badge.svg)](https://github.com/sharkdp/hyperfine/actions/workflows/CICD.yml) [![Version info](https://img.shields.io/crates/v/hyperfine.svg)](https://crates.io/crates/hyperfine) [中文](https://github.com/chinanf-boy/hyperfine-zh) A command-line benchmarking tool. **Demo**: Benchmarking [`fd`](https://github.com/sharkdp/fd) and [`find`](https://www.gnu.org/software/findutils/): ![hyperfine](https://i.imgur.com/z19OYxE.gif) ## Features * Statistical analysis across multiple runs. * Support for arbitrary shell commands. * Constant feedback about the benchmark progress and current estimates. * Warmup runs can be executed before the actual benchmark. * Cache-clearing commands can be set up before each timing run. * Statistical outlier detection to detect interference from other programs and caching effects. * Export results to various formats: CSV, JSON, Markdown, AsciiDoc. * Parameterized benchmarks (e.g. vary the number of threads). * Cross-platform ## Usage ### Basic benchmarks To run a benchmark, you can simply call `hyperfine ...`. The argument(s) can be any shell command. For example: ```sh hyperfine 'sleep 0.3' ``` Hyperfine will automatically determine the number of runs to perform for each command. By default, it will perform *at least* 10 benchmarking runs and measure for at least 3 seconds. To change this, you can use the `-r`/`--runs` option: ```sh hyperfine --runs 5 'sleep 0.3' ``` If you want to compare the runtimes of different programs, you can pass multiple commands: ```sh hyperfine 'hexdump file' 'xxd file' ``` ### Warmup runs and preparation commands For programs that perform a lot of disk I/O, the benchmarking results can be heavily influenced by disk caches and whether they are cold or warm. If you want to run the benchmark on a warm cache, you can use the `-w`/`--warmup` option to perform a certain number of program executions before the actual benchmark: ```sh hyperfine --warmup 3 'grep -R TODO *' ``` Conversely, if you want to run the benchmark for a cold cache, you can use the `-p`/`--prepare` option to run a special command before *each* timing run. For example, to clear harddisk caches on Linux, you can run ```sh sync; echo 3 | sudo tee /proc/sys/vm/drop_caches ``` To use this specific command with hyperfine, call `sudo -v` to temporarily gain sudo permissions and then call: ```sh hyperfine --prepare 'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches' 'grep -R TODO *' ``` ### Parameterized benchmarks If you want to run a series of benchmarks where a single parameter is varied (say, the number of threads), you can use the `-P`/`--parameter-scan` option and call: ```sh hyperfine --prepare 'make clean' --parameter-scan num_threads 1 12 'make -j {num_threads}' ``` This also works with decimal numbers. The `-D`/`--parameter-step-size` option can be used to control the step size: ```sh hyperfine --parameter-scan delay 0.3 0.7 -D 0.2 'sleep {delay}' ``` This runs `sleep 0.3`, `sleep 0.5` and `sleep 0.7`. For non-numeric parameters, you can also supply a list of values with the `-L`/`--parameter-list` option: ``` hyperfine -L compiler gcc,clang '{compiler} -O2 main.cpp' ``` ### Intermediate shell By default, commands are executed using a predefined shell (`/bin/sh` on Unix, `cmd.exe` on Windows). If you want to use a different shell, you can use the `-S, --shell ` option: ```sh hyperfine --shell zsh 'for i in {1..10000}; do echo test; done' ``` Note that hyperfine always *corrects for the shell spawning time*. To do this, it performs a calibration procedure where it runs the shell with an empty command (multiple times), to measure the startup time of the shell. It will then subtract this time from the total to show the actual time used by the command 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 cannot use shell syntax like `*` or `~` in this case. ``` hyperfine -N 'grep TODO /home/user' ``` ### Shell functions and aliases If you are using bash, you can export shell functions to directly benchmark them with hyperfine: ```bash my_function() { sleep 1; } export -f my_function hyperfine --shell=bash my_function ``` Otherwise, inline them into or source them from the benchmarked program: ```sh hyperfine 'my_function() { sleep 1; }; my_function' echo 'alias my_alias="sleep 1"' > /tmp/my_alias.sh hyperfine '. /tmp/my_alias.sh; my_alias' ``` ### Exporting results Hyperfine has multiple options for exporting benchmark results to CSV, JSON, Markdown and other formats (see `--help` text for details). #### Markdown You can use the `--export-markdown ` option to create tables like the following: | Command | Mean [s] | Min [s] | Max [s] | Relative | |:---|---:|---:|---:|---:| | `find . -iregex '.*[0-9]\.jpg$'` | 2.275 ± 0.046 | 2.243 | 2.397 | 9.79 ± 0.22 | | `find . -iname '*[0-9].jpg'` | 1.427 ± 0.026 | 1.405 | 1.468 | 6.14 ± 0.13 | | `fd -HI '.*[0-9]\.jpg$'` | 0.232 ± 0.002 | 0.230 | 0.236 | 1.00 | #### JSON The JSON output is useful if you want to analyze the benchmark results in more detail. The [`scripts/`](https://github.com/sharkdp/hyperfine/tree/master/scripts) folder includes a lot of helpful Python programs to further analyze benchmark results and create helpful visualizations, like a histogram of runtimes or a whisker plot to compare multiple benchmarks: | ![](doc/histogram.png) | ![](doc/whisker.png) | |---:|---:| ### Detailed benchmark flowchart The following chart explains the execution order of various timing runs when using options like `--warmup`, `--prepare `, `--setup ` or `--cleanup `: ![](doc/execution-order.png) ## Installation [![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`: ``` wget https://github.com/sharkdp/hyperfine/releases/download/v1.16.1/hyperfine_1.16.1_amd64.deb sudo dpkg -i hyperfine_1.16.1_amd64.deb ``` ### On Fedora On Fedora, hyperfine can be installed from the official repositories: ```sh dnf install hyperfine ``` ### On Alpine Linux On Alpine Linux, hyperfine can be installed [from the official repositories](https://pkgs.alpinelinux.org/packages?name=hyperfine): ``` apk add hyperfine ``` ### On Arch Linux 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 testing repositories](https://packages.debian.org/testing/main/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): ``` emerge app-benchmarks/hyperfine ``` ### On NixOS On NixOS, hyperfine can be installed [from the official repositories](https://nixos.org/nixos/packages.html?query=hyperfine): ``` 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 ``` xbps-install -S hyperfine ``` ### On macOS Hyperfine can be installed via [Homebrew](https://brew.sh): ``` brew install hyperfine ``` Or you can install using [MacPorts](https://www.macports.org): ``` sudo port selfupdate sudo port install hyperfine ``` ### On FreeBSD Hyperfine can be installed via pkg: ``` pkg install hyperfine ``` ### On OpenBSD ``` 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: ``` conda install -c conda-forge hyperfine ``` ### With cargo (Linux, macOS, Windows) Hyperfine can be installed from source via [cargo](https://doc.rust-lang.org/cargo/): ``` cargo install --locked hyperfine ``` Make sure that you use Rust 1.76 or newer. ### From binaries (Linux, macOS, Windows) Download the corresponding archive from the [Release page](https://github.com/sharkdp/hyperfine/releases). ## Alternative tools 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. 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. ## Origin of the name The name *hyperfine* was chosen in reference to the hyperfine levels of caesium 133 which play a crucial role in the [definition of our base unit of time](https://en.wikipedia.org/wiki/Second#History_of_definition) — the second. ## Citing hyperfine Thank you for considering to cite hyperfine in your research work. Please see the information in the sidebar on how to properly cite hyperfine. ## License `hyperfine` is dual-licensed under the terms of the MIT License and the Apache License 2.0. See the [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) files for details. hyperfine-1.19.0/build.rs000064400000000000000000000011101046102023000133430ustar 00000000000000use std::fs; use clap_complete::{generate_to, Shell}; include!("src/cli.rs"); fn main() { let var = std::env::var_os("SHELL_COMPLETIONS_DIR").or_else(|| std::env::var_os("OUT_DIR")); let outdir = match var { None => return, Some(outdir) => outdir, }; fs::create_dir_all(&outdir).unwrap(); let mut command = build_command(); for shell in [ Shell::Bash, Shell::Fish, Shell::Zsh, Shell::PowerShell, Shell::Elvish, ] { generate_to(shell, &mut command, "hyperfine", &outdir).unwrap(); } } hyperfine-1.19.0/doc/execution-order.png000064400000000000000000002543321046102023000163050ustar 00000000000000PNG  IHDRXQ> pHYs QtEXtSoftwarewww.inkscape.org< IDATxw`UEm NҥcEuaW @DDH@z%dAaM½$_yΙyr%Of<3F_' """Шc*DDDD ,:fu""nI&QXXT|ЧO*_"Rk*DDԩNŧ-[FYYNEV4E(" iVէPZZ4DjMfy_!`hPD_XlFݻ3l0L&7ofΜ9FFEXb̘1D;<֮]Kee%`̙ 4֮]f˖-޽!C0h "~F#X"" ʕ+׿%\n`0/رc=z4SN.߿? I&7SN9fFF;v~l2JJJؽ{7*zK/eƌdffǟXi ),,dΝ3rHGFAvh߾=cŊDEEM`` IIIs CjՊ޽{uzDN4"i RSS;7oÇ /uֱj*)))uddwtKS%"ҀJEE]w}e˖ѲeK.#>T⑮{|Xbƍ#((d233ٶmv-[A||<YYY,_}.YݻSQQƍk- cDDDg}bb6ʘ7oDFF2tPZjgdd?r8餓8k\^EƨQHHHv3k,rssׯVΝ;Ǘ_~Inn.qgҮ]Z;vpдiZ#k*DD@]Xۗ_~Iii)Ǐ6U`ICO?b4S"R`?2zh_ 4%"6f{%::iԚFDDٰa}1oИL&FCE1M1X""""uLHS%"""RT`1m "R\pnSiP XRa u*"GKDΜLT\cp׿Kp1NE0KDط.b#C0O( SjX\Ux1o;Iӳ1G:#4%"RۗTqćM2p~Uk 8ԠE""㰂*9ljNHS%"""RT`1X""""uLHS%"""RT`1m4*">0xn\{MNET`EST|Uk())!22שԊEDj%8 iԫ ~ / 7qFDDtS?|v%48)+X""~rw?н][\h3]|1qֻc_n9ߒ7~]p)Cr:yytkۚ/3 /X-[1 p2ӺyyIILdX6+N\o*meҿ[*c kࣇ&qI͉ED>f\qVoԏ>OӋWg}ń^[6,XiT9y&s-Pt1*Ɵ5['ygp3ev޽xm3eoO+q3~|7>7/#>:ޓQ%"F#)͹qnum݊BEX""~jr_bGu*"KD,^~UfB;mħE""~faҶ[,l(T`HKґNgu">KDD\xR")z{7Lt|7-|KD`0nBpTapbZt1;@ F#I M&BC:j/͒l毉Bbc=,8B%$& [q)%R]DŴnYO?LQf6Q]ߦ@1#~&C[w2vC@hb۴+ouWռ)X۶:f+-'` Lhl GE%z]%8䓺WH]ESP;xJV9GE%+9T`aŊ>`w?`  0<\Z\B2{BSe海sS2:6grogfusձS޾|r2-cGGO)ʡ˘ܟ HN"Ѽ7t{M{y@i,4E("́$ueEkN(ݓk6Ե* Incw9m17׾ nmvSzZ2k'lOQY鍉4f*DDXb9BKZnҌ= qʓd-]ڗ ,?Veu)\d"<-fEFдG7roUmZQm;p;FH ve۶w|dCD5X""~ncrS(L&n8t,GĤ F8p u"RT`rb441,GU|&LשԚ ,?dn90_Sl\Faa!111NEV]D8&u"74r4N0Σ= .RO"i.:e4#O>/}_v[JiEo{/jէR%"reTjntX]Y5VQaV֑ҪZ;b%5܂|?7U6}7u{lڽ=k\gy9F7?ȉMS""~gî-tjَyi4O@I!>|#]Zu`ρ}$$­aLFA޾}lpk9c;Sڲ~f޾yH-o;MӖ҇odpZ?W\JRLeE0v=~eSƶzz"KDz k721L8].̦s^}N5/Ù\Wӯs"By iYy{hӴ%?a2kOM :Bu9.!zW`ٱu%P~EN"3o^Ő'{ C ;6ңC7 ueî-xdבa8U\1O۹0{c+e^.~ŶZ)r1 #ƌnlCiE奜}} =I1x/&Q:Q Oesahd*! [% ?h?!oGT*V(ݰPBCl; b夰/+*+`0qLH_GHJO7c=Ai#}E#X""~pCB 9:_HP@7Y\DGIG%KRF۷gUNçK7". rXJi컳={oLqh߾=|'_3Fn~59v.#jt;]qA[8ݻw{NA qkcD<=pQ , ]Hxd9ƐCۅćᶖQܹEps4dT./a]1E~qͥ1`]10o˦k ֕_b[-׼m7T.khw2K>¹w };ӠGa FP3 hك]^@(Ͽ :i$έ<~qXPB^MF {O cD?J`SEDqr@@ˣosa0Ydƹ/=[ȹԀuW,) w̢ɋ$>XW}m#bCuFGwc'/lUH\!^GEO`]k}n+]U_'/IGj*uL!.uWT,|Gav0}xG.m-Ʊk<\Ds)9z[T.GV4-:iKDD/biօ.6}X1$K7 qUvSB+;kBɸ~떖'a=p]Yr?O+1|2V};w.D]:Fx?007UZ!,cd"*Fp0'PH4:s)_: .:g,)p.#d6 KTr6R8R,)0E7u RP0Ev)".,M;cm@QfS3?ΠtcKկcNMHbߺ 9#} KJ}8v$8lƙ*9㈹}"/zV=kǭt&NݓC|vSy6Ͳ7'˒a2xРTNnz#s_ʀn<纴nUIߨsA =r(+.#80

b|;.~];ERhp0q5(߹CܟHcKDy\n7&YޢtHIpptƝ.ǃ``[V6mMtm[1x4[`wTqۅ|4`)SLu"" }ط.ptidqrg_%3}Lb&w]|!YnIDԏ>*-RrLfd;w3ݗ^L&I2/0[lΡɩ.~eW,ݸD&M9XSVYs|Fai~=.7[囷PZQIv^>۷-($UŽҊŠOp椶E`&_}+lCJ7ֶ-,!޻ [6ޯtKR۴QGHCO1}M}{i"R ,?`29lfm7i_ IDATzSYW.Z&r"ٳ'oOzu*>xv=.TDjMHLLOd׮]xZX""~511LXV}HYYYz-t߸* yiΗZ",'6X""~`Æ tl: X{K !!שԊDnю\h4v}H:! {1A꣬DX""R/BB_Ƿo˸7sìD94IJJ^JJ՟64Xh?4"6adn]p줜ܛſۮ5|=MҺH97]J\6,=F2~]m;y;c{׬8sNP> *he/pĻe`NdwP @ױ>HboNE1#hw)*4]x.͒iw0^UV+c_}S@i%Y2IfdRwg?-<\'k)ʡy3PΣGz=8 oPi4%" ]/#I͛ԭ3ou>kal-[[t6=)'!4!:VCeq{z\"kշHC5X""~tc"‰lڄ}޿u@Zd/`41LDӤ{*>w`{:-RHsP N{v 撽|kkawVZ܉0[hte&u J0ʓD|iǶԣ/&Q:Q OesLL75V9&7<ߜc=Ai#?9EDlƣrpݘL&_!Rk"{f3yvu*"KD·ٻwSDrr*DDDVV7ou>c2 "66שԚ ,?{n὆wgvy$Ò ,?i&N9`KSi's}HS""~`8.W0 h iT`4rW=q;NSo?V팜p)m/9X}}W*DD|Z>}v+M|P>E`²b>i6%hߍ3P7f鏄qicHI`ն#gvlbPZ?t?fAi.BzvHcDa6ʭ0 |齇^}l*zw~س۳wo ۆ4Kk_C3 NCOW-ks#>*']C6icw8ڛKIE)e匝/#COmW,ް6~QXZw3ޟ?egW4'&s7a8`7λ}ڇ{Oȋ9_ 9Z+r"P%"g2rh׬c}&=-O͢mVug}4orXf^vne -g`>uڿ?S%"gb"ٹ'㈱HK֮Qی&+oO]@bl%GEFsNӹsO`s/kpoP\^z0v:Blm99 ^µW氓C^1lډag/ߒxJ&sߟqCo?fG.9=՟0md@qV[#i(4%"gR (,-O;,ʬ0>"Cy}³FDЮY+܇l9["BhԜn>Nt"K]{0Li tvl_O9OqWcs3PnNDp:YD+Ȼ7We 4FSjXlӉ=BT`Wae_?#c 8NA1Gi(g߃! FH,:KDDD}HCcwb ש48&{[bw?Z%"Rn^LM!ȀH {Fb?,zi|߱braMu&EkDD^*Q`c*DDDD&EDGO֎ݍsd6sSMbb|F۞MMRRRr3*DDٿ?#n"_#RT`1Ú;(*+_5⿭Ɲp\]/._e+_={\nw6W˅jb JKkĖg/EkW\5_3X!rP%"Ǫ\L~-J+xptzqw8ػ{c~1?EʍLc U\6*ϛ}n_((-e=} [mfO5ky'g~I O=7`6 9{'E""~jMHr\,KGĥ#3SLKt,y} im§_0[ju;O|=$Drp{ޑ-ޯ׌>i}e#3N"e_lKV:+urޓ?Q%"f!A@"=kM&z /s'[33apoߟmV٦iSri5XXc'#N[O {`KF&I ^?F Vl"0RZhPa0ntm?\v~;~f[AS"`9=|ڵ=3m`bR `3ns8 ߿^,.+'62J98z[?mCF^a)a2W4^BDϙM&>MvKw፝$⣢8{y[h׼ﻇ_{rȰ0&\v1MbcƍCx[9 GGԱ+x.ʮcΜ9 $俦*z\y5>z/U:6|NYWVlO)t{[ FDoEDDD ,?f6xk}uf̛6E&X""~ ""îFv\}M&tTm֕"BCC}Hi8p ӧ>KG29$}H&'hNP%"<<{6lu*c00`(:l_g"Rk*DDoFt}O-CNuXr"w?PXXHA$džPVV4DjMo:܄IEAԹNIh\k]:rLjHn⣬Dze0|ĵkMx$,!NIީsa p+ ;rVl-I];1ΛS>_HpL4i]ٳzQ-W7C %8:Y:W7Oi3[HNK@,zv:wgO{WzHBqo@5+(O=3<"Oޡ)B?7;W1[ۼ̙O?/N磋 }7l%iڏ8^ZFL%c+.ᧃ'?-X|rٵ||ɿk׆zsi5d* }=lR @`D8M&LLy^ S^x]"~A3ʹ9u+<6l @|K߼ ͣE>gKr۩,,QYf\3VPHU፹Ut2!,!ۦ"_mXBBHJedik=KDď1MTYmBk\wZm" y3JQ^ze iwԪoDsTTR +!Y2FsrV)ΨUS)ݓڏ>'{J*sY{n׊%8Vy]Dğy<,y5Nr[!/yUJ#c2F< W1,zۿ-v::L6V3Ӕ)S: f~a8-uLo߾-݇S 83M_~UEKƚ>#w*RlJRJsrJU?@ƯKlm;X)=X,ʤhw&UΤ<?x`XO(̦<AQUd-]Ay~E%d9g-5z2r8aaaG}gyN()cz"Nj:DD}E(ϴ]D4|.)6r_!Rk*DD-ۥiկj?PaDNl*DDҊr=^vf|6yErkS~V}+X""b쐑ǭ[pī(()$+?+rꢪMӖ5 b#cXHv]؜]{3҆[Ԫ?FDDL#Ƭ?cAG>PБ k%!Aub1Viv&ad$E ,?ӱE;>_8爱M[-;'`Kf: ;jZc2n4q{܇]7)AI!0LJakV::tZe{,&3.騹4D*DD;Բ=K7dؔF_܌n#;/!AA J{6 ' fs3w!-K7[i|" _ lᦱWLZLziBBYm=sk*ml܁Ʈ=2{ߔ[+bֵs XvfقؗͶ{oAi/'*,}볲qk7ѮY+BBp\,ݴzKW((eᚥD3koMȣ_+ȣEVO7nŶtt:ŅNnAp`Pi۹w6V)meE:ZFRRQz2cz"Nj ,zPR% >E("+:4|.s""t&KD׏~h8 ֿ'!!!NET`+DhPDDDc*DDDD ,:KDDDS""~b֭Y3СCIHHu*"KDcqSfL};{YĦED֭[=@שԠnTDjEkDD@C?rou5G .qP%"r(()ׇG5b]WAI Vm+($o)*+;?5s\\S/,b6V%EDQŰnj*vVnʋwN.()/g|EE.[D&Mس?)T .zw`喭t IiEF'O$bIc9|`!Y[t|hd?O &"˖(sEEtX^Nn^7{ٞM}4⢢LzM>~d2oN˳~}n_a!^p/s'\p̚퍽2.ޜx/?0f6lؖ{a*1TlL~->}!ޜx/7ʹ?>^oGiKDXh5vG]x>~;1L_ r6}&( Mձ=o~7%#o[JAq {理fUUvy :ߺ?{U6p=HBBtA:*E S(( PyDQ E(H(!M6Yz sٹH<3+| 9D;_A(bK"ʐJ%TJ>#Ijg,C+oWj2 鋗thҘIC#jU*gXXlGAL&3:=j'GbR3$FCx{14m-qDWam[IW-|-6zzAx{ʻ -Fekye 660 >Xf[cgq?~ݲ_F7|9su#tu@Py_q".+WJ*Mf^Ξ IDATcm6f:o/|==?z_ފIŅ$x ?3.q%WM9z1j5TR?,-ۈ̥˜p6 xxVsHIY2h&T<kpDbXpqq)s&xGTwAKi{QK\oPܺuhZp7i;]N $0b/'`-b +ǥdBPNc/rrqS鉻r~=-+_O.%'v\.'V)СW1LJ$w0aOt"00,Z+%'o K:rj5k֔wW9sO5R lذD,ׯϓO>Y9)ԍwZ@H@@`a6Wj' zrs~҂+Axr믿2vXNʐ!C0;v-ZӧIIIjRAxdR^ճL۔ˤһG)#`M6Ɍ3j֬YjPΜ9!P(0.x2RϔieEg0(edL4DϥK^:AAAe999,Q.N`4,xj+ SNFڵٙz?''ٌT*6~Ξ=7LjcV!JQt:932LĐKdd$^^^AN˖-|4^Rn&RhS]Vt֭[>S|M#GzjRSSQ_GЬY3[ͷ~ٶm*Yf9 l̙̚5|BBB駟xjՊ'NIFFƍ/l6Rt+Wbp%Z-K.+Wسgýw^j`ֿiժӦM#??@:uؾ}{ rǎ6trrbĉbL3www}6\,J^ A(:zwxwߑӇɓ'3b{05p@-[Ɔ ӧhԩSC~7~G8@~.@JJ :I&˞x 'N䐖 /[ϳm64h5kݻ7 ,wޱ;wݺugƍ?[oQre v?A֡C8t#`T*:uD-%]&N|Ӈ7rEj׶JڥK|||XbCj* }-n9s&...̙3(/_nz=3f`РA7///~'BCCʕ+FG͚5pގeN:؂Ý;wrҥ_3suue˖-i^zę3g}8;;|r{7|۷矋K3g􏉐> H$||~5ry$7U!i(0`0دQׯw[j;wƧksԩL&k{!;;ݻ;nݺ۷:t=Y_fŊ7e0rxf-[,Qm۶,AXTW|YDV=P0p@6oL=ȑ#,]/  ##~-!!3uTIIIԫWYRJTTv>N6)`0KyyyVp֘ R8;!H1 £ڴiCʕYb=z`ժUj@fz=Œ+ ~饗J]xU}B@.ӸqcFYjNA;1"ڷa;3הj5{t%v6Yػ;XQJ$abE%7XAg.{(,Tʳ>ˢE0 Z=zYVddd8xӯu"D"VZ P1E<і毾 k13ӈT=(o=[w{gZzQYu3lL30낖]M?_'5,~ٿԟi0<8OTiKfݘ)zc^a!Ty1J5e^+,$&qd/U*:|ܫ$< @W_BѥA4:]sFO9$8E7F``X9pqy>B^~*W!/J3Z9I[3&M2MVUZl l45CA--Fc&@)!de_.296Y٘n[SQb,dL#RI_%2 -$-䚭tV!HMO2X؝e$db2Y9cJJmM- PVʴ|3)F -=nir8dT$3!PIjF2 gi`Ox> 8晭2l"{ k*NOeĈT渣q51bD+M?/|ݝz-Yf I&1i$Rǿ 6̙3۷jiРtG#Flӎb'ǗCKdd$-֭%4IRɦMoXv-&883x[ D*:t$ٗ8DTEűe,\Ls۟{?ȾHRt Nk.`+OK/(ȕPQ{yr 5,T®x¶r0sr_Zm#-ge>7;r癘[{؃>9R.,#TwP5<`K?v^ ^;Gb{k.2 01Lk4Z9IOނBBʾl#9;1X?׽EP& KH1XRH4ZٓmWg]ȧ7?][A: sR-0l?N TR~I[ЊYm BBBoJ\o޼9͛7 1cܰ}Bqˣ4aaa|7޴εneĮozJWT5QFR JTn&&(sAVY eM {t&9K d!Ho%g9kxs/d\ʘWӖ|FcfW`V=@ se ,MΌRte<7Wt,)̩QtG3w93VUgoTql&94\M=x-zւpgWql&93-\M=x)n-l2r+ҹjkBc\Q<4Z!;sNƌ :FAţr/;g[/y)wu*5ߏlx)o13j2.~~wu.CN[5 _K*Lf4*EexIVH0Xl!begg]`JsET؆ʂbV H[XrUe+SגI`\Apw㹴qZ3]䰴-W%Gb[ޙζ`L%"-zfM=Dʨ7*d[hVXVjժE۶mi֬_uywIX-VNXK1X?Ж)ơɣxW و);JTKH&7+{_ojvODUW֮-4ݟER[! ,}W)Cd4u4IGD7jv?&Y*as#~hs(u}`I$ƍGdd$-Za]JŇ~HnSAޞ[Hd柳 bVi+ZY3j و Wj r$b..<&-bȾAOܿѤu)Dׇu15ZĬ^o:710ju8 G@dm[WP{[DTnϏϭ4:՞/er -"L~Eٓmbb$D8؝eۜ=YFUR^ M ^Yr ^ߍ'g36V0x7(>cMN79J 'ys>[eprrb7 ejrbZN\[LeK:ؿ>a ̲.%(;OS6rjF?._ƞ#5uZy7jtLQ#7Cc>JR f_Բ8Ny:Tibx2.> NQHaj[fQg4k-x+$b`amW ;MN̲$=J),HБlpEoe*@+:UR}\d7tlR&B G\WtSXlA}W9&멮%-N!Q Wx(,AJB#>:9cI*aK#Fj(YRʎ&1T¿=1QϾl52+6?@ࢷbT9X'dK/ jx5hdQUlkedO/?SԸ%TRIЃ)zRucw mБ n2 CdB=x )[ 0Y]&a\smJjێI_%(~Xaj VrU&aw >pisXjۗ I4̽%k4&y =Ye\0ӏo~VB JVGۧ#X  eDXB3 /=r!a}vΝ>[(7VmPV 5` '3Vc8 h9qNPJDL@"yPF-|?c2H;.{(U|| 7q;?˂zh5n)"[5!?cm3lS\KGIAkAxn!T.Y{~͇@rgO ;ۈqyݡAoq3l/d؂kr@x͠ `p7f^r~{F +[-b9/1-ae_p?U6Zq>86j4𫢲S?{uql+ŶĪcq?[$;EBm_{@H#86ux?@pz [] .>PN q,\ ըKl| ݽ'gFxz9'}dH~6c/DZps2iT>/uӍwzx'RZŊo<}Z,\}1Gh\>&Ͳ-P)TsumW|~LFVn=ahX[@ܾ˞aMK/aXѱU! ՓP@pϴp 4TzЦ Z+xwǓ/9 kʖ?udޟ&pz$)ڻ.==Ax@d1hk6zun &#.hrWRҬ1ld>ޜ?in۝'!93'S7ٍdi_ǝB&naɦvxsGϙWo~H&Lbtuq}hǽx|P!<VYU4u IgґDbVb F(Noҿk*ӎyiwVZ-mڿ~ lͮjNfCwz^rz^>`Bi\>ێ_Ѻ3 k~W <}c5lI福h{z |_C {raۄdw}_Ax<VǷn^xToKbZl]&B![mKbTV7'>im NMlVN}v`}c)~59I50[ҼD )T (,5+w/k_woҳ3KLH!'Lw-珝3iנ-z4V9$\:jK6%xr3!?˖KU\=2/7 90m[76ؒ3ѥzjL:7^HDB#k-R Dz^_ n0Cer}4-l9I O*^\a9eYK: Jo1A&=Yi?өʜ$ޖ?zȻZwHl4/cC|+ ؛떲Vn_Ϲx:4n}6tc/6ӓ.,f0[lTe < |Ӿa+^̉,XI;eấl:Nad 37]8u!LvE 5Kl.*<\VYpmyW؂*mmP`ۀ6-Yoy\QMu쿶Vv wz>߶WTO ~e[W>}ܶ=2.T굾b ,kwm_߫Xg3Mj5 `ʰmTs}q,wnŕ_[`_ ظf}Ѧ?OV?o^ل8j@\& ?JTH\]ٺ ə,ߺ=AH@0nYr˶SyxP? Zth>eͮ}u|?y^ a_3\]AxUb Z֦cX-6uwB IDATxBg^W b/"iT>jw&y ֗H$<׹u۫^۾DqB4Ok_w4>-tI4̽%4vyض=4} <=o2Q+,A M6|GTqG5%tJ*:` TNNNL5!BpsaMAA!` 7: PGs_A  B  B9X `gDR)U`0#W pSWF>JJbuV *)dFP>rWHŽ;֡>)>Jvv6A+bP! 9afP[k`l@22x"68@ʄVYpFjm?htsC{rAxPᆤ)x 77 gCv6=Ů˂ ƽl$в^]\˻KpO<y/WguVS\!V[&#%*^ U*[b3 U_p:4wqDv*1ۛϪT)14#9ɜj1[Tsrb7o.gR֠c|Gm pkLf3HYvxWOTJ,]N/6a >xu8ZEs!*FvM&lLj޾ooFaosŶ9x42'[Uz^3!o܈b4xalx*-vmhYMFݧ''KL~^^4sqakN볳G+WWs$ZNyp~>WFu:l\}V<%ux8;Z5VWPOKP~~~ 99R:^2-|t9gH9sOb/ج Va}Mp>b/&C}Yq3F.ҽ+Nf/];d+ݯiY&z5*߫?j0nk7o@.1ysyn!FH_//d SSy~ '3^%Vf֪EՂNN|~~^^zm} EU i& +UIqz=fg_FïT~sF"JLj;Q>G{Y\JNfxasrKJ|b"?N 3|@Q0^|x͎M`4HʦNժ'<UOUzegpr n S;'##`$Q򢎳3K-߰Q]!\\"!bqW .vJܷ,+|Y诹 F^^\3zTҶ- 姟 !%\>`AYlo 7?k4:U.NNW2s3[,(AV˖͍Vu ՕwN㭀.ηii4Qoo`;gx ʧceDψ$wAoҳ * JJ兛Zӧ޴ʹbmnJJf&\Sԋ(_r kթ+_m[s2>4UM! /^Dg0P ƒL$?S~}}j''F#ӿU_mE'1˯r?NL(?gV_8m 9L]OR߽e]l=x {ߋG%#9UR)94+U~fsѳj5`KS{JLs)X Jo  <׵3$WEBi<]]yӦ!ݺ0[ez<ŰO_)RÑ#{?/77yqheow HXedsN{'7F#J}jV8=svfKn.sqbBSQ*GJEw8w(?tswe__ bZf^\; FBٟMw(dbzU*~LORAAy.>:Z5޼|k)KNR[Z*eiժLrV@uVի3UaZqJʕ"i{JsEeڦJɑeۦ $V8L f4&\gL.lS&O e9 s/jye{a_fȄI(oᗷhreOXV9*\"'+U @TTXs=|HRZuK Tt"A :wB.%AxKjL~yGt ̀c4sFywEA*]v(G~7=) 8Ѩ B鐉5G8+tW NX B SO|2%,A̹U ySkLFAxy*C˱gpK J ýD瑱Eir'@2Jgoϛ%#U\v wsM%j/tY9Hb'A*8P>Y/Ah[@d'i0Y %l-_gH0VZ.;k4{<c]V6>le\:L 7FAc;/UK۾A /":Wc~>Uɺt3gL}J$&=>IU9X ?~FYI.jFooKΞǯfu?p(GLip ?#9b ߗ} d'$ՊbxT扷ǒy2[ޛuW}LbdAI4̽%[ȌR˥r9!fds 9K#`岚XfNQ8;a6*-r<{5V }N.RBQ[Gp% |yȔ {RF"/BZ,&',z7}fZ /(YNO?M $FA rr= {yac1[`6Ȉh/v-&c@]5`і <*D BNA[(wy9ܼ TpbK$s?|ŁLΝPbR0"A ZnM֭˻ ` TyyyywaޏVXr?uRywEA*ݻwS/Z])W;bwɃA"A*\&~uV9a0ʻpĿfAGEc2pwqes?}Ϋ=!FA` <<ѓ'[t%G{@<99^l]S**` TP D?FIls'zz&.|'חW6#֤vXRq`2m.߹h%7X-ğܕxҵY|zAx)BA h7r<Bw'ͥ_i Ukr%-@o>7ȤRTJGceo( V+bY%踓8 |o|$L*>/‡YDwgFArF}]>C>xJdkr@ܴ[$™{K;YKdf29~߬]B&m75V+b#4w+]Zƶ#8L*uhN[/v{=le jG={Ts_N:1A*DBTTJywL9 Zб'k(` x){U% ^Xa5hL*-IT^v1=z1f3Ѱzݻ <*teX:u*/BywE *11~wWʌD"AgЗZ&HX,f`IR[R)'5c[*H}^zᵩV9L/YNrOJJ"22!BѣG˻eeݦ>`rZ4Հ]`08p(j7i6eg~Q6T %FʞNjVf =' ]LNUߌ g! ?%VP àmTs:4nM}";?oJ ^k_`opB,r/_{ِ;t0OWXY<rGbr4d7`$,)Dbו $!!EѸqJ?~<5j`ݕG^tt4cܸqԬyl½k.f͚Up0霆*}Z֭G= '5.</3SQqS:]>mC0T\RF .:د z4Z >l j2ɺn{YH%R<]oGX(o Z+%'煊E = ._V[rOGٳgYx}F!)) |_[&&&\-;5'u@mWm^/wR+L~ݡ3{lBBB[. L6ÇӣG{=@^^:m%VyȮYXbnnnVRR+V **!Zb/^ٳk6nȁ/pv-WT|lڴ~/ҡCRG@G5痘ݕr#Jŏ&'kRJfΜŋINNf޼y<ݻoauooosNԩc]9r$ݺuW^1e˖U+11s1Zl-95qy饗ݻN#''ǡF/QFL2žgŴmOOOZjEVػw/GgN:eΟ?ϧ~J@@իW?g۶m89ْ;FΝqqq_/,+o>rrrׯÊ=zp!:vx>QI:tWmڴ/V!ƌCN=z4Æ + p=֚5kصkWeƍc٥rׇ|q㈈UV<9&&&&0%`ZIJJOU6ZWHR&L@J[RJۗ+W2tP7nL6mh֬Y8P&HNN.rJ%%uu2i$&Nɓ'E%P<ѣ;L&cfڵYp!gϞ=,_~zÇۧ w<ȉ'JS8U^ v4QΝ9}4&LW^ 0aAJǎٽ{7vbxxx0n8)•y , +V`ʕJx$kJS|EڭRմoߞۓϼyXn50+ TQdgg^UW^szj~vz\`O9<ӧOgΜ9,^zyy0gΜ /++#vǎcҷo_WA`#j]ؗo_J%&ud2Y:zKx{{<^r9}嫯J*̘1>tEDDРA233Ϸ_܏iyvpai󈏏G8dff'0uT|||7o  B"2̈́ S]D"a?~|f3|dddzjժqUIHHpSR%RRR8<̘1ާh"Z-z+V^{1qDN>ѣh4]v1uTbccEGGsaI#22ÇcZjڵVZ\ζm0 X,N>LÇ1 XV˷~{ϵyf:j%55# cƌرc|T0Uس2|g$~ #9EX֤R)̛7yR[>ڵk;ԩ999/Z \h$22<% 6ӧ3bJ%֭[3qD{=z{nƌ;^T*l6ӲeK[iݺ5 6ٳA999;Bjd2رcڐH$L2>{BhDPн{wL&S=y>|8-b۶md2V+SLYfzCe 8\}Qjժ|h4L&*QFQRoJcZqG<0'\-]ʖ 3eeeB#",Cٜ;wV7˳FFsΑwqQ_wp  (ƽG4mJSsU\enMsiģJ.0 IDATQi TY*Ȟ~ `|G~?ww|ޟޞjժ!44\<== %%Dʕ+q>>>DDDnnn%z ZMTTjpqq){E__5k>3'+99;w Hpss+40'22sssjժT*%>>>}0`HPPjL(Zy܋`,X/Lwg_Q0"/E*433WEe2hӌ4yE[(HxfWMAoFgg5fmmAP&FFF 0͘<|Gd@#=РPNK%2ՓPX Ax@SSbW] %#}}rUU)B,DB6mʺ /iѢ ":|2"AxgEV'Bњ7o_(}Vꫯcƌ),--+Zfԩ1O ..NoLF˰Gۅ qvvZje#ߊeݕ2%H>|8[#^Yk\\x3gIٵkWڶmJ8#WL58{l """]SѱK\NPPZjwUT'11cccrssf͚dѦHT~iiiSN_PKPzՏ:t(C`͚5\t{TmܸQsϞ=˰'BIRdѢET*\\\غu++0={6^^^ϴiӀMolllغu+;w"&&$ 6mbÆ H$\s<<<:t(jq(u!RŋINN‚f͚UsjqJڵkӦMM322~:ᤧceeEڵiڴ9P(l???^͛7jܽ{>}h~)j5;vv-ݻGΝ9{,ѺuRST*9tvĄmꬾR(;w0rrrpuu]v%_Ell,fffk׎ի봋ի!˱m۶:[T*O˖-177ɓDEEakkKݱjA\\\ܖѣGˑ#GQ;v8*WL׮]6V>|[nѣG " S!!!ZϛB5>>>;)))߿պ3&&&Zy)ݕH$',V)a޽8::JHHcΩP(Xt)ԭ[LƉ'8~8 |}}ٲe ̞={8pfZٳgȑ#$%%IDDsϏGXJR+vGʕ+DEEիW9x ӧO/|DGGӣG )#͛Gtt45k___vu|^|jj׮MXXg„ tUuHNNZٳgYb* kkk:͆ 4Cر;jXiii`jj`ٳZjLj022b׮]=zիWk,DŋرcϟgѢDJ9GVVֿ_&u֚+WҲeKFɃ?~yќXR)vBV)9<{gϞ5Jgp%M8p???ϟOF eo[ӶCtQVYj{O>:#<Ӈ  JdС>}DJdeeG?кu~.?`ժUo޽{,^:uد]RdXYYdQlڴ͛7ӤIlmm5ϟѣGL8~Yfٳ̙3GZjy.]b̙ժ.]bѢE:t!ChIRzE֭Xl'N?r/|}t5nܘm۶1h IHHNwe``@nnԡ7DBbb&9>>>^Q(233۴4+333 ?,كWH 333n߾]qDRJlذ SVs4i iӦ>}Z+zzP"дiSN:Ž{UqKKKX[[}.0aP0o>bccqqqg͚5( Ǝ˛oYdv=._L^4 WnѢŋ= 5kI"0x`9/=zкޓlll ::wU%~=5kU YfXZZru-3gʕ+lܸ'2|pzU~iӦ2zh011aƌ8kkkիDŽ 011aҳgO֭[رc111ޞӧ;ʕ+qrr ;;;ܹC100Zgbbk۶- .$ WiHLLW^O `pܹB}4oޜɓ'z\___U6JEXXZS9%LBB^^^\~]L&޽{:IJJ… <|OJ:AJi ?R5..,???rssY|ZaBBB@NNall\H`` )))(Jbcc\t\Zt7_ DV=4nܘUV1eN:%rdȐ!{(BsLBnn.qLɓ5SOuԉۣV5=ךزe  mڴ{T*oAxUÇӯ_B988`hhի =^بIvv6Jˤw___\s7={k.<==W+WF*rڵ˪9VtjѢfkذaŎ`Nc:~:dܹsv䄙Ͻ+ˬfnn^HZ˗/qFRRRFJA*E:O'Ho%JEiBӫ oOzzzI (۷/^mؾ};m۶Զ¦G)~EYkܸ16lLJ73~BWLߕQp޵k>s`˗/?3/Z$%%yf.\@ƍYx W۶mV BE#VD'AAAjg}Cٙ7o>\qqq%CE"RtE=/SSSƏW_}Enn.SLy b6WDRt\G"hVl(ty?⢙ԪUGDEE re۷/ ,`͚5L8SSS9v 4(t%=;wMjFJӓիWsvͅ XvT#Z?ˋ#H̙3;AsT*R\iOOOj׮ͶmpssӌrFFFr Œ45^בd]0P('>>^+/i׮]#00uV!''7xCFV3g֭Ç)}!\-[Ν;ߥ߿?eA(1`M2j(n݊/*Ax5)BAD&Me߿ ͱٻw/{%//_d5jS^bK5annΗ_~ɽ{8y$&MbÆ !H033GGGJcE@` 6RSSٙV5 #99֭[ӦM7nL*U4522";;[ARRd ?+88jժ'UC5qeك >Ktܙ7o2i$lllHOOg…'+ FA5hԨ{!%%\lmmNFFԭ[}ЂPNJ /L&C&<*D5ӓRSSQ*S$&&bdd\ӱ#''L+iii 8LJws%eI` FR)E,D"RJ:bhhO%>ظ`N*2` /˫nL-",Ar@__ VjժU|v }϶MAxpɴ3J_EѭZ>Ee'aYP]+IJbwʺ+ SIIܖ0tвJeggWBR1%V\\޵ju7;;cbdX)V[[\\(T^!B)(Zb B9/6m~b-+%G.)G^C+뮼V$zEVEʧr` ³(U*FR]|/|{N_WjOL*?+F&Ad_S=y}IX ^ٛ˺'CX$82{l9;УN;TW57oݍ̔ލ*!1%+(JnAqpThFRڊu 9=yjTBdoQ0e )!a(TJ6h}r.N*/%g6 -0O"AuW##;s7nVw8jW̓ ';[r.W57k;w ܜV`TqHRIpd՝0B@ )ԩ^U /с5=+$q;4:<C.\ T}: Mj{bg[ i>4ԟf`z0Rk BY9 xKgotoՂuN~#~J$iS+ ,۾L߇)~kss:񏒩O `Dec~j% SR3064dN?v$nt4oi98W itnķ&cZ 23Y'vO7[\\ v'=F$!JXDjJ~boM@@]HT\<ڶ曩=aj\)YYTbZtrZ:oMQ#v(d0q@_>}O{Ƙ/s&FIH³+?~Nv[^QqZXF/_q$+33nQVVbn6z>* 9aov:W=} M|7I8 "NJJy$h;FW/%V Q=-rf4SӻG.rYv{,Q,ݶ vφ@1_?|1{{;1kc~:}8~ɟGi\ٺ6v'GFz/ZZ֫/Nm>[hT&6l&9=]Gu`2'/_eޖ~]@kar2/Z\BέYa,Qҧgi$:9bi9BgrMnH{{fDںUv$t@t|<g-N~ձ4՞fw˅e-T\.'OZⳃ ru\5_pVܦA.~Y<,ٺMg6cOHJKc$=bߗ vO)c}~ƭ3=BôZ:bVJV ;A|R]'}ʊ]yF*EEpH[B^]"*E5T"a;14qo寓 cld{Q;SzR)zwl?zL|֬t&&ݶ uݫԔ&=5#eMss Udw;#>r>rXtc3ڌٝ}'O[SX0xB>^f^{|гGSMcBvV|ػYYPhaW lMj{bdh_Zͺ_& =}==}*$!%ǹHO<}O!8ssVMM4F*[[7;7{{|q549[7Ǩ0O煹;;Ktګj;P\`O>@[som3߉mmYq+33۰S3[|2h*Y#ejTx}qg:@+0 _z3m{y+fq\A_!Aj'Od6jjXѱAxU"W(QM5$дZ{XcxaxL,IiiCgqMjU-6?E4﫾⻃Y^1#ܴsv=?*7ti{05ΖջhD͓s-,iNe-4G?Grz:WCBɕQF&&toՂOtlCz/t)5'}$+A'RJ؛N]w |jdO?s)(?`ڰ O}sI ZhY@ƍ67'4*vlBZ f}A P/+Tb;D9XVV>r{{޸R̔NV-5#PG/^й( N_xIcul6 3ooV׮U͍؇93A;x>\9]̵up•PcbY0 )7#aUXxEF|=cF.^Jhߨ!Y99 F"5ZS7"Z֫KXt !؞&W! ԁbW+߱<>Kչs8=Դ m;43e:~>Q6NM156& 61hV?~?]ߚGMLd5m߯@{QP2^0~d2LLLP^"*E4G8sV~Xnf{kkК:44(<Ȉgp3)4U/Xhq';[LG$#مoЩ`fl~ I϶ys'nݍ%= SS\jO-}aiߨΞ^B"֬6$?lӊmZzlJH(ܫ3{0:x7jL.ڼ׬bFE#}\6. F]5Khk sOBZPRRiרOSpvVZS{k~~z1ÓP:Ҙ*j4~U#BAP"%&Qd%oٍXa#Ԥ1+?Pf}` 82Cyp2,^ϋi!gÇa^e҇d?/%m,B,ٍGbؼFu5yVF^FmoQn/s/,Ar@.cRHNVsjϯ\=yCcc}F mk`d\.* >. TA/x>K^=X8u*_WAK2è^oJmW]ml>E3cL^n,Hfn._ƫ@Pyy/T?ښJ7ǮePi+ٹt%FRE3e2yyj\kX3rX|7{>;d%?3u͝ ϜʊW{jG chҹ#ۗ,gM <Bɑ3jQ97͚ˮV3vBڼ݃>Ɛ܁CѰ>\oVD\ɐϦs=#;g2l~zVU1(36B8B'jyWƽF85smzI~ۯ'Co K"r h#X.a$+;rm^֭݀Oź涤w9k_MO)-M`.'eM^"!$*M"7a$,NNa䤦m%c[:<=P)Vyz2&ѥ#gM 28:k>h=Mڊ#vx,Hc)dQnO{8=kZϛJDTD*ymD;i`f&66V߯\<;['<7-Ck{OB (gN>/Jō4fNے9$EѝR9 111VJʺP**TJnml\yp3ȶ&6I< 5jG:#B/mh -􈬤Bዣzh5H$ 2KKZy#A\SʃG1QؖuWʌJ:Uͮ ?uW*TcTmYKЁ#\XБ,eA}鳓- tv 2c#zLvNj=;/|^"헝tjf_v~{DԲ+ׅF\ȕ_dILLyX+BXpߨ-Z}4m8\drRӰ(|'3OpHXTvapX>B8ƚ4d&&̓cZ$gfD8R%\.G-!JQ@\EV -EoWjSԄ FpmTrBȈHojՑѹ=VCQ8_7d/Uqyyܿv5\@~ݬ"6}^^}#m&%?1`5Ѣf}uT:.Ԭ\Ua* 7G|'curn ykb"N"-.jUqkӒk7r4jK wNΖ:{wߎǒ~p BPjZb]**:}" mOi+}7羚 #K <{tz5-Hwm ]ScKTjH\`>tA!Kc3;|; scS e0RT `Ϯǥޑ?-RӃxN-]Mj<{}?ґ܌L.mkV _%}HR ?8q)>Q<ꢿߝSHzNTr4>5tBT}я,ǭM \Z4 ⃂Q>.u[ ~;GHo?á'Zm%R)F$H&ܿvCA]f4IZvWTU'JDG9\ :K[ e܌9heLO,璿ջfehUѕ4tD"R?E <{W\r"$".]|IF }ׅD.;n0Yz9A(|#F9Xd=|0BNn{ z,y#_9%9Y,=#F]Evӎ,ػ!(j8V!".^+6e/F_;Tv|,+u2$%ia* ݆Ag05 FH7jK63#ccnFf?f:gӇ_Nߺs 9>^㳟˨?0jgA,Arnv|z.к5Ztüȕ 7|u\LN^.va3Qd3u*rȕ T*5NeT*ɕS?R\5IHTeZ֬γGHgE\ ;//>5GfKI' *lYm< Oj|8;No@A(z!@XX[݌"dނ ~$R 5#ʺ+:JsaDBQoRDOU˸_Px],J;dP۷~uWJ$yIqKiWϺ˸ߓDwuPx{6U)96M`]]sKHHe'^5*/wJMͿ]+9jܗv=ZMO3 2]$jW&W?_>Gvbf_])3j5>R^$r`)Jzn(+5F_E(TEOKJErzKFEСAKj毘o j~&+naJzR)ۑ[33Yw'ARabd̠<.޺ŠIH}7ad`­|{ȇ052[|̍ ƲrzB@O*S|:`VЖ Y0SGPfGGүMw>~pv(2}3Trz>dGv>ܖAcKsJbo;A 9fƦ|cHΊ ֑)ʫVޭѭiG+W%,NmW!Wz}Zط?o~&fIVF6"Kx$0ztTj5ޝH '7boy`p~>ѫeҳ2YwG>Zgə37}jsq?G/G74m*7#8:CӚ IH\|+WGZVY9(a܊ ZyCbab΁ G9vNTa.q |1xz m`L/ I'o\i7B3wQ\]݅$@! EJ kKq(PZh)B|,,, $@d~p{3dw߽s(_ NИ{~9t$iZ֯>?v$ȇE2v V]c*U*Ғy1ۖe,޽&hy|Bx"?'k{|zvQl`%hDB77Ʀ;ְ-mkzŠڛ!4FRɕ?GFWGmg1a,֌׼Oyn7璬q;/d8s2:Ƴj/BH,xm$R ŧ  XcۈMgqR!q*ATf*/˄n#4O!{j7#Y5z.FlܞG%w>fBt*'LrR1Wc 72VLyzY佯CHL{Zc: Ѵ/> =ɯ:z1d2z%},ğM'v7o_ XLhZve?8t$+v <3 #sc,2Aӿ*Wِ!DDX1O_rv׭a[ܜK2ퟅ9K%̝KWo;9IRnknM"mXT"8s2:Rj{SORӻTq-/ F!5L 4KUc<|FerǷױmt׊sq3"޶ƸzWaZ *dU\rs)Kbfh@kK\~tzk7|NJus+\~xϪ 4 Zer|Ă'r-XM9#9`W-LbL"Zf>DD{R赓D u-NۺunF4tb 9‹cilPX"Zŏ+ꈷ7 [HA9qqqL9z둒‘0u4(ӑNxote:X{yK@qo.AD\Vhiw>['C= t ̓|+{դcn ҸrZ~;V[k۽dHj]l(T*oNM-ё[ͺO UN7134P+ ztRKGXyte:L7fy 2W]F$1jhy4\6dnzfj;gv!28RN\LKFeCٻr]Guif'7j=@N :syx!VКST@pfxT@|hgf mG v6}Ais|;VGFV&/jWf8eMzFi@m8eɳHM5GD|4o%bʮ1q7TlNU*&`mjɜA?ǐ_y@9#tr+|tloL?pY# >xLs~Z}7{s03 >9Q-8.ѕp%Zr&+HhP&_#"^;;ߗAvxZglW(^:Ǿg)9v*DoU̡J:+?JUy%'үtH^?ѵGy5^MTp$Z/HLx >OܨD`Z_3NZd@`d(GoK6 FuĤ1vtZVoLbj2NʎM:~p9߰@*/Kd|4g^};*/L*cAJz*VZ*+ +i^!!l=7]6?N?.eωrP5Xkۖe< ŭh)"9uKZ}נj ;{/}jpbf&}f g^a蹔t,󉵮єͧ%Sh)>uû@>b_\5֦qi~865=q/Ys|߸$0*jJV*^WO~\lċofK`jOFxb[BJux+s޶ןah cSƒw:~CsFPݽmKojbz7Yf&w*/KGW_cuuS3xws&%*>s%=]T/C^v#3#11 >{ڭk6cľx|NPY ̬7gn]}#}aba/o^ nHjS}_bbڸ*~8==00QOqp';kcsc2z 6„ȠHlS8 ?mc籡(j Zć_p>"VI 4Z;=oVCĝSH; ܼ8pF gwe2[,,gY?e=?F"qDGWtRIJsϥ}4l?Þ{s:!۾}v>!2b勓H=u#!&-"1.5ʒ@_blJb\w]}]JT*YCʤ?ז51N.F"sϢ!&pJ-r?"X_<_UX ,!]@@@ )?JexTo#xI#P)A$|n5 0*T}+WެV峡T*s}kǒz?LDV(?dnX)1K 7‚'qxiDT=8s8::2Ԧ8*y&ʑ 9P"1}Gn< El0"bWSq!HL@$F=o8>029UzKW*+er 3lT(Sz Xa5gϞiL  RM(meeEz>_54ERv%P&Ǽc"o_Ђ Q%5>K'aH,wzr⮩$\Ye^<RےXO9O̒RLDE/FͿǰ0R/l"v VRmDo̱,ޗPeJ3,zM/x|ߢ[R$["9!Ml0N`z[ax0FŠno#xnFX [}߲҉YElbS1]}xK,ӤIϭW*3L[^H"C<SjbftWGbHv =G*v}K&L& ̎5ukaӡ~~H æy*briI9㶹-+]] eA,0{ܜP&E[.f}#xNR L'?nSPe|{寉o` | ~v$/s<Ufz嚐ӫ?$2^B˨H>K%kvEIٚv" Db@#zk* +a(S0Dj]zmZJ*#Li(S[?-S{""(tZE ,J#|Ad{!XNse##BxufȊ9ʤ ڗBl`nHq_RιԾ ['>Yw?l/ (T^HʅM(Ye^"s~i`BZ{ϑG uRIIG`AH"CĢH[>㰙~*mH95*@!&wMǴo#}^SFʉ̹!~(LB|sf?"k?"I0~ %j'(?.Bj󢎩X~ͮZK5"+ +.?bU18)ХrT޷TÇ{n̓%JiРAnr4DoA.UxP@@@3qJ`ђ6fR$eS8 0>}JujJRxkH"|S@V?[VC5W׼,  '+#ZseE"W}/>n? o* |T*Ғ"-%E?%!u2ʤ8C3҉D!9RTyrSRFLhX6c%}4"47kʅb+\9tm Z@&[SKX==l]IOJ+K@@@EF0zɾk5}ׯ$1a?3kk@IZ?e&Ӷmbt[,y5gD$ƌdtaVb1ԭpUMOV) 1? u1ᴔӷ黡l[,۵$O |M,LWZ+jgedD":d2JdO6f$}_ 2K@@@ B"#d:[k)JJĄncF8X,A$R<~?PUs4bBT| uͱJbY8/F]x_.K@@@IOIEЀ^wp.]:1˻J& ER5ryfNo8Qg#{ne(^i)T:˓L5ƘXkOR72?|q@GT2 &[[6f"LM?b^J406fҭ/V&׏SUsr}dTnXKWĆG`dfsV}HKcy6N.&4]zkoܓqrʸWefA4х:m[T'}K8pFkgedMպ}~89odOIH|34D"hOMJ"2("4lڛƹ1ihړb4KOD|)DhOEFj2lEKaggMJ`LGٗ MM9ؘnesSW79u_|&&iHe2w 0b#9%2)9"aXlkTVE@@!Jcq>n"Ŋ|uepUldefF>*M6_H IDAT oeh'Mܪ|,:s! Oh+)) csϭ@F$_έ[X.,d2<==)ZVE@)@aL޹a_)"iG9nR$e#|uk%`Wԅ{SQϭZ [φ w :::oy 0B|?dgn?E&hܭqq6[No1Xs!  ,/Nq6f%0a=ncFaf2oBTJZt{Waϟkj N2UuZq7ƷϘ6}(n+ 'Qus2V#2(b:j%*WԒݶp!>ϑH%Zؾp)70`'IJgZ^<+54r.WodHuض`)cncC}4^ܗ[v 11ZZRv )okGOpi! -(JΙNyBri!cpp-Fa)^5_5@TH(3TnTd޾wZ͝эq*%Ԣk]_|T4b:x8e 1k߆Fiɍ[__ͨ׾ D$~꟦ew?|@n7 [g'9/~Jzu0 /m[L߶IX041ىk6PqCVmJ|t %TCdiZ:zWóWw:sYu/b:ur9͎%bhbؕK\9t0 LӃ_|eD* 3LO'59m RYSCy#XլgD|ͷb!=5^IIH`xzӋE'Z9dHĭX0ϛE]tvt :$[wҠS{@jU045%/@K6"0ɝ{O]Pȳ8܃;] H0x#gqۦk'McZ~4ҁ~L*"D":NJ$&I`adঝp1?Rezr;\ :) lEdɵd) lBRѹJV@$quܾeTޜwnuRXvt щ8[G'Bb#5cxg/(_x=+Ս^ |$6i9/~^@ua5SPrtf?h2GGsE|gξ8)bdj/-%5QI#ƯYH$t)NF󾽲uS(ڽES?iڣFƵ?[u[X~-7Oa8HdB,r9av,S?o$$6acj @5W7Rs4!~6iTΝԌtx(@JօqT}~ >?~Ly +{b#ѳpUײdžGD|T{WFtHXtCERl6JΜDCfzmTmH3Cz惎Vj ԱSP(9:zt=R}z_FjldCC.4o ~{.sVa?c st{;m;yx:aadDk>Wo֔4T(IIH %)z߾ i?|%*Uuun'} 07$4.:[9Qq8M޵L /7He3HwX6&E9g،w^K ;gFճ)' 5P( U{\iXɉ̌ CVD*ŭ{NrU/Y1Ohҭ3Ff꟧s. Azjj6}ADoaI; LTp ^3fV5e.<lLKغ8?빸 Ϙ&5a k-(-Ξc CEJB"V[vɬ[(]Uߏ-R&ݻy>Pr%uZ.T._eDn7t9,yCRQ~ OX;3K^:wXq) lܞOn}˞r%VvؚYRLeSr(+c3WoP6lCX\N6vacbqD / %R(c>ZASQJ%n?kK23rB) _Bre5?|)I?EfTiT'O ysDmXr۴`LGsJ:sNcu#}Ԡ."yLVwKT*硝!=㞇Cb8cy(Y".GVل^1#{9n`\}"ңnsjJbX2Գ gdÙ}\oDIin{Eʾ/̓ Ȗ=Z+j{48@O`ЊiT/Q1w<o_†:+RtE_ٽSɉ?)[K`uMhy7_mXQfg@|5ae~}l_;U+u #éT -E#8IQazf| uH,gr:oDjR7T'dta :tud>L`h͆He2L\>x}~`$/ݸp~>G!l745aY1fiw)[Z+MD~Pƽ*!O6 1_ xJT?iDjڰyrߦodĿ/`hbBoTȳXg낥,W֎L\RKZjP?Zǁ5Q*UUaQ]ofNOџ;?sۮ5Yl3w Hߡ-b+]} ]]]D*q!F;_ "U1̚SGظV%WUM& n<%+3E2 jJ0):;=DACH TNVf&1e5?/3,qĄG`baS7Oa7Ùm%+W$Cl]ӒBD@ ""%]5K⣢^z?C,Z<xC0xri-m( IOMáxwZ/sfjhs {g!'TFTD*5a޴<2jE&ARO{R=X2 E֗W->D}1<[9k N^Jڼs==\+y )V6Ob$O%Ͳ䣍|h`-i$ ع&RI6lmmyt27@K> hڨV#_8}4^P#׌L)[6o//o[66ω.xnߜ'"+&cgա +kkN] ϢCJJ $>)S"Khܰ!޽{Q챘-k$tttr(,/:pLnE=]X(x7s2S@@B"S+6m!{j JB,c°qyFD62331uͫaVv̜5+V|nUXBZJ:=g5<0v8MI覣DGRƅZ"l2 ֛H0,r\Гi*~X2sη׵Zмo a̍s ( <^AfG'Rs.- q> dߪ}$&RrIi:]縴"zzڞɌOo=Ti\ZI V,Y$mE0{׹z "-ji& n*}+#JW+:7OԖHudt׊T!cWtёE-& P(,MC7w:lq{ ʼchVgN@%."Y}fRU-% GrtB-7x. ? b㬍{Vgo߲-GbcguaXY";g^CZ:эG{.#AJ%=m ֞E#mȠ싽;g`` REƳ웟034?*sk .~n.HqM{4L}ž "6t{N֌۶.]vɆڤ՚Vihqѭ_͐ۘSaelJ,|z) n}1{JT*A:w,v-E:}DИwcۈO) 2:RYaVGЫ^K_>PM=]jy2?X,r&&$&(nX,vdR\&9⊋c됦Rʚ*VPC}JW+z  %P1G*r5>%#iVWؔȄXNݿx3XK[zoEq"fs8\ mhU.5jK}c&$ţKMy[T) ˹s)hagZsrؔx|IJCG*tIҕP(}xNeWf lԎREOMb#dR3ҙyIC^Zۓ.% ,"ߚVU3{:-L@0c`œOs쳴$&,ZsMnhוֹH$?u*'C)YE]r ֎ָ{jPls*bu(ӺݵogNגJ$,ʹs!a ڵ^32*b&'rFL6 ϊ57/uo!I{ˆhVĴd#~p)+Ҏt.?ˠ&]i< 凖=ynitVw$ğR.L:ήLުQu,MNGT+^=G! YksGn_$5#ƣ'?*4{-}QD:C~E>CsϜY4p`AݏK٢ܿxf}chGO6L '" jj٠c?p.½ hٿVT(y&2V@,cdÙgҨ ׎^#6UUnLLyz 11[0o<,,027 6Jr 5g |U:KOðf]Ԥ1 \1rߨʹ3}*^:i/X߈ͱH$bVMN˂q$vɌ#hވ=Ok3Y_>Ly=6'&9kfag@:(۫X,_IH{22dJ%굤:r"Gn_Ժ_;wV,>devٳM+qu?4._]٢VX4{3kOfϸEyKVDTpagZi0~^[t`6@a҄ٻfqrInAjWBWbov:FL2 =C=JW+mjԥVDS+3fsq/kːĹ]g76TRS]=5f4W^ݳ:T+V|q|DTm\ [g[ t%bj6|]*ڞæbmbcYz/oxU^Gx4OBK;PޙW{fl~RO ՜7)-@*)JeR3I)iFhS3Е;O] xqETR<Fb*$b1-*X IDAT :10dwqڙYѭns]>NX\[NܻweƶK;F|R(X:X}lש@:rlS4zǸ(Q%*jH%HHDw[Q fpZ(dN,8N~1E,mstX7qu/R{LPAq[ǏS*r/YɕVUaon1FW8!efi[cb`+RIJ#[rOHc޾J Kc3(4듕!/wn./Be` EƶK嘵s A19x]{>%=-[_JFvVuޙ[zmA \zr4*8C<^HGQΙ^K~2Eu8\J⑷>3iH-}*le67]X@S $nZe(Zʪ;pz/Y ı T*rX?\mЫ^K]>ƴ4reEB:1QLjD"&wJCj ]=3W߷I גQz |i29[ |P&6m٢{/ݵLjF:QLeu9T*j(nkjIQk{n>&֦< ކ-}#IN <>jj+c%u(-eЊx=ykqeӜE6e8)@zf CcIJOeș4pv,XG,m%/R íHq\LR<.Юz#<*B"cnhŒQ(s'@rVLvL91u/>cٻ!{vf++G6#?BvNqoz hld{~ Eb[&ĄF`0܋â(їSn2pEaYkbC#)7(3T@5:$Vq vܨGItah5znpyVq ćEQþ4QW\PPwBurAnE$΋FP6ux\Y8?ku]G# a>>Ubr SI$%7G=x;y<:_ܒ*;[*{w'֩8ў#9~\tʕS8ԫ(Bq!y}5n\64BxÉ%'Pס!NsޱS.<)JڴiCd䩧.x}O\uO!MPRPXGu(U);?Zt[ l/^̫,ɘwp˫P(!DU;Y`^]nN'݋QصDg N@]E]G#D^`ҲW+I԰m#,n7c3ǩ Q氁YQ7h«,ݎR%CBs8PB%5W}nD6):}:CZ^`(>m)Jٚ¾MЗVJ (»Hu8#v`ͯ_8"QSt/҂76o|O'U;Y4mw}⼐K\6ʋپ|}n[ס\6)D0ML~hߛMPk{3)+,MurZ_j4 v^{SV ]|ğ:Y+$X,`ɫ)+*?ğ6WQFUY%JE IW%J&(,-mVҒi8q㪍$͋7s4(jfѪg` 593vѤ}Cd4ZԚnI ޏƄ0dpҬss3Xju|.JB4: cxdT^;~g Oq~Gǐ~|Tgҟ' XŢr%Q!3?A Hג+wdzgntxݰuh.=} \Xj~ {nǵ=}f1>wq f~YEdHIthRm_'%}w:mz\<;W$Ieg(30}x#h|p|#3 a@'뿙|6Q/RH7+:P+c@^r#a82]7 Q}Cb>gbh٣%o˧`5z"}FpW";a}&AD69jM;6_b 0_Ej)\}NLM&vG˓ύg| pwcIwu5jp29Myַ,,n "yHhP.Vk֬# PK(//'0, Q.5XMTиmcmOb:K|xT>*w{,d: P_B?&Sy k|lc "ܛYZyIh@aV!%%5\kxnoƠP*(q4FbφglAKWҝ+xڮcm5:X‚ g_~p1±T(<<}i=ǭ&k:1Mc8@tzg6ϟ$鿼^dzU (/xO Q-;5gϜKNY/$<P!.6`ՒD_g{??-F $Po/ȶe[Iٖ+c=7ߙEq^1ٱb;;Ysw$_jP f'؇f篯"sof:LB[~m+g`*u{ Fa3=FvŞuK?xf 7}ED\8cg໯(hvȤG 7#݈Pn!.VЦw?q=?GdC)-,ER2=|v$EY)Džc(7`(3pzW_džs{2y{[SVT i۷؉Ihצm42{%K竻lrbRUD\8:ϫ*6I3'VŊ_Gǁ9rsHT=/^׽FD\yXL1VZ8RߨЁ]wVY+7T3gOO|jʏQ/woՠqϻ SycDžSUWD秫u}/ Y16_,QTfQٶlo|s[wL{eo|zMeIhKP0PB FЁk J|~7$048Vᮍ\srٲuU'Uiڇe [:tҍt^N6;~۱E1h(w9owFEѱ0?s6V9i՝N}> m[Eԋ`7ܼlێBWжw/!<>f?bX5IlV++~ \у]wMhݣ6k*z-ٔ.Rpi9r˿݀vg%ʊطqeEeD-ƖZr`~rRsQkմڂk2dP(<.8 AU6P{8]j<({j6s_^qW ʜ>Unz >Ѿ[ܰO!^/f#;b8ff?L>Xc(+wgYDƻzY~qtЗ?/ׯG^T$Ͽb[=*瑉z$nǥ$mepDGWvdh_-ą#X g^tM‚lBEDZaf4i7s 7ju1:^raL~Z: Txϗg\Wu>mH ӎRńSTadԯ-ȩ =5jmdžFekJ>{%'N5J%7=&|N]=I?m/Ճ1m|v9 p{n^ucwo&-Xȡ] 'c2J)᜴ RwagP(c;WI׬C㫣qV QǼ: #{[kbY1gX]r^)JJגr]8C⼒,!Hs'R| FQ~Z* ZSA~ETyFWxx`kmwe0S:' 0GOtHA*P)U4=ϓCGJX`rwM=O۞$:$_Sdo{Fԧu|^r(BIAY1iY6Q5Y̤fXΤ$XB޺~,DPF^!FL|ܒ"n97ã_}xp'_>*׎w?OBt,)e7?2&<'@8v`O}o݅E_[W!njvl!28GoC}b}wFMFK&=w>mB)"B/UѼ&tR/u|r -)`tn\9VfjQn4`w8mh|2b, ⾁7ʈiMp! K;6JdoV;c1es$7K!JB(}TTasݏvJY d{$e{Su:N~hZ9(@,!bCyҤXu0;F׺׶a3v]:[HbQkjtkԼ#4GQ~hQgOH̄T,!ro2wP^! pʍzz6oϓCne|x&k)V!)_%֍M3)HjFϟa^m֗KI( ~pI=?Պf%<0"0bZ<C01[jDfa.eĄF}ב<%VGQy 6M(7.. PGQE)1* ؉4f{rgU.B\f#[RT[v(ϝW/wWZp^TQ jZv,Ikx_-&dgf* q95XB㱗=%ԫ'{d` !СM;u(uFP0juL,!îưkuBZ BxěDl1p:!\3=Sסq$B/zj?r~::JJJ P8']!dB~Lršɿf!Om6|n)2h`‚jTl4$#XBqkӫ5M7eьEk(8cUOM̠Iߓ~N $B//SUy#eIAVeEeǭf+6 UnUxNGB6Ҫ[*ui5WnLj0zc?_m_E9Ezss/;%SBfO 7$\^q=()(eV=̛'٩9K+p:3~T>*f?%I$'7=T)č:| Mb).&Yff+>!VHbז] y^|xUo#N,!2b|D4: & ySѬc3zn6;czмcsl+/~;G{=B^f1 1$If$&=V_377=}3V'=.[4c!q?'x=4jȣl}H$N.htKKmPh%[ǣP(P(W'epWZu6-i{E;5~[LZ eh}u<鬝X՗)?Z.k-[vIA3h`؟ئq2se!$XB%O: !D-)B!BZ& B!D-K!I%BQ$B!erBx;Tڽ)BPпg/:tPסq$B/}vuJ7}-[Du(B"B/pab$un2^-)..@Q qB*c~>}95ШH|2 |.B/t8J9dbd(Oݵ lUk<a1yֻi٭ MϿr+/!̞8 `*JJL? 6ѸM+ʊ9u_ov%u~ƅKhգ3ƽϓL$mkB \ كBp8.ₒK!}`7eьYkԘ'46ǽ;w@EI)~ SV-AG=*m6iYs փ{⃏gC]6]5l(w,zS]WGuI6+(}T4jhj6-f3GsbU6 jpvLvkھ2|j$6l0bZZ,Lz_}nqgA$XB y}Gs̼4Kw=Femr߸׈Ih FC&= ~ج6n{YRWhnV}@׫tOuhtЗ+{ڇqIth}u<1>z)ww<"i2R'Sˢ;m q)R8Ng]!)z&eIM420|@qj6s_+{3VYpn6<#uNǝڳ nԜ3M6*A МUB/B\$&N윪 A6R" Bx1N|VhxwjM!'I a1WTڗGE!Wj??3W"w!}`Cyޣp@б]ㅸI%^@V3W: !D-K!ĬfqV.K*nrzP8g` !HJJbޕXCuJ6} ;vD's_!Lnn.I*|Â)))08g` !u$DuBIBԺy䪛P(1xO}^-qtgok ¯3؝/xf&G}1K!y& ]Zf|p4x~˷ >gsٙod}>K)^kQ+ﭸLH72-mdBx9Z͓CFӫEJ f!KvITP?c{ZZvf΍m_g{ (kװ:gn!һeGT %wm?cyX;z^ZÛ|Aj^Z̵w`Hr?ggf /O J3s,*pG}-'C1C=P.B/ -_řP/@T0߿m<ݝ4%ןN70PT4qd,:?:}GMx~']{mѳy޵]_Ŧ]o~Z;}C_CPTZ #8y>O)^_6_k9J"j`r7R 2zNN>?bŃzxW;Zx吁sUUaToXUl LH7Rjs=;hgIY&&f1;x/֔XfŃzf9qFX(;(Z\\daFNnbylU۞_`a ZH:'FJlNfx吁#v>4rs޳ɉ$XBTJufbZ+-")e7cXovkren=b$Rf#:cei&rKnQ}4VqѼ%rãm:~\L|Ҥ59lOߏfej_=bvZ%X'2:$_m=Q%&I=6ehs $hÄtf9u;H*6@S,:! Z\daRU JK7eLr켝jIeV=wcw96'ofDr ~*F,w'Rvk arW(dR;6;!LuKy`o݂j6磀cmL.#`Fñ32%¿'$sSr:pն2T hî zT !Ъ5ꫮ XAYf|,9[:?=6kow'AlO/&iټ=#z 8k+-q: PqkEVY5Zo/[ރ^>H*eLC_9J˛ Dv~r;C\e4~S?qm@Q? D?z|w 8mt (-S`uQh\e[QZ%/^SZ%#"5O70eH|,0怞i᧪ѹ*!BÄt_ ʫ}*N`JU80!@ATNnQ2*J[6/I‹U ՗Ѭ~I(;RGlh$*AdSݚP3вF;\Kh Npצyb-<:]f)Zpm=u :K;{0ؙndU'W' 9'}&;O8g=Q_Gbs|}?vH%^t9{Z7~p69<ȫ#d?p.K?BxLTp(ݛ׵JfwN~גY @FLw,;u`_V:)s죠scA?%X'r;Q) Bs`G6"9q ngΰe,L ֔Xy+}j`b<PNA*k6ub>)gRB /Գ[F뚬B/㚅7Daj3 -b/ aC˙W~^lWّceK]e+KiӶAS*LFn96_r/+_/fs^d$/g_7,@o6(5T~[m{ڒir`9aX(UXj˱HBd(7Fi U +v[6TuS6gMr* R䖝^A~8pT,:!GmZeѪB@RnYlV~۸mi?/Ke;7ؑ^9x:s7,uu4E%|7k*uK/3*2,BCB u Om<Ԭ.O0״5e跹jvm4UA3IsQΆ2+>Jc0s SO͑}z^9d@Me6 Wf3尉b)zط갛R3p2[N:R9zcu4:>ԌO3VX){gtׅkP)`Zx<,.VuZ4'?N`U~*Sz;* %L{v|x$yK('DN ҍhQ;݃}(:4>UKt ATmvBFP 65J"5JJlN Wx;j$dSnA>[l)?uKZ5;P*4<'D>+tqOQԆJA E,!8kexWٓ~~b yjTl,!.Bx>~gB]!%` !6c(Z `AuL,!6l Wkԥj52{+ q6d!ԋ 0\th: !Ι$XBq۵n'kٻi95[/ĥN,!ZK73IΌw(2gɓyWTTU\$XBq[/XKg.aWڬ׾`} q"w!BVn?c6oݯCHD9eQk\,awo>2fв[+}^w[Ob6i؀_ӯZ6{J+G `㓸}mDEb1Y浯IۙFV 1M]|VJ ǐ7~w>#2s9I%^&;5ϟ~~%?ue;Hia)cH6 D586w/Cd؃È +_%oy 1ݴ3¥8(JUdǑ]{?0-|Duc6+1Mz4ЌagɠqƵqڄ*2E(^f_>;1 1$vMDUyioGEht(mz!yuLPXj0 e-XG=͖Z֨)-73( DQ/lutԅbz#M5a{)KoMHFTTX蒡܀oo/ Cu>ߡ]mRf8l[^?]n1Y6ո` !mś-nMvj6u +%;֨-l=eBjnMP*q:\ǝN']VQ4هhީ\Y$j[L>|h"nL1v!.F` !s}_~~IVع&^HZ>|h">j_A4IDATc68;XW/}EÖ ٹ&߸֪7ujFFQ0!?6ɓcjЀCcxct9#Shӳ;t(eքIϠ=쓨||hӫmzp5 & mEE^) v; b23kGxem ditٵq&'K2`^yxfL#Ϙ+C,C\Y# !nȠ`FM#{3fϹΟz*NpAiqg>qYv&zB:ub&qdo>0+nø'غ s` /lx{L&Ci3%X""~">>`EEE: P%"N:"(CEj7bCi3%X""~` fp: "##}H.B?`q9 F#a,abM&$F6 !D(""^܉xk,XQrNspffXݩ*x)iu cD4ֳ//ZEfa.%UX#i;\zu``a9mnMd涜s8C{'`cn+ڛŠ;96؄!`0`4.TVK9QFiݶzNAdˇ$TFkz\tJDDcdg}ե+7%OE3,8ocg~C9{Qv?>L/o /:1iߦ#S^V:+.mݛK.'ye_1yP95<H2B].KDďZыy_ufpkoOvƥ.Ss8]-3ڮ>fP89cL7U75b4г/9' =K7 h1y9<*lMm0"~N ;:a'0y`ܦ wssi㉛oUZB,`2Gl9M1ڦADď5;:rk&-JH+YE_#}z-3O;3t]"(~=zЊg-.$17=ZvOi3xgq^sSi/4%"Gc6wM ;o5lc~+p:5/`۰;\nm44p:-mn7 M-mM8Nvdgp넫xs 4q%o @u; N-q+-`A@Rdt 7mYIsE|vݾBDb3/e TVdD3}e \^:s]~_ zVy=h`Z~ wڨ}N A%B?вw'&ߕx3:߹.D3iwD("m_ ס Ezг\`>}ꘛpfrV,wJDDDTTC u>c0 u"^KDtR\5סUɥZ%,?3&סTC]#DEE:6]""~tb0j`*D %X""s<ǒ;bك4΋@EqE˯8'[yH{H7/9YãW?ڦqJ Jin:W>g=NxV.\soc m:Q%"\N#Iib' b0 ӧ_3RqC|?zN|zA}M=؛?9cMb,??,[ŷ/Dg ?Fq*MFw'=) v# k>aD7͹E?}@ϸt%´{~œs,\8a>f=7-~$MI"_3N7ξ^ϰa٨7LG|Kz>lZ2~3m.Fe-L\T 3W˭gYA#ym:>550t;rs Vy=h`Z~ wڨ}D("F# . Q5I1bZŸ;3 v'?]w:6S%"bbbu";;rl6g4%%X""~"22_!"^ """"^KDDD˔`x,/S%"""eJDD.N&NuC{v1 G刈\{j$Wft#6 ]dQ%"rƢr`y[FuE[:Q%"""eZ%"""eJDDDDL ie$::cu88^~e_"fJDD-[>}:AAAŧ:DEEEM`͆ln7O<.P,X~80DڬcC\d K,a:O3X""LSS~)uuu 4V`׮])G֒Ayy9111\z -;;v0gȑzڂ ==Ç闛KII ǏWO:`#MMM̝;Knn.lٙ~˖-ٳs쫯J]]iii,Y2;w.^"N3X""ȇ~Hll,f`ԩ2c M@NNf@\\?<3gMNNnߦnݺ)))\s5ڵl ?VS%"Ҏ緺$xZf hyperfine --warmup 2 --runs 3 --setup <setup> --cleanup <cleanup> --prepare <prepare1> <command1> --conclude <conclude1> --prepare <prepare2> <command2> --conclude <conclude2> 2 warmup runs 3 benchmark runs 2 warmup runs 3 benchmark runs command1 prepare1 conclude1 command1 prepare1 conclude1 command1 prepare1 conclude1 command1 prepare1 conclude1 command1 prepare1 conclude1 cleanup setup setup command2 prepare2 conclude2 command2 prepare2 conclude2 command2 prepare2 conclude2 command2 prepare2 conclude2 command2 prepare2 conclude2 cleanup hyperfine-1.19.0/doc/histogram.png000064400000000000000000000255441046102023000151670ustar 00000000000000PNG  IHDR `g pHYs+ IDATx}\p/7r/+e+,ILț ϱ-ܴՃGEϘ R@dfnG{D7YJ 7 Z¼]s]\8{iz 3b 1H$EL"IS)@R )b 1H$EL"IS)@R )b 1H$ELtgϞҵ`zkzmcxG׋)偁kWVVַo_Uwaʘ}g:::><===44TY4mڴ6lw}L744$''uuuFݻw;{ٳ݄ TUeʘzk^^^gyO>Q JLL_eoo 7n{QS 3}aʘg ݿ+sZmԬ]6''gB7g߾}111&, tEjR[[i׫W~^Rf555EGG+/BCC [mƈJ5SΝ;xellM8{9r駟nhhBTVV:88??V[[|m? PJ)&/2:tݻwŵ}^o{K%%%)i;j 0d*`a06ǔ nڴ髯ׯW ,,O>{ zGF9j(??fe~PPбc^y/rrry??ٳgus"z괴 6\r7ߜ0aŋ,Xu:O?=vr?mll,X/--y.]se˖ÇGGGO:… ΝU23gƍ|ҥ?0))ɐ!o߾wާ~:%%q۶m7rKVUU}v k W\9s3 h3gΜիWĴ}XSRKII2dMll?_ !t:^>}={ B3fL8!!!EqqqÆ ׯ˗Zv;vh~^{lmm 0k,e<ɓzJy˛o9siӦ7B{'N\p*o~g ߭e˖=?sܹVtÇؼ+UUU3Ν;rH{{ &ӧӿ&p -@^7oެ2eMMM#FB̞={...B}ǿo7ckkb<'((hر)))l``6Wyȝٳ~n~~N;s8'N<8ZjjjBBѣG[ZZ7mڤ3gKr護.>K&$$/߿aaammkה%Kdffn߾~ݿKx7+WXBo۶mΝnVXdɒnܸaccS____6l[&Mu麺,$ b @j.\8}tOOψↆO>ٳJ YtiMMͻ+puu裏?xzz._w.\\\*++!&&f̙/bt֬Y=رcDŽ /233Z?u[nzjΝ}#۹]C ȒߒVuի.DRY>)@R )b EEEqGZ )b 1pg]RP@kBFKS ? f윝/^hooochWKKŋ,)4II|G3#!$$>ppp1pg666p|Ȍ#@R )b 1H$EL"IS)@R )b 1Hvv[ ,)@R )b 1H$EL"IS)@Rtܝ1H$EL"IYX#|c NIS)@R)?xϞ=}}}Ǐ)"^УGӧO544̙3e„ &, zaʘPXXxGGѣG_rEYbŊ+Wfee9rDՎ3ʢ;vlٲvܸq& ]q`+}cްa7|3~x^dɒ8!6o}۷/&&ƄHsSjkk===%%%"GG#F ! BCCEkT3$8 @VL;w#""B???R???efeefZ-2|rUHEfj4ô^7~i쎋t:Ԩ1e…6m:p@~9ZVSQTUU)+Z"cNNNFL^3cJRR҆ 80``Vlll}yo~Mh]S8bqIS)]NDZ@CL"ISb 1H$EL"IL.Kxֆ )b 1H$EL"tI<` )@RĔnZ_ )b Ths)@Rɓ6lzȑ#n榬j*;;I&Ս5jݺu&, E4<SƔ(^v={''L$EL"InN-]>ֲ)@R )b 1HmvsX@FL"ts3t? )b 1H$ELуB1$EL"IS)@R )b 1H$]hgh )b },= )P )@R )b 1H$EL"ISvs芈)@R )b 1Hf IS)@R )b 1źp !IS)@R)Ǐh4;w4=z􈊊:}aQCCÜ9s|||\\\&LP^^nzQwLSnܸ1hРVWXrʬ#Gh1c\~]Ycǎ-[֎7ل%΄ۊm5Sgdd,Y$..N~z??͛7Ϝ9fڵ999GBlܸO>틉1aURܔh奣# EEEMMMEʢVk]3Z&zLB)3+++<==.je]3h4i^otU%1EՊc**epE666VWW]Ԋk2P=k\ecc###ENR JQ.))9q℗W߾}BBBBBBҜL"pww>} ”~LS=:rHezBu-Z.!!zذa{ussSV[jݤIFn:[[[.SƔ(^vFIMMMMMm)33333ӄegIS)@R<72:a@h )0 Fk&h )c4H$ELF<%Sa 9b  b 1օ1B)@R )b 7˷΍#)fE@8$EL"Iqn 'Th )'UUUƃ+Ɯ܍V`QoJCCÙ3g~k!C!<;uRS/.1%99y}Zlٵk5MbbbZZZHHHHHHZZ)SP:P8sĔɓ'_tw}rE w^7773sĔ-[qFIMMMMM5C у@Ruk;qqEL2db!`;GcJǔ+WN>}ƌB={|閮 $ۻMwd B2was]ӶV[[XwN? Or)EEE:0'::j իW555&/&,udjL;40]LogMj|?׿禌)۾]Yx|x/a{BnӶ;jM-3f﶑x7nۣVKi;jdi~E7nZQ~bz^ޢ~!7|co?íVKIIQGYY9}0_*t:]RR2r%__߶PMMM```YYz{Ӹq*5]ֻwon,S|||lmm+++ sZdxiݭZAֆƭ [50$;88FFFZ$ ?/fͲtQlSSS-[AhhwZZڻ[WW3h ˖|33t!F.h҅[n\7DOEL"IS%dgg;99w\NNN:>b IDAT[zaӧ'Nh222Z">>׷GaaaTRnӟܣG~-]ENӃ4f͚7^^^#G,((-[J?={?~ST>Wk4D,n4޽?ܶm6tP-@`˖-k֬)..7oK+..^f,:|prr~jWZe_~СC%%%_}Uqq_l]JJJmꚑa:2eJffc%$$8;;߲xtt|ɿ'N|Y{kz+>c͛7L-uzwvn߾}3g~nj [[-*bO<1k,tV,Zh3g>|xu[}[/^822z;W_5'Lո^oiiy衇?w|^nܸagg_jPׯ1B^VoӸ|!CO-ill,**6̉.,,lڷ~kNLLѣGΝ; /kYYYziCj?!g}t?6tM1Rܲx+f{=ٳǎ;zhj 6n=n7^g>S߲Et{ݗK.577?aVܺuҥKlNJJt'N3gI;$Uxڵ677ۓ'O6mfx gg{[oeܹ0ij[l9vؑ#GT+Tmz>Z5?jjjz衆[[1ct|cBz˻j :4==]1dȐ~ߗ_BƷnݚy8q"111 >>DUIv_[ W$''ښF.\7o޽{.~Vֶnvĉϟ?_~QQQ߲uckkk 3BնZۻ?WZeM@.\^z%!DXXXYYYzz$MxVV֒%K'x⾶l)5npM6}WSNR񢢢pesss~~~VV?Q0UvƻM>sLzzzTTn\ÜVEDDwޡC۷'|ٳgϞ 4E&j7o޴<왤񌌌ŋڵ׿nRk\aÆ 0@:IGu :tԩ'N!nw7c--- ߲e] QZvmqqqbbKii^t/rWRRRqqڵkjhh8~?~9eÇ-[vܹm۶S9L5C)m߾gѢEߺukmW^m˒P77]~"=ޑzJn4޽?rssw}n͚5o"?00W#F07d>0U3~׮]{1GGǐoii1SKR׮]7o^߾}-Y|]u4oY*58)))fk#HSj6ލ?i{-YNNN[l-N3cES)@R )b 1H$EL"弼{ƍY!99w,]UTT([n}7 ѣzիG;+(Hb&h {h_6m?ᑒr֭ zyy?6l… &M~JKK;R֣GooѣG߸qCfHd8/\rҥcǎ|^^^[uV\\\PPPXXXBBHsSml??~~~ʴwUU 絛駟AbbbzzzSaZѴz" ߴi}}}߲۽{fff.YСC&-t8~_;wW^666O>[ouq;vZEL`VSNy뒒Λ7w:t(--ѣϟ/^裏`Af윟?1..=ШQzz왟qڵ{/66< P^^ȑ#ۿ"(((11111 0'W޽ߵBZZVsb4~!Vrʕ+W9@BL1HB-UnIENDB`hyperfine-1.19.0/doc/hyperfine.1000064400000000000000000000250451046102023000145330ustar 00000000000000.TH HYPERFINE 1 .SH NAME hyperfine \- command\-line benchmarking tool .SH SYNOPSIS .B hyperfine .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 .RS * Statistical analysis across multiple runs .RE .RS * Support for arbitrary shell commands .RE .RS * Constant feedback about the benchmark progress and current estimates .RE .RS * Warmup runs can be executed before the actual benchmark .RE .RS * Cache-clearing commands can be set up before each timing run .RE .RS * Statistical outlier detection to detect interference from other programs and caching effects .RE .RS * Export results to various formats: CSV, JSON, Markdown, AsciiDoc .RE .RS * Parameterized benchmarks (e.g. vary the number of threads) .RE .SH OPTIONS .HP \fB\-w\fR, \fB\-\-warmup\fR \fINUM\fP .IP 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 \fINUM\fP .IP Perform at least \fINUM\fP runs for each command. Default: 10. .HP \fB\-M\fR, \fB\-\-max\-runs\fR \fINUM\fP .IP Perform at most \fINUM\fP runs for each command. By default, there is no limit. .HP \fB\-r\fR, \fB\-\-runs\fR \fINUM\fP .IP 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 .IP 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 .IP 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 .IP 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. It only runs once a series of benchmark runs, as opposed to \fB\-\-conclude\fR option which runs after ever run. .HP \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. .IP .RS Example: .RS \fBhyperfine\fR \fB\-P\fR threads 1 8 'make \-j {threads}' .RE .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 .IP This argument requires \fB\-\-parameter\-scan\fR to be specified as well. Traverse the range \fIMIN..MAX\fP in steps of \fIDELTA\fP. .IP .RS Example: .RS \fBhyperfine\fR \fB\-P\fR delay 0.3 0.7 \fB\-D\fR 0.2 'sleep {delay}' .RE .RE .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 .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. .IP .RS Example: .RS \fBhyperfine\fR \fB\-L\fR compiler gcc,clang '{compiler} \-O2 main.cpp' .RE .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\-S\fR, \fB\-\-shell\fR \fISHELL\fP .IP 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\-N\fR .IP 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\-\-style\fR \fITYPE\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. .HP \fB\-\-sort\fR \fIMETHOD\fP .IP 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 .IP 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 .IP Export the timing summary statistics and timings of individual runs as JSON to 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\-orgmode\fR \fIFILE\fP .IP 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 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\-\-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 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 .HP \fB\-V\fR, \fB\-\-version\fR .IP Print version .SH EXAMPLES .LP Basic benchmark of 'find . -name todo.txt': .RS .nf \fBhyperfine\fR 'find . -name todo.txt' .fi .RE .LP Perform benchmarks for 'sleep 0.2' and 'sleep 3.2' with a minimum 5 runs each: .RS .nf \fBhyperfine\fR \fB\-\-min\-runs\fR 5 'sleep 0.2' 'sleep 3.2' .fi .RE .LP Perform a benchmark of 'grep' with a warm disk cache by executing 3 runs up front that are not part of the measurement: .RS .nf \fBhyperfine\fR \fB\-\-warmup\fR 3 'grep -R TODO *' .fi .RE .LP Export the results of a parameter scan benchmark to a markdown table: .RS .nf \fBhyperfine\fR \fB\-\-export\-markdown\fR output.md \fB\-\-parameter-scan\fR time 1 5 'sleep {time}' .fi .RE .LP 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 .RE .RE .SH AUTHOR .LP David Peter .LP Source, bug tracker, and additional information can be found on GitHub: .I https://github.com/sharkdp/hyperfine hyperfine-1.19.0/doc/whisker.png000064400000000000000000000121411046102023000146330ustar 00000000000000PNG  IHDRl|iCCPicmx}=H@_S*E;(dNDE EjVL.& IZpc⬫ ~:)HK -b=8ǻ{wP-2j46JWt}Of1'Iq_.³Zst>xMAv Tf>I4г \\74e ٔ]OSf34 tyq$ pp({Ż;{LgrSǍHPLTE888!!!nnnֽJJJ~~~ơ\\\1@DLIDATxz*F6Li@ڵf2id hhED$ި""Ϊu|6p8]`:w,"YC4"biYɦND7"-+f /Z "Dqn(KʉŸgqEܠ5J _6cD.\΂= 7: H|a w&ڋDkL@\7q$ 4ivsB\h gUBąOxSq:NIDdB;լ3BK¸qE\E\?\f@?`TU }\hY$"g Z"wC+ZJhlp b;mg!.C4".à: .j8%q%FUV{E\L>wH\h}RBS-qcpm6AM.g{qvqm&q!N/ݏ ^l.Ϝ=pLߐe!FNC_W'TJnCiѪ/k!.|p^Uj➧J-n z\hH[ .>C\@\@\@\@\@\E\@\Ľ3*:[|#.V/+ .֮ "n]jz"nJ@\%qqqv* nk :A .K@WqL@qI\E\j\ą*: q[4$[ǀ$.".5.])̚DqZq+؋o0^㒸R`SD$# 5f㒸[^tq+ի,"q]Yj\ĭ}WtX~>.V̜֡!n!O-XyڲC:]qL[LMfw}mU ߜުK٬}.G 6ujTՔT,E]h,w԰ ;k@!MfM&Ӵnފe/C4"n{=hR1=7t(x4 C٫uH]5)Z1)(;_,vZUdjUUm 7a퉛\iհ!.Q[Iq+tЈWܴNxz)68IE{yF]Q(m*q}v;\ʽο叮‰O{E&c'g8q4`㾼Ө&;uhlN/sKty\^$k5'IvJ^}u}cZ\jHFMpS3g~(-n?= { N"2ןRhrfQ{\\·:WElh2cwx/ZUHSUxxẍZVN{ÛQDHrҎRՃ\U]^䥃! !Or4=s9ZW\gfyW^}7qpы/_KwxR'JwOrM\95rJ8qɴcpID.Uwn&S:]Iϊam  w/yLu\1- 70{ .S}p>t5Z+I ՜S6v)\9;.vn䡕Ç< Q;9ImeN'yFrg|q)%Gp>~l|]4,7*ofE*Hj;.vRtXr5g\\6.B!nQ$33Rw#gpw/5^j*fýtpqpS0oP]i&űqgpȃpJ)%> 4#\*}+yP.%qqp 7#\h7uSF \pA/G zO#5]pAMp K \pA K \pA %\.@ \ %\.@ \. \.p. \.p  .pAp pht}jVD b66ֻkZIb#bVR,vJkgk$1K#b:%5N)9Aph gg{R%jh9\AEqw?/%\.p. \.p.pAp  .pAp K \pA K \pA %\.@ \ %\.@ \. \.p. \.p  .pAp K \pA ~>Z8͏O$i233b66;Im&sbVR,6Jj4yvp̱.7vd1vyyJJ.vQR;S7?.$ U$Վ}\ď5JmRNFn].@Ԡ m4ߍ"\7φw \ %\.@ \. \.p. \.p  .pAp  .pA K \pA K \ %\.@ \ %\.p. \.p.pAp GNS8IR-6ؔ[$IηyFb#bVR,6JjT;I]%\5ܳ5sKč{I}$\5\grm4I9Y# mlPZ- Rr+O|w2CLuVgg}ʗG97tXzߟ; wQq9-φ(dM[$0Cw;HW=rWyy =<mcׇF3|;ukԭ~E}}clyutk' 1Sl_+?>9wMZgnC-;T3z/[^TS?9CC?.q0T8 ݼUkf&yDxʝtV-ARZ_a м90TٗCW隩,C^Է*$klO;'XI5 z[*ݬwItˡtM5pS)d"O,ǓC/Lo77/oWn5tݚɢ~fQZ%ܠRRz"xzh/nWJ.bWa,M]UAbߵ1۽g6wKл5~eT0كg[ӧn7uŢt޹T^{TI9tO<19_|yypwC׭SZԇm>]ς\{ ,J=qɡC} .bw7o:t՚>;uEnQˆ~ПIENDB`hyperfine-1.19.0/scripts/README.md000064400000000000000000000014401046102023000146520ustar 00000000000000This folder contains scripts that can be used in combination with hyperfines `--export-json` option. ### Example: ```bash hyperfine 'sleep 0.020' 'sleep 0.021' 'sleep 0.022' --export-json sleep.json ./plot_whisker.py sleep.json ``` ### Pre-requisites 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 ``` hyperfine-1.19.0/scripts/advanced_statistics.py000075500000000000000000000021521046102023000177700ustar 00000000000000#!/usr/bin/env python # /// script # requires-python = ">=3.10" # dependencies = [ # "numpy", # ] # /// import argparse import json import numpy as np parser = argparse.ArgumentParser() parser.add_argument("file", help="JSON file with benchmark results") args = parser.parse_args() with open(args.file) as f: results = json.load(f)["results"] commands = [b["command"] for b in results] times = [b["times"] for b in results] for command, ts in zip(commands, times): p05 = np.percentile(ts, 5) p25 = np.percentile(ts, 25) p75 = np.percentile(ts, 75) p95 = np.percentile(ts, 95) iqr = p75 - p25 print(f"Command '{command}'") print(f" runs: {len(ts):8d}") print(f" mean: {np.mean(ts):8.3f} s") print(f" stddev: {np.std(ts, ddof=1):8.3f} s") print(f" median: {np.median(ts):8.3f} s") print(f" min: {np.min(ts):8.3f} s") print(f" max: {np.max(ts):8.3f} s") print() print(" percentiles:") print(f" P_05 .. P_95: {p05:.3f} s .. {p95:.3f} s") print(f" P_25 .. P_75: {p25:.3f} s .. {p75:.3f} s (IQR = {iqr:.3f} s)") print() hyperfine-1.19.0/scripts/plot_histogram.py000075500000000000000000000050001046102023000167770ustar 00000000000000#!/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 matplotlib.pyplot as plt import numpy as np 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( "--labels", help="Comma-separated list of entries for the plot legend" ) parser.add_argument("--bins", help="Number of bins (default: auto)") parser.add_argument( "--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( "--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() with open(args.file) as f: results = json.load(f)["results"] if args.labels: labels = args.labels.split(",") else: labels = [b["command"] for b in results] all_times = [b["times"] for b in results] t_min = float(args.t_min) if args.t_min else np.min(list(map(np.min, all_times))) t_max = float(args.t_max) if args.t_max else np.max(list(map(np.max, all_times))) 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), ) plt.legend( loc=args.legend_location, fancybox=True, shadow=True, prop={"size": 10, "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, dpi=600) else: plt.show() hyperfine-1.19.0/scripts/plot_parametrized.py000075500000000000000000000060131046102023000174760ustar 00000000000000#!/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 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( "--parameter-name", metavar="name", type=str, help="Deprecated; parameter names are now inferred from benchmark files", ) parser.add_argument( "--log-x", help="Use a logarithmic x (parameter) axis", action="store_true" ) parser.add_argument( "--log-time", help="Use a logarithmic time axis", action="store_true" ) 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.") args = parser.parse_args() if args.parameter_name is not None: sys.stderr.write( "warning: --parameter-name is deprecated; names are inferred from " "benchmark results\n" ) def die(msg): sys.stderr.write(f"fatal: {msg}\n") sys.exit(1) def extract_parameters(results): """Return `(parameter_name: str, parameter_values: List[float])`.""" if not results: die("no benchmark data to plot") (names, values) = zip(*(unique_parameter(b) for b in results)) names = frozenset(names) if len(names) != 1: die( f"benchmarks must all have the same parameter name, but found: {sorted(names)}" ) return (next(iter(names)), list(values)) def unique_parameter(benchmark): """Return the unique parameter `(name: str, value: float)`, or die.""" params_dict = benchmark.get("parameters", {}) if not params_dict: die("benchmarks must have exactly one parameter, but found none") if len(params_dict) > 1: die( f"benchmarks must have exactly one parameter, but found multiple: {sorted(params_dict)}" ) [(name, value)] = params_dict.items() return (name, float(value)) parameter_name = None for filename in args.file: with open(filename) as f: results = json.load(f)["results"] (this_parameter_name, parameter_values) = extract_parameters(results) if parameter_name is not None and this_parameter_name != parameter_name: die( f"files must all have the same parameter name, but found {parameter_name!r} vs. {this_parameter_name!r}" ) parameter_name = this_parameter_name times_mean = [b["mean"] for b in results] times_stddev = [b["stddev"] for b in results] plt.errorbar(x=parameter_values, y=times_mean, yerr=times_stddev, capsize=2) plt.xlabel(parameter_name) plt.ylabel("Time [s]") if args.log_time: plt.yscale("log") else: plt.ylim(0, None) if args.log_x: plt.xscale("log") if args.titles: plt.legend(args.titles.split(",")) if args.output: plt.savefig(args.output) else: plt.show() hyperfine-1.19.0/scripts/plot_progression.py000075500000000000000000000040351046102023000173630ustar 00000000000000#!/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, thermal throttling and similar effects. """ import argparse import json import matplotlib.pyplot as plt import numpy as np def moving_average(times, num_runs): times_padded = np.pad( times, (num_runs // 2, num_runs - 1 - num_runs // 2), mode="edge" ) kernel = np.ones(num_runs) / num_runs return np.convolve(times_padded, kernel, mode="valid") 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("-o", "--output", help="Save image to the given filename.") parser.add_argument( "-w", "--moving-average-width", type=int, 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"] for result in results: label = result["command"] times = result["times"] num = len(times) nums = range(num) plt.scatter(x=nums, y=times, marker=".") plt.ylim([0, None]) plt.xlim([-1, num]) if not args.no_moving_average: moving_average_width = ( num // 5 if args.moving_average_width is None else args.moving_average_width ) average = moving_average(times, moving_average_width) plt.plot(nums, average, "-") if args.title: plt.title(args.title) 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: plt.show() hyperfine-1.19.0/scripts/plot_whisker.py000075500000000000000000000037611046102023000164720ustar 00000000000000#!/usr/bin/env python # /// script # requires-python = ">=3.10" # dependencies = [ # "matplotlib", # "pyqt6", # ] # /// """This program shows `hyperfine` benchmark results as a box and whisker plot. Quoting from the matplotlib documentation: The box extends from the lower to upper quartile values of the data, with a line at the median. The whiskers extend from the box to show the range of the data. Flier points are those past the end of the whiskers. """ 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.") args = parser.parse_args() with open(args.file, encoding="utf-8") as f: results = json.load(f)["results"] if args.labels: labels = args.labels.split(",") else: 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.get_cmap("rainbow") colors = [cmap(val / len(times)) for val in range(len(times))] for patch, color in zip(boxplot["boxes"], colors): patch.set_facecolor(color) if args.title: plt.title(args.title) 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: plt.show() hyperfine-1.19.0/scripts/ruff.toml000064400000000000000000000001041046102023000152260ustar 00000000000000target-version = "py310" [lint] extend-select = ["I", "UP", "RUF"] hyperfine-1.19.0/scripts/welch_ttest.py000075500000000000000000000020471046102023000163010ustar 00000000000000#!/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 the same.""" import argparse import json import sys from scipy import stats parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("file", help="JSON file with two benchmark results") args = parser.parse_args() with open(args.file) as f: results = json.load(f)["results"] if len(results) != 2: 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]) 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(f"t = {t:.3}, p = {p:.3}") print() if dispose: print(f"There is a difference between the two benchmarks (p < {th}).") else: print(f"The two benchmarks are almost the same (p >= {th}).") hyperfine-1.19.0/src/benchmark/benchmark_result.rs000064400000000000000000000030421046102023000203230ustar 00000000000000use std::collections::BTreeMap; use serde::Serialize; use crate::util::units::Second; /// Set of values that will be exported. // NOTE: `serde` is used for JSON serialization, but not for CSV serialization due to the // `parameters` map. Update `src/hyperfine/export/csv.rs` with new fields, as appropriate. #[derive(Debug, Default, Clone, Serialize, PartialEq)] 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, /// The standard deviation of all run times. Not available if only one run has been performed pub stddev: Option, /// The median run time pub median: Second, /// Time spent in user mode pub user: Second, /// Time spent in kernel mode pub system: Second, /// Minimum of all measured times pub min: Second, /// Maximum of all measured times pub max: Second, /// All run time measurements #[serde(skip_serializing_if = "Option::is_none")] pub times: Option>, /// Exit codes of all command invocations pub exit_codes: Vec>, /// Parameter values for this benchmark #[serde(skip_serializing_if = "BTreeMap::is_empty")] pub parameters: BTreeMap, } hyperfine-1.19.0/src/benchmark/executor.rs000064400000000000000000000246521046102023000166430ustar 00000000000000#[cfg(windows)] use std::os::windows::process::CommandExt; use std::process::ExitStatus; use crate::command::Command; 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; use crate::util::units::Second; 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, /// when running commands through a shell, we need to /// measure the shell spawning time separately in order /// to subtract it from the full runtime later. fn calibrate(&mut self) -> Result<()>; /// Return the time overhead for this executor when /// performing a measurement. This should return the time /// that is being used in addition to the actual runtime /// of the command. fn time_overhead(&self) -> Second; } 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(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() { 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) } pub struct RawExecutor<'a> { options: &'a Options, } impl<'a> RawExecutor<'a> { pub fn new(options: &'a Options) -> Self { RawExecutor { options } } } 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()?, iteration, command_failure_action.unwrap_or(self.options.command_failure_action), &self.options.command_input_policy, output_policy, &command.get_command_line(), )?; Ok(( TimingResult { time_real: result.time_real, time_user: result.time_user, time_system: result.time_system, }, result.status, )) } fn calibrate(&mut self) -> Result<()> { Ok(()) } fn time_overhead(&self) -> Second { 0.0 } } pub struct ShellExecutor<'a> { options: &'a Options, shell: &'a Shell, shell_spawning_time: Option, } impl<'a> ShellExecutor<'a> { pub fn new(shell: &'a Shell, options: &'a Options) -> Self { ShellExecutor { shell, options, shell_spawning_time: None, } } } 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 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, iteration, command_failure_action.unwrap_or(self.options.command_failure_action), &self.options.command_input_policy, output_policy, &command.get_command_line(), )?; // Subtract shell spawning time if let Some(spawning_time) = self.shell_spawning_time { result.time_real = (result.time_real - spawning_time.time_real).max(0.0); result.time_user = (result.time_user - spawning_time.time_user).max(0.0); result.time_system = (result.time_system - spawning_time.time_system).max(0.0); } Ok(( TimingResult { time_real: result.time_real, time_user: result.time_user, time_system: result.time_system, }, result.status, )) } /// Measure the average shell spawning time fn calibrate(&mut self) -> Result<()> { const COUNT: u64 = 50; let progress_bar = if self.options.output_style != OutputStyleOption::Disabled { Some(get_progress_bar( COUNT, "Measuring shell spawning time", self.options.output_style, )) } else { None }; let mut times_real: Vec = vec![]; let mut times_user: Vec = vec![]; let mut times_system: Vec = vec![]; for _ in 0..COUNT { // Just run the shell without any command let res = self.run_command_and_measure( &Command::new(None, ""), BenchmarkIteration::NonBenchmarkRun, None, &CommandOutputPolicy::Null, ); match res { Err(_) => { let shell_cmd = if cfg!(windows) { format!("{} /C \"\"", self.shell) } else { format!("{} -c \"\"", self.shell) }; bail!( "Could not measure shell execution time. Make sure you can run '{}'.", shell_cmd ); } Ok((r, _)) => { times_real.push(r.time_real); times_user.push(r.time_user); times_system.push(r.time_system); } } if let Some(bar) = progress_bar.as_ref() { bar.inc(1) } } if let Some(bar) = progress_bar.as_ref() { bar.finish_and_clear() } self.shell_spawning_time = Some(TimingResult { time_real: mean(×_real), time_user: mean(×_user), time_system: mean(×_system), }); Ok(()) } fn time_overhead(&self) -> Second { self.shell_spawning_time.unwrap().time_real } } #[derive(Clone)] pub struct MockExecutor { shell: Option, } impl MockExecutor { pub fn new(shell: Option) -> Self { MockExecutor { shell } } fn extract_time>(sleep_command: S) -> Second { assert!(sleep_command.as_ref().starts_with("sleep ")); sleep_command .as_ref() .trim_start_matches("sleep ") .parse::() .unwrap() } } 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 = { use std::os::unix::process::ExitStatusExt; ExitStatus::from_raw(0) }; #[cfg(windows)] let status = { use std::os::windows::process::ExitStatusExt; ExitStatus::from_raw(0) }; Ok(( TimingResult { time_real: Self::extract_time(command.get_command_line()), time_user: 0.0, time_system: 0.0, }, status, )) } fn calibrate(&mut self) -> Result<()> { Ok(()) } fn time_overhead(&self) -> Second { match &self.shell { None => 0.0, Some(shell) => Self::extract_time(shell), } } } #[test] fn test_mock_executor_extract_time() { assert_eq!(MockExecutor::extract_time("sleep 0.1"), 0.1); } hyperfine-1.19.0/src/benchmark/mod.rs000064400000000000000000000363441046102023000155650ustar 00000000000000pub mod benchmark_result; pub mod executor; pub mod relative_speed; pub mod scheduler; pub mod timing_result; use std::cmp; use crate::benchmark::executor::BenchmarkIteration; use crate::command::Command; 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::{OutlierWarningOptions, Warnings}; use crate::parameter::ParameterNameAndValue; use crate::util::exit_code::extract_exit_code; use crate::util::min_max::{max, min}; use crate::util::units::Second; use benchmark_result::BenchmarkResult; use timing_result::TimingResult; use anyhow::{anyhow, Result}; use colored::*; use statistical::{mean, median, standard_deviation}; use self::executor::Executor; /// Threshold for warning about fast execution time pub const MIN_EXECUTION_TIME: Second = 5e-3; pub struct Benchmark<'a> { number: usize, command: &'a Command<'a>, options: &'a Options, executor: &'a dyn Executor, } impl<'a> Benchmark<'a> { pub fn new( number: usize, command: &'a Command<'a>, options: &'a Options, executor: &'a dyn Executor, ) -> Self { Benchmark { number, command, options, executor, } } /// Run setup, cleanup, or preparation commands fn run_intermediate_command( &self, command: &Command<'_>, error_output: &'static str, output_policy: &CommandOutputPolicy, ) -> Result { self.executor .run_command_and_measure( command, executor::BenchmarkIteration::NonBenchmarkRun, Some(CmdFailureAction::RaiseError), output_policy, ) .map(|r| r.0) .map_err(|_| anyhow!(error_output)) } /// Run the command specified by `--setup`. fn run_setup_command( &self, parameters: impl IntoIterator>, output_policy: &CommandOutputPolicy, ) -> Result { let command = self .options .setup_command .as_ref() .map(|setup_command| Command::new_parametrized(None, setup_command, parameters)); let error_output = "The setup command terminated with a non-zero exit code. \ 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, output_policy)) .transpose()? .unwrap_or_default()) } /// Run the command specified by `--cleanup`. fn run_cleanup_command( &self, parameters: impl IntoIterator>, output_policy: &CommandOutputPolicy, ) -> Result { let command = self .options .cleanup_command .as_ref() .map(|cleanup_command| Command::new_parametrized(None, cleanup_command, parameters)); let error_output = "The cleanup command terminated with a non-zero exit code. \ 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, output_policy)) .transpose()? .unwrap_or_default()) } /// Run the command specified by `--prepare`. 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, 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 { if self.options.output_style != OutputStyleOption::Disabled { println!( "{}{}: {}", "Benchmark ".bold(), (self.number + 1).to_string().bold(), 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 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] } else { &values[self.number] }; Command::new_parametrized( None, preparation_command, self.command.get_parameters().iter().cloned(), ) }); let run_preparation_command = || { preparation_command .as_ref() .map(|cmd| self.run_preparation_command(cmd, output_policy)) .transpose() }; 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 { let progress_bar = if self.options.output_style != OutputStyleOption::Disabled { Some(get_progress_bar( self.options.warmup_count, "Performing warmup runs", self.options.output_style, )) } else { None }; for i in 0..self.options.warmup_count { let _ = run_preparation_command()?; 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) } } if let Some(bar) = progress_bar.as_ref() { bar.finish_and_clear() } } // Set up progress bar (and spinner for initial measurement) let progress_bar = if self.options.output_style != OutputStyleOption::Disabled { Some(get_progress_bar( self.options.run_bounds.min, "Initial time measurement", self.options.output_style, )) } else { None }; let preparation_result = run_preparation_command()?; let preparation_overhead = 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, 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 + conclusion_overhead)) as u64; let count = { let min = cmp::max(runs_in_min_time, self.options.run_bounds.min); self.options .run_bounds .max .as_ref() .map(|max| cmp::min(min, *max)) .unwrap_or(min) }; let count_remaining = count - 1; // Save the first result times_real.push(res.time_real); times_user.push(res.time_user); times_system.push(res.time_system); exit_codes.push(extract_exit_code(status)); all_succeeded = all_succeeded && success; // Re-configure the progress bar if let Some(bar) = progress_bar.as_ref() { bar.set_length(count) } if let Some(bar) = progress_bar.as_ref() { bar.inc(1) } // Gather statistics (perform the actual benchmark) for i in 0..count_remaining { run_preparation_command()?; let msg = { let mean = format_duration(mean(×_real), self.options.time_unit); format!("Current estimate: {}", mean.to_string().green()) }; if let Some(bar) = progress_bar.as_ref() { bar.set_message(msg.to_owned()) } 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); exit_codes.push(extract_exit_code(status)); all_succeeded = all_succeeded && success; if let Some(bar) = progress_bar.as_ref() { bar.inc(1) } run_conclusion_command()?; } if let Some(bar) = progress_bar.as_ref() { bar.finish_and_clear() } // Compute statistical quantities let t_num = times_real.len(); let t_mean = mean(×_real); let t_stddev = if times_real.len() > 1 { Some(standard_deviation(×_real, Some(t_mean))) } else { None }; let t_median = median(×_real); let t_min = min(×_real); let t_max = max(×_real); let user_mean = mean(×_user); let system_mean = mean(×_system); // Formatting and console output 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!("{t_num} runs"); let user_str = format_duration(user_mean, Some(time_unit)); let system_str = format_duration(system_mean, Some(time_unit)); if self.options.output_style != OutputStyleOption::Disabled { if times_real.len() == 1 { println!( " Time ({} ≡): {:>8} {:>8} [User: {}, System: {}]", "abs".green().bold(), mean_str.green().bold(), " ", // alignment user_str.blue(), system_str.blue() ); } else { let stddev_str = format_duration(t_stddev.unwrap(), Some(time_unit)); println!( " Time ({} ± {}): {:>8} ± {:>8} [User: {}, System: {}]", "mean".green().bold(), "σ".green(), mean_str.green().bold(), stddev_str.green(), user_str.blue(), system_str.blue() ); println!( " Range ({} … {}): {:>8} … {:>8} {}", "min".cyan(), "max".purple(), min_str.cyan(), max_str.purple(), num_str.dimmed() ); } } // Warnings let mut warnings = vec![]; // Check execution time if matches!(self.options.executor_kind, ExecutorKind::Shell(_)) && times_real.iter().any(|&t| t < MIN_EXECUTION_TIME) { warnings.push(Warnings::FastExecutionTime); } // 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], outlier_warning_options, )); } else if scores.iter().any(|&s| s.abs() > OUTLIER_THRESHOLD) { warnings.push(Warnings::OutliersDetected(outlier_warning_options)); } if !warnings.is_empty() { eprintln!(" "); for warning in &warnings { eprintln!(" {}: {}", "Warning".yellow(), warning); } } if self.options.output_style != OutputStyleOption::Disabled { println!(" "); } self.run_cleanup_command(self.command.get_parameters().iter().cloned(), output_policy)?; Ok(BenchmarkResult { 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, user: user_mean, system: system_mean, min: t_min, max: t_max, times: Some(times_real), exit_codes, parameters: self .command .get_parameters() .iter() .map(|(name, value)| (name.to_string(), value.to_string())) .collect(), }) } } hyperfine-1.19.0/src/benchmark/relative_speed.rs000064400000000000000000000125551046102023000177770ustar 00000000000000use std::cmp::Ordering; use super::benchmark_result::BenchmarkResult; 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_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 fastest_of(results: &[BenchmarkResult]) -> &BenchmarkResult { results .iter() .min_by(|&l, &r| compare_mean_time(l, r)) .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(compute_relative_speeds(results, fastest, sort_order)) } /// 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)] fn create_result(name: &str, mean: Scalar) -> BenchmarkResult { use std::collections::BTreeMap; BenchmarkResult { command: name.into(), command_with_unused_parameters: name.into(), mean, stddev: Some(1.0), median: mean, user: mean, system: 0.0, min: mean, max: mean, times: None, exit_codes: Vec::new(), parameters: BTreeMap::new(), } } #[test] fn test_compute_relative_speed() { use approx::assert_relative_eq; let results = vec![ create_result("cmd1", 3.0), create_result("cmd2", 2.0), create_result("cmd3", 5.0), ]; 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_with_check(&results, SortOrder::Command); assert!(annotated_results.is_none()); } hyperfine-1.19.0/src/benchmark/scheduler.rs000064400000000000000000000142641046102023000167610ustar 00000000000000use 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::{Command, Commands}; use crate::export::ExportManager; use crate::options::{ExecutorKind, Options, OutputStyleOption, SortOrder}; use anyhow::Result; pub struct Scheduler<'a> { commands: &'a Commands<'a>, options: &'a Options, export_manager: &'a ExportManager, results: Vec, } impl<'a> Scheduler<'a> { pub fn new( commands: &'a Commands, options: &'a Options, export_manager: &'a ExportManager, ) -> Self { Self { commands, options, export_manager, results: vec![], } } pub fn run_benchmarks(&mut self) -> Result<()> { let mut executor: Box = match self.options.executor_kind { ExecutorKind::Raw => Box::new(RawExecutor::new(self.options)), ExecutorKind::Mock(ref shell) => Box::new(MockExecutor::new(shell.clone())), ExecutorKind::Shell(ref shell) => Box::new(ShellExecutor::new(shell, self.options)), }; let reference = self .options .reference_command .as_ref() .map(|cmd| Command::new(None, cmd)); executor.calibrate()?; for (number, cmd) in reference.iter().chain(self.commands.iter()).enumerate() { self.results .push(Benchmark::new(number, cmd, self.options, &*executor).run()?); // 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, self.options.sort_order_exports, true, )?; } Ok(()) } pub fn print_relative_speed_comparison(&self) { if self.options.output_style == OutputStyleOption::Disabled { return; } if self.results.len() < 2 { return; } 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 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, self.options.sort_order_exports, false) } } hyperfine-1.19.0/src/benchmark/timing_result.rs000064400000000000000000000004741046102023000176660ustar 00000000000000use crate::util::units::Second; /// Results from timing a single command #[derive(Debug, Default, Copy, Clone)] pub struct TimingResult { /// Wall clock time pub time_real: Second, /// Time spent in user mode pub time_user: Second, /// Time spent in kernel mode pub time_system: Second, } hyperfine-1.19.0/src/cli.rs000064400000000000000000000436261046102023000136240ustar 00000000000000use std::ffi::OsString; use clap::{ builder::NonEmptyStringValueParser, crate_version, Arg, ArgAction, ArgMatches, Command, ValueHint, }; pub fn get_cli_arguments<'a, I, T>(args: I) -> ArgMatches where I: IntoIterator, T: Into + Clone + 'a, { let command = build_command(); command.get_matches_from(args) } /// Build the clap command for parsing command line arguments fn build_command() -> Command { Command::new("hyperfine") .version(crate_version!()) .next_line_help(true) .hide_possible_values(true) .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 \ line like \"grep -i todo\" or a shell command like \"sleep 0.5 && echo test\". \ The latter is only available if the shell is not explicitly disabled via \ '--shell=none'. If multiple commands are given, hyperfine will show a \ comparison of the respective runtimes.") .required(true) .action(ArgAction::Append) .value_hint(ValueHint::CommandString) .value_parser(NonEmptyStringValueParser::new()), ) .arg( Arg::new("warmup") .long("warmup") .short('w') .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.", ), ) .arg( Arg::new("min-runs") .long("min-runs") .short('m') .action(ArgAction::Set) .value_name("NUM") .help("Perform at least NUM runs for each command (default: 10)."), ) .arg( Arg::new("max-runs") .long("max-runs") .short('M') .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"]) .short('r') .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."), ) .arg( Arg::new("setup") .long("setup") .short('s') .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 \ other work that should happen once before a series of benchmark runs, \ 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("prepare") .long("prepare") .short('p') .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 \ 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.", ), ) .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') .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. \ This is useful if the commands to be benchmarked produce \ artifacts that need to be cleaned up." ), ) .arg( Arg::new("parameter-scan") .long("parameter-scan") .short('P') .action(ArgAction::Set) .allow_hyphen_values(true) .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 \ Example: hyperfine -P threads 1 8 'make -j {threads}'\n\n\ This performs benchmarks for 'make -j 1', 'make -j 2', …, 'make -j 8'.\n\n\ To have the value increase following different patterns, use shell arithmetics.\n\n \ Example: hyperfine -P size 0 3 'sleep $((2**{size}))'\n\n\ This performs benchmarks with power of 2 increases: 'sleep 1', 'sleep 2', 'sleep 4', …\n\ The exact syntax may vary depending on your shell and OS." ), ) .arg( Arg::new("parameter-step-size") .long("parameter-step-size") .short('D') .action(ArgAction::Set) .value_names(["DELTA"]) .requires("parameter-scan") .help( "This argument requires --parameter-scan to be specified as well. \ Traverse the range MIN..MAX in steps of DELTA.\n\n \ Example: hyperfine -P delay 0.3 0.7 -D 0.2 'sleep {delay}'\n\n\ This performs benchmarks for 'sleep 0.3', 'sleep 0.5' and 'sleep 0.7'.", ), ) .arg( Arg::new("parameter-list") .long("parameter-list") .short('L') .action(ArgAction::Append) .allow_hyphen_values(true) .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\ .\n\nExample: hyperfine -L compiler gcc,clang '{compiler} -O2 main.cpp'\n\n\ This performs benchmarks for 'gcc -O2 main.cpp' and 'clang -O2 main.cpp'.\n\n\ The option can be specified multiple times to run benchmarks for all \ possible parameter combinations.\n" ), ) .arg( Arg::new("shell") .long("shell") .short('S') .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 \ 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.") ) .arg( Arg::new("no-shell") .short('N') .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::SetTrue) .short('i') .help("Ignore non-zero exit codes of the benchmarked programs."), ) .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') .action(ArgAction::Set) .value_name("UNIT") .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") .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") .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."), ) .arg( Arg::new("export-json") .long("export-json") .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") .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") .action(ArgAction::Set) .value_name("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. \ 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.", ), ) .arg( Arg::new("output") .long("output") .conflicts_with("show-output") .action(ArgAction::Append) .value_name("WHERE") .help( "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') .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."), ) // This option is hidden for now, as it is not yet clear yet if we want to 'stabilize' this, // see discussion in https://github.com/sharkdp/hyperfine/issues/527 .arg( Arg::new("min-benchmarking-time") .long("min-benchmarking-time") .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 \ `--runs` option.") ) .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