8000 ci: schedule P16 binding benchmarks from main by adityamukho · Pull Request #833 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/bench/bindings-report.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Binding jobs use Bencher JSON (nanoseconds), with separate foreign testbeds.
const fs = require('node:fs');
const { audit } = require('./report.cjs');
function expected(binding) {
if (!['python', 'browser', 'node', 'c'].includes(binding)) throw new Error('unknown binding');
const suffixes = ['prepared_execute/1k', 'cursor_batch_1/1k', 'cursor_batch_256/1k', 'set_roundtrip/2', 'predicate/1k', 'aggregate/1k'];
if (binding === 'browser') suffixes.push('indexeddb_write/1k', 'indexeddb_reopen/1k');
return suffixes.map(s => `binding/${binding}/${s}`);
}
function validate(binding, metrics) {
const ids = expected(binding);
if (JSON.stringify(Object.keys(metrics).sort()) !== JSON.stringify([...ids].sort())) throw new Error('binding IDs do not match shortlist');
for (const id of ids) {
const value = metrics[id]?.latency?.value;
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) throw new Error(`invalid latency: ${id}`);
}
return ids;
}
function main(command) {
const read = p => fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : '';
const binding = process.env.BINDING;
const ids = expected(binding);
if (command === 'validate') {
validate(binding, JSON.parse(read('binding/binding-results.json')));
const executed = read('binding-execution.txt').split('\n').filter(s => s.startsWith('ok: ')).map(s => s.slice(4));
if (executed.length !== ids.length || new Set(executed).size !== ids.length || executed.some(id => !ids.includes(id))) throw new Error('executed IDs do not match shortlist');
return;
}
if (command !== 'finish') throw new Error('unknown command');
// Reuse the tested upload/alert parser without representing these as native
// Criterion measurements: only the list of executed IDs is adapted.
const execution = read('binding-execution.txt').replace(/^ok: (.+)$/gm, 'Benchmarking $1: Analyzing');
const result = audit(ids, execution, read('binding-bencher.txt'), process.env.BENCHER_OUTCOME);
const report = {
binding, coreSha: process.env.BENCH_SHA, bindingSha: read('binding-sha.txt').trim(),
workflowSha: process.env.WORKFLOW_SHA, ref: process.env.BENCH_REF, historyBranch: process.env.BINDING_HISTORY,
testbed: `binding-${binding}-ubuntu24-v1`, selected: ids, ...result,
};
fs.writeFileSync('binding-report.json', JSON.stringify(report, null, 2) + '\n');
const summary = `### Binding benchmark: ${binding}\n\nCore: ${report.coreSha}; binding: ${report.bindingSha}; workflow: ${report.workflowSha}\n\nHistory branch: ${report.historyBranch}; testbed: ${report.testbed}; executed ${result.executed.length}/${ids.length}; uploaded ${result.uploaded.length}/${ids.length}; alerts ${result.alerts.length}; upload ${result.uploadStatus}\n\n${result.errors.join('\n')}\n`;
console.log(summary);
if (process.env.GITHUB_STEP_SUMMARY) fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary);
if (result.errors.length) process.exitCode = 1;
}
module.exports = { expected, validate };
if (require.main === module) {
try { main(process.argv[2]); } catch (e) { console.error(e.message); process.exitCode = 1; }
}
46 changes: 46 additions & 0 deletions .github/bench/bindings-report.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const { test } = require('node:test');
const assert = require('node:assert/strict');
const { expected, validate } = require('./bindings-report.cjs');
const data = binding => Object.fromEntries(expected(binding).map(id => [id, { latency: { value: 100 } }]));
test('exact dedicated routes and finite positive latency only', () => {
for (const binding of ['python', 'browser', 'node', 'c']) {
assert.equal(validate(binding, data(binding)).length, binding === 'browser' ? 8 : 6);
const missing = data(binding); delete missing[expected(binding)[0]];
assert.throws(() => validate(binding, missing), /IDs/);
const extra = data(binding); extra['native/query'] = { latency: { value: 1 } };
assert.throws(() => validate(binding, extra), /IDs/);
for (const value of [0, -1, null, Infinity, '12']) {
const bad = data(binding); bad[expected(binding)[0]].latency.value = value;
assert.throws(() => validate(binding, bad), /latency/);
}
}
assert.throws(() => expected('unknown'), /binding/);
});

const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
test('CLI verifies execution and records provenance even when upload is partial', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'p16-report-'));
try {
fs.mkdirSync(path.join(dir, 'binding'));
fs.writeFileSync(path.join(dir, 'binding/binding-results.json'), JSON.stringify(data('python')));
fs.writeFileSync(path.join(dir, 'binding-execution.txt'), expected('python').map(id => `ok: ${id}`).join('\n'));
fs.writeFileSync(path.join(dir, 'binding-sha.txt'), 'binding-commit\n');
const env = { ...process.env, BINDING: 'python', BENCH_SHA: 'core-commit', WORKFLOW_SHA: 'workflow-commit', BENCH_REF: 'v3', BINDING_HISTORY: 'binding-experiment/v3/pr', BENCHER_OUTCOME: 'success' };
const run = command => spawnSync(process.execPath, [path.join(__dirname, 'bindings-report.cjs'), command], { cwd: dir, env, encoding: 'utf8' });
assert.equal(run('validate').status, 0);
fs.appendFileSync(path.join(dir, 'binding-execution.txt'), '\nok: binding/python/aggregate/1k');
assert.notEqual(run('validate').status, 0);
assert.notEqual(run('finish').status, 0);
const report = JSON.parse(fs.readFileSync(path.join(dir, 'binding-report.json')));
assert.equal(report.coreSha, 'core-commit');
assert.equal(report.bindingSha, 'binding-commit');
assert.equal(report.workflowSha, 'workflow-commit');
assert.equal(report.testbed, 'binding-python-ubuntu24-v1');
assert.equal(report.historyBranch, 'binding-experiment/v3/pr');
assert.equal(report.uploaded.length, 0);
assert.ok(report.errors.length);
} finally { fs.rmSync(dir, { recursive: true, force: true }); }
});
60 changes: 60 additions & 0 deletions .github/bench/bindings.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Called from the core checkout. Never changes checked-in binding dependencies.
set -euo pipefail
mode=$1
binding=$2
core_dir=$PWD
export CARGO_TARGET_DIR="$PWD/binding/target/p16"
export CARGO_BUILD_JOBS=2
cd binding
if [[ "$mode" = build ]]; then
mkdir -p .cargo
# Benchmark the exact measured core checkout, not the binding's release pin.
cat > .cargo/config.toml <<EOF
[patch."https://github.com/project-minigraf/minigraf"]
minigraf = { path = "$core_dir" }
EOF
cargo update -p minigraf
cargo metadata --format-version 1 > target-metadata.json
python3 - "$core_dir" <<'PY'
import json, pathlib, sys
packages = json.load(open('target-metadata.json'))['packages']
core = [p for p in packages if p['name'] == 'minigraf']
assert len(core) == 1 and core[0]['source'] is None
assert pathlib.Path(core[0]['manifest_path']).parent == pathlib.Path(sys.argv[1])
PY
case "$binding" in
python)
python3 -m venv target/bench-venv
source target/bench-venv/bin/activate
pip install 'maturin>=1.5,<2'
cargo build --release --bin uniffi-bindgen
maturin develop --release
;;
browser)
cargo install wasm-pack --locked --version 0.13.1
wasm-pack build --target web --out-dir pkg -- --features browser
npm install --no-save --package-lock=false playwright@1.55.0
npx playwright install --with-deps chromium
;;
node)
cargo build --release --lib
cp "$CARGO_TARGET_DIR/release/libminigraf_node.so" minigraf.linux-x64-gnu.node
;;
c)
cargo build --release --lib
cc -O2 -std=c11 -Wall -Wextra -Werror -Iinclude bench/nightly.c \
"$CARGO_TARGET_DIR/release/libminigraf.a" -lpthread -ldl -lm -o "$CARGO_TARGET_DIR/nightly"
;;
*) exit 1 ;;
esac
else
# --test is forwarded verbatim and never writes timing data.
shift 2
case "$binding" in
python) target/bench-venv/bin/python bench/nightly.py "$@" ;;
browser|node) node bench/nightly.mjs "$@" ;;
c) "$CARGO_TARGET_DIR/nightly" "$@" ;;
*) exit 1 ;;
esac
fi
2 changes: 1 addition & 1 deletion .github/workflows/bench-report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: '24'
- run: node --test .github/bench/report.test.cjs
- run: node --test .github/bench/*.test.cjs
Loading
Loading
0