-
Notifications
You must be signed in to change notification settings - Fork 192
138 lines (125 loc) · 5.54 KB
/
Copy pathbenchmark-latency.yml
File metadata and controls
138 lines (125 loc) · 5.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# 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: 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 (err) {
// Fork PRs get a read-only GITHUB_TOKEN and can't post comments;
// don't fail the whole job just because the summary couldn't be posted.
core.warning(`Could not post latency budget comment: ${err.message}`);
} catch (error) {
// Fork PRs get a read-only GITHUB_TOKEN and can't post comments;
// don't fail the whole job just because the summary couldn't be posted.
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();
}