Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Backports:SLE-15-SP4
hyperfine
hyperfine-1.12.0.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File hyperfine-1.12.0.obscpio of Package hyperfine
07070100000000000041ED000000000000000000000001616C612800000000000000000000000000000000000000000000001900000000hyperfine-1.12.0/.github07070100000001000081A4000000000000000000000001616C612800000090000000000000000000000000000000000000002800000000hyperfine-1.12.0/.github/dependabot.ymlversion: 2 updates: - package-ecosystem: cargo directory: "/" schedule: interval: monthly time: "04:00" timezone: Europe/Berlin 07070100000002000041ED000000000000000000000001616C612800000000000000000000000000000000000000000000002300000000hyperfine-1.12.0/.github/workflows07070100000003000081A4000000000000000000000001616C6128000035F2000000000000000000000000000000000000002C00000000hyperfine-1.12.0/.github/workflows/CICD.ymlname: CICD env: MIN_SUPPORTED_RUST_VERSION: "1.46.0" CICD_INTERMEDIATES_DIR: "_cicd-intermediates" on: workflow_dispatch: pull_request: push: branches: - master tags: - '*' jobs: min_version: name: Minimum supported rust version runs-on: ubuntu-20.04 steps: - name: Checkout source code uses: actions/checkout@v2 - name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }}) uses: actions-rs/toolchain@v1 with: toolchain: ${{ env.MIN_SUPPORTED_RUST_VERSION }} default: true profile: minimal # minimal component installation (ie, no documentation) components: clippy, rustfmt - name: Ensure `cargo fmt` has been run uses: actions-rs/cargo@v1 with: command: fmt args: -- --check - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix) uses: actions-rs/cargo@v1 with: command: clippy args: --locked --all-targets --all-features -- --allow clippy::unknown_clippy_lints - name: Run tests uses: actions-rs/cargo@v1 with: command: test args: --locked build: name: ${{ matrix.job.target }} (${{ matrix.job.os }}) runs-on: ${{ matrix.job.os }} strategy: fail-fast: false matrix: job: - { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } - { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true } - { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true } - { target: i686-pc-windows-msvc , os: windows-2019 } - { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } - { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } - { target: x86_64-apple-darwin , os: macos-10.15 } - { target: x86_64-pc-windows-gnu , os: windows-2019 } - { target: x86_64-pc-windows-msvc , os: windows-2019 } - { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04 } - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } steps: - name: Checkout source code uses: actions/checkout@v2 - 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: Extract crate information shell: bash run: | echo "PROJECT_NAME=hyperfine" >> $GITHUB_ENV echo "PROJECT_VERSION=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)" >> $GITHUB_ENV echo "PROJECT_MAINTAINER=$(sed -n 's/^authors = \["\(.*\)"\]/\1/p' Cargo.toml)" >> $GITHUB_ENV echo "PROJECT_HOMEPAGE=$(sed -n 's/^homepage = "\(.*\)"/\1/p' Cargo.toml)" >> $GITHUB_ENV - name: Install Rust toolchain uses: actions-rs/toolchain@v1 with: toolchain: stable target: ${{ matrix.job.target }} override: true profile: minimal # minimal component installation (ie, no documentation) - name: Show version information (Rust, cargo, GCC) shell: bash run: | gcc --version || true rustup -V rustup toolchain list rustup default cargo -V rustc -V - name: Build uses: actions-rs/cargo@v1 with: use-cross: ${{ matrix.job.use-cross }} command: build args: --locked --release --target=${{ matrix.job.target }} - name: Strip debug information from executable id: strip shell: bash run: | # Figure out suffix of binary EXE_suffix="" case ${{ matrix.job.target }} in *-pc-windows-*) EXE_suffix=".exe" ;; esac; # Figure out what strip tool to use if any STRIP="strip" case ${{ matrix.job.target }} in arm-unknown-linux-*) STRIP="arm-linux-gnueabihf-strip" ;; aarch64-unknown-linux-gnu) STRIP="aarch64-linux-gnu-strip" ;; *-pc-windows-msvc) STRIP="" ;; esac; # Setup paths BIN_DIR="${{ env.CICD_INTERMEDIATES_DIR }}/stripped-release-bin/" mkdir -p "${BIN_DIR}" BIN_NAME="${{ env.PROJECT_NAME }}${EXE_suffix}" BIN_PATH="${BIN_DIR}/${BIN_NAME}" # Copy the release build binary to the result location cp "target/${{ matrix.job.target }}/release/${BIN_NAME}" "${BIN_DIR}" # Also strip if possible if [ -n "${STRIP}" ]; then "${STRIP}" "${BIN_PATH}" fi # Let subsequent steps know where to find the (stripped) bin echo ::set-output name=BIN_PATH::${BIN_PATH} echo ::set-output name=BIN_NAME::${BIN_NAME} - 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 ${PROJECT_NAME}" ;; esac; echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS} - name: Run tests uses: actions-rs/cargo@v1 with: use-cross: ${{ matrix.job.use-cross }} command: test args: --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}} - name: Create tarball id: package shell: bash run: | PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac; PKG_BASENAME=${PROJECT_NAME}-v${PROJECT_VERSION}-${{ matrix.job.target }} PKG_NAME=${PKG_BASENAME}${PKG_suffix} echo ::set-output name=PKG_NAME::${PKG_NAME} 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.strip.outputs.BIN_PATH }}" "$ARCHIVE_DIR" # Man page cp 'doc/${{ env.PROJECT_NAME }}.1' "$ARCHIVE_DIR" # README, LICENSE and CHANGELOG files cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "CHANGELOG.md" "$ARCHIVE_DIR" # Autocompletion files cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.bash' "$ARCHIVE_DIR/autocomplete/" cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.fish' "$ARCHIVE_DIR/autocomplete/" cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'_${{ env.PROJECT_NAME }}.ps1' "$ARCHIVE_DIR/autocomplete/" cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'_${{ env.PROJECT_NAME }}' "$ARCHIVE_DIR/autocomplete/" # 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 ::set-output name=PKG_PATH::"${PKG_STAGING}/${PKG_NAME}" - 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=${PROJECT_NAME} DPKG_CONFLICTS=${PROJECT_NAME}-musl case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${PROJECT_NAME}-musl ; DPKG_CONFLICTS=${PROJECT_NAME} ;; esac; DPKG_VERSION=${PROJECT_VERSION} 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 ::set-output name=DPKG_NAME::${DPKG_NAME} # Binary install -Dm755 "${{ steps.strip.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.strip.outputs.BIN_NAME }}" # Man page install -Dm644 'doc/${{ env.PROJECT_NAME }}.1' "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1" gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1" # Autocompletion files install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.bash' "${DPKG_DIR}/usr/share/bash-completion/completions/${{ env.PROJECT_NAME }}" install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.fish' "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ env.PROJECT_NAME }}.fish" install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'_${{ env.PROJECT_NAME }}' "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ env.PROJECT_NAME }}" # 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" <<EOF Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: ${{ env.PROJECT_NAME }} Source: ${{ env.PROJECT_HOMEPAGE }} Files: * Copyright: ${{ env.PROJECT_MAINTAINER }} Copyright: $COPYRIGHT_YEARS ${{ env.PROJECT_MAINTAINER }} License: Apache-2.0 or MIT License: Apache-2.0 On Debian systems, the complete text of the Apache-2.0 can be found in the file /usr/share/common-licenses/Apache-2.0. License: MIT 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. EOF chmod 644 "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright" # control file mkdir -p "${DPKG_DIR}/DEBIAN" cat > "${DPKG_DIR}/DEBIAN/control" <<EOF Package: ${DPKG_BASENAME} Version: ${DPKG_VERSION} Section: utils Priority: optional Maintainer: ${{ env.PROJECT_MAINTAINER }} Homepage: ${{ env.PROJECT_HOMEPAGE }} Architecture: ${DPKG_ARCH} Provides: ${{ env.PROJECT_NAME }} Conflicts: ${DPKG_CONFLICTS} Description: A command-line benchmarking tool EOF DPKG_PATH="${DPKG_STAGING}/${DPKG_NAME}" echo ::set-output name=DPKG_PATH::${DPKG_PATH} # 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 ::set-output name=IS_RELEASE::${IS_RELEASE} - name: Publish archives and packages uses: softprops/action-gh-release@v1 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 }} 07070100000004000081A4000000000000000000000001616C612800000015000000000000000000000000000000000000001C00000000hyperfine-1.12.0/.gitignore /target/ **/*.rs.bk 07070100000005000081A4000000000000000000000001616C6128000024FA000000000000000000000000000000000000001E00000000hyperfine-1.12.0/CHANGELOG.md# unreleased ## Features ## Changes ## Bugfixes ## Other ## Packaging # 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 <VAR> <VALUES>` option can be used to run a parametrized benchmark on a user-specified list of values. This is similar to `--parameter-scan <VAR> <MIN> <MAX>`, 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 <CMD>` 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 <CMD>` 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 <VAR> <MIN> <MAX>`, 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 07070100000006000081A4000000000000000000000001616C6128000051C0000000000000000000000000000000000000001C00000000hyperfine-1.12.0/Cargo.lock# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "aho-corasick" version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] [[package]] name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ "winapi", ] [[package]] name = "approx" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e" dependencies = [ "num-traits", ] [[package]] name = "arrayvec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "assert_cmd" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e996dc7940838b7ef1096b882e29ec30a3149a3a443cdc8dba19ed382eca1fe2" dependencies = [ "bstr", "doc-comment", "predicates", "predicates-core", "predicates-tree", "wait-timeout", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bstr" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "lazy_static", "memchr", "regex-automata", "serde", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ "ansi_term", "atty", "bitflags", "strsim", "term_size", "textwrap", "unicode-width", "vec_map", ] [[package]] name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ "bitflags", ] [[package]] name = "colored" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" dependencies = [ "atty", "lazy_static", "winapi", ] [[package]] name = "console" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" dependencies = [ "encode_unicode", "libc", "once_cell", "terminal_size", "winapi", ] [[package]] name = "csv" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" dependencies = [ "bstr", "csv-core", "itoa", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" 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 = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[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 = "getrandom" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "hyperfine" version = "1.12.0" dependencies = [ "approx", "assert_cmd", "atty", "clap", "colored", "csv", "indicatif", "libc", "predicates", "rand 0.8.4", "rust_decimal", "serde", "serde_json", "shell-words", "statistical", "tempfile", "winapi", ] [[package]] name = "indicatif" version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d207dc617c7a380ab07ff572a6e52fa202a2a8f355860ac9c38e23f8196be1b" dependencies = [ "console", "lazy_static", "number_prefix", "regex", ] [[package]] name = "itertools" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" dependencies = [ "either", ] [[package]] name = "itoa" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce" [[package]] name = "memchr" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[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.0.1", "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.0.1", "num-traits", ] [[package]] name = "num-integer" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg 1.0.1", "num-traits", ] [[package]] name = "num-iter" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" dependencies = [ "autocfg 1.0.1", "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.0.1", "num-bigint", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg 1.0.1", ] [[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.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "ppv-lite86" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741" [[package]] name = "predicates" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6ce811d0b2e103743eec01db1c50612221f173084ce2f7941053e94b6bb474" dependencies = [ "difflib", "float-cmp", "itertools", "normalize-line-endings", "predicates-core", "regex", ] [[package]] name = "predicates-core" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" [[package]] name = "predicates-tree" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "338c7be2905b732ae3984a2f40032b5e94fd8f52505b186c7d4d68d193445df7" dependencies = [ "predicates-core", "termtree", ] [[package]] name = "proc-macro2" version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70" dependencies = [ "unicode-xid", ] [[package]] name = "quote" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" dependencies = [ "autocfg 0.1.7", "libc", "rand_chacha 0.1.1", "rand_core 0.4.2", "rand_hc 0.1.0", "rand_isaac", "rand_jitter", "rand_os", "rand_pcg", "rand_xorshift", "winapi", ] [[package]] name = "rand" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.3", "rand_hc 0.3.1", ] [[package]] name = "rand_chacha" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" dependencies = [ "autocfg 0.1.7", "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.3", ] [[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.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 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_hc" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" dependencies = [ "rand_core 0.6.3", ] [[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.7", "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 = "redox_syscall" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "remove_dir_all" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ "winapi", ] [[package]] name = "rust_decimal" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0f1028de22e436bb35fce070310ee57d57b5e59ae77b4e3f24ce4773312b813" dependencies = [ "arrayvec", "num-traits", "serde", ] [[package]] name = "ryu" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "serde" version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "shell-words" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fa3938c99da4914afedd13bf3d79bcb6c277d1b2c398d23257a304d9e1b074" [[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.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "tempfile" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if", "libc", "rand 0.8.4", "redox_syscall", "remove_dir_all", "winapi", ] [[package]] name = "term_size" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" dependencies = [ "libc", "winapi", ] [[package]] name = "terminal_size" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" dependencies = [ "libc", "winapi", ] [[package]] name = "termtree" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78fbf2dd23e79c28ccfa2472d3e6b3b189866ffef1aeb91f17c2d968b6586378" [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "term_size", "unicode-width", ] [[package]] name = "unicode-width" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[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.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[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" 07070100000007000081A4000000000000000000000001616C612800000416000000000000000000000000000000000000001C00000000hyperfine-1.12.0/Cargo.toml[package] authors = ["David Peter <mail@david-peter.de>"] categories = ["command-line-utilities"] description = "A command-line benchmarking tool" homepage = "https://github.com/sharkdp/hyperfine" license = "MIT/Apache-2.0" name = "hyperfine" readme = "README.md" repository = "https://github.com/sharkdp/hyperfine" version = "1.12.0" edition = "2018" build = "build.rs" [dependencies] colored = "2.0" indicatif = "0.16" statistical = "1.0" atty = "0.2" csv = "1.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" rust_decimal = "1.16" rand = "0.8" shell-words = "1.0" [target.'cfg(not(windows))'.dependencies] libc = "0.2" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["processthreadsapi", "minwindef", "winnt"] } [dependencies.clap] version = "2" default-features = false features = ["suggestions", "color", "wrap_help"] [dev-dependencies] approx = "0.5" assert_cmd = "2.0" predicates = "2.0" tempfile = "3.2" [build-dependencies] clap = "2" atty = "0.2" [profile.release] lto = true 07070100000008000081A4000000000000000000000001616C612800002C5D000000000000000000000000000000000000002000000000hyperfine-1.12.0/LICENSE-APACHE 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. 07070100000009000081A4000000000000000000000001616C61280000043E000000000000000000000000000000000000001D00000000hyperfine-1.12.0/LICENSE-MITMIT License Copyright (c) 2018-2020 The hyperfine developers 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. 0707010000000A000081A4000000000000000000000001616C612800001D63000000000000000000000000000000000000001B00000000hyperfine-1.12.0/README.md# 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 benchmark To run a benchmark, you can simply call `hyperfine <command>...`. The argument(s) can be any shell command. For example: ``` bash 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. To change this, you can use the `-m`/`--min-runs` option: ``` bash hyperfine --min-runs 5 'sleep 0.2' 'sleep 3.2' ``` ### Warmup runs and preparation commands If the program execution time is limited by 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: ``` bash 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 ``` bash 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: ``` bash hyperfine --prepare 'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches' 'grep -R TODO *' ``` ### Parameterized benchmarks If you want to run a benchmark where only a single parameter is varied (say, the number of threads), you can use the `-P`/`--parameter-scan` option and call: ``` bash 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: ``` bash 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`. ### Shell functions and aliases If you are using bash, you can export shell functions to directly benchmark them with hyperfine: ``` $ my_function() { sleep 1; } $ export -f my_function $ hyperfine my_function ``` If you are using a different shell, or if you want to benchmark shell aliases, you may try to put them in a separate file: ```bash echo 'my_function() { sleep 1 }' > /tmp/my_function.sh echo 'alias my_alias="sleep 1"' > /tmp/my_alias.sh hyperfine 'source /tmp/my_function.sh; eval my_function' hyperfine 'source /tmp/my_alias.sh; eval my_alias' ``` ### Export results Hyperfine has multiple options for exporting benchmark results: CSV, JSON, Markdown (see `--help` text for details). To export results to Markdown, for example, you can use the `--export-markdown` option that will create tables like this: | 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 | The JSON output is useful if you want to analyze the benchmark results in more detail. See the [`scripts/`](https://github.com/sharkdp/hyperfine/tree/master/scripts) folder for some examples. ## Installation [![Packaging status](https://repology.org/badge/vertical-allrepos/hyperfine.svg)](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.12.0/hyperfine_1.12.0_amd64.deb sudo dpkg -i hyperfine_1.12.0_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://www.archlinux.org/packages/community/x86_64/hyperfine/): ``` pacman -S 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 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 ``` ### 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 via [cargo](https://doc.rust-lang.org/cargo/): ``` cargo install hyperfine ``` Make sure that you use Rust 1.46 or higher. ### 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/Gabriel439/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. 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. ## 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. 0707010000000B000081A4000000000000000000000001616C612800000243000000000000000000000000000000000000001A00000000hyperfine-1.12.0/build.rsuse std::fs; use clap::Shell; include!("src/app.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 app = build_app(); app.gen_completions("hyperfine", Shell::Bash, &outdir); app.gen_completions("hyperfine", Shell::Fish, &outdir); app.gen_completions("hyperfine", Shell::Zsh, &outdir); app.gen_completions("hyperfine", Shell::PowerShell, &outdir); } 0707010000000C000041ED000000000000000000000001616C612800000000000000000000000000000000000000000000001500000000hyperfine-1.12.0/doc0707010000000D000081A4000000000000000000000001616C6128000017E9000000000000000000000000000000000000002100000000hyperfine-1.12.0/doc/hyperfine.1.TH HYPERFINE 1 .SH NAME hyperfine \- command\-line benchmarking tool .SH SYNOPSIS .B hyperfine .RB [ \-ihV ] .RB [ \-w .IR warmupruns ] .RB [ \-r .IR runs ] .RB [ \-p .IR cmd... ] .RB [ \-c .IR cmd ] .RB [ \-s .IR style ] .RI [ cmd... ] .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 \fIwarmupruns\fP .IP Perform \fIwarmupruns\fP (number) before the actual benchmark. This can be used to fill (disk) caches for I/O\-heavy programs. .HP \fB\-m\fR, \fB\-\-min\-runs\fR \fIminruns\fP .IP Perform at least \fIminruns\fP (number) runs for each command. Default: 10. .HP \fB\-M\fR, \fB\-\-max\-runs\fR \fImaxruns\fP .IP Perform at most \fImaxruns\fP (number) runs for each command. Default: no limit. .HP \fB\-r\fR, \fB\-\-runs\fR \fIruns\fP .IP Perform exactly \fIruns\fP (number) runs for each command. If this option is not specified, \fBhyperfine\fR automatically determines the number of runs. .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 \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. .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'. .RE .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'. .HP \fB\-s\fR, \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\-S\fR, \fB\-\-shell\fR \fIshell\fP .IP Set the \fIshell\fP to use for executing benchmarked commands. .HP \fB\-i\fR, \fB\-\-ignore\-failure\fR .IP Ignore non\-zero exit codes of the benchmarked programs. .HP \fB\-u\fR, \fB\-\-time\-unit\fR \fIunit\fP .IP Set the time \fIunit\fP to be used. Default: second. Possible values: millisecond, second. .HP \fB\-\-export\-asciidoc\fR \fIfile\fP .IP Export the timing summary statistics as an AsciiDoc table to the given \fIfile\fP. .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. .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. .HP \fB\-\-export\-markdown\fR \fIfile\fP .IP Export the timing summary statistics as a Markdown table to the given \fIfile\fP. .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\-n\fR, \fB\-\-command\-name\fR \fIname\fP .IP Identify a command with the given \fIname\fP. Commands and names are paired in the same order: the first command executed gets the first name passed as option. .HP \fB\-h\fR, \fB\-\-help\fR .IP Print help message. .HP \fB\-V\fR, \fB\-\-version\fR .IP Show version information. .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 .SH AUTHOR .LP David Peter (sharkdp) .LP Source, bug tracker, and additional information can be found on GitHub at: .I https://github.com/sharkdp/hyperfine 0707010000000E000041ED000000000000000000000001616C612800000000000000000000000000000000000000000000001900000000hyperfine-1.12.0/scripts0707010000000F000081A4000000000000000000000001616C6128000001D4000000000000000000000000000000000000002300000000hyperfine-1.12.0/scripts/README.mdThis 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 > python plot_whisker.py sleep.json ``` ### Pre-requisites To make these scripts work, you will need to install `numpy` and `matplotlib`. Install them via your package manager or `pip`: ```bash pip install numpy matplotlib # pip3, if you are using python3 ``` 07070100000010000081ED000000000000000000000001616C61280000044D000000000000000000000000000000000000003000000000hyperfine-1.12.0/scripts/advanced_statistics.py#!/usr/bin/env python 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("Command '{}'".format(command)) print(" mean: {:8.3f} s".format(np.mean(ts))) print(" stddev: {:8.3f} s".format(np.std(ts, ddof=1))) print(" median: {:8.3f} s".format(np.median(ts))) print(" min: {:8.3f} s".format(np.min(ts))) print(" max: {:8.3f} s".format(np.max(ts))) print() print(" percentiles:") print(" P_05 .. P_95: {:.3f} s .. {:.3f} s".format(p05, p95)) print( " P_25 .. P_75: {:.3f} s .. {:.3f} s " "(IQR = {:.3f} s)".format(p25, p75, iqr) ) print() 07070100000011000081ED000000000000000000000001616C612800000594000000000000000000000000000000000000002B00000000hyperfine-1.12.0/scripts/plot_histogram.py#!/usr/bin/env python """This program shows `hyperfine` benchmark results as a histogram.""" import argparse import json import numpy as np 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( "--labels", help="Comma-separated list of entries for the plot legend" ) parser.add_argument("--bins", help="Number of bins (default: auto)") parser.add_argument( "--type", help="Type of histogram (*bar*, barstacked, step, stepfilled)" ) parser.add_argument( "-o", "--output", help="Save image to the given filename." ) 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 = np.min(list(map(np.min, all_times))) t_max = 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.hist( all_times, label=labels, bins=bins, histtype=histtype, range=(t_min, t_max), ) plt.legend(prop={"family": ["Source Code Pro", "Fira Mono", "Courier New"]}) plt.xlabel("Time [s]") if args.title: plt.title(args.title) if args.output: plt.savefig(args.output) else: plt.show() 07070100000012000081ED000000000000000000000001616C612800000BD5000000000000000000000000000000000000002E00000000hyperfine-1.12.0/scripts/plot_parametrized.py#!/usr/bin/env python """This program shows parametrized `hyperfine` benchmark results as an errorbar plot.""" import argparse import json import matplotlib.pyplot as plt import sys 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("fatal: %s\n" % (msg,)) 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( "benchmarks must all have the same parameter name, but found: %s" % 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( "benchmarks must have exactly one parameter, but found multiple: %s" % 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( "files must all have the same parameter name, but found %r vs. %r" % (parameter_name, this_parameter_name) ) 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() 07070100000013000081ED000000000000000000000001616C6128000005DE000000000000000000000000000000000000002900000000hyperfine-1.12.0/scripts/plot_whisker.py#!/usr/bin/env python """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( "--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) 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] boxplot = plt.boxplot(times, vert=True, patch_artist=True) cmap = plt.cm.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) if args.output: plt.savefig(args.output) else: plt.show() 07070100000014000081ED000000000000000000000001616C6128000003FA000000000000000000000000000000000000002800000000hyperfine-1.12.0/scripts/welch_ttest.py#!/usr/bin/env python """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("Command 1: {}".format(a)) print("Command 2: {}\n".format(b)) t, p = stats.ttest_ind(X, Y, equal_var=False) th = 0.05 dispose = p < th print("t = {:.3}, p = {:.3}".format(t, p)) print() if dispose: print("There is a difference between the two benchmarks (p < {}).".format(th)) else: print("The two benchmarks are almost the same (p >= {}).".format(th)) 07070100000015000041ED000000000000000000000001616C612800000000000000000000000000000000000000000000001500000000hyperfine-1.12.0/src07070100000016000081A4000000000000000000000001616C61280000287D000000000000000000000000000000000000001C00000000hyperfine-1.12.0/src/app.rsuse std::ffi::OsString; use atty::Stream; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; pub fn get_arg_matches<I, T>(args: I) -> ArgMatches<'static> where I: IntoIterator<Item = T>, T: Into<OsString> + Clone, { let app = build_app(); app.get_matches_from(args) } /// Build the clap app for parsing command line arguments fn build_app() -> App<'static, 'static> { let clap_color_setting = if atty::is(Stream::Stdout) { AppSettings::ColoredHelp } else { AppSettings::ColorNever }; App::new("hyperfine") .version(crate_version!()) .setting(clap_color_setting) .setting(AppSettings::DeriveDisplayOrder) .setting(AppSettings::UnifiedHelpMessage) .setting(AppSettings::NextLineHelp) .setting(AppSettings::HidePossibleValuesInHelp) .max_term_width(90) .about("A command-line benchmarking tool.") .arg( Arg::with_name("command") .help("Command to benchmark") .required(true) .multiple(true) .empty_values(false), ) .arg( Arg::with_name("warmup") .long("warmup") .short("w") .takes_value(true) .value_name("NUM") .help( "Perform NUM warmup runs before the actual benchmark. This can be used \ to fill (disk) caches for I/O-heavy programs.", ), ) .arg( Arg::with_name("min-runs") .long("min-runs") .short("m") .takes_value(true) .value_name("NUM") .help("Perform at least NUM runs for each command (default: 10)."), ) .arg( Arg::with_name("max-runs") .long("max-runs") .short("M") .takes_value(true) .value_name("NUM") .help("Perform at most NUM runs for each command. By default, there is no limit."), ) .arg( Arg::with_name("runs") .long("runs") .conflicts_with_all(&["max-runs", "min-runs"]) .short("r") .takes_value(true) .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::with_name("prepare") .long("prepare") .short("p") .takes_value(true) .multiple(true) .number_of_values(1) .value_name("CMD") .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::with_name("cleanup") .long("cleanup") .short("c") .takes_value(true) .value_name("CMD") .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::with_name("parameter-scan") .long("parameter-scan") .short("P") .takes_value(true) .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::with_name("parameter-step-size") .long("parameter-step-size") .short("D") .takes_value(true) .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::with_name("parameter-list") .long("parameter-list") .short("L") .takes_value(true) .multiple(true) .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::with_name("style") .long("style") .short("s") .takes_value(true) .value_name("TYPE") .possible_values(&["auto", "basic", "full", "nocolor", "color", "none"]) .help( "Set output style type (default: auto). Set this to 'basic' to disable output \ coloring and interactive elements. Set it to 'full' to enable all effects \ even if no interactive terminal was detected. Set this to 'nocolor' to \ keep the interactive output without any colors. Set this to 'color' to keep \ the colors without any interactive output. Set this to 'none' to disable all \ the output of the tool.", ), ) .arg( Arg::with_name("shell") .long("shell") .short("S") .takes_value(true) .value_name("SHELL") .overrides_with("shell") .help("Set the shell to use for executing benchmarked commands."), ) .arg( Arg::with_name("ignore-failure") .long("ignore-failure") .short("i") .help("Ignore non-zero exit codes of the benchmarked programs."), ) .arg( Arg::with_name("time-unit") .long("time-unit") .short("u") .takes_value(true) .value_name("UNIT") .possible_values(&["millisecond", "second"]) .help("Set the time unit to be used. Possible values: millisecond, second."), ) .arg( Arg::with_name("export-asciidoc") .long("export-asciidoc") .takes_value(true) .value_name("FILE") .help("Export the timing summary statistics as an AsciiDoc table to the given FILE."), ) .arg( Arg::with_name("export-csv") .long("export-csv") .takes_value(true) .value_name("FILE") .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."), ) .arg( Arg::with_name("export-json") .long("export-json") .takes_value(true) .value_name("FILE") .help("Export the timing summary statistics and timings of individual runs as JSON to the given FILE."), ) .arg( Arg::with_name("export-markdown") .long("export-markdown") .takes_value(true) .value_name("FILE") .help("Export the timing summary statistics as a Markdown table to the given FILE."), ) .arg( Arg::with_name("show-output") .long("show-output") .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::with_name("command-name") .long("command-name") .short("n") .takes_value(true) .multiple(true) .number_of_values(1) .value_name("NAME") .help("Give a meaningful name to a command"), ) .help_message("Print this help message.") .version_message("Show version information.") } 07070100000017000081A4000000000000000000000001616C6128000039CC000000000000000000000000000000000000002200000000hyperfine-1.12.0/src/benchmark.rsuse std::cmp; use std::io; use std::process::{ExitStatus, Stdio}; use colored::*; use statistical::{mean, median, standard_deviation}; use crate::benchmark_result::BenchmarkResult; use crate::command::Command; use crate::format::{format_duration, format_duration_unit}; use crate::min_max::{max, min}; use crate::options::{CmdFailureAction, HyperfineOptions, OutputStyleOption, Shell}; use crate::outlier_detection::{modified_zscores, OUTLIER_THRESHOLD}; use crate::progress_bar::get_progress_bar; use crate::shell::execute_and_time; use crate::timer::wallclocktimer::WallClockTimer; use crate::timer::{TimerStart, TimerStop}; use crate::units::Second; use crate::warnings::Warnings; /// Threshold for warning about fast execution time pub const MIN_EXECUTION_TIME: Second = 5e-3; /// Results from timing a single shell 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, } /// Correct for shell spawning time fn subtract_shell_spawning_time(time: Second, shell_spawning_time: Second) -> Second { if time < shell_spawning_time { 0.0 } else { time - shell_spawning_time } } /// Run the given shell command and measure the execution time pub fn time_shell_command( shell: &Shell, command: &Command<'_>, show_output: bool, failure_action: CmdFailureAction, shell_spawning_time: Option<TimingResult>, ) -> io::Result<(TimingResult, ExitStatus)> { let (stdout, stderr) = if show_output { (Stdio::inherit(), Stdio::inherit()) } else { (Stdio::null(), Stdio::null()) }; let wallclock_timer = WallClockTimer::start(); let result = execute_and_time(stdout, stderr, &command.get_shell_command(), shell)?; let mut time_real = wallclock_timer.stop(); let mut time_user = result.user_time; let mut time_system = result.system_time; if failure_action == CmdFailureAction::RaiseError && !result.status.success() { return Err(io::Error::new( io::ErrorKind::Other, format!( "{}. \ Use the '-i'/'--ignore-failure' option if you want to ignore this. \ Alternatively, use the '--show-output' option to debug what went wrong.", result.status.code().map_or( "The process has been terminated by a signal".into(), |c| format!("Command terminated with non-zero exit code: {}", c) ) ), )); } // Correct for shell spawning time if let Some(spawning_time) = shell_spawning_time { time_real = subtract_shell_spawning_time(time_real, spawning_time.time_real); time_user = subtract_shell_spawning_time(time_user, spawning_time.time_user); time_system = subtract_shell_spawning_time(time_system, spawning_time.time_system); } Ok(( TimingResult { time_real, time_user, time_system, }, result.status, )) } /// Measure the average shell spawning time pub fn mean_shell_spawning_time( shell: &Shell, style: OutputStyleOption, show_output: bool, ) -> io::Result<TimingResult> { const COUNT: u64 = 50; let progress_bar = if style != OutputStyleOption::Disabled { Some(get_progress_bar( COUNT, "Measuring shell spawning time", style, )) } else { None }; let mut times_real: Vec<Second> = vec![]; let mut times_user: Vec<Second> = vec![]; let mut times_system: Vec<Second> = vec![]; for _ in 0..COUNT { // Just run the shell without any command let res = time_shell_command( shell, &Command::new(None, ""), show_output, CmdFailureAction::RaiseError, None, ); match res { Err(_) => { let shell_cmd = if cfg!(windows) { format!("{} /C \"\"", shell) } else { format!("{} -c \"\"", shell) }; return Err(io::Error::new( io::ErrorKind::Other, format!( "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() } Ok(TimingResult { time_real: mean(×_real), time_user: mean(×_user), time_system: mean(×_system), }) } fn run_intermediate_command( shell: &Shell, command: &Option<Command<'_>>, show_output: bool, error_output: &'static str, ) -> io::Result<TimingResult> { if let Some(ref cmd) = command { let res = time_shell_command(shell, cmd, show_output, CmdFailureAction::RaiseError, None); if res.is_err() { return Err(io::Error::new(io::ErrorKind::Other, error_output)); } return res.map(|r| r.0); } Ok(TimingResult { ..Default::default() }) } /// Run the command specified by `--prepare`. fn run_preparation_command( shell: &Shell, command: &Option<Command<'_>>, show_output: bool, ) -> io::Result<TimingResult> { 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."; run_intermediate_command(shell, command, show_output, error_output) } /// Run the command specified by `--cleanup`. fn run_cleanup_command( shell: &Shell, command: &Option<Command<'_>>, show_output: bool, ) -> io::Result<TimingResult> { 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."; run_intermediate_command(shell, command, show_output, error_output) } #[cfg(unix)] fn extract_exit_code(status: ExitStatus) -> Option<i32> { use std::os::unix::process::ExitStatusExt; /* From the ExitStatus::code documentation: "On Unix, this will return None if the process was terminated by a signal." In that case, ExitStatusExt::signal should never return None. */ status.code().or_else(|| /* To differentiate between "normal" exit codes and signals, we are using something similar to bash exit codes (https://tldp.org/LDP/abs/html/exitcodes.html) by adding 128 to a signal integer value. */ status.signal().map(|s| 128 + s)) } #[cfg(not(unix))] fn extract_exit_code(status: ExitStatus) -> Option<i32> { status.code() } /// Run the benchmark for a single shell command pub fn run_benchmark( num: usize, cmd: &Command<'_>, shell_spawning_time: TimingResult, options: &HyperfineOptions, ) -> io::Result<BenchmarkResult> { let command_name = cmd.get_name(); if options.output_style != OutputStyleOption::Disabled { println!( "{}{}: {}", "Benchmark ".bold(), (num + 1).to_string().bold(), command_name, ); } let mut times_real: Vec<Second> = vec![]; let mut times_user: Vec<Second> = vec![]; let mut times_system: Vec<Second> = vec![]; let mut exit_codes: Vec<Option<i32>> = vec![]; let mut all_succeeded = true; // Run init command let prepare_cmd = options.preparation_command.as_ref().map(|values| { let preparation_command = if values.len() == 1 { &values[0] } else { &values[num] }; Command::new_parametrized(None, preparation_command, cmd.get_parameters().clone()) }); // Warmup phase if options.warmup_count > 0 { let progress_bar = if options.output_style != OutputStyleOption::Disabled { Some(get_progress_bar( options.warmup_count, "Performing warmup runs", options.output_style, )) } else { None }; for _ in 0..options.warmup_count { let _ = run_preparation_command(&options.shell, &prepare_cmd, options.show_output)?; let _ = time_shell_command( &options.shell, cmd, options.show_output, options.failure_action, None, )?; 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 options.output_style != OutputStyleOption::Disabled { Some(get_progress_bar( options.runs.min, "Initial time measurement", options.output_style, )) } else { None }; let prepare_res = run_preparation_command(&options.shell, &prepare_cmd, options.show_output)?; // Initial timing run let (res, status) = time_shell_command( &options.shell, cmd, options.show_output, options.failure_action, Some(shell_spawning_time), )?; let success = status.success(); // Determine number of benchmark runs let runs_in_min_time = (options.min_time_sec / (res.time_real + prepare_res.time_real + shell_spawning_time.time_real)) as u64; let count = { let min = cmp::max(runs_in_min_time, options.runs.min); options .runs .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 for _ in 0..count_remaining { run_preparation_command(&options.shell, &prepare_cmd, options.show_output)?; let msg = { let mean = format_duration(mean(×_real), 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) = time_shell_command( &options.shell, cmd, options.show_output, options.failure_action, Some(shell_spawning_time), )?; 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) } } 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 = standard_deviation(×_real, Some(t_mean)); 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, options.time_unit); let stddev_str = format_duration(t_stddev, Some(time_unit)); let min_str = format_duration(t_min, Some(time_unit)); let max_str = format_duration(t_max, Some(time_unit)); let num_str = format!("{} runs", t_num); let user_str = format_duration(user_mean, Some(time_unit)); let system_str = format_duration(system_mean, Some(time_unit)); if options.output_style != OutputStyleOption::Disabled { 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 times_real.iter().any(|&t| t < MIN_EXECUTION_TIME) { warnings.push(Warnings::FastExecutionTime); } // Check programm exit codes if !all_succeeded { warnings.push(Warnings::NonZeroExitCode); } // Run outlier detection let scores = modified_zscores(×_real); if scores[0] > OUTLIER_THRESHOLD { warnings.push(Warnings::SlowInitialRun(times_real[0])); } else if scores.iter().any(|&s| s.abs() > OUTLIER_THRESHOLD) { warnings.push(Warnings::OutliersDetected); } if !warnings.is_empty() { eprintln!(" "); for warning in &warnings { eprintln!(" {}: {}", "Warning".yellow(), warning); } } if options.output_style != OutputStyleOption::Disabled { println!(" "); } // Run cleanup command let cleanup_cmd = options.cleanup_command.as_ref().map(|cleanup_command| { Command::new_parametrized(None, cleanup_command, cmd.get_parameters().clone()) }); run_cleanup_command(&options.shell, &cleanup_cmd, options.show_output)?; Ok(BenchmarkResult::new( command_name, t_mean, t_stddev, t_median, user_mean, system_mean, t_min, t_max, times_real, exit_codes, cmd.get_parameters() .iter() .map(|(name, value)| ((*name).to_string(), value.to_string())) .collect(), )) } 07070100000018000081A4000000000000000000000001616C61280000075A000000000000000000000000000000000000002900000000hyperfine-1.12.0/src/benchmark_result.rsuse std::collections::BTreeMap; use serde::Serialize; use crate::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 command that was run pub command: String, /// The mean run time pub mean: Second, /// The standard deviation of all run times pub stddev: Second, /// The median run time pub median: Second, /// Time spend in user space pub user: Second, /// Time spent in system space pub system: Second, /// Min time measured pub min: Second, /// Max time measured pub max: Second, /// All run time measurements #[serde(skip_serializing_if = "Option::is_none")] pub times: Option<Vec<Second>>, /// All run exit codes pub exit_codes: Vec<Option<i32>>, /// Any parameter values used #[serde(skip_serializing_if = "BTreeMap::is_empty")] pub parameters: BTreeMap<String, String>, } impl BenchmarkResult { /// Create a new entry with the given values. #[allow(clippy::too_many_arguments)] pub fn new( command: String, mean: Second, stddev: Second, median: Second, user: Second, system: Second, min: Second, max: Second, times: Vec<Second>, exit_codes: Vec<Option<i32>>, parameters: BTreeMap<String, String>, ) -> Self { BenchmarkResult { command, mean, stddev, median, user, system, min, max, times: Some(times), exit_codes, parameters, } } } 07070100000019000081A4000000000000000000000001616C612800000D26000000000000000000000000000000000000002000000000hyperfine-1.12.0/src/command.rsuse std::collections::BTreeMap; use std::fmt; use crate::types::ParameterValue; /// A command that should be benchmarked. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Command<'a> { /// The command name (without parameter substitution) name: Option<&'a str>, /// The command that should be executed (without parameter substitution) expression: &'a str, /// Zero or more parameter values. parameters: Vec<(&'a str, ParameterValue)>, } impl<'a> Command<'a> { pub fn new(name: Option<&'a str>, expression: &'a str) -> Command<'a> { Command { name, expression, parameters: Vec::new(), } } pub fn new_parametrized( name: Option<&'a str>, expression: &'a str, parameters: Vec<(&'a str, ParameterValue)>, ) -> Command<'a> { Command { name, expression, parameters, } } pub fn get_name(&self) -> String { self.name.map_or_else( || self.get_shell_command(), |name| self.replace_parameters_in(name), ) } pub fn get_shell_command(&self) -> String { self.replace_parameters_in(self.expression) } pub fn get_parameters(&self) -> &Vec<(&'a str, ParameterValue)> { &self.parameters } fn replace_parameters_in(&self, original: &str) -> String { let mut result = String::new(); let mut replacements = BTreeMap::<String, String>::new(); for (param_name, param_value) in &self.parameters { replacements.insert( format!("{{{param_name}}}", param_name = param_name), param_value.to_string(), ); } let mut remaining = original; // Manually replace consecutive occurrences to avoid double-replacing: e.g., // // hyperfine -L foo 'a,{bar}' -L bar 'baz,quux' 'echo {foo} {bar}' // // should not ever run 'echo baz baz'. See `test_get_shell_command_nonoverlapping`. 'outer: while let Some(head) = remaining.chars().next() { for (k, v) in &replacements { if remaining.starts_with(k.as_str()) { result.push_str(v); remaining = &remaining[k.len()..]; continue 'outer; } } result.push(head); remaining = &remaining[head.len_utf8()..]; } result } } #[test] fn test_get_shell_command_nonoverlapping() { let cmd = Command::new_parametrized( None, "echo {foo} {bar}", vec![ ("foo", ParameterValue::Text("{bar} baz".into())), ("bar", ParameterValue::Text("quux".into())), ], ); assert_eq!(cmd.get_shell_command(), "echo {bar} baz quux"); } #[test] fn test_get_parameterized_command_name() { let cmd = Command::new_parametrized( Some("name-{bar}-{foo}"), "echo {foo} {bar}", vec![ ("foo", ParameterValue::Text("baz".into())), ("bar", ParameterValue::Text("quux".into())), ], ); assert_eq!(cmd.get_name(), "name-quux-baz"); } impl<'a> fmt::Display for Command<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.get_shell_command()) } } 0707010000001A000081A4000000000000000000000001616C612800000DA3000000000000000000000000000000000000001E00000000hyperfine-1.12.0/src/error.rsuse std::error::Error; use std::fmt; use std::num; use std::num::ParseIntError; use rust_decimal::Error as DecimalError; #[derive(Debug)] pub enum ParameterScanError { ParseIntError(num::ParseIntError), ParseDecimalError(DecimalError), EmptyRange, TooLarge, ZeroStep, StepRequired, UnexpectedCommandNameCount(usize, usize), } impl From<num::ParseIntError> for ParameterScanError { fn from(e: num::ParseIntError) -> ParameterScanError { ParameterScanError::ParseIntError(e) } } impl From<DecimalError> for ParameterScanError { fn from(e: DecimalError) -> ParameterScanError { ParameterScanError::ParseDecimalError(e) } } impl fmt::Display for ParameterScanError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { ParameterScanError::ParseIntError(ref e) => write!(f, "{}", e), ParameterScanError::ParseDecimalError(ref e) => write!(f, "{}", e), ParameterScanError::EmptyRange => write!(f, "Empty parameter range"), ParameterScanError::TooLarge => write!(f, "Parameter range is too large"), ParameterScanError::ZeroStep => write!(f, "Zero is not a valid parameter step"), ParameterScanError::StepRequired => write!( f, "A step size is required when the range bounds are \ floating point numbers. The step size can be specified \ with the '--parameter-step-size' parameter" ), ParameterScanError::UnexpectedCommandNameCount(real, expected) => { write!( f, "'--command-name' has been specified {} times. It has to appear exactly once, or exactly {} times (number of benchmarks)", real, expected ) } } } } impl Error for ParameterScanError {} #[derive(Debug)] pub enum OptionsError<'a> { RunsBelowTwo, EmptyRunsRange, TooManyCommandNames(usize), UnexpectedCommandNameCount(usize, usize), NumericParsingError(&'a str, ParseIntError), EmptyShell, ShellParseError(shell_words::ParseError), } impl<'a> fmt::Display for OptionsError<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { OptionsError::EmptyRunsRange => write!(f, "Empty runs range"), OptionsError::RunsBelowTwo => write!(f, "Number of runs below two"), OptionsError::TooManyCommandNames(n) => { write!(f, "Too many --command-name options: expected {} at most", n) } OptionsError::UnexpectedCommandNameCount(real, expected) => { write!( f, "'--command-name' has been specified {} times. It has to appear exactly once, or exactly {} times (number of benchmarks)", real, expected ) } OptionsError::NumericParsingError(cmd, ref err) => write!( f, "Could not read numeric argument to '--{cmd}': {err}", cmd = cmd, err = err ), OptionsError::EmptyShell => write!(f, "Empty command at --shell option"), OptionsError::ShellParseError(ref err) => { write!(f, "Could not parse --shell value as command line: {}", err) } } } } impl<'a> Error for OptionsError<'a> {} 0707010000001B000041ED000000000000000000000001616C612800000000000000000000000000000000000000000000001C00000000hyperfine-1.12.0/src/export0707010000001C000081A4000000000000000000000001616C612800001A9A000000000000000000000000000000000000002800000000hyperfine-1.12.0/src/export/asciidoc.rsuse std::io::Result; use super::Exporter; use crate::benchmark_result::BenchmarkResult; use crate::format::format_duration_value; use crate::units::Unit; #[derive(Default)] pub struct AsciidocExporter {} impl Exporter for AsciidocExporter { fn serialize(&self, results: &[BenchmarkResult], unit: Option<Unit>) -> Result<Vec<u8>> { let unit = if let Some(unit) = unit { // Use the given unit for all entries. unit } else if let Some(first_result) = results.first() { // Use the first BenchmarkResult entry to determine the unit for all entries. format_duration_value(first_result.mean, None).1 } else { // Default to `Second`. Unit::Second }; let mut res: Vec<u8> = Vec::new(); res.append(&mut table_open()); res.append(&mut table_startend()); res.append(&mut table_header(unit)); for result in results { res.push(b'\n'); res.append(&mut table_row(result, unit)); } res.append(&mut table_startend()); Ok(res) } } fn table_open() -> Vec<u8> { "[cols=\"<,>,>\"]\n".bytes().collect() } fn table_startend() -> Vec<u8> { "|===\n".bytes().collect() } fn table_header(unittype: Unit) -> Vec<u8> { let unit_short_name = unittype.short_name(); format!( "| Command | Mean [{unit}] | Min…Max [{unit}]\n", unit = unit_short_name ) .into_bytes() } fn table_row(entry: &BenchmarkResult, unit: Unit) -> Vec<u8> { let form = |val| format_duration_value(val, Some(unit)); format!( "| `{}`\n\ | {} ± {}\n\ | {}…{}\n", entry.command.replace("|", "\\|"), form(entry.mean).0, form(entry.stddev).0, form(entry.min).0, form(entry.max).0 ) .into_bytes() } /// Ensure various options for the header generate correct results #[test] fn test_asciidoc_header() { let conms: Vec<u8> = "| Command | Mean [ms] | Min…Max [ms]\n".bytes().collect(); let cons: Vec<u8> = "| Command | Mean [s] | Min…Max [s]\n".bytes().collect(); let genms = table_header(Unit::MilliSecond); let gens = table_header(Unit::Second); assert_eq!(conms, genms); assert_eq!(cons, gens); } /// Ensure each table row is generated properly #[test] fn test_asciidoc_table_row() { use std::collections::BTreeMap; let result = BenchmarkResult::new( String::from("sleep 1"), // command 0.10491992406666667, // mean 0.00397851689425097, // stddev 0.10491992406666667, // median 0.005182013333333333, // user 0.0, // system 0.1003342584, // min 0.10745223440000001, // max vec![ // times 0.1003342584, 0.10745223440000001, 0.10697327940000001, ], vec![Some(0), Some(0), Some(0)], // exit codes BTreeMap::new(), // param ); let expms = format!( "| `{}`\n\ | {} ± {}\n\ | {}…{}\n", result.command, Unit::MilliSecond.format(result.mean), Unit::MilliSecond.format(result.stddev), Unit::MilliSecond.format(result.min), Unit::MilliSecond.format(result.max) ) .into_bytes(); let exps = format!( "| `{}`\n\ | {} ± {}\n\ | {}…{}\n", result.command, Unit::Second.format(result.mean), Unit::Second.format(result.stddev), Unit::Second.format(result.min), Unit::Second.format(result.max) ) .into_bytes(); let genms = table_row(&result, Unit::MilliSecond); let gens = table_row(&result, Unit::Second); assert_eq!(expms, genms); assert_eq!(exps, gens); } /// Ensure commands get properly escaped #[test] fn test_asciidoc_table_row_command_escape() { use std::collections::BTreeMap; let result = BenchmarkResult::new( String::from("sleep 1|"), // command 0.10491992406666667, // mean 0.00397851689425097, // stddev 0.10491992406666667, // median 0.005182013333333333, // user 0.0, // system 0.1003342584, // min 0.10745223440000001, // max vec![ // times 0.1003342584, 0.10745223440000001, 0.10697327940000001, ], vec![Some(0), Some(0), Some(0)], // exit codes BTreeMap::new(), // param ); let exps = format!( "| `sleep 1\\|`\n\ | {} ± {}\n\ | {}…{}\n", Unit::Second.format(result.mean), Unit::Second.format(result.stddev), Unit::Second.format(result.min), Unit::Second.format(result.max) ) .into_bytes(); let gens = table_row(&result, Unit::Second); assert_eq!(exps, gens); } /// Integration test #[test] fn test_asciidoc() { use std::collections::BTreeMap; let exporter = AsciidocExporter::default(); // NOTE: results are fabricated, unlike above let results = vec![ BenchmarkResult::new( String::from("FOO=1 BAR=2 command | 1"), 1.0, 2.0, 1.0, 3.0, 4.0, 5.0, 6.0, vec![7.0, 8.0, 9.0], vec![Some(0), Some(0), Some(0)], { let mut params = BTreeMap::new(); params.insert("foo".into(), "1".into()); params.insert("bar".into(), "2".into()); params }, ), BenchmarkResult::new( String::from("FOO=1 BAR=7 command | 2"), 11.0, 12.0, 11.0, 13.0, 14.0, 15.0, 16.0, vec![17.0, 18.0, 19.0], vec![Some(0), Some(0), Some(0)], { let mut params = BTreeMap::new(); params.insert("foo".into(), "1".into()); params.insert("bar".into(), "7".into()); params }, ), ]; // NOTE: only testing with s, s/ms is tested elsewhere let exps: String = String::from( "[cols=\"<,>,>\"]\n\ |===\n\ | Command | Mean [s] | Min…Max [s]\n\ \n\ | `FOO=1 BAR=2 command \\| 1`\n\ | 1.000 ± 2.000\n\ | 5.000…6.000\n\ \n\ | `FOO=1 BAR=7 command \\| 2`\n\ | 11.000 ± 12.000\n\ | 15.000…16.000\n\ |===\n\ ", ); let gens = String::from_utf8(exporter.serialize(&results, Some(Unit::Second)).unwrap()).unwrap(); assert_eq!(exps, gens); } 0707010000001D000081A4000000000000000000000001616C612800000CDB000000000000000000000000000000000000002300000000hyperfine-1.12.0/src/export/csv.rsuse std::borrow::Cow; use std::io::{Error, ErrorKind, Result}; use csv::WriterBuilder; use super::Exporter; use crate::benchmark_result::BenchmarkResult; use crate::units::Unit; #[derive(Default)] pub struct CsvExporter {} impl Exporter for CsvExporter { fn serialize(&self, results: &[BenchmarkResult], _unit: Option<Unit>) -> Result<Vec<u8>> { let mut writer = WriterBuilder::new().from_writer(vec![]); { let mut headers: Vec<Cow<[u8]>> = [ // The list of times and exit codes cannot be exported to the CSV file - omit them. "command", "mean", "stddev", "median", "user", "system", "min", "max", ] .iter() .map(|x| Cow::Borrowed(x.as_bytes())) .collect(); if let Some(res) = results.first() { for param_name in res.parameters.keys() { headers.push(Cow::Owned(format!("parameter_{}", param_name).into_bytes())); } } writer.write_record(headers)?; } for res in results { let mut fields = vec![Cow::Borrowed(res.command.as_bytes())]; for f in &[ res.mean, res.stddev, res.median, res.user, res.system, res.min, res.max, ] { fields.push(Cow::Owned(f.to_string().into_bytes())) } for v in res.parameters.values() { fields.push(Cow::Borrowed(v.as_bytes())) } writer.write_record(fields)?; } writer .into_inner() .map_err(|e| Error::new(ErrorKind::Other, e)) } } #[test] fn test_csv() { use std::collections::BTreeMap; let exporter = CsvExporter::default(); // NOTE: results are fabricated let results = vec![ BenchmarkResult::new( String::from("FOO=one BAR=two command | 1"), 1.0, 2.0, 1.0, 3.0, 4.0, 5.0, 6.0, vec![7.0, 8.0, 9.0], vec![Some(0), Some(0), Some(0)], { let mut params = BTreeMap::new(); params.insert("foo".into(), "one".into()); params.insert("bar".into(), "two".into()); params }, ), BenchmarkResult::new( String::from("FOO=one BAR=seven command | 2"), 11.0, 12.0, 11.0, 13.0, 14.0, 15.0, 16.5, vec![17.0, 18.0, 19.0], vec![Some(0), Some(0), Some(0)], { let mut params = BTreeMap::new(); params.insert("foo".into(), "one".into()); params.insert("bar".into(), "seven".into()); params }, ), ]; let exps: String = String::from( "command,mean,stddev,median,user,system,min,max,parameter_bar,parameter_foo\n\ FOO=one BAR=two command | 1,1,2,1,3,4,5,6,two,one\n\ FOO=one BAR=seven command | 2,11,12,11,13,14,15,16.5,seven,one\n\ ", ); let gens = String::from_utf8(exporter.serialize(&results, Some(Unit::Second)).unwrap()).unwrap(); assert_eq!(exps, gens); } 0707010000001E000081A4000000000000000000000001616C6128000002A6000000000000000000000000000000000000002400000000hyperfine-1.12.0/src/export/json.rsuse std::io::{Error, ErrorKind, Result}; use serde::*; use serde_json::to_vec_pretty; use super::Exporter; use crate::benchmark_result::BenchmarkResult; use crate::units::Unit; #[derive(Serialize, Debug)] struct HyperfineSummary<'a> { results: &'a [BenchmarkResult], } #[derive(Default)] pub struct JsonExporter {} impl Exporter for JsonExporter { fn serialize(&self, results: &[BenchmarkResult], _unit: Option<Unit>) -> Result<Vec<u8>> { let mut output = to_vec_pretty(&HyperfineSummary { results }); for content in output.iter_mut() { content.push(b'\n'); } output.map_err(|e| Error::new(ErrorKind::Other, e)) } } 0707010000001F000081A4000000000000000000000001616C61280000298D000000000000000000000000000000000000002800000000hyperfine-1.12.0/src/export/markdown.rsuse std::io::{Error, ErrorKind, Result}; use super::Exporter; use crate::benchmark_result::BenchmarkResult; use crate::format::format_duration_value; use crate::relative_speed::{self, BenchmarkResultWithRelativeSpeed}; use crate::units::Unit; #[derive(Default)] pub struct MarkdownExporter {} impl Exporter for MarkdownExporter { fn serialize(&self, results: &[BenchmarkResult], unit: Option<Unit>) -> Result<Vec<u8>> { let unit = if let Some(unit) = unit { // Use the given unit for all entries. unit } else if let Some(first_result) = results.first() { // Use the first BenchmarkResult entry to determine the unit for all entries. format_duration_value(first_result.mean, None).1 } else { // Default to `Second`. Unit::Second }; if let Some(annotated_results) = relative_speed::compute(results) { let mut destination = start_table(unit); for result in annotated_results { add_table_row(&mut destination, &result, unit); } Ok(destination) } else { Err(Error::new( ErrorKind::Other, "Relative speed comparison is not available for Markdown export.", )) } } } fn table_header(unit_short_name: String) -> String { format!( "| Command | Mean [{unit}] | Min [{unit}] | Max [{unit}] | Relative |\n|:---|---:|---:|---:|---:|\n", unit = unit_short_name ) } fn start_table(unit: Unit) -> Vec<u8> { table_header(unit.short_name()).bytes().collect() } fn add_table_row(dest: &mut Vec<u8>, entry: &BenchmarkResultWithRelativeSpeed, unit: Unit) { let result = &entry.result; let mean_str = format_duration_value(result.mean, Some(unit)).0; let stddev_str = format_duration_value(result.stddev, Some(unit)).0; let min_str = format_duration_value(result.min, Some(unit)).0; let max_str = format_duration_value(result.max, Some(unit)).0; let rel_str = format!("{:.2}", entry.relative_speed); let rel_stddev_str = if entry.is_fastest { "".into() } else { format!(" ± {:.2}", entry.relative_speed_stddev) }; dest.extend( format!( "| `{command}` | {mean} ± {stddev} | {min} | {max} | {rel}{rel_stddev} |\n", command = result.command.replace("|", "\\|"), mean = mean_str, stddev = stddev_str, min = min_str, max = max_str, rel = rel_str, rel_stddev = rel_stddev_str, ) .as_bytes(), ); } /// Ensure the markdown output includes the table header and the multiple /// benchmark results as a table. The list of actual times is not included /// in the output. /// /// This also demonstrates that the first entry's units (ms) are used to set /// the units for all entries when the time unit is not given. #[test] fn test_markdown_format_ms() { use std::collections::BTreeMap; let exporter = MarkdownExporter::default(); let timing_results = vec![ BenchmarkResult::new( String::from("sleep 0.1"), 0.1057, // mean 0.0016, // std dev 0.1057, // median 0.0009, // user_mean 0.0011, // system_mean 0.1023, // min 0.1080, // max vec![0.1, 0.1, 0.1], // times vec![Some(0), Some(0), Some(0)], // exit codes BTreeMap::new(), // parameter ), BenchmarkResult::new( String::from("sleep 2"), 2.0050, // mean 0.0020, // std dev 2.0050, // median 0.0009, // user_mean 0.0012, // system_mean 2.0020, // min 2.0080, // max vec![2.0, 2.0, 2.0], // times vec![Some(0), Some(0), Some(0)], // exit codes BTreeMap::new(), // parameter ), ]; let formatted = String::from_utf8(exporter.serialize(&timing_results, None).unwrap()).unwrap(); let formatted_expected = format!( "{}\ | `sleep 0.1` | 105.7 ± 1.6 | 102.3 | 108.0 | 1.00 | | `sleep 2` | 2005.0 ± 2.0 | 2002.0 | 2008.0 | 18.97 ± 0.29 | ", table_header("ms".to_string()) ); assert_eq!(formatted_expected, formatted); } /// This (again) demonstrates that the first entry's units (s) are used to set /// the units for all entries when the time unit is not given. #[test] fn test_markdown_format_s() { use std::collections::BTreeMap; let exporter = MarkdownExporter::default(); let timing_results = vec![ BenchmarkResult::new( String::from("sleep 2"), 2.0050, // mean 0.0020, // std dev 2.0050, // median 0.0009, // user_mean 0.0012, // system_mean 2.0020, // min 2.0080, // max vec![2.0, 2.0, 2.0], // times vec![Some(0), Some(0), Some(0)], // exit codes BTreeMap::new(), // parameter ), BenchmarkResult::new( String::from("sleep 0.1"), 0.1057, // mean 0.0016, // std dev 0.1057, // median 0.0009, // user_mean 0.0011, // system_mean 0.1023, // min 0.1080, // max vec![0.1, 0.1, 0.1], // times vec![Some(0), Some(0), Some(0)], // exit codes BTreeMap::new(), // parameter ), ]; let formatted = String::from_utf8(exporter.serialize(&timing_results, None).unwrap()).unwrap(); let formatted_expected = format!( "{}\ | `sleep 2` | 2.005 ± 0.002 | 2.002 | 2.008 | 18.97 ± 0.29 | | `sleep 0.1` | 0.106 ± 0.002 | 0.102 | 0.108 | 1.00 | ", table_header("s".to_string()) ); assert_eq!(formatted_expected, formatted); } /// The given time unit (s) is used to set the units for all entries. #[test] fn test_markdown_format_time_unit_s() { use std::collections::BTreeMap; let exporter = MarkdownExporter::default(); let timing_results = vec![ BenchmarkResult::new( String::from("sleep 0.1"), 0.1057, // mean 0.0016, // std dev 0.1057, // median 0.0009, // user_mean 0.0011, // system_mean 0.1023, // min 0.1080, // max vec![0.1, 0.1, 0.1], // times vec![Some(0), Some(0), Some(0)], // exit codes BTreeMap::new(), // parameter ), BenchmarkResult::new( String::from("sleep 2"), 2.0050, // mean 0.0020, // std dev 2.0050, // median 0.0009, // user_mean 0.0012, // system_mean 2.0020, // min 2.0080, // max vec![2.0, 2.0, 2.0], // times vec![Some(0), Some(0), Some(0)], // exit codes BTreeMap::new(), // parameter ), ]; let formatted = String::from_utf8( exporter .serialize(&timing_results, Some(Unit::Second)) .unwrap(), ) .unwrap(); let formatted_expected = format!( "{}\ | `sleep 0.1` | 0.106 ± 0.002 | 0.102 | 0.108 | 1.00 | | `sleep 2` | 2.005 ± 0.002 | 2.002 | 2.008 | 18.97 ± 0.29 | ", table_header("s".to_string()) ); assert_eq!(formatted_expected, formatted); } /// This (again) demonstrates that the given time unit (ms) is used to set /// the units for all entries. #[test] fn test_markdown_format_time_unit_ms() { use std::collections::BTreeMap; let exporter = MarkdownExporter::default(); let timing_results = vec![ BenchmarkResult::new( String::from("sleep 2"), 2.0050, // mean 0.0020, // std dev 2.0050, // median 0.0009, // user_mean 0.0012, // system_mean 2.0020, // min 2.0080, // max vec![2.0, 2.0, 2.0], // times vec![Some(0), Some(0), Some(0)], // exit codes BTreeMap::new(), // parameter ), BenchmarkResult::new( String::from("sleep 0.1"), 0.1057, // mean 0.0016, // std dev 0.1057, // median 0.0009, // user_mean 0.0011, // system_mean 0.1023, // min 0.1080, // max vec![0.1, 0.1, 0.1], // times vec![Some(0), Some(0), Some(0)], // exit codes BTreeMap::new(), // parameter ), ]; let formatted = String::from_utf8( exporter .serialize(&timing_results, Some(Unit::MilliSecond)) .unwrap(), ) .unwrap(); let formatted_expected = format!( "{}\ | `sleep 2` | 2005.0 ± 2.0 | 2002.0 | 2008.0 | 18.97 ± 0.29 | | `sleep 0.1` | 105.7 ± 1.6 | 102.3 | 108.0 | 1.00 | ", table_header("ms".to_string()) ); assert_eq!(formatted_expected, formatted); } 07070100000020000081A4000000000000000000000001616C61280000091E000000000000000000000000000000000000002300000000hyperfine-1.12.0/src/export/mod.rsuse std::fs::{File, OpenOptions}; use std::io::{Result, Write}; mod asciidoc; mod csv; mod json; mod markdown; use self::asciidoc::AsciidocExporter; use self::csv::CsvExporter; use self::json::JsonExporter; use self::markdown::MarkdownExporter; use crate::benchmark_result::BenchmarkResult; use crate::units::Unit; /// The desired form of exporter to use for a given file. #[derive(Clone)] pub enum ExportType { /// Asciidoc Table Asciidoc, /// CSV (comma separated values) format Csv, /// JSON format Json, /// Markdown table Markdown, } /// Interface for different exporters. trait Exporter { /// Export the given entries in the serialized form. fn serialize(&self, results: &[BenchmarkResult], unit: Option<Unit>) -> Result<Vec<u8>>; } struct ExporterWithFilename { exporter: Box<dyn Exporter>, filename: String, } /// Handles the management of multiple file exporters. #[derive(Default)] pub struct ExportManager { exporters: Vec<ExporterWithFilename>, } impl ExportManager { /// Add an additional exporter to the ExportManager pub fn add_exporter(&mut self, export_type: ExportType, filename: &str) -> Result<()> { let _ = File::create(filename)?; let exporter: Box<dyn Exporter> = match export_type { ExportType::Asciidoc => Box::new(AsciidocExporter::default()), ExportType::Csv => Box::new(CsvExporter::default()), ExportType::Json => Box::new(JsonExporter::default()), ExportType::Markdown => Box::new(MarkdownExporter::default()), }; self.exporters.push(ExporterWithFilename { exporter, filename: filename.to_string(), }); Ok(()) } /// Write the given results to all Exporters contained within this manager pub fn write_results(&self, results: &[BenchmarkResult], unit: Option<Unit>) -> Result<()> { for e in &self.exporters { let file_content = e.exporter.serialize(results, unit)?; write_to_file(&e.filename, &file_content)?; } Ok(()) } } /// Write the given content to a file with the specified name fn write_to_file(filename: &str, content: &[u8]) -> Result<()> { let mut file = OpenOptions::new().write(true).open(filename)?; file.write_all(content) } 07070100000021000081A4000000000000000000000001616C612800000883000000000000000000000000000000000000001F00000000hyperfine-1.12.0/src/format.rsuse crate::units::{Second, Unit}; /// Format the given duration as a string. The output-unit can be enforced by setting `unit` to /// `Some(target_unit)`. If `unit` is `None`, it will be determined automatically. pub fn format_duration(duration: Second, unit: Option<Unit>) -> String { let (duration_fmt, _) = format_duration_unit(duration, unit); duration_fmt } /// Like `format_duration`, but returns the target unit as well. pub fn format_duration_unit(duration: Second, unit: Option<Unit>) -> (String, Unit) { let (out_str, out_unit) = format_duration_value(duration, unit); (format!("{} {}", out_str, out_unit.short_name()), out_unit) } /// Like `format_duration`, but returns the target unit as well. pub fn format_duration_value(duration: Second, unit: Option<Unit>) -> (String, Unit) { if (duration < 1.0 && unit.is_none()) || unit == Some(Unit::MilliSecond) { (Unit::MilliSecond.format(duration), Unit::MilliSecond) } else { (Unit::Second.format(duration), Unit::Second) } } #[test] fn test_format_duration_unit_basic() { let (out_str, out_unit) = format_duration_unit(1.3, None); assert_eq!("1.300 s", out_str); assert_eq!(Unit::Second, out_unit); let (out_str, out_unit) = format_duration_unit(1.0, None); assert_eq!("1.000 s", out_str); assert_eq!(Unit::Second, out_unit); let (out_str, out_unit) = format_duration_unit(0.999, None); assert_eq!("999.0 ms", out_str); assert_eq!(Unit::MilliSecond, out_unit); let (out_str, out_unit) = format_duration_unit(0.0, None); assert_eq!("0.0 ms", out_str); assert_eq!(Unit::MilliSecond, out_unit); let (out_str, out_unit) = format_duration_unit(1000.0, None); assert_eq!("1000.000 s", out_str); assert_eq!(Unit::Second, out_unit); } #[test] fn test_format_duration_unit_with_unit() { let (out_str, out_unit) = format_duration_unit(1.3, Some(Unit::Second)); assert_eq!("1.300 s", out_str); assert_eq!(Unit::Second, out_unit); let (out_str, out_unit) = format_duration_unit(1.3, Some(Unit::MilliSecond)); assert_eq!("1300.0 ms", out_str); assert_eq!(Unit::MilliSecond, out_unit); } 07070100000022000081A4000000000000000000000001616C612800003B51000000000000000000000000000000000000001D00000000hyperfine-1.12.0/src/main.rsuse std::cmp; use std::collections::BTreeMap; use std::env; use std::io; use atty::Stream; use clap::ArgMatches; use colored::*; pub mod app; pub mod benchmark; pub mod benchmark_result; pub mod command; pub mod error; pub mod export; pub mod format; pub mod min_max; pub mod options; pub mod outlier_detection; pub mod parameter_range; pub mod progress_bar; pub mod relative_speed; pub mod shell; pub mod timer; pub mod tokenize; pub mod types; pub mod units; pub mod warnings; use app::get_arg_matches; use benchmark::{mean_shell_spawning_time, run_benchmark}; use benchmark_result::BenchmarkResult; use command::Command; use error::OptionsError; use export::{ExportManager, ExportType}; use options::{CmdFailureAction, HyperfineOptions, OutputStyleOption, Shell}; use parameter_range::get_parameterized_commands; use tokenize::tokenize; use types::ParameterValue; use units::Unit; /// Print error message to stderr and terminate pub fn error(message: &str) -> ! { eprintln!("{} {}", "Error:".red(), message); std::process::exit(1); } pub fn write_benchmark_comparison(results: &[BenchmarkResult]) { if results.len() < 2 { return; } if let Some(mut annotated_results) = relative_speed::compute(results) { annotated_results.sort_by(|l, r| relative_speed::compare_mean_time(l.result, r.result)); let fastest = &annotated_results[0]; let others = &annotated_results[1..]; println!("{}", "Summary".bold()); println!(" '{}' ran", fastest.result.command.cyan()); for item in others { println!( "{} ± {} times faster than '{}'", format!("{:8.2}", item.relative_speed).bold().green(), format!("{:.2}", item.relative_speed_stddev).green(), &item.result.command.magenta() ); } } else { eprintln!( "{}: The benchmark comparison could not be computed as some benchmark times are zero. \ This could be caused by background interference during the initial calibration phase \ of hyperfine, in combination with very fast commands (faster than a few milliseconds). \ Try to re-run the benchmark on a quiet system. If it does not help, you command is \ most likely too fast to be accurately benchmarked by hyperfine.", "Note".bold().red() ); } } /// Runs the benchmark for the given commands fn run( commands: &[Command<'_>], options: &HyperfineOptions, export_manager: &ExportManager, ) -> io::Result<()> { let shell_spawning_time = mean_shell_spawning_time(&options.shell, options.output_style, options.show_output)?; let mut timing_results = vec![]; if let Some(preparation_command) = &options.preparation_command { if preparation_command.len() > 1 && commands.len() != preparation_command.len() { error( "The '--prepare' option has to be provided just once or N times, where N is the \ number of benchmark commands.", ); } } // Run the benchmarks for (num, cmd) in commands.iter().enumerate() { timing_results.push(run_benchmark(num, cmd, shell_spawning_time, options)?); // Export (intermediate) results let ans = export_manager.write_results(&timing_results, options.time_unit); if let Err(e) = ans { error(&format!( "The following error occurred while exporting: {}", e )); } } // Print relative speed comparison if options.output_style != OutputStyleOption::Disabled { write_benchmark_comparison(&timing_results); } Ok(()) } fn main() { let matches = get_arg_matches(env::args_os()); let options = build_hyperfine_options(&matches); let commands = build_commands(&matches); let export_manager = match build_export_manager(&matches) { Ok(export_manager) => export_manager, Err(ref e) => error(&e.to_string()), }; let res = match options { Ok(ref opts) => run(&commands, opts, &export_manager), Err(ref e) => error(&e.to_string()), }; match res { Ok(_) => {} Err(e) => error(&e.to_string()), } } /// Build the HyperfineOptions that correspond to the given ArgMatches fn build_hyperfine_options<'a>( matches: &ArgMatches<'a>, ) -> Result<HyperfineOptions, OptionsError<'a>> { // Enabled ANSI colors on Windows 10 #[cfg(windows)] colored::control::set_virtual_terminal(true).unwrap(); let mut options = HyperfineOptions::default(); let param_to_u64 = |param| { matches .value_of(param) .map(|n| { n.parse::<u64>() .map_err(|e| OptionsError::NumericParsingError(param, e)) }) .transpose() }; options.warmup_count = param_to_u64("warmup")?.unwrap_or(options.warmup_count); let mut min_runs = param_to_u64("min-runs")?; let mut max_runs = param_to_u64("max-runs")?; if let Some(runs) = param_to_u64("runs")? { min_runs = Some(runs); max_runs = Some(runs); } match (min_runs, max_runs) { (Some(min), _) if min < 2 => { // We need at least two runs to compute a variance. return Err(OptionsError::RunsBelowTwo); } (Some(min), None) => { options.runs.min = min; } (_, Some(max)) if max < 2 => { // We need at least two runs to compute a variance. return Err(OptionsError::RunsBelowTwo); } (None, Some(max)) => { // Since the minimum was not explicit we lower it if max is below the default min. options.runs.min = cmp::min(options.runs.min, max); options.runs.max = Some(max); } (Some(min), Some(max)) if min > max => { return Err(OptionsError::EmptyRunsRange); } (Some(min), Some(max)) => { options.runs.min = min; options.runs.max = Some(max); } (None, None) => {} }; options.preparation_command = matches .values_of("prepare") .map(|values| values.map(String::from).collect::<Vec<String>>()); options.cleanup_command = matches.value_of("cleanup").map(String::from); options.show_output = matches.is_present("show-output"); options.output_style = match matches.value_of("style") { Some("full") => OutputStyleOption::Full, Some("basic") => OutputStyleOption::Basic, Some("nocolor") => OutputStyleOption::NoColor, Some("color") => OutputStyleOption::Color, Some("none") => OutputStyleOption::Disabled, _ => { if !options.show_output && atty::is(Stream::Stdout) { OutputStyleOption::Full } else { OutputStyleOption::Basic } } }; match options.output_style { OutputStyleOption::Basic | OutputStyleOption::NoColor => { colored::control::set_override(false) } OutputStyleOption::Full | OutputStyleOption::Color => colored::control::set_override(true), OutputStyleOption::Disabled => {} }; if let Some(shell) = matches.value_of("shell") { options.shell = Shell::parse(shell)?; } if matches.is_present("ignore-failure") { options.failure_action = CmdFailureAction::Ignore; } options.time_unit = match matches.value_of("time-unit") { Some("millisecond") => Some(Unit::MilliSecond), Some("second") => Some(Unit::Second), _ => None, }; Ok(options) } /// Build the ExportManager that will export the results specified /// in the given ArgMatches fn build_export_manager(matches: &ArgMatches<'_>) -> io::Result<ExportManager> { let mut export_manager = ExportManager::default(); { let mut add_exporter = |flag, exporttype| -> io::Result<()> { if let Some(filename) = matches.value_of(flag) { export_manager.add_exporter(exporttype, filename)?; } Ok(()) }; add_exporter("export-asciidoc", ExportType::Asciidoc)?; add_exporter("export-json", ExportType::Json)?; add_exporter("export-csv", ExportType::Csv)?; add_exporter("export-markdown", ExportType::Markdown)?; } Ok(export_manager) } /// Build the commands to benchmark fn build_commands<'a>(matches: &'a ArgMatches<'_>) -> Vec<Command<'a>> { let command_names = matches.values_of("command-name"); let command_strings = matches.values_of("command").unwrap(); if let Some(args) = matches.values_of("parameter-scan") { let step_size = matches.value_of("parameter-step-size"); match get_parameterized_commands(command_names, command_strings, args, step_size) { Ok(commands) => commands, Err(e) => error(&e.to_string()), } } else if let Some(args) = matches.values_of("parameter-list") { let command_names = command_names.map_or(vec![], |names| names.collect::<Vec<&str>>()); let args: Vec<_> = args.collect(); let param_names_and_values: Vec<(&str, Vec<String>)> = args .chunks_exact(2) .map(|pair| { let name = pair[0]; let list_str = pair[1]; (name, tokenize(list_str)) }) .collect(); { let dupes = find_dupes(param_names_and_values.iter().map(|(name, _)| *name)); if !dupes.is_empty() { error(&format!("duplicate parameter names: {}", &dupes.join(", "))) } } let command_list = command_strings.collect::<Vec<&str>>(); let dimensions: Vec<usize> = std::iter::once(command_list.len()) .chain( param_names_and_values .iter() .map(|(_, values)| values.len()), ) .collect(); let param_space_size = dimensions.iter().product(); if param_space_size == 0 { return Vec::new(); } // `--command-name` should appear exactly once or exactly B times, // where B is the total number of benchmarks. let command_name_count = command_names.len(); if command_name_count > 1 && command_name_count != param_space_size { let err = OptionsError::UnexpectedCommandNameCount(command_name_count, param_space_size); error(&err.to_string()); } let mut i = 0; let mut commands = Vec::with_capacity(param_space_size); let mut index = vec![0usize; dimensions.len()]; 'outer: loop { let name = command_names .get(i) .or_else(|| command_names.get(0)) .copied(); i += 1; let (command_index, params_indices) = index.split_first().unwrap(); let parameters = param_names_and_values .iter() .zip(params_indices) .map(|((name, values), i)| (*name, ParameterValue::Text(values[*i].clone()))) .collect(); commands.push(Command::new_parametrized( name, command_list[*command_index], parameters, )); // Increment index, exiting loop on overflow. for (i, n) in index.iter_mut().zip(dimensions.iter()) { *i += 1; if *i < *n { continue 'outer; } else { *i = 0; } } break 'outer; } commands } else { let command_names = command_names.map_or(vec![], |names| names.collect::<Vec<&str>>()); if command_names.len() > command_strings.len() { let err = OptionsError::TooManyCommandNames(command_strings.len()); error(&err.to_string()); } let command_list = command_strings.collect::<Vec<&str>>(); let mut commands = Vec::with_capacity(command_list.len()); for (i, s) in command_list.iter().enumerate() { commands.push(Command::new(command_names.get(i).copied(), s)); } commands } } /// Finds all the strings that appear multiple times in the input iterator, returning them in /// sorted order. If no string appears more than once, the result is an empty vector. fn find_dupes<'a, I: IntoIterator<Item = &'a str>>(i: I) -> Vec<&'a str> { let mut counts = BTreeMap::<&'a str, usize>::new(); for s in i { *counts.entry(s).or_default() += 1; } counts .into_iter() .filter_map(|(k, n)| if n > 1 { Some(k) } else { None }) .collect() } #[test] fn test_build_commands_cross_product() { let matches = get_arg_matches(vec![ "hyperfine", "-L", "par1", "a,b", "-L", "par2", "z,y", "echo {par1} {par2}", "printf '%s\n' {par1} {par2}", ]); let result = build_commands(&matches); // Iteration order: command list first, then parameters in listed order (here, "par1" before // "par2", which is distinct from their sorted order), with parameter values in listed order. let pv = |s: &str| ParameterValue::Text(s.to_string()); let cmd = |cmd: usize, par1: &str, par2: &str| { let expression = ["echo {par1} {par2}", "printf '%s\n' {par1} {par2}"][cmd]; let params = vec![("par1", pv(par1)), ("par2", pv(par2))]; Command::new_parametrized(None, expression, params) }; let expected = vec![ cmd(0, "a", "z"), cmd(1, "a", "z"), cmd(0, "b", "z"), cmd(1, "b", "z"), cmd(0, "a", "y"), cmd(1, "a", "y"), cmd(0, "b", "y"), cmd(1, "b", "y"), ]; assert_eq!(result, expected); } #[test] fn test_build_parameter_list_commands() { let matches = get_arg_matches(vec![ "hyperfine", "echo {foo}", "--parameter-list", "foo", "1,2", "--command-name", "name-{foo}", ]); let commands = build_commands(&matches); assert_eq!(commands.len(), 2); assert_eq!(commands[0].get_name(), "name-1"); assert_eq!(commands[1].get_name(), "name-2"); assert_eq!(commands[0].get_shell_command(), "echo 1"); assert_eq!(commands[1].get_shell_command(), "echo 2"); } #[test] fn test_build_parameter_range_commands() { let matches = get_arg_matches(vec![ "hyperfine", "echo {val}", "--parameter-scan", "val", "1", "2", "--parameter-step-size", "1", "--command-name", "name-{val}", ]); let commands = build_commands(&matches); assert_eq!(commands.len(), 2); assert_eq!(commands[0].get_name(), "name-1"); assert_eq!(commands[1].get_name(), "name-2"); assert_eq!(commands[0].get_shell_command(), "echo 1"); assert_eq!(commands[1].get_shell_command(), "echo 2"); } 07070100000023000081A4000000000000000000000001616C6128000002D0000000000000000000000000000000000000002000000000hyperfine-1.12.0/src/min_max.rsuse std::iter::Iterator; /// A max function for f64's without NaNs pub fn max(vals: &[f64]) -> f64 { *vals .iter() .max_by(|a, b| a.partial_cmp(b).unwrap()) .unwrap() } /// A min function for f64's without NaNs pub fn min(vals: &[f64]) -> f64 { *vals .iter() .min_by(|a, b| a.partial_cmp(b).unwrap()) .unwrap() } #[test] fn test_max() { let assert_float_eq = |a: f64, b: f64| { assert!((a - b).abs() < f64::EPSILON); }; assert_float_eq(1.0, max(&[1.0])); assert_float_eq(-1.0, max(&[-1.0])); assert_float_eq(-1.0, max(&[-2.0, -1.0])); assert_float_eq(1.0, max(&[-1.0, 1.0])); assert_float_eq(1.0, max(&[-1.0, 1.0, 0.0])); } 07070100000024000081A4000000000000000000000001616C6128000014D6000000000000000000000000000000000000002000000000hyperfine-1.12.0/src/options.rsuse std::fmt; use std::process::Command; use crate::error::OptionsError; use crate::units::{Second, Unit}; #[cfg(not(windows))] pub const DEFAULT_SHELL: &str = "sh"; #[cfg(windows)] pub const DEFAULT_SHELL: &str = "cmd.exe"; /// Shell to use for executing benchmarked commands #[derive(Debug)] pub enum Shell { /// Default shell command Default(&'static str), /// Custom shell command specified via --shell Custom(Vec<String>), } impl Default for Shell { fn default() -> Self { Shell::Default(DEFAULT_SHELL) } } impl fmt::Display for Shell { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Shell::Default(cmd) => write!(f, "{}", cmd), Shell::Custom(cmdline) => write!(f, "{}", shell_words::join(cmdline)), } } } impl Shell { /// Parse given string as shell command line pub fn parse<'a>(s: &str) -> Result<Self, OptionsError<'a>> { let v = shell_words::split(s).map_err(OptionsError::ShellParseError)?; if v.is_empty() || v[0].is_empty() { return Err(OptionsError::EmptyShell); } Ok(Shell::Custom(v)) } pub fn command(&self) -> Command { match self { Shell::Default(cmd) => Command::new(cmd), Shell::Custom(cmdline) => { let mut c = Command::new(&cmdline[0]); c.args(&cmdline[1..]); c } } } } /// Action to take when an executed command fails. #[derive(Debug, Clone, Copy, PartialEq)] pub enum CmdFailureAction { /// Exit with an error message RaiseError, /// Simply ignore the non-zero exit code Ignore, } /// Output style type option #[derive(Debug, Clone, Copy, PartialEq)] pub enum OutputStyleOption { /// Do not output with colors or any special formatting Basic, /// Output with full color and formatting Full, /// Keep elements such as progress bar, but use no coloring NoColor, /// Keep coloring, but use no progress bar Color, /// Disable all the output Disabled, } /// Number of runs for a benchmark pub struct Runs { /// Minimum number of benchmark runs pub min: u64, /// Maximum number of benchmark runs pub max: Option<u64>, } impl Default for Runs { fn default() -> Runs { Runs { min: 10, max: None } } } /// A set of options for hyperfine pub struct HyperfineOptions { /// Number of warmup runs pub warmup_count: u64, /// Number of benchmark runs pub runs: Runs, /// Minimum benchmarking time pub min_time_sec: Second, /// Whether or not to ignore non-zero exit codes pub failure_action: CmdFailureAction, /// Command to run before each timing run pub preparation_command: Option<Vec<String>>, /// Command to run after each benchmark pub cleanup_command: Option<String>, /// What color mode to use for output pub output_style: OutputStyleOption, /// The shell to use for executing commands. pub shell: Shell, /// Forward benchmark's stdout to hyperfine's stdout pub show_output: bool, /// Which time unit to use for CLI & Markdown output pub time_unit: Option<Unit>, /// A list of custom command names that, if defined, /// will be used instead of the command itself in /// benchmark outputs. pub names: Option<Vec<String>>, } impl Default for HyperfineOptions { fn default() -> HyperfineOptions { HyperfineOptions { names: None, warmup_count: 0, runs: Runs::default(), min_time_sec: 3.0, failure_action: CmdFailureAction::RaiseError, preparation_command: None, cleanup_command: None, output_style: OutputStyleOption::Full, shell: Shell::default(), show_output: false, time_unit: None, } } } #[test] fn test_shell_default_command() { let shell = Shell::default(); let s = format!("{}", shell); assert_eq!(&s, DEFAULT_SHELL); let cmd = shell.command(); // Command::get_program is not yet available in stable channel. // https://doc.rust-lang.org/std/process/struct.Command.html#method.get_program let s = format!("{:?}", cmd); assert_eq!(s, format!("\"{}\"", DEFAULT_SHELL)); } #[test] fn test_shell_parse_command() { let shell = Shell::parse("shell -x 'aaa bbb'").unwrap(); let s = format!("{}", shell); assert_eq!(&s, "shell -x 'aaa bbb'"); let cmd = shell.command(); // Command::get_program and Command::args are not yet available in stable channel. // https://doc.rust-lang.org/std/process/struct.Command.html#method.get_program let s = format!("{:?}", cmd); assert_eq!(&s, r#""shell" "-x" "aaa bbb""#); // Error cases match Shell::parse("shell 'foo").unwrap_err() { OptionsError::ShellParseError(_) => { /* ok */ } e => assert!(false, "Unexpected error: {}", e), } match Shell::parse("").unwrap_err() { OptionsError::EmptyShell => { /* ok */ } e => assert!(false, "Unexpected error: {}", e), } match Shell::parse("''").unwrap_err() { OptionsError::EmptyShell => { /* ok */ } e => assert!(false, "Unexpected error: {}", e), } } 07070100000025000081A4000000000000000000000001616C612800000D71000000000000000000000000000000000000002A00000000hyperfine-1.12.0/src/outlier_detection.rs//! A module for statistical outlier detection. //! //! References: //! - Boris Iglewicz and David Hoaglin (1993), "Volume 16: How to Detect and Handle Outliers", //! The ASQC Basic References in Quality Control: Statistical Techniques, Edward F. Mykytka, //! Ph.D., Editor. use statistical::median; /// Minimum modified Z-score for a datapoint to be an outlier. Here, 1.4826 is a factor that /// converts the MAD to an estimator for the standard deviation. The second factor is the number /// of standard deviations. pub const OUTLIER_THRESHOLD: f64 = 1.4826 * 10.0; /// Compute modifized Z-scores for a given sample. A (unmodified) Z-score is defined by /// `(x_i - x_mean)/x_stddev` whereas the modified Z-score is defined by `(x_i - x_median)/MAD` /// where MAD is the median absolute deviation. /// /// References: /// - <https://en.wikipedia.org/wiki/Median_absolute_deviation> pub fn modified_zscores(xs: &[f64]) -> Vec<f64> { assert!(!xs.is_empty()); // Compute sample median: let x_median = median(xs); // Compute the absolute deviations from the median: let deviations: Vec<f64> = xs.iter().map(|x| (x - x_median).abs()).collect(); // Compute median absolute deviation: let mad = median(&deviations); // Handle MAD == 0 case let mad = if mad > 0.0 { mad } else { f64::EPSILON }; // Compute modified Z-scores (x_i - x_median) / MAD xs.iter().map(|&x| (x - x_median) / mad).collect() } /// Return the number of outliers in a given sample. Outliers are defined as data points with a /// modified Z-score that is larger than `OUTLIER_THRESHOLD`. #[cfg(test)] pub fn num_outliers(xs: &[f64]) -> usize { if xs.is_empty() { return 0; } let scores = modified_zscores(xs); scores .iter() .filter(|&&s| s.abs() > OUTLIER_THRESHOLD) .count() } #[test] fn test_detect_outliers() { // Should not detect outliers in small samples assert_eq!(0, num_outliers(&[])); assert_eq!(0, num_outliers(&[50.0])); assert_eq!(0, num_outliers(&[1000.0, 0.0])); // Should not detect outliers in low-variance samples let xs = [-0.2, 0.0, 0.2]; assert_eq!(0, num_outliers(&xs)); // Should detect a single outlier let xs = [-0.2, 0.0, 0.2, 4.0]; assert_eq!(1, num_outliers(&xs)); // Should detect a single outlier let xs = [0.5, 0.30, 0.29, 0.31, 0.30]; assert_eq!(1, num_outliers(&xs)); // Should detect no outliers in sample drawn from normal distribution let xs = [ 2.33269488, 1.42195907, -0.57527698, -0.31293437, 2.2948158, 0.75813273, -1.0712388, -0.96394741, -1.15897446, 1.10976285, ]; assert_eq!(0, num_outliers(&xs)); // Should detect two outliers that were manually added let xs = [ 2.33269488, 1.42195907, -0.57527698, -0.31293437, 2.2948158, 0.75813273, -1.0712388, -0.96394741, -1.15897446, 1.10976285, 20.0, -500.0, ]; assert_eq!(2, num_outliers(&xs)); } #[test] fn test_detect_outliers_if_mad_becomes_0() { // See https://stats.stackexchange.com/q/339932 let xs = [10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 100.0]; assert_eq!(1, num_outliers(&xs)); let xs = [10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 100.0, 100.0]; assert_eq!(2, num_outliers(&xs)); } 07070100000026000081A4000000000000000000000001616C612800002092000000000000000000000000000000000000002800000000hyperfine-1.12.0/src/parameter_range.rsuse std::convert::TryInto; use std::ops::{Add, AddAssign, Div, Sub}; use std::str::FromStr; use clap::Values; use rust_decimal::Decimal; use crate::command::Command; use crate::error::ParameterScanError; use crate::types::{NumericType, ParameterValue}; trait Numeric: Add<Output = Self> + Sub<Output = Self> + Div<Output = Self> + AddAssign + PartialOrd + Copy + Clone + From<i32> + Into<NumericType> { } impl< T: Add<Output = Self> + Sub<Output = Self> + Div<Output = Self> + AddAssign + PartialOrd + Copy + Clone + From<i32> + Into<NumericType>, > Numeric for T { } #[derive(Debug)] struct RangeStep<T> { state: T, end: T, step: T, } impl<T: Numeric> RangeStep<T> { fn new(start: T, end: T, step: T) -> Result<Self, ParameterScanError> { if end < start { return Err(ParameterScanError::EmptyRange); } if step == T::from(0) { return Err(ParameterScanError::ZeroStep); } const MAX_PARAMETERS: usize = 100_000; match range_step_size_hint(start, end, step) { (_, Some(size)) if size <= MAX_PARAMETERS => Ok(Self { state: start, end, step, }), _ => Err(ParameterScanError::TooLarge), } } } impl<T: Numeric> Iterator for RangeStep<T> { type Item = T; fn next(&mut self) -> Option<Self::Item> { if self.state > self.end { return None; } let return_val = self.state; self.state += self.step; Some(return_val) } fn size_hint(&self) -> (usize, Option<usize>) { range_step_size_hint(self.state, self.end, self.step) } } fn range_step_size_hint<T: Numeric>(start: T, end: T, step: T) -> (usize, Option<usize>) { if step == T::from(0) { return (usize::MAX, None); } let steps = (end - start + T::from(1)) / step; steps .into() .try_into() .map_or((usize::MAX, None), |u| (u, Some(u))) } fn build_parameterized_commands<'a, T: Numeric>( param_min: T, param_max: T, step: T, command_names: Vec<&'a str>, command_strings: Vec<&'a str>, param_name: &'a str, ) -> Result<Vec<Command<'a>>, ParameterScanError> { let param_range = RangeStep::new(param_min, param_max, step)?; let param_count = param_range.size_hint().1.unwrap(); let command_name_count = command_names.len(); // `--command-name` should appear exactly once or exactly B times, // where B is the total number of benchmarks. if command_name_count > 1 && command_name_count != param_count { return Err(ParameterScanError::UnexpectedCommandNameCount( command_name_count, param_count, )); } let mut i = 0; let mut commands = vec![]; for value in param_range { for cmd in &command_strings { let name = command_names .get(i) .or_else(|| command_names.get(0)) .copied(); commands.push(Command::new_parametrized( name, cmd, vec![(param_name, ParameterValue::Numeric(value.into()))], )); i += 1; } } Ok(commands) } pub fn get_parameterized_commands<'a>( command_names: Option<Values<'a>>, command_strings: Values<'a>, mut vals: clap::Values<'a>, step: Option<&str>, ) -> Result<Vec<Command<'a>>, ParameterScanError> { let command_names = command_names.map_or(vec![], |names| names.collect::<Vec<&str>>()); let command_strings = command_strings.collect::<Vec<&str>>(); let param_name = vals.next().unwrap(); let param_min = vals.next().unwrap(); let param_max = vals.next().unwrap(); // attempt to parse as integers if let (Ok(param_min), Ok(param_max), Ok(step)) = ( param_min.parse::<i32>(), param_max.parse::<i32>(), step.unwrap_or("1").parse::<i32>(), ) { return build_parameterized_commands( param_min, param_max, step, command_names, command_strings, param_name, ); } // try parsing them as decimals let param_min = Decimal::from_str(param_min)?; let param_max = Decimal::from_str(param_max)?; if step.is_none() { return Err(ParameterScanError::StepRequired); } let step = Decimal::from_str(step.unwrap())?; build_parameterized_commands( param_min, param_max, step, command_names, command_strings, param_name, ) } #[test] fn test_integer_range() { let param_range: Vec<i32> = RangeStep::new(0, 10, 3).unwrap().collect(); assert_eq!(param_range.len(), 4); assert_eq!(param_range[0], 0); assert_eq!(param_range[3], 9); } #[test] fn test_decimal_range() { let param_min = Decimal::from(0); let param_max = Decimal::from(1); let step = Decimal::from_str("0.1").unwrap(); let param_range: Vec<Decimal> = RangeStep::new(param_min, param_max, step) .unwrap() .collect(); assert_eq!(param_range.len(), 11); assert_eq!(param_range[0], Decimal::from(0)); assert_eq!(param_range[10], Decimal::from(1)); } #[test] fn test_range_step_validate() { let result = RangeStep::new(0, 10, 3); assert!(result.is_ok()); let result = RangeStep::new( Decimal::from(0), Decimal::from(1), Decimal::from_str("0.1").unwrap(), ); assert!(result.is_ok()); let result = RangeStep::new(11, 10, 1); assert_eq!(format!("{}", result.unwrap_err()), "Empty parameter range"); let result = RangeStep::new(0, 10, 0); assert_eq!( format!("{}", result.unwrap_err()), "Zero is not a valid parameter step" ); let result = RangeStep::new(0, 100_001, 1); assert_eq!( format!("{}", result.unwrap_err()), "Parameter range is too large" ); } #[test] fn test_get_parameterized_commands_int() { let commands = build_parameterized_commands(1i32, 7i32, 3i32, vec![], vec!["echo {val}"], "val").unwrap(); assert_eq!(commands.len(), 3); assert_eq!(commands[2].get_name(), "echo 7"); assert_eq!(commands[2].get_shell_command(), "echo 7"); } #[test] fn test_get_parameterized_commands_decimal() { let param_min = Decimal::from_str("0").unwrap(); let param_max = Decimal::from_str("1").unwrap(); let step = Decimal::from_str("0.33").unwrap(); let commands = build_parameterized_commands( param_min, param_max, step, vec![], vec!["echo {val}"], "val", ) .unwrap(); assert_eq!(commands.len(), 4); assert_eq!(commands[3].get_name(), "echo 0.99"); assert_eq!(commands[3].get_shell_command(), "echo 0.99"); } #[test] fn test_get_parameterized_command_names() { let commands = build_parameterized_commands( 1i32, 3i32, 1i32, vec!["name-{val}"], vec!["echo {val}"], "val", ) .unwrap(); assert_eq!(commands.len(), 3); let command_names = commands .iter() .map(|c| c.get_name()) .collect::<Vec<String>>(); assert_eq!(command_names, vec!["name-1", "name-2", "name-3"]); } #[test] fn test_get_specified_command_names() { let commands = build_parameterized_commands( 1i32, 3i32, 1i32, vec!["name-a", "name-b", "name-c"], vec!["echo {val}"], "val", ) .unwrap(); assert_eq!(commands.len(), 3); let command_names = commands .iter() .map(|c| c.get_name()) .collect::<Vec<String>>(); assert_eq!(command_names, vec!["name-a", "name-b", "name-c"]); } #[test] fn test_different_command_name_count_with_parameters() { let result = build_parameterized_commands( 1i32, 3i32, 1i32, vec!["name-1", "name-2"], vec!["echo {val}"], "val", ); assert_eq!( format!("{}", result.unwrap_err()), "'--command-name' has been specified 2 times. It has to appear exactly once, or exactly 3 times (number of benchmarks)" ); } 07070100000027000081A4000000000000000000000001616C612800000406000000000000000000000000000000000000002500000000hyperfine-1.12.0/src/progress_bar.rsuse indicatif::{ProgressBar, ProgressStyle}; use crate::options::OutputStyleOption; #[cfg(not(windows))] const TICK_SETTINGS: (&str, u64) = ("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ ", 80); #[cfg(windows)] const TICK_SETTINGS: (&str, u64) = (r"+-x| ", 200); /// Return a pre-configured progress bar pub fn get_progress_bar(length: u64, msg: &str, option: OutputStyleOption) -> ProgressBar { let progressbar_style = match option { OutputStyleOption::Basic | OutputStyleOption::Color => ProgressStyle::default_bar(), _ => ProgressStyle::default_spinner() .tick_chars(TICK_SETTINGS.0) .template(" {spinner} {msg:<30} {wide_bar} ETA {eta_precise}"), }; let progress_bar = match option { OutputStyleOption::Basic | OutputStyleOption::Color => ProgressBar::hidden(), _ => ProgressBar::new(length), }; progress_bar.set_style(progressbar_style); progress_bar.enable_steady_tick(TICK_SETTINGS.1); progress_bar.set_message(msg.to_owned()); progress_bar } 07070100000028000081A4000000000000000000000001616C612800000A72000000000000000000000000000000000000002700000000hyperfine-1.12.0/src/relative_speed.rsuse std::cmp::Ordering; use crate::{benchmark_result::BenchmarkResult, units::Scalar}; #[derive(Debug)] pub struct BenchmarkResultWithRelativeSpeed<'a> { pub result: &'a BenchmarkResult, pub relative_speed: Scalar, pub relative_speed_stddev: Scalar, pub is_fastest: bool, } pub fn compare_mean_time(l: &BenchmarkResult, r: &BenchmarkResult) -> Ordering { l.mean.partial_cmp(&r.mean).unwrap_or(Ordering::Equal) } pub fn compute(results: &[BenchmarkResult]) -> Option<Vec<BenchmarkResultWithRelativeSpeed>> { let fastest: &BenchmarkResult = results .iter() .min_by(|&l, &r| compare_mean_time(l, r)) .expect("at least one benchmark result"); if fastest.mean == 0.0 { return None; } Some( results .iter() .map(|result| { let ratio = result.mean / fastest.mean; // https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulas // Covariance asssumed to be 0, i.e. variables are assumed to be independent let ratio_stddev = ratio * ((result.stddev / result.mean).powi(2) + (fastest.stddev / fastest.mean).powi(2)) .sqrt(); BenchmarkResultWithRelativeSpeed { result, relative_speed: ratio, relative_speed_stddev: ratio_stddev, is_fastest: result == fastest, } }) .collect(), ) } #[cfg(test)] fn create_result(name: &str, mean: Scalar) -> BenchmarkResult { use std::collections::BTreeMap; BenchmarkResult { command: name.into(), mean, stddev: 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(&results).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_for_zero_times() { let results = vec![create_result("cmd1", 1.0), create_result("cmd2", 0.0)]; let annotated_results = compute(&results); assert!(annotated_results.is_none()); } 07070100000029000081A4000000000000000000000001616C6128000008F7000000000000000000000000000000000000001E00000000hyperfine-1.12.0/src/shell.rsuse std::io; use std::process::{ExitStatus, Stdio}; use crate::options::Shell; use crate::timer::get_cpu_timer; /// Used to indicate the result of running a command #[derive(Debug, Copy, Clone)] pub struct ExecuteResult { /// The amount of user time the process used pub user_time: f64, /// The amount of cpu time the process used pub system_time: f64, /// The exit status of the process pub status: ExitStatus, } /// Execute the given command and return a timing summary #[cfg(windows)] pub fn execute_and_time( stdout: Stdio, stderr: Stdio, command: &str, shell: &Shell, ) -> io::Result<ExecuteResult> { let mut child = run_shell_command(stdout, stderr, command, shell)?; let cpu_timer = get_cpu_timer(&child); let status = child.wait()?; let (user_time, system_time) = cpu_timer.stop(); Ok(ExecuteResult { user_time, system_time, status, }) } /// Execute the given command and return a timing summary #[cfg(not(windows))] pub fn execute_and_time( stdout: Stdio, stderr: Stdio, command: &str, shell: &Shell, ) -> io::Result<ExecuteResult> { let cpu_timer = get_cpu_timer(); let status = run_shell_command(stdout, stderr, command, shell)?; let (user_time, system_time) = cpu_timer.stop(); Ok(ExecuteResult { user_time, system_time, status, }) } /// Run a standard shell command using `sh -c` #[cfg(not(windows))] fn run_shell_command( stdout: Stdio, stderr: Stdio, command: &str, shell: &Shell, ) -> io::Result<std::process::ExitStatus> { shell .command() .arg("-c") .arg(command) .env( "HYPERFINE_RANDOMIZED_ENVIRONMENT_OFFSET", "X".repeat(rand::random::<usize>() % 4096usize), ) .stdin(Stdio::null()) .stdout(stdout) .stderr(stderr) .status() } /// Run a Windows shell command using `cmd.exe /C` #[cfg(windows)] fn run_shell_command( stdout: Stdio, stderr: Stdio, command: &str, shell: &Shell, ) -> io::Result<std::process::Child> { shell .command() .arg("/C") .arg(command) .stdin(Stdio::null()) .stdout(stdout) .stderr(stderr) .spawn() } 0707010000002A000041ED000000000000000000000001616C612800000000000000000000000000000000000000000000001B00000000hyperfine-1.12.0/src/timer0707010000002B000081A4000000000000000000000001616C6128000003DA000000000000000000000000000000000000002200000000hyperfine-1.12.0/src/timer/mod.rsuse std::process::Child; use crate::units::Second; pub mod wallclocktimer; #[cfg(windows)] mod windows_timer; #[cfg(windows)] pub use self::windows_timer::get_cpu_timer; #[cfg(not(windows))] mod unix_timer; #[cfg(not(windows))] pub use self::unix_timer::get_cpu_timer; /// Defines start functionality of a timer. pub trait TimerStart { fn start() -> Self; fn start_for_process(process: &Child) -> Self; } /// Defines stop functionality of a timer. pub trait TimerStop { type Result; fn stop(&self) -> Self::Result; } #[derive(Debug, Copy, Clone)] pub struct CPUTimes { /// Total amount of time spent executing in user mode pub user_usec: i64, /// Total amount of time spent executing in kernel mode pub system_usec: i64, } #[derive(Debug, Copy, Clone)] pub struct CPUInterval { /// Total amount of time spent executing in user mode pub user: Second, /// Total amount of time spent executing in kernel mode pub system: Second, } 0707010000002C000081A4000000000000000000000001616C61280000099F000000000000000000000000000000000000002900000000hyperfine-1.12.0/src/timer/unix_timer.rs#![cfg(not(windows))] use std::mem; use std::process::Child; use crate::timer::{CPUInterval, CPUTimes, TimerStart, TimerStop}; use crate::units::Second; pub fn get_cpu_timer() -> Box<dyn TimerStop<Result = (Second, Second)>> { Box::new(CPUTimer::start()) } struct CPUTimer { start_cpu: CPUTimes, } impl TimerStart for CPUTimer { fn start() -> Self { CPUTimer { start_cpu: get_cpu_times(), } } fn start_for_process(_: &Child) -> Self { Self::start() } } impl TimerStop for CPUTimer { type Result = (Second, Second); fn stop(&self) -> Self::Result { let end_cpu = get_cpu_times(); let cpu_interval = cpu_time_interval(&self.start_cpu, &end_cpu); (cpu_interval.user, cpu_interval.system) } } /// Read CPU execution times ('user' and 'system') fn get_cpu_times() -> CPUTimes { use libc::{getrusage, rusage, RUSAGE_CHILDREN}; let result: rusage = unsafe { let mut buf = mem::zeroed(); let success = getrusage(RUSAGE_CHILDREN, &mut buf); assert_eq!(0, success); buf }; const MICROSEC_PER_SEC: i64 = 1000 * 1000; #[allow(clippy::useless_conversion)] CPUTimes { user_usec: i64::from(result.ru_utime.tv_sec) * MICROSEC_PER_SEC + i64::from(result.ru_utime.tv_usec), system_usec: i64::from(result.ru_stime.tv_sec) * MICROSEC_PER_SEC + i64::from(result.ru_stime.tv_usec), } } /// Compute the time intervals in between two `CPUTimes` snapshots fn cpu_time_interval(start: &CPUTimes, end: &CPUTimes) -> CPUInterval { CPUInterval { user: ((end.user_usec - start.user_usec) as f64) * 1e-6, system: ((end.system_usec - start.system_usec) as f64) * 1e-6, } } #[cfg(test)] use approx::assert_relative_eq; #[test] fn test_cpu_time_interval() { let t_a = CPUTimes { user_usec: 12345, system_usec: 54321, }; let t_b = CPUTimes { user_usec: 20000, system_usec: 70000, }; let t_zero = cpu_time_interval(&t_a, &t_a); assert!(t_zero.user.abs() < f64::EPSILON); assert!(t_zero.system.abs() < f64::EPSILON); let t_ab = cpu_time_interval(&t_a, &t_b); assert_relative_eq!(0.007655, t_ab.user); assert_relative_eq!(0.015679, t_ab.system); let t_ba = cpu_time_interval(&t_b, &t_a); assert_relative_eq!(-0.007655, t_ba.user); assert_relative_eq!(-0.015679, t_ba.system); } 0707010000002D000081A4000000000000000000000001616C61280000026E000000000000000000000000000000000000002D00000000hyperfine-1.12.0/src/timer/wallclocktimer.rsuse std::process::Child; use std::time::Instant; use crate::timer::{TimerStart, TimerStop}; use crate::units::Second; pub struct WallClockTimer { start: Instant, } impl TimerStart for WallClockTimer { fn start() -> WallClockTimer { WallClockTimer { start: Instant::now(), } } fn start_for_process(_: &Child) -> Self { Self::start() } } impl TimerStop for WallClockTimer { type Result = Second; fn stop(&self) -> Second { let duration = self.start.elapsed(); duration.as_secs() as f64 + f64::from(duration.subsec_nanos()) * 1e-9 } } 0707010000002E000081A4000000000000000000000001616C612800000920000000000000000000000000000000000000002C00000000hyperfine-1.12.0/src/timer/windows_timer.rs#![cfg(windows)] use std::mem; use std::os::windows::io::{AsRawHandle, RawHandle}; use std::process::Child; use winapi::um::processthreadsapi::GetProcessTimes; use winapi::um::winnt::HANDLE; use crate::timer::{CPUTimes, TimerStart, TimerStop}; use crate::units::Second; const HUNDRED_NS_PER_MS: i64 = 10; pub fn get_cpu_timer(process: &Child) -> Box<dyn TimerStop<Result = (Second, Second)>> { Box::new(CPUTimer::start_for_process(process)) } struct CPUTimer { handle: RawHandle, } impl TimerStart for CPUTimer { fn start() -> Self { panic!() } fn start_for_process(process: &Child) -> Self { CPUTimer { handle: process.as_raw_handle(), } } } impl TimerStop for CPUTimer { type Result = (Second, Second); fn stop(&self) -> Self::Result { let times = get_cpu_times(self.handle); ( times.user_usec as f64 * 1e-6, times.system_usec as f64 * 1e-6, ) } } /// Read CPU execution times (dummy for now) fn get_cpu_times(handle: RawHandle) -> CPUTimes { let (user_usec, system_usec) = unsafe { let mut _ctime = mem::zeroed(); let mut _etime = mem::zeroed(); let mut kernel_time = mem::zeroed(); let mut user_time = mem::zeroed(); let res = GetProcessTimes( handle as HANDLE, &mut _ctime, &mut _etime, &mut kernel_time, &mut user_time, ); // GetProcessTimes will exit with non-zero if success as per: https://msdn.microsoft.com/en-us/library/windows/desktop/ms683223(v=vs.85).aspx if res != 0 { // Extract times as laid out here: https://support.microsoft.com/en-us/help/188768/info-working-with-the-filetime-structure // Both user_time and kernel_time are spans that the proces spent in either. let user: i64 = (((user_time.dwHighDateTime as i64) << 32) + user_time.dwLowDateTime as i64) / HUNDRED_NS_PER_MS; let kernel: i64 = (((kernel_time.dwHighDateTime as i64) << 32) + kernel_time.dwLowDateTime as i64) / HUNDRED_NS_PER_MS; (user, kernel) } else { (0, 0) } }; CPUTimes { user_usec, system_usec, } } 0707010000002F000081A4000000000000000000000001616C6128000006D7000000000000000000000000000000000000002100000000hyperfine-1.12.0/src/tokenize.rspub fn tokenize(values: &str) -> Vec<String> { let mut tokens = vec![]; let mut buf = String::new(); let mut iter = values.chars(); while let Some(c) = iter.next() { match c { '\\' => match iter.next() { Some(c2 @ ',') | Some(c2 @ '\\') => { buf.push(c2); } Some(c2) => { buf.push('\\'); buf.push(c2); } None => buf.push('\\'), }, ',' => { tokens.push(buf); buf = String::new(); } _ => { buf.push(c); } }; } tokens.push(buf); tokens } #[test] fn test_tokenize_single_value() { assert_eq!(tokenize(r""), vec![""]); assert_eq!(tokenize(r"foo"), vec!["foo"]); assert_eq!(tokenize(r" "), vec![" "]); assert_eq!(tokenize(r"hello\, world!"), vec!["hello, world!"]); assert_eq!(tokenize(r"\,"), vec![","]); assert_eq!(tokenize(r"\,\,\,"), vec![",,,"]); assert_eq!(tokenize(r"\n"), vec![r"\n"]); assert_eq!(tokenize(r"\\"), vec![r"\"]); assert_eq!(tokenize(r"\\\,"), vec![r"\,"]); } #[test] fn test_tokenize_multiple_values() { assert_eq!(tokenize(r"foo,bar,baz"), vec!["foo", "bar", "baz"]); assert_eq!(tokenize(r"hello world,foo"), vec!["hello world", "foo"]); assert_eq!(tokenize(r"hello\,world!,baz"), vec!["hello,world!", "baz"]); } #[test] fn test_tokenize_empty_values() { assert_eq!(tokenize(r"foo,,bar"), vec!["foo", "", "bar"]); assert_eq!(tokenize(r",bar"), vec!["", "bar"]); assert_eq!(tokenize(r"bar,"), vec!["bar", ""]); assert_eq!(tokenize(r",,"), vec!["", "", ""]); } 07070100000030000081A4000000000000000000000001616C6128000005F6000000000000000000000000000000000000001E00000000hyperfine-1.12.0/src/types.rsuse std::convert::TryFrom; use std::fmt; use rust_decimal::prelude::ToPrimitive; use rust_decimal::Decimal; use serde::Serialize; #[derive(Debug, Clone, Serialize, Copy, PartialEq, Eq)] #[serde(untagged)] pub enum NumericType { Int(i32), Decimal(Decimal), } impl fmt::Display for NumericType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { NumericType::Int(i) => fmt::Display::fmt(&i, f), NumericType::Decimal(i) => fmt::Display::fmt(&i, f), } } } impl From<i32> for NumericType { fn from(x: i32) -> NumericType { NumericType::Int(x) } } impl From<Decimal> for NumericType { fn from(x: Decimal) -> NumericType { NumericType::Decimal(x) } } impl TryFrom<NumericType> for usize { type Error = (); fn try_from(numeric: NumericType) -> Result<Self, Self::Error> { match numeric { NumericType::Int(i) => usize::try_from(i).map_err(|_| ()), NumericType::Decimal(d) => match d.to_u64() { Some(u) => usize::try_from(u).map_err(|_| ()), None => Err(()), }, } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ParameterValue { Text(String), Numeric(NumericType), } impl<'a> ToString for ParameterValue { fn to_string(&self) -> String { match self { ParameterValue::Text(ref value) => value.clone(), ParameterValue::Numeric(value) => value.to_string(), } } } 07070100000031000081A4000000000000000000000001616C612800000452000000000000000000000000000000000000001E00000000hyperfine-1.12.0/src/units.rs//! This module contains common units. pub type Scalar = f64; /// Type alias for unit of time pub type Second = Scalar; /// Supported time units #[derive(Debug, Clone, Copy, PartialEq)] pub enum Unit { Second, MilliSecond, } impl Unit { /// The abbreviation of the Unit. pub fn short_name(self) -> String { match self { Unit::Second => String::from("s"), Unit::MilliSecond => String::from("ms"), } } /// Returns the Second value formatted for the Unit. pub fn format(self, value: Second) -> String { match self { Unit::Second => format!("{:.3}", value), Unit::MilliSecond => format!("{:.1}", value * 1e3), } } } #[test] fn test_unit_short_name() { assert_eq!("s", Unit::Second.short_name()); assert_eq!("ms", Unit::MilliSecond.short_name()); } // Note - the values are rounded when formatted. #[test] fn test_unit_format() { let value: Second = 123.456789; assert_eq!("123.457", Unit::Second.format(value)); assert_eq!("123456.8", Unit::MilliSecond.format(value)); } 07070100000032000081A4000000000000000000000001616C61280000066A000000000000000000000000000000000000002100000000hyperfine-1.12.0/src/warnings.rsuse std::fmt; use crate::benchmark::MIN_EXECUTION_TIME; use crate::format::format_duration; use crate::units::Second; /// A list of all possible warnings pub enum Warnings { FastExecutionTime, NonZeroExitCode, SlowInitialRun(Second), OutliersDetected, } impl fmt::Display for Warnings { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Warnings::FastExecutionTime => write!( f, "Command took less than {:.0} ms to complete. Results might be inaccurate.", MIN_EXECUTION_TIME * 1e3 ), Warnings::NonZeroExitCode => write!(f, "Ignoring non-zero exit code."), Warnings::SlowInitialRun(t_first) => write!( f, "The first benchmarking run for this command was significantly slower than the \ rest ({}). This could be caused by (filesystem) caches that were not filled until \ after the first run. You should consider using the '--warmup' option to fill \ those caches before the actual benchmark. Alternatively, use the '--prepare' \ option to clear the caches before each timing run.", format_duration(t_first, None) ), Warnings::OutliersDetected => write!( f, "Statistical outliers were detected. Consider re-running this benchmark on a quiet \ PC without any interferences from other programs. It might help to use the \ '--warmup' or '--prepare' options." ), } } } 07070100000033000041ED000000000000000000000001616C612800000000000000000000000000000000000000000000001700000000hyperfine-1.12.0/tests07070100000034000081A4000000000000000000000001616C612800001514000000000000000000000000000000000000002C00000000hyperfine-1.12.0/tests/integration_tests.rsuse std::{fs::File, io::Read, path::PathBuf, process::Command}; use assert_cmd::cargo::CommandCargoExt; use tempfile::{tempdir, TempDir}; fn hyperfine_raw_command() -> Command { let mut cmd = Command::cargo_bin("hyperfine").unwrap(); cmd.current_dir("tests/"); cmd } fn hyperfine() -> assert_cmd::Command { assert_cmd::Command::from_std(hyperfine_raw_command()) } #[test] fn hyperfine_runs_successfully() { hyperfine() .arg("--runs=2") .arg("echo dummy benchmark") .assert() .success(); } #[test] fn at_least_two_runs_are_required() { hyperfine() .arg("--runs=1") .arg("echo dummy benchmark") .assert() .failure(); } struct ExecutionOrderTest { cmd: assert_cmd::Command, expected_content: String, logfile_path: PathBuf, #[allow(dead_code)] tempdir: TempDir, } impl ExecutionOrderTest { fn new() -> Self { let tempdir = tempdir().unwrap(); let logfile_path = tempdir.path().join("output.log"); ExecutionOrderTest { cmd: hyperfine(), expected_content: String::new(), logfile_path, tempdir, } } fn arg<S: AsRef<str>>(&mut self, arg: S) -> &mut Self { self.cmd.arg(arg.as_ref()); self } fn get_command(&self, output: &str) -> String { format!( "echo {output} >> {path}", output = output, path = self.logfile_path.to_string_lossy() ) } fn command(&mut self, output: &str) -> &mut Self { self.arg(self.get_command(output)); self } fn prepare(&mut self, output: &str) -> &mut Self { self.arg("--prepare"); self.command(output) } fn cleanup(&mut self, output: &str) -> &mut Self { self.arg("--cleanup"); self.command(output) } fn expect_output(&mut self, output: &str) -> &mut Self { self.expected_content.push_str(output); #[cfg(windows)] { self.expected_content.push_str(" \r"); } self.expected_content.push('\n'); self } fn run(&mut self) { self.cmd.assert().success(); let mut f = File::open(&self.logfile_path).unwrap(); let mut content = String::new(); f.read_to_string(&mut content).unwrap(); assert_eq!(content, self.expected_content); } } impl Default for ExecutionOrderTest { fn default() -> Self { Self::new() } } #[test] fn benchmarks_are_executed_sequentially() { ExecutionOrderTest::new() .arg("--runs=2") .command("command 1") .command("command 2") .expect_output("command 1") .expect_output("command 1") .expect_output("command 2") .expect_output("command 2") .run(); } #[test] fn warmup_runs_are_executed_before_benchmarking_runs() { ExecutionOrderTest::new() .arg("--runs=2") .arg("--warmup=3") .command("command 1") .expect_output("command 1") .expect_output("command 1") .expect_output("command 1") .expect_output("command 1") .expect_output("command 1") .run(); } #[test] fn prepare_commands_are_executed_before_each_timing_run() { ExecutionOrderTest::new() .arg("--runs=2") .prepare("prepare") .command("command 1") .command("command 2") .expect_output("prepare") .expect_output("command 1") .expect_output("prepare") .expect_output("command 1") .expect_output("prepare") .expect_output("command 2") .expect_output("prepare") .expect_output("command 2") .run(); } #[test] fn cleanup_commands_are_executed_once_after_each_benchmark() { ExecutionOrderTest::new() .arg("--runs=2") .cleanup("cleanup") .command("command 1") .command("command 2") .expect_output("command 1") .expect_output("command 1") .expect_output("cleanup") .expect_output("command 2") .expect_output("command 2") .expect_output("cleanup") .run(); } #[test] fn single_parameter_value() { ExecutionOrderTest::new() .arg("--runs=2") .arg("--parameter-list") .arg("number") .arg("1,2,3") .command("command {number}") .expect_output("command 1") .expect_output("command 1") .expect_output("command 2") .expect_output("command 2") .expect_output("command 3") .expect_output("command 3") .run(); } #[test] fn multiple_parameter_values() { ExecutionOrderTest::new() .arg("--runs=2") .arg("--parameter-list") .arg("number") .arg("1,2,3") .arg("--parameter-list") .arg("letter") .arg("a,b") .command("command {number} {letter}") .expect_output("command 1 a") .expect_output("command 1 a") .expect_output("command 2 a") .expect_output("command 2 a") .expect_output("command 3 a") .expect_output("command 3 a") .expect_output("command 1 b") .expect_output("command 1 b") .expect_output("command 2 b") .expect_output("command 2 b") .expect_output("command 3 b") .expect_output("command 3 b") .run(); } 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!401 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor