Merge pull request #859 from devgbmuyiwa/fix/381-environment-management #237
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Latency Budget Enforcement CI | |
| # | |
| # Runs the CLI cold-start and command-latency Criterion benchmarks on every | |
| # push / pull request and checks the results against the project's latency | |
| # budgets. Fails the workflow when a statistically meaningful regression is | |
| # detected, helping to keep the CLI snappy for all users. | |
| # | |
| # See CLI_LATENCY_BUDGETS.md for budget definitions and environment overrides. | |
| name: Latency Budget | |
| on: | |
| push: | |
| branches: [master, main] | |
| paths: | |
| - '**.rs' | |
| - 'Cargo.toml' | |
| - 'Cargo.lock' | |
| - 'benches/**' | |
| - '.github/workflows/benchmark-latency.yml' | |
| pull_request: | |
| paths: | |
| - '**.rs' | |
| - 'Cargo.toml' | |
| - 'Cargo.lock' | |
| - 'benches/**' | |
| - '.github/workflows/benchmark-latency.yml' | |
| # Allow manual trigger for ad-hoc budget checks. | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| jobs: | |
| latency-budget: | |
| name: Latency Budget Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Install system dependencies | |
| run: sudo apt-get update && sudo apt-get install -y libudev-dev | |
| # Build the project first so benchmark compilation is faster. | |
| - name: Build project | |
| run: cargo build --locked | |
| # Run only the latency-relevant benchmark groups. This keeps CI fast | |
| # while still exercising cold-start and command-dispatch paths. | |
| - name: Run latency benchmarks | |
| run: | | |
| mkdir -p target/criterion | |
| # Criterion's harness takes a SINGLE positional filter, which it | |
| # treats as a regex. Passing the three group names as separate | |
| # arguments made it exit 2 with | |
| # "unexpected argument 'cli_command_latency' found", so this step | |
| # failed before any budget was ever measured. Use one alternation. | |
| cargo bench --locked -- \ | |
| 'cli_cold_start|cli_command_latency|latency_budget' \ | |
| 2>&1 | tee target/criterion/latency-bench-output.txt | |
| # Parse Criterion output and check against latency budgets. | |
| # The check-latency-budgets.sh script extracts median values from the | |
| # default Criterion stdout format and compares them against the budgets | |
| # defined in the bash script (which mirror src/utils/latency_budget.rs). | |
| - name: Parse and check latency budget report | |
| id: budget-check | |
| run: | | |
| chmod +x scripts/check-latency-budgets.sh | |
| # Capture stdout only. The script writes its JSON report to stdout and | |
| # progress notes to stderr; folding stderr in with 2>&1 made `jq` fail | |
| # with "Invalid numeric literal". Because the result was then compared | |
| # only against the literal "false", an unparseable report silently | |
| # counted as a pass -- this gate could not fail. Let stderr flow to the | |
| # job log instead. | |
| REPORT=$(bash scripts/check-latency-budgets.sh \ | |
| --input target/criterion/latency-bench-output.txt \ | |
| --report-path target/criterion/latency-budget-report.json) || EXIT_CODE=$? | |
| echo "::group::Latency Budget Report" | |
| echo "$REPORT" | |
| echo "::endgroup::" | |
| ALL_PASS=$(printf '%s' "$REPORT" | jq -r '.all_pass' 2>/dev/null || echo "unparseable") | |
| echo "all_pass=$ALL_PASS" >> "$GITHUB_OUTPUT" | |
| # Fail closed: only an explicit `true` is a pass. A missing or | |
| # unparseable report is a failure, not a free pass. | |
| if [ "$ALL_PASS" != "true" ]; then | |
| echo "❌ Latency budget check did not pass (all_pass=$ALL_PASS)" | |
| echo "failures=true" >> "$GITHUB_OUTPUT" | |
| exit 1 | |
| fi | |
| echo "✅ All latency budgets met." | |
| - name: Upload Criterion report (artefact) | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: criterion-latency-report | |
| path: | | |
| target/criterion/cli_cold_start/ | |
| target/criterion/cli_command_latency/ | |
| target/criterion/latency_budget/ | |
| target/criterion/latency-bench-output.txt | |
| target/criterion/latency-budget-report.json | |
| # Post a PR comment with the budget check summary when run on a PR. | |
| # Posting the summary is a courtesy, not the gate: the budget check step | |
| # above is what fails the job. A `pull_request` from a fork gets a | |
| # read-only GITHUB_TOKEN, so createComment returns 403 | |
| # ("Resource not accessible by integration"). That used to fail this job | |
| # even when every budget passed. | |
| - name: Comment PR with budget summary | |
| if: github.event_name == 'pull_request' && always() | |
| continue-on-error: true | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const reportPath = 'target/criterion/latency-budget-report.json'; | |
| let summary = '## ⏱ CLI Latency Budget Check\n\n'; | |
| if (fs.existsSync(reportPath)) { | |
| const report = JSON.parse(fs.readFileSync(reportPath, 'utf8')); | |
| let details = ''; | |
| for (const check of report.checks) { | |
| const icon = check.status === 'PASS' ? '✅' : | |
| check.status === 'FAIL' ? '❌' : | |
| check.status === 'NOISY' ? '⚠️' : | |
| check.status === 'SKIPPED' ? '⏭️' : '❗'; | |
| summary += `| ${icon} ${check.budget} | ${check.budget_max_ms} ms | ${check.actual_median_ms} ms | ${check.status} |\n`; | |
| } | |
| if (report.any_fail) summary += '\n❌ **Some budgets were violated.**'; | |
| else summary += '\n✅ **All budgets met.**'; | |
| } else { | |
| summary += '_No latency budget report was generated._'; | |
| } | |
| try { | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: summary | |
| }); | |
| } catch (error) { | |
| core.warning( | |
| `Could not post the latency budget comment (this is expected ` + | |
| `for pull requests from forks, which get a read-only token): ` + | |
| `${error.message}` | |
| ); | |
| core.summary.addRaw(summary).write(); | |
| } |