diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 0000000..4d37173 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,127 @@ +# TODO(template) this workflow requires a `gh-pages` branch to exist. +# Create it once with: +# git checkout --orphan gh-pages +# git reset --hard +# git commit --allow-empty -m "init gh-pages" +# git push origin gh-pages +# +# The benchmark dashboard will be available at: +# https://.github.io//dev/bench/ +name: Benchmarks + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: write + deployments: write + pull-requests: write + +env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + CARGO_NET_RETRY: 10 + RUSTUP_MAX_RETRIES: 10 + +jobs: + benchmark: + name: Continuous benchmarking + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable + id: rust-toolchain + with: + toolchain: stable + + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-bench-${{ steps.rust-toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-bench-${{ steps.rust-toolchain.outputs.cachekey }}- + ${{ runner.os }}-bench- + + # TODO(template) update the bench command if you use a different + # benchmark harness or need to pass extra flags + - name: Run benchmarks + shell: bash + run: | + set -euo pipefail + cargo bench -p template_crate --bench benchmark -- --output-format bencher 2>/dev/null | tee benchmark-output.txt + + # On PRs: restore the most-recent baseline written by a main-branch run. + # Using only restore-keys (no matching primary key) guarantees we always + # get the latest entry; the primary key includes the run ID so each main + # run saves a fresh entry and PRs never hit a stale one. + # Note: if no baseline cache exists yet (e.g. the very first PR before any + # main-branch run has completed), the restore will miss and no regression + # alert will fire. This is expected and benign — the action creates a fresh + # data file and the next main-branch run will establish the baseline. + - name: Restore benchmark baseline + if: github.event_name == 'pull_request' + uses: actions/cache/restore@v4 + with: + path: ./cache + key: ${{ runner.os }}-benchmark-baseline-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-benchmark-baseline- + + - name: Store benchmark result (PR) + if: github.event_name == 'pull_request' + uses: benchmark-action/github-action-benchmark@4bdcce38c94cec68da58d012ac24b7b1155efe8b # v1 + with: + # TODO(template) update the name to match your project + name: Rust Template Benchmark + tool: "cargo" + output-file-path: benchmark-output.txt + external-data-json-path: ./cache/benchmark-data.json + alert-threshold: "130%" + comment-on-alert: true + fail-on-alert: true + github-token: ${{ secrets.GITHUB_TOKEN }} + + # On main: push results to gh-pages for the historical dashboard, then + # save the updated baseline so future PRs compare against current main. + # external-data-json-path writes the latest result into ./cache so the + # subsequent save step has data to cache for PR comparisons. + - name: Store benchmark result (main) + if: github.event_name == 'push' + uses: benchmark-action/github-action-benchmark@4bdcce38c94cec68da58d012ac24b7b1155efe8b # v1 + with: + # TODO(template) update the name to match your project + name: Rust Template Benchmark + tool: "cargo" + output-file-path: benchmark-output.txt + external-data-json-path: ./cache/benchmark-data.json + gh-pages-branch: gh-pages + benchmark-data-dir-path: dev/bench + alert-threshold: "130%" + comment-on-alert: true + github-token: ${{ secrets.GITHUB_TOKEN }} + auto-push: true + + # Save the freshly-updated benchmark data as the new baseline. + # The key embeds the run ID so every main run creates a new cache entry; + # actions/cache would skip saving if the key already existed. + - name: Save benchmark baseline + if: github.event_name == 'push' + uses: actions/cache/save@v4 + with: + path: ./cache + key: ${{ runner.os }}-benchmark-baseline-${{ github.run_id }} diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 9c3779f..2d69a8c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -38,5 +38,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Build run: cargo test --workspace --verbose --no-run + - name: Build all targets + run: cargo build --workspace --all-features --all-targets - name: Run tests - run: cargo test --workspace --verbose + run: cargo test --workspace --verbose \ No newline at end of file diff --git a/template_crate/Cargo.toml b/template_crate/Cargo.toml index e0b327d..57c4ab9 100644 --- a/template_crate/Cargo.toml +++ b/template_crate/Cargo.toml @@ -15,10 +15,15 @@ license.workspace = true thiserror = { version = "1.0", default-features = false } [dev-dependencies] +criterion = { version = "0.5", features = ["html_reports"] } proptest = { workspace = true } # TODO(template) # don't forget to put this at every crate # to inherit workspace's lints +[[bench]] +name = "benchmark" +harness = false + [lints] workspace = true diff --git a/template_crate/benches/benchmark.rs b/template_crate/benches/benchmark.rs new file mode 100644 index 0000000..804ff84 --- /dev/null +++ b/template_crate/benches/benchmark.rs @@ -0,0 +1,32 @@ +//! Benchmarks for template_crate + +use criterion::{Criterion, black_box, criterion_group}; +use template_crate::{add_small_integers, sub_small_integers}; + +/// Benchmarks for `add_small_integers` function +fn bench_add_small_integers(c: &mut Criterion) { + c.bench_function("add_small_integers valid", |b| { + b.iter(|| add_small_integers(black_box(50), black_box(30))) + }); + + c.bench_function("add_small_integers bound check", |b| { + b.iter(|| add_small_integers(black_box(200), black_box(5))) + }); +} + +/// Benchmarks for `sub_small_integers` function +fn bench_sub_small_integers(c: &mut Criterion) { + c.bench_function("sub_small_integers valid", |b| { + b.iter(|| sub_small_integers(black_box(50), black_box(30))) + }); + + c.bench_function("sub_small_integers bound check", |b| { + b.iter(|| sub_small_integers(black_box(200), black_box(5))) + }); +} + +/// Entry point for benchmarks +fn main() { + criterion_group!(benches, bench_add_small_integers, bench_sub_small_integers); + benches(); +}