From 96765e1859c3d48ac0f04092c0613b4e8ad8ae21 Mon Sep 17 00:00:00 2001 From: helly25 <6420169+helly25@users.noreply.github.com> Date: Sun, 8 Nov 2026 23:49:51 +0000 Subject: [PATCH 1/4] Carry extended production benchmarks into continuous history --- .github/workflows/benchmark_pages.yml | 2 + .github/workflows/benchmarks.yml | 87 +++++++ docs/benchmark-extended-history-campaign.md | 62 +++++ tools/BUILD.bazel | 153 ++++++++++++ tools/benchmark_datasets.py | 8 +- tools/benchmark_extended_build.py | 85 ++++--- tools/benchmark_extended_build_test.py | 44 ++++ tools/benchmark_extended_ci.py | 163 +++++++++++++ tools/benchmark_extended_ci_test.py | 114 +++++++++ tools/benchmark_extended_history.py | 11 +- tools/benchmark_extended_records.py | 249 ++++++++++++++++++++ tools/benchmark_extended_records_test.py | 87 +++++++ tools/benchmark_extended_site.py | 161 +++++++++++++ tools/benchmark_extended_site_test.py | 112 +++++++++ tools/benchmark_extended_storage.py | 116 +++++++++ tools/benchmark_extended_storage_test.py | 134 +++++++++++ tools/benchmark_extended_test_support.py | 98 ++++++++ tools/benchmark_history_test.py | 2 +- tools/benchmark_reconcile_ci.py | 74 +++++- tools/benchmark_reconcile_ci_test.py | 123 ++++++++++ tools/benchmark_recovery.py | 2 +- 21 files changed, 1844 insertions(+), 43 deletions(-) create mode 100644 tools/benchmark_extended_ci.py create mode 100644 tools/benchmark_extended_ci_test.py create mode 100644 tools/benchmark_extended_records.py create mode 100644 tools/benchmark_extended_records_test.py create mode 100644 tools/benchmark_extended_site.py create mode 100644 tools/benchmark_extended_site_test.py create mode 100644 tools/benchmark_extended_storage.py create mode 100644 tools/benchmark_extended_storage_test.py create mode 100644 tools/benchmark_extended_test_support.py create mode 100644 tools/benchmark_reconcile_ci_test.py diff --git a/.github/workflows/benchmark_pages.yml b/.github/workflows/benchmark_pages.yml index eb121563984..e9dbaaa412c 100644 --- a/.github/workflows/benchmark_pages.yml +++ b/.github/workflows/benchmark_pages.yml @@ -73,6 +73,8 @@ jobs: python3 source/tools/benchmark_landscape.py --site-root=site/benchmarks \ --renderer-js=source/tools/benchmark_web/dist/landscape.js --pulls=pulls.json \ --repository="${GITHUB_REPOSITORY}" + python3 source/tools/benchmark_extended_site.py publish \ + --root=site/benchmarks --repository="${GITHUB_REPOSITORY}" python3 source/tools/site_artwork.py source/docs/assets site python3 source/tools/compact_site.py site git -C site add --all diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index fd343f91bd2..504d20a8940 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -17,6 +17,93 @@ concurrency: cancel-in-progress: false jobs: + extended: + name: Extended production (${{ matrix.platform }}) + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + platform: linux + timeout: 120 + - os: macos-latest + platform: macos + timeout: 180 + runs-on: ${{ matrix.os }} + timeout-minutes: ${{ matrix.timeout }} + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + persist-credentials: false + - uses: bazel-contrib/setup-bazel@0.19.1 + - uses: actions/setup-python@v7 + with: + python-version: "3.13" + - uses: ./.github/actions/bazel-cache-restore + with: + namespace: default + - name: Install extended reference tools + run: | + if [[ "${RUNNER_OS}" == Linux ]]; then + sudo apt-get update && sudo apt-get install -y findutils ripgrep tar + else + brew install findutils ripgrep gnu-tar + fi + - name: Freeze native accounting helper before production builds + run: | + set -euo pipefail + bazel build //tools:process_resources_native --config=clang_opt --disk_cache="${HOME}/.cache/bazel-disk" + launcher="$(bazel cquery 'config(//tools:process_resources_native, target)' --config=clang_opt \ + --output=starlark --starlark:expr='target.files_to_run.executable.path')" + cp "${launcher}" "${RUNNER_TEMP}/extended-resource-launcher" + - name: Freeze full production head and first parent + run: | + set -euo pipefail + git worktree add --detach "${RUNNER_TEMP}/extended-base" HEAD^1 + python3 tools/benchmark_extended_ci.py build --repo="${GITHUB_WORKSPACE}" --role=head \ + --root="${RUNNER_TEMP}/extended-build" --disk-cache="${HOME}/.cache/bazel-disk" + python3 tools/benchmark_extended_ci.py build --repo="${RUNNER_TEMP}/extended-base" --role=base \ + --root="${RUNNER_TEMP}/extended-build" --disk-cache="${HOME}/.cache/bazel-disk" + tar -cf "${RUNNER_TEMP}/benchmark-extended-build.tar" -C "${RUNNER_TEMP}/extended-build" . + bazel --nosystem_rc --nohome_rc shutdown + (cd "${RUNNER_TEMP}/extended-base" && bazel --nosystem_rc --nohome_rc shutdown) + - uses: actions/upload-artifact@v7 + with: + name: benchmark-extended-build-${{ matrix.platform }} + path: ${{ runner.temp }}/benchmark-extended-build.tar + retention-days: 30 + if-no-files-found: error + - uses: actions/download-artifact@v8 + with: + name: benchmark-extended-build-${{ matrix.platform }} + path: ${{ runner.temp }}/extended-retention + - name: Restore the original retained production bytes + run: | + mkdir -p "${RUNNER_TEMP}/extended-retained-build" + tar -xf "${RUNNER_TEMP}/extended-retention/benchmark-extended-build.tar" -C "${RUNNER_TEMP}/extended-retained-build" + - name: Measure the complete extended contract in memory + run: | + set -euo pipefail + cat >"${RUNNER_TEMP}/extended-collection.json" <<'JSON' + {"run_id":${{ github.run_id }},"run_attempt":${{ github.run_attempt }},"head_sha":"${{ github.sha }}","repository":"${{ github.repository }}"} + JSON + python3 tools/benchmark_extended_ci.py collect \ + --build-root="${RUNNER_TEMP}/extended-retained-build" --root="${RUNNER_TEMP}/extended-measurements" \ + --resource-launcher="${RUNNER_TEMP}/extended-resource-launcher" --collection="${RUNNER_TEMP}/extended-collection.json" + - uses: actions/upload-artifact@v7 + if: always() + with: + name: benchmark-extended-report-${{ matrix.platform }} + path: | + ${{ runner.temp }}/extended-measurements/observation.json + ${{ runner.temp }}/extended-measurements/measurements + ${{ runner.temp }}/extended-measurements/storage.json + ${{ runner.temp }}/extended-measurements/storage-release.json + retention-days: 30 + if-no-files-found: warn + build: strategy: fail-fast: false diff --git a/docs/benchmark-extended-history-campaign.md b/docs/benchmark-extended-history-campaign.md index 9cf393f59e7..dc925c5123a 100644 --- a/docs/benchmark-extended-history-campaign.md +++ b/docs/benchmark-extended-history-campaign.md @@ -243,3 +243,65 @@ The [synthetic viewer check](benchmarks/extended-history-viewer-check-20261107.j both exported dataset views in Chromium. Both historical versions produce their exact expected heights; 60 measured points remain present and all 20 unavailable cells remain empty. This artifact contains synthetic renderer checks only, not performance measurements. + +## Continuous publication + +The `Benchmarks` workflow collects `extended-production-v1` independently on Linux and macOS. +It uses the complete 60-cell grid above, one warm-up and nine measured rounds, retaining seven. +Each platform builds the full production head and its exact first parent with ThinLTO. Each +configured executable is copied immediately after its own build/query, before any later Bazel +invocation. Both Bazel servers stop before measurement. The original executables and build/link +evidence are uploaded, downloaded again, and verified before their bytes are measured. + +Linux creates a new directory on verified `/dev/shm` tmpfs. macOS creates a disposable 2 GiB +case-sensitive HFS+ RAM image, records its device and volume UUID, and checks live ownership before +formatting, use and release. Cleanup refuses changed ownership. Mac worker counts are requests; +Linux records an allowed CPU mask. The fixture, executable, reference tools and native accounting +launcher are checked again after collection. No timing result is accepted after an integrity +failure. Local Linux validation can reuse an existing verified fixture with `--fixture`; this +does not remove that fixture and is forbidden for CI collections. + +Head cells rotate head XFF, first-parent XFF and the traditional tool through execution order. +A second complete grid measures the parent against itself as a diagnostic control. All successful +outputs must match the independent oracle. Raw warm-ups, measured samples, command lines, +environment, pressure context and failed attempts remain in the report artifact. A control +failure leaves a complete, checked head observation recoverable, with an explicit control error; +a failed or incomplete head never becomes a publishable observation. + +The existing 3D browser offers two separate views per series: traditional tools and parent XFF. +The actual parent remains visible for each version; changing parents does not create a new +dataset. Original local history keeps its fixed PR #1130 reference, local origin and separate +series. Core benchmarks, local fixed-reference history and CI parent comparisons retain their +own contracts. Extended observations do not satisfy the core merged-PR coverage inventory. +No unavailable sizes or worker allocations are invented. + +The publisher recovers valid completed platform artifacts even when another job failed. A +retry of failed jobs preserves successful platforms from their original attempts. CI publication +checks the trusted workflow, main push, repository, run/attempt, exact head and GitHub's actual +first parent. An artifact's own claims cannot establish that provenance. Complete observations +are immutable under `benchmarks/extended/observations/`; generated views live under +`benchmarks/extended/views/`. The dataset registry includes those views without putting them into +the core benchmark normalizer or overview. Build/report artifacts retain the existing 30-day +retention policy; publication retains the validated observations. + +### Import the original measured history + +Use the original measurement directory and retained build tree. The importer validates every +declared cell, exact commands, output oracle, sample population, source identity and production +link evidence. It reads and hashes the retained executables; rebuilding a revision cannot +retroactively verify its original measurements. Incomplete or invalid campaigns are rejected. + +```sh +python3.13 tools/benchmark_extended_site.py import \ + --campaign=MEASUREMENT_ROOT --build-root=BUILD_ROOT \ + --root=SITE/benchmarks --series=linux-zen5-extended-20261107 \ + --platform=linux --machine=x86_64 + +python3.13 tools/benchmark_extended_site.py publish --root=SITE/benchmarks +``` + +Publication runs after the ordinary core renderer and before site compaction. Existing core +catalog entries remain intact. Tests cover known chart heights, warm-up exclusion, missing cells, +stable parent datasets, local/CI separation, packed observations, idempotent publication, +untrusted provenance, partial workflow recovery and RAM-resource ownership. Synthetic test +observations are explicitly labeled and are never performance evidence. diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel index 9f7740d8501..13651de1829 100644 --- a/tools/BUILD.bazel +++ b/tools/BUILD.bazel @@ -926,8 +926,12 @@ test_suite( ":benchmark_compare_test", ":benchmark_datasets_test", ":benchmark_extended_build_test", + ":benchmark_extended_ci_test", ":benchmark_extended_history_test", + ":benchmark_extended_records_test", ":benchmark_extended_report_test", + ":benchmark_extended_site_test", + ":benchmark_extended_storage_test", ":benchmark_extended_test", ":benchmark_fixture_test", ":benchmark_history_test", @@ -940,6 +944,7 @@ test_suite( ":benchmark_preview_ci_test", ":benchmark_preview_test", ":benchmark_provenance_test", + ":benchmark_reconcile_ci_test", ":benchmark_regex_test", ":benchmark_shards_test", ":benchmark_test", @@ -1263,6 +1268,129 @@ py_test( deps = [":benchmark_extended_report_lib"], ) +py_library( + name = "benchmark_extended_records_lib", + srcs = ["benchmark_extended_records.py"], + imports = ["."], + deps = [ + ":benchmark_extended_lib", + ":benchmark_extended_report_lib", + ":benchmark_provenance_lib", + ], +) + +py_library( + name = "benchmark_extended_storage_lib", + srcs = ["benchmark_extended_storage.py"], + imports = ["."], +) + +py_library( + name = "benchmark_extended_ci_lib", + srcs = ["benchmark_extended_ci.py"], + imports = ["."], + deps = [ + ":benchmark_extended_build_lib", + ":benchmark_extended_history_lib", + ":benchmark_extended_lib", + ":benchmark_extended_records_lib", + ":benchmark_extended_storage_lib", + ":process_resources_lib", + ], +) + +py_binary( + name = "benchmark_extended_ci", + srcs = ["benchmark_extended_ci.py"], + deps = [ + ":benchmark_extended_build_lib", + ":benchmark_extended_history_lib", + ":benchmark_extended_lib", + ":benchmark_extended_records_lib", + ":benchmark_extended_storage_lib", + ":process_resources_lib", + ], +) + +py_library( + name = "benchmark_extended_site_lib", + srcs = ["benchmark_extended_site.py"], + imports = ["."], + deps = [ + ":benchmark_datasets_lib", + ":benchmark_extended_lib", + ":benchmark_extended_records_lib", + ":benchmark_landscape_lib", + ":benchmark_matrix_lib", + ":benchmark_normalization_lib", + ], +) + +py_binary( + name = "benchmark_extended_site", + srcs = ["benchmark_extended_site.py"], + deps = [ + ":benchmark_datasets_lib", + ":benchmark_extended_lib", + ":benchmark_extended_records_lib", + ":benchmark_landscape_lib", + ":benchmark_matrix_lib", + ":benchmark_normalization_lib", + ], +) + +py_library( + name = "benchmark_extended_test_support_lib", + testonly = True, + srcs = ["benchmark_extended_test_support.py"], + imports = ["."], + deps = [ + ":benchmark_extended_lib", + ":benchmark_extended_records_lib", + ], +) + +py_test( + name = "benchmark_extended_records_test", + size = "small", + srcs = ["benchmark_extended_records_test.py"], + deps = [ + ":benchmark_extended_records_lib", + ":benchmark_extended_test_support_lib", + ], +) + +py_test( + name = "benchmark_extended_storage_test", + size = "small", + srcs = ["benchmark_extended_storage_test.py"], + deps = [ + ":benchmark_extended_storage_lib", + ], +) + +py_test( + name = "benchmark_extended_ci_test", + size = "small", + srcs = ["benchmark_extended_ci_test.py"], + deps = [ + ":benchmark_extended_ci_lib", + ":benchmark_extended_test_support_lib", + ], +) + +py_test( + name = "benchmark_extended_site_test", + size = "small", + srcs = ["benchmark_extended_site_test.py"], + deps = [ + ":benchmark_extended_lib", + ":benchmark_extended_records_lib", + ":benchmark_extended_site_lib", + ":benchmark_extended_test_support_lib", + ], +) + py_binary( name = "benchmark_compare", srcs = ["benchmark_compare.py"], @@ -1371,17 +1499,42 @@ py_library( ], ) +py_library( + name = "benchmark_reconcile_ci_lib", + srcs = ["benchmark_reconcile_ci.py"], + imports = ["."], + deps = [ + ":benchmark_campaign_lib", + ":benchmark_extended_records_lib", + ":benchmark_history_lib", + ":benchmark_preview_ci_lib", + ":benchmark_records_lib", + ], +) + py_binary( name = "benchmark_reconcile_ci", srcs = ["benchmark_reconcile_ci.py"], deps = [ ":benchmark_campaign_lib", + ":benchmark_extended_records_lib", ":benchmark_history_lib", ":benchmark_preview_ci_lib", ":benchmark_records_lib", ], ) +py_test( + name = "benchmark_reconcile_ci_test", + size = "small", + srcs = ["benchmark_reconcile_ci_test.py"], + deps = [ + ":benchmark_extended_records_lib", + ":benchmark_extended_test_support_lib", + ":benchmark_reconcile_ci_lib", + ], +) + py_library( name = "benchmark_recovery_lib", srcs = ["benchmark_recovery.py"], diff --git a/tools/benchmark_datasets.py b/tools/benchmark_datasets.py index 98f5fc335bd..597ee6473f7 100644 --- a/tools/benchmark_datasets.py +++ b/tools/benchmark_datasets.py @@ -36,13 +36,17 @@ def recipe(report, local_contract=None): cache=contract.get('cache'), output=contract.get('output')) if contract.get('layouts'): result['layouts'] = [dict(layout) for layout in contract['layouts']] + if contract.get('suite'): + result.update(suite=contract['suite'], comparison=contract.get('comparison'), + production_target=contract.get('production_target')) return result def build_catalog(root): paths = {*records.run_paths(root), *root.glob('backfills/*/*/*/*/report.json'), - *root.glob('local/*/*/*/report.json')} - paths.update(path.with_suffix('') for pattern in ('backfills/*/*/*/*/report.json.gz', 'local/*/*/*/report.json.gz') + *root.glob('local/*/*/*/report.json'), *root.glob('extended/views/*/*/report.json')} + paths.update(path.with_suffix('') for pattern in ('backfills/*/*/*/*/report.json.gz', 'local/*/*/*/report.json.gz', + 'extended/views/*/*/report.json.gz') for path in root.glob(pattern)) datasets = {} for path in sorted(paths): diff --git a/tools/benchmark_extended_build.py b/tools/benchmark_extended_build.py index d9e5e535e7c..c359eed7172 100644 --- a/tools/benchmark_extended_build.py +++ b/tools/benchmark_extended_build.py @@ -30,11 +30,58 @@ def git(checkout, *args): def clean_source(checkout, sha): if git(checkout, 'rev-parse', 'HEAD') != sha or git(checkout, 'status', '--porcelain'): raise ValueError('historical checkout must exactly match its clean revision') - if (checkout / '.bazelrc.user').exists(): + if any((checkout / name).exists() for name in ('.bazelrc.user', '.bazelrc.user.end')): raise ValueError('workspace Bazel override is forbidden') return {'revision': sha, 'tree': git(checkout, 'rev-parse', 'HEAD^{tree}'), 'clean': True} +def freeze_build(checkout, directory, sha, disk_cache, jobs=6, build_library_path=None): + """Build and immediately freeze one exact checkout without selecting another revision.""" + directory.mkdir(parents=True, exist_ok=True) + executable = directory / 'xff_full' + if (directory / 'build.json').exists(): + saved = json.loads((directory / 'build.json').read_text()) + if saved['source'] != clean_source(checkout, sha) or saved['sha256'] != digest(executable): + raise ValueError('retained executable mismatch: ' + sha) + return saved + started = time.monotonic() + source = clean_source(checkout, sha) + bazel = ['bazel', '--nosystem_rc', '--nohome_rc'] + options = ['--config=clang_release', '--config=xff_full', + '--disk_cache=' + str(disk_cache.resolve()), '--jobs=' + str(jobs)] + if build_library_path: + options.append('--action_env=LD_LIBRARY_PATH=' + str(build_library_path.resolve())) + command = [*bazel, 'build', '//xff/cli:xff_full', *options] + with (directory / 'build.log').open('w') as log: + subprocess.run(command, cwd=checkout, stdout=log, stderr=subprocess.STDOUT, check=True) + query = [*bazel, 'cquery', 'config(//xff/cli:xff_full, target)', *options, + '--output=starlark', '--starlark:expr=target.files_to_run.executable.path'] + artifact = subprocess.check_output(query, cwd=checkout, stderr=log, text=True).strip() + if not artifact or len(artifact.splitlines()) != 1: + raise ValueError('configured executable query is ambiguous') + if clean_source(checkout, sha) != source: + raise ValueError('source identity changed during build') + built = checkout / artifact + binary_hash = digest(built) + shutil.copy2(built, executable) + if digest(executable) != binary_hash: + raise ValueError('frozen executable copy mismatch') + # Link actions record the actual production LTO command for this revision. + with (directory / 'link-actions.json').open('w') as actions: + subprocess.run([*bazel, 'aquery', 'mnemonic("CppLink", //xff/cli:xff_full)', + *options, '--output=jsonproto'], cwd=checkout, stdout=actions, + stderr=log, check=True) + saved = {'source': source, 'sha256': binary_hash, 'bytes': executable.stat().st_size, + 'command': command, 'query': query, 'artifact': artifact, + 'elapsed_seconds': time.monotonic() - started, + 'completed_at': datetime.now(timezone.utc).isoformat(), + 'configuration': {name: digest(checkout / name) for name in + ('.bazelrc', '.bazelversion', 'MODULE.bazel', 'MODULE.bazel.lock', + 'bazelmod/llvm.MODULE.bazel') if (checkout / name).is_file()}} + write(directory / 'build.json', saved) + return saved + + def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--root', type=Path, required=True) @@ -79,40 +126,8 @@ def main(): write(status_path, status) try: subprocess.run(['git', '-C', str(checkout), 'checkout', '--detach', sha], check=True) - source = clean_source(checkout, sha) - bazel = ['bazel', *policy['startup_flags']] - options = ['--config=clang_release', '--config=xff_full', - '--disk_cache=' + str(args.disk_cache.resolve()), '--jobs=' + str(args.jobs)] - if args.build_library_path: - options.append('--action_env=LD_LIBRARY_PATH=' + str(args.build_library_path.resolve())) - command = [*bazel, 'build', '//xff/cli:xff_full', *options] - with (directory / 'build.log').open('w') as log: - subprocess.run(command, cwd=checkout, stdout=log, stderr=subprocess.STDOUT, check=True) - query = [*bazel, 'cquery', 'config(//xff/cli:xff_full, target)', *options, - '--output=starlark', '--starlark:expr=target.files_to_run.executable.path'] - artifact = subprocess.check_output(query, cwd=checkout, stderr=log, text=True).strip() - if not artifact or len(artifact.splitlines()) != 1: - raise ValueError('configured executable query is ambiguous') - if clean_source(checkout, sha) != source: - raise ValueError('source identity changed during build') - built = checkout / artifact - binary_hash = digest(built) - shutil.copy2(built, executable) - if digest(executable) != binary_hash: - raise ValueError('frozen executable copy mismatch') - # Link actions record the actual production LTO command for this revision. - with (directory / 'link-actions.json').open('w') as actions: - subprocess.run([*bazel, 'aquery', 'mnemonic("CppLink", //xff/cli:xff_full)', - *options, '--output=jsonproto'], cwd=checkout, stdout=actions, - stderr=log, check=True) - saved = {'source': source, 'sha256': binary_hash, 'bytes': executable.stat().st_size, - 'command': command, 'query': query, 'artifact': artifact, - 'elapsed_seconds': time.monotonic() - started, - 'completed_at': datetime.now(timezone.utc).isoformat(), - 'configuration': {name: digest(checkout / name) for name in - ('.bazelrc', '.bazelversion', 'MODULE.bazel', 'MODULE.bazel.lock', - 'bazelmod/llvm.MODULE.bazel') if (checkout / name).is_file()}} - write(identity, saved) + saved = freeze_build(checkout, directory, sha, args.disk_cache, args.jobs, args.build_library_path) + binary_hash = saved['sha256'] status[sha] = {'status': 'built', 'sha256': binary_hash, 'seconds': saved['elapsed_seconds']} print(f'BUILT {index}/{len(order)} {sha[:10]} {binary_hash} {saved["elapsed_seconds"]:.1f}s', flush=True) except (subprocess.CalledProcessError, ValueError, OSError) as error: diff --git a/tools/benchmark_extended_build_test.py b/tools/benchmark_extended_build_test.py index 491b0dea477..854bffb61fc 100644 --- a/tools/benchmark_extended_build_test.py +++ b/tools/benchmark_extended_build_test.py @@ -3,6 +3,8 @@ """Keep historical source and binary provenance fail closed without invoking Bazel.""" from pathlib import Path +import json +import shutil import tempfile import unittest from unittest import mock @@ -39,6 +41,48 @@ def test_identity_hash_changes_with_artifact_contents(self): build.write(Path(temporary) / 'identity.json', {'hash': old}) self.assertFalse((Path(temporary) / 'identity.json.tmp').exists()) + def test_each_configured_build_is_copied_before_any_later_bazel_invocation(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + checkout, directory = root / 'checkout', root / 'frozen' + artifact = 'bazel-out/production/bin/xff/cli/xff_full' + built = checkout / artifact + built.parent.mkdir(parents=True) + frozen_bytes = b'synthetic production binary' + events = [] + copy = shutil.copy2 + + def run(command, **kwargs): + if 'build' in command: + events.append('build') + built.write_bytes(frozen_bytes) + else: + events.append('aquery') + self.assertEqual((directory / 'xff_full').read_bytes(), frozen_bytes) + kwargs['stdout'].write(json.dumps({'actions': [ + {'arguments': ['clang++', '-flto=thin', '-o', artifact]}]})) + built.write_bytes(b'alias changed after the frozen copy') + + def query(command, **kwargs): + events.append('configured query') + self.assertIn('--config=clang_release', command) + self.assertIn('--config=xff_full', command) + self.assertIn('config(//xff/cli:xff_full, target)', command) + return artifact + '\n' + + def freeze(source, destination): + events.append('copy') + return copy(source, destination) + + with mock.patch.object(build, 'clean_source', return_value={'revision': 'revision', 'tree': 'tree', 'clean': True}), \ + mock.patch.object(build.subprocess, 'run', side_effect=run), \ + mock.patch.object(build.subprocess, 'check_output', side_effect=query), \ + mock.patch.object(build.shutil, 'copy2', side_effect=freeze): + saved = build.freeze_build(checkout, directory, 'revision', root / 'cache') + self.assertEqual(events, ['build', 'configured query', 'copy', 'aquery']) + self.assertEqual(saved['sha256'], build.digest(directory / 'xff_full')) + self.assertNotEqual(saved['sha256'], build.digest(built)) + if __name__ == '__main__': unittest.main() diff --git a/tools/benchmark_extended_ci.py b/tools/benchmark_extended_ci.py new file mode 100644 index 00000000000..1f723467329 --- /dev/null +++ b/tools/benchmark_extended_ci.py @@ -0,0 +1,163 @@ +# SPDX-FileCopyrightText: Copyright (c) M. Boerger, the MBO Works authors +# SPDX-License-Identifier: Apache-2.0 +"""Freeze paired production builds and collect the separate extended CI contract.""" + +import argparse +from dataclasses import asdict +import json +from pathlib import Path +import platform +import re +import shutil +import subprocess +import sys + +import benchmark_extended as suite +import benchmark_extended_build as build +import benchmark_extended_history as history +import benchmark_extended_records as records +import benchmark_extended_storage as storage +import process_resources + + +def revision(repo): + sha = build.git(repo, 'rev-parse', 'HEAD') + subject = build.git(repo, 'show', '-s', '--format=%s', sha) + numbers = re.findall(r'\(#([0-9]+)\)', subject) + return {'sha': sha, 'date': build.git(repo, 'show', '-s', '--format=%cI', sha), + 'subject': subject, 'pr': int(numbers[-1]) if numbers else 0} + + +def build_endpoint(repo, root, role, disk_cache, jobs): + selected = revision(repo) + build.freeze_build(repo, root / 'binaries' / selected['sha'], selected['sha'], disk_cache, jobs) + history.freeze(root / (role + '.json'), dict(revision=selected, parent=build.git(repo, 'rev-parse', 'HEAD^1'))) + if all((root / (side + '.json')).exists() for side in ('head', 'base')): + head, base = (records.read(root / (side + '.json')) for side in ('head', 'base')) + records.require(head['parent'] == base['revision']['sha'], 'baseline must be the first parent') + history.freeze(root / 'revisions.json', [head['revision'], base['revision']]) + history.freeze(root / 'build-contract.json', {'target': '//xff/cli:xff_full', + 'configs': ['clang_release', 'xff_full'], 'revisions': [head['revision'], base['revision']], + 'drivers': {Path(module).name: build.digest(Path(module)) for module in (__file__, build.__file__)}}) + + +def collect(build_root, root, launcher_path, collection=None, series=None, existing_fixture=None): + platform_name = {'linux': 'linux', 'darwin': 'macos'}.get(sys.platform) + records.require(platform_name is not None, 'requires Linux or macOS') + revisions = records.read(build_root / 'revisions.json') + records.require(len(revisions) == 2, 'requires exactly head and first-parent builds') + builds = {} + for selected in revisions: + directory = build_root / 'binaries' / selected['sha'] + manifest = records.read(directory / 'build.json') + selected.update(binary=str(directory / 'xff_full'), binary_sha256=manifest['sha256'], + build_sha256=build.digest(directory / 'build.json'), + link_actions_sha256=build.digest(directory / 'link-actions.json')) + builds[selected['sha']] = records.build_proof(directory, selected) + head, anchor = revisions + origin = 'ci' if collection is not None else 'local' + records.require(collection is None or existing_fixture is None, 'CI must create its own isolated fixture') + if collection is not None: + records.require(collection['head_sha'] == head['sha'], 'workflow head/build mismatch') + collection = dict(collection, base_sha=anchor['sha']) + series = series or ('github-ci-' + platform_name) + cases = suite.cases() + affinity = history.cpu_allocation(max(case.cpus for case in cases)) + tools = {} + for name in ('find', 'rg', 'tar'): + executable = shutil.which({'find': 'gfind', 'tar': 'gtar'}.get(name, name) if sys.platform == 'darwin' else name) + records.require(executable is not None, 'missing reference tool: ' + name) + path = Path(executable).resolve(strict=True) + tools[name] = {'path': str(path), 'sha256': build.digest(path), + 'version': subprocess.check_output([str(path), '--version'], text=True).strip()} + launcher = process_resources.launcher_identity(launcher_path) + output = root / 'measurements' + output.mkdir(parents=True, exist_ok=True) + controls, control_error = [], None + with storage.fixture(root, existing_fixture) as (fixture, filesystem): + contract = {'recipe': suite.RECIPE, 'cases': [asdict(case) for case in cases], 'revisions': revisions, + 'repetitions': 9, 'keep': 7, 'estimator': 'mean-fastest', 'warmup_rounds': 1, + 'anchor': anchor, 'fixture': str(fixture), + 'filesystem': 'tmpfs' if sys.platform == 'linux' else filesystem, + 'tools': tools, 'launcher': launcher, 'affinity': affinity, 'platform': platform.platform(), + 'comparison': 'parent', 'python': sys.version, + 'drivers': {Path(module).name: build.digest(Path(module)) for module in + (__file__, suite.__file__, history.__file__, storage.__file__, process_resources.__file__)}, + 'build_contract_sha256': build.digest(build_root / 'build-contract.json'), + 'order': 'head cells then identical-parent controls; participant start rotates every round', + 'output_retention': 'all outputs checked; successful hash/count retained; failed output gzip retained'} + history.freeze(output / 'contract.json', contract) + if existing_fixture is None: + suite.prepare(fixture, cases) + manifest = suite.verify(fixture, cases) + history.freeze(output / 'fixtures.json', manifest) + rows = [] + for index, case in enumerate(cases): + print(f'HEAD {index + 1}/{len(cases)} {case.key}', flush=True) + rows.append(history.collect_cell(case, head, anchor, output, fixture, {k: v['path'] for k, v in tools.items()}, + launcher['path'], affinity, index, 9)) + # Head results remain usable if an independent diagnostic control fails. + try: + for index, case in enumerate(cases): + print(f'CONTROL {index + 1}/{len(cases)} {case.key}', flush=True) + controls.append(history.collect_cell(case, anchor, anchor, output, fixture, {k: v['path'] for k, v in tools.items()}, + launcher['path'], affinity, index + len(cases), 9)) + except (ValueError, OSError) as error: + control_error = repr(error) + controls = [] + try: + records.require(suite.verify(fixture, cases) == manifest, 'fixture identity changed') + for selected in revisions: + records.build_proof(build_root / 'binaries' / selected['sha'], selected) + for tool in tools.values(): + records.require(build.digest(Path(tool['path'])) == tool['sha256'], 'reference executable changed') + records.require(process_resources.launcher_identity(launcher_path) == launcher, 'resource launcher changed') + except BaseException as error: + history.write(output / 'invalid.json', {'error': repr(error)}) + raise + record = records.make_record(contract, head, rows, builds, platform_name, platform.machine().lower(), series, + 'parent', origin, collection, controls, + ('https://github.com/' + collection['repository'] + '/actions/runs/' + + str(collection['run_id']) + '/attempts/' + str(collection['run_attempt']) + + ' (benchmark-extended-build-' + platform_name + ')') if collection is not None + else 'original frozen full-production executable used for local paired validation') + record['storage_release'] = records.read(root / 'storage-release.json') + record['control_error'] = control_error + record['identity'] = records.observation_identity(record) + records.validate(record) + history.write(root / 'observation.json', record) + history.write(output / 'progress.json', {'state': 'complete' if control_error is None else 'head-complete-controls-failed', + 'head_cells': len(rows), 'control_cells': len(controls)}) + if control_error is not None: + raise ValueError('head measurements retained; control diagnostic failed: ' + control_error) + return record + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + modes = parser.add_subparsers(dest='mode', required=True) + freeze = modes.add_parser('build') + freeze.add_argument('--repo', type=Path, required=True) + freeze.add_argument('--root', type=Path, required=True) + freeze.add_argument('--disk-cache', type=Path, required=True) + freeze.add_argument('--role', choices=('head', 'base'), required=True) + freeze.add_argument('--jobs', type=int, default=3) + measure = modes.add_parser('collect') + measure.add_argument('--build-root', type=Path, required=True) + measure.add_argument('--root', type=Path, required=True) + measure.add_argument('--resource-launcher', type=Path, required=True) + measure.add_argument('--collection', type=Path, help='Trusted workflow identity JSON; absence means local validation') + measure.add_argument('--series', help='Explicit series for local paired validation') + measure.add_argument('--fixture', type=Path, help='Reuse a verified tmpfs fixture for local validation; never removed') + args = parser.parse_args() + if args.mode == 'build': + build_endpoint(args.repo.resolve(), args.root.resolve(), args.role, args.disk_cache.resolve(), args.jobs) + else: + if args.collection is None and not args.series: + parser.error('local collection requires an explicit --series') + collect(args.build_root.resolve(), args.root.resolve(), args.resource_launcher.resolve(), + records.read(args.collection) if args.collection else None, args.series, args.fixture) + + +if __name__ == '__main__': + main() diff --git a/tools/benchmark_extended_ci_test.py b/tools/benchmark_extended_ci_test.py new file mode 100644 index 00000000000..0c196b22e9a --- /dev/null +++ b/tools/benchmark_extended_ci_test.py @@ -0,0 +1,114 @@ +# SPDX-FileCopyrightText: Copyright (c) M. Boerger, the MBO Works authors +# SPDX-License-Identifier: Apache-2.0 +"""Exercise the complete collector contract with explicitly synthetic samples.""" + +from contextlib import contextmanager +import hashlib +from pathlib import Path +import unittest +from unittest import mock + +import benchmark_extended_ci as ci +from benchmark_extended_test_support import ExtendedFixture + + +class BenchmarkExtendedCiTest(unittest.TestCase): + def setUp(self): + self.fixture = ExtendedFixture() + self.addCleanup(self.fixture.close) + self.root = self.fixture.root / 'collection' + ci.history.write(self.fixture.root / 'revisions.json', self.fixture.revisions) + ci.history.write(self.fixture.root / 'build-contract.json', {'synthetic-only': True}) + self.tools = {} + for name in ('find', 'rg', 'tar'): + path = self.fixture.root / name + path.write_bytes(('synthetic ' + name).encode()) + self.tools[name] = str(path) + self.launcher = self.fixture.root / 'launcher' + self.launcher.write_bytes(b'synthetic native launcher') + self.calls = 0 + self.fail_at = None + self.mutate_at = None + + @contextmanager + def memory(self, root, existing=None): + root.mkdir(parents=True, exist_ok=True) + try: + yield root / 'fixture', {'storage': 'memory', 'filesystem': 'tmpfs'} + finally: + ci.history.write(root / 'storage-release.json', {'synthetic-only': True, 'released_at': '2026-11-08T12:00:00Z'}) + + def invoke(self, spec, wanted, separator, failure): + self.calls += 1 + if self.fail_at == self.calls: + raise ValueError('synthetic failed output') + if self.mutate_at == self.calls: + Path(self.tools['find']).write_bytes(b'changed reference executable') + output = separator.join(wanted) + separator + return {'elapsed_seconds': 1, 'user_cpu_seconds': .5, 'system_cpu_seconds': .2, + 'first_stdout_seconds': .1, 'peak_child_rss_bytes': 1024, 'exit_codes': [0], + 'stderr_bytes': 0, 'stderr': '', 'stdout_bytes': len(output), + 'stdout_sha256': hashlib.sha256(output).hexdigest(), 'validated_records': len(wanted)} + + def collect(self, collection=None, existing=None): + with mock.patch.object(ci.sys, 'platform', 'linux'), \ + mock.patch.object(ci.platform, 'platform', return_value='Linux-synthetic-x86_64'), \ + mock.patch.object(ci.platform, 'machine', return_value='x86_64'), \ + mock.patch.object(ci.shutil, 'which', side_effect=self.tools.get), \ + mock.patch.object(ci.subprocess, 'check_output', return_value='synthetic tool'), \ + mock.patch.object(ci.history, 'cpu_allocation', return_value=[0, 1, 2]), \ + mock.patch.object(ci.history, 'host_context', return_value={'utc': '2026-11-08T12:00:00Z'}), \ + mock.patch.object(ci.history, 'invoke', side_effect=self.invoke), \ + mock.patch.object(ci.storage, 'fixture', side_effect=self.memory), \ + mock.patch.object(ci.process_resources, 'launcher_identity', return_value={ + 'path': str(self.launcher), 'sha256': ci.build.digest(self.launcher), + 'wall_method': 'synthetic wall', 'memory_method': 'synthetic RSS'}): + return ci.collect(self.fixture.root, self.root, self.launcher, collection, 'synthetic-only', existing) + + def test_complete_head_and_identical_parent_controls_are_retained_after_final_checks(self): + value = self.collect() + self.assertEqual(value['origin'], 'local') + self.assertEqual(value['comparison'], 'parent') + self.assertEqual(len(value['rows']), len(self.fixture.cases)) + self.assertEqual(len(value['controls']), len(self.fixture.cases)) + self.assertEqual(self.calls, len(self.fixture.cases) * 30 * 2) + self.assertEqual(value['controls'][0]['revision'], value['contract']['anchor']) + self.assertTrue((self.root / 'observation.json').exists()) + self.assertEqual(ci.records.read(self.root / 'measurements/progress.json')['state'], 'complete') + + def test_control_failure_retains_complete_head_and_failed_attempt_diagnostics(self): + self.fail_at = len(self.fixture.cases) * 30 + 1 + with self.assertRaisesRegex(ValueError, 'head measurements retained'): + self.collect() + value = ci.records.read(self.root / 'observation.json') + ci.records.validate(value) + self.assertEqual(value['controls'], []) + self.assertIn('synthetic failed output', value['control_error']) + self.assertTrue(list((self.root / 'measurements/cells').glob('**/failure.json'))) + self.assertEqual(ci.records.read(self.root / 'measurements/progress.json')['state'], 'head-complete-controls-failed') + + def test_head_failure_never_creates_publishable_observation(self): + self.fail_at = 1 + with self.assertRaisesRegex(ValueError, 'synthetic failed output'): + self.collect() + self.assertFalse((self.root / 'observation.json').exists()) + self.assertTrue((self.root / 'storage-release.json').exists()) + + def test_changed_reference_invalidates_whole_collection_after_successful_samples(self): + self.mutate_at = len(self.fixture.cases) * 30 * 2 + with self.assertRaisesRegex(ValueError, 'reference executable changed'): + self.collect() + self.assertFalse((self.root / 'observation.json').exists()) + self.assertTrue((self.root / 'measurements/invalid.json').exists()) + + def test_ci_cannot_reuse_a_local_fixture_or_measure_a_different_head(self): + collection = self.fixture.record(origin='ci', comparison='parent')['collection'] + with self.assertRaisesRegex(ValueError, 'own isolated fixture'): + self.collect(collection, Path('/shared-fixture')) + with self.assertRaisesRegex(ValueError, 'workflow head/build mismatch'): + self.collect(dict(collection, head_sha='f' * 40)) + self.assertEqual(self.calls, 0) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/benchmark_extended_history.py b/tools/benchmark_extended_history.py index 27a8f35b3ac..475af6be245 100644 --- a/tools/benchmark_extended_history.py +++ b/tools/benchmark_extended_history.py @@ -49,6 +49,10 @@ def freeze(path, value): def cpu_allocation(count): """Prefer separate physical cores over sibling hardware threads.""" + if sys.platform == 'darwin': + if count > (os.cpu_count() or 0): + raise ValueError('worker count exceeds available logical CPUs') + return [] cores = {} for cpu in sorted(os.sched_getaffinity(0)): topology = Path(f'/sys/devices/system/cpu/cpu{cpu}/topology') @@ -65,7 +69,8 @@ def worker(connection, spec, wanted, separator, failure_path): sample = None try: os.setsid() - os.sched_setaffinity(0, spec['affinity']) + if spec['affinity']: + os.sched_setaffinity(0, spec['affinity']) with open(spec['stdin'], 'rb') as source: sample = process_resources.measure_pipeline( [spec['command']], source=source, cwd=spec['cwd'], environment=spec['environment'], @@ -110,6 +115,10 @@ def invoke(spec, wanted, separator, failure_path, timeout=120): def host_context(): + if sys.platform == 'darwin': + return {'utc': datetime.now(timezone.utc).isoformat(), 'loadavg': os.getloadavg(), + 'vm_stat': subprocess.check_output(['vm_stat'], text=True), + 'memory_pressure': subprocess.check_output(['sysctl', 'vm.swapusage'], text=True)} return {'utc': datetime.now(timezone.utc).isoformat(), 'loadavg': os.getloadavg(), 'meminfo': Path('/proc/meminfo').read_text(), 'cpu_stat': Path('/proc/stat').read_text(), diff --git a/tools/benchmark_extended_records.py b/tools/benchmark_extended_records.py new file mode 100644 index 00000000000..770c6ed1f11 --- /dev/null +++ b/tools/benchmark_extended_records.py @@ -0,0 +1,249 @@ +# SPDX-FileCopyrightText: Copyright (c) M. Boerger, the MBO Works authors +# SPDX-License-Identifier: Apache-2.0 +"""Validate and retain extended observations separately from core benchmark history.""" + +from dataclasses import asdict +from datetime import datetime +from functools import lru_cache +import gzip +import hashlib +import json +import math +from pathlib import Path +import re +import tempfile + +import benchmark_extended as suite +import benchmark_extended_report as report +import benchmark_provenance + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +def identity(value): + return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(',', ':'), allow_nan=False).encode()).hexdigest() + + +def read(path): + return json.loads(path.read_text() if path.exists() else gzip.decompress(path.with_suffix('.json.gz').read_bytes())) + + +def digest(path): + with path.open('rb') as source: + return hashlib.file_digest(source, 'sha256').hexdigest() + + +def observation_identity(record): + # Retention attestations and trusted publication metadata can be added later; + # the observation identity binds the original contract, builds and raw rows. + return identity({key: value for key, value in record.items() + if key not in ('identity', 'view_verification', 'publication_source')}) + + +def build_proof(directory, revision): + manifest = read(directory / 'build.json') + require(manifest['source']['revision'] == revision['sha'] and manifest['source']['clean'] is True, + 'build source identity changed') + require(digest(directory / 'build.json') == revision['build_sha256'] + and digest(directory / 'link-actions.json') == revision['link_actions_sha256'], 'build evidence changed') + require(digest(directory / 'xff_full') == manifest['sha256'] == revision['binary_sha256'], 'frozen binary changed') + links = [action['arguments'] for action in read(directory / 'link-actions.json')['actions'] + if manifest['artifact'] in action.get('arguments', [])] + require(len(links) == 1 and '-flto=thin' in links[0], 'production ThinLTO link evidence missing') + require(all(flag in manifest['command'] for flag in ('//xff/cli:xff_full', '--config=clang_release', '--config=xff_full')), + 'full production configuration missing') + return {'manifest': manifest, 'link_arguments': links[0], + 'manifest_sha256': revision['build_sha256'], 'link_actions_sha256': revision['link_actions_sha256']} + + +@lru_cache(maxsize=256) +def oracle(name, shape, files, fixture): + wanted = suite.expected(suite.Case(name, shape, files, 1), Path(fixture)) + return len(wanted), hashlib.sha256(b'\0'.join(sorted(wanted))).hexdigest(), sum(map(len, wanted)) + len(wanted) + + +@lru_cache(maxsize=64) +def fixture_identity(shape, files, archive): + return suite.fixture_identity(shape, files, archive) + + +def validate_rows(rows, contract, revision): + expected = {suite.Case(**value).key: value for value in contract['cases']} + require(len(rows) == len(expected), 'incomplete extended revision') + seen = set() + for row in rows: + case = suite.Case(**row['case']) + require(case.key in expected and case.key not in seen and row['case'] == expected[case.key], + 'unexpected or repeated extended cell') + seen.add(case.key) + require(row['revision'] == revision and row['anchor'] == contract['anchor'], 'cell provenance changed') + count, expected_hash, expected_bytes = oracle(case.name, case.shape, case.files, contract['fixture']) + require(row['expected_records'] == count and row['expected_sha256'] == expected_hash, 'cell oracle changed') + commands, cwd, _ = suite.commands(case, Path(contract['fixture']), revision['binary'], + {key: value['path'] for key, value in contract['tools'].items()}) + anchor, _, _ = suite.commands(case, Path(contract['fixture']), contract['anchor']['binary'], + {key: value['path'] for key, value in contract['tools'].items()}) + commands['anchor'] = anchor['xff'] + require(row['commands'] == commands and row['cwd'] == str(cwd) + and row['affinity'] == contract['affinity'][:case.cpus], 'cell commands or allocation changed') + require(row['environment'] == {'PATH': '/usr/bin:/bin', 'HOME': str(Path(contract['fixture']) / 'home'), + 'LC_ALL': 'C', 'TZ': 'UTC'}, 'cell environment changed') + samples = row['samples'] + keys = {(label, phase, round_number) for label in commands + for phase, rounds in [('warmup', [0]), ('measured', range(1, contract['repetitions'] + 1))] + for round_number in rounds} + require(len(samples) == len(keys) and {(s['label'], s['phase'], s['round']) for s in samples} == keys, + 'incomplete or repeated extended samples') + for sample in samples: + require(sample['exit_codes'] == [0] and sample['stderr_bytes'] == 0 and not sample.get('stderr') + and sample['validated_records'] == count and sample['stdout_bytes'] == expected_bytes, + 'unsuccessful or unvalidated extended sample') + require(re.fullmatch('[0-9a-f]{64}', sample['stdout_sha256']) is not None, 'missing output hash') + for field in ('elapsed_seconds', 'user_cpu_seconds', 'system_cpu_seconds', 'peak_child_rss_bytes'): + value = sample[field] + require(type(value) in (int, float) and math.isfinite(value) and value >= 0, 'invalid resource sample') + require(sample['elapsed_seconds'] > 0, 'invalid elapsed sample') + # Also validate the existing chart's estimator and complete participant populations. + adapted = report.as_report([row], contract, 'traditional') + for task in adapted['tasks']: + for participant in task['participants'].values(): + report.matrix.selected_samples(adapted, participant) + + +def validate(record, source=None): + require(record.get('schema') == 1 and record.get('kind') == 'extended-production', 'unsupported extended record') + require(record['identity'] == observation_identity(record), + 'extended record identity changed') + contract = record['contract'] + require(contract['recipe'] == suite.RECIPE and contract['cases'] == [asdict(case) for case in suite.cases()], + 'extended workload contract changed') + require(contract['warmup_rounds'] == 1 and contract['estimator'] == 'mean-fastest' + and (contract['repetitions'], contract['keep']) == (9, 7), 'invalid extended sampling') + require(record['comparison'] in ('parent', 'fixed-anchor'), 'unknown extended comparison') + require(record['origin'] in ('local', 'ci') and re.fullmatch('[A-Za-z0-9_.-]+', record['series']), 'invalid extended series') + require(record['platform'] in ('linux', 'macos'), 'unknown extended platform') + require(record['machine'] in contract['platform'].lower(), 'extended architecture does not match platform') + require(contract['filesystem'] == 'tmpfs' if record['platform'] == 'linux' else + isinstance(contract['filesystem'], dict) and contract['filesystem'].get('storage') == 'memory', + 'unverified memory fixture') + affinity = contract['affinity'] + require((len(affinity) >= 3 and len(affinity) == len(set(affinity)) + and all(type(cpu) is int and cpu >= 0 for cpu in affinity)) if record['platform'] == 'linux' + else affinity == [], 'invalid extended CPU allocation') + revision = record['revision'] + require(revision in contract['revisions'], 'revision not in extended contract') + for entry in (revision, contract['anchor']): + require(re.fullmatch('[0-9a-f]{40}', entry['sha']) is not None, 'invalid revision') + require(datetime.fromisoformat(entry['date']).tzinfo is not None, 'revision date needs a timezone') + proof = record['builds'][entry['sha']] + manifest = proof['manifest'] + require(manifest['source']['revision'] == entry['sha'] and manifest['source']['clean'] is True + and manifest['sha256'] == entry['binary_sha256'] + and proof['manifest_sha256'] == entry['build_sha256'] + and proof['link_actions_sha256'] == entry['link_actions_sha256'], 'binary/build identity mismatch') + require('-flto=thin' in proof['link_arguments'] and manifest['artifact'] in proof['link_arguments'] + and all(flag in manifest['command'] for flag in ('//xff/cli:xff_full', '--config=clang_release', '--config=xff_full')), + 'production link/configuration evidence missing') + if record['origin'] == 'ci': + collection = record['collection'] + require(all(type(collection[key]) is int and collection[key] > 0 for key in ('run_id', 'run_attempt')), + 'invalid extended workflow identity') + require(record['comparison'] == 'parent' and collection['head_sha'] == revision['sha'] + and collection['base_sha'] == contract['anchor']['sha'], 'CI revision/parent changed') + if source is not None: + require(source['name'] == 'Benchmarks' and source['path'] == '.github/workflows/benchmarks.yml' + and source['event'] == 'push' and source['head_branch'] == 'main' and source['status'] == 'completed', + 'untrusted extended workflow') + require(all(str(collection[key]) == str(source[field]) for key, field in + [('run_id', 'id'), ('run_attempt', 'run_attempt'), ('head_sha', 'head_sha')]), + 'extended workflow provenance mismatch') + require(collection['repository'] == source['repository']['full_name'], 'extended repository mismatch') + require(source['base_sha'] == contract['anchor']['sha'], 'extended anchor is not the trusted first parent') + validate_rows(record['rows'], contract, revision) + if record.get('controls'): + validate_rows(record['controls'], contract, contract['anchor']) + for view in ('traditional', 'anchor'): + checked = benchmark_provenance.describe(view_report(record, view), record['view_verification'][view]) + require(checked['state'] == 'Verified against Retention', checked['reason']) + + +def view_report(record, view): + adapted = report.as_report(record['rows'], record['contract'], view) + anchor = record['contract']['anchor'] + tools = {key: dict(value, status='available') for key, value in record['contract']['tools'].items()} + tools['xff'] = {'path': record['revision']['binary'], 'sha256': record['revision']['binary_sha256'], 'status': 'available'} + if view == 'anchor': + old = next(key for key in adapted['tasks'][0]['participants'] if key != 'xff') + name = 'xff-parent' if record['comparison'] == 'parent' else old + tools[name] = {'path': anchor['binary'], 'sha256': anchor['binary_sha256'], 'status': 'available'} + if name != old: + for task in adapted['tasks']: + task['participants'][name] = task['participants'].pop(old) + adapted['tools'] = tools + for task in adapted['tasks']: + task['fixture_identity'] = fixture_identity(task['dataset'], task['files'], task['name'] == 'archive-tar') + adapted['contract'].update(comparison=record['comparison'] if view == 'anchor' else 'traditional', + production_target='//xff/cli:xff_full', + machine=record['machine'], warmup_rounds=1, + storage={'filesystem': 'tmpfs' if record['platform'] == 'linux' else 'case-sensitive HFS+', + 'memory_required': True}, + resource_accounting={key: record['contract']['launcher'][key] + for key in ('wall_method', 'memory_method')}) + # Empty means unpinned workers, never an enforceable zero-length mask. + if not record['contract']['affinity']: + adapted['contract']['affinity_by_cpu_count'] = {str(n): None for n in adapted['contract']['cpu_counts']} + source = record.get('publication_source', {'head_sha': record['revision']['sha']}) + source = dict(source, reference_time=record['revision']['date']) + if record['origin'] == 'local': + source = dict(source, created_at=record['completed_at'], reference_time=record['revision']['date'], + event='local', head_branch='local') + result = {'schema': 1, 'kind': 'extended-production-view', 'head': record['revision']['sha'], + 'base': anchor['sha'], 'revision': record['revision'], 'platform': record['platform'], + 'series': record['series'], 'completed_at': record['completed_at'], + 'source': source, 'build': record['builds'][record['revision']['sha']]['manifest'], + 'tool_comparisons': adapted} + if record['origin'] == 'local': + result.update(kind='backfill', purpose='local-addition') + if view in record.get('view_verification', {}): + result['binary_verification'] = record['view_verification'][view] + return result + + +def make_record(contract, revision, rows, builds, platform, machine, series, comparison, + origin='local', collection=None, controls=None, retention_reference='original frozen executable'): + result = {'schema': 1, 'kind': 'extended-production', 'contract': contract, 'revision': revision, + 'rows': rows, 'builds': builds, 'platform': platform, 'machine': machine, 'series': series, + 'comparison': comparison, 'origin': origin, 'collection': collection, + 'controls': controls or [], 'completed_at': max(row['after']['utc'] for row in rows)} + result['view_verification'] = {} + for view in ('traditional', 'anchor'): + wrapper = view_report(result, view) + with Path(revision['binary']).open('rb') as executable: + result['view_verification'][view] = benchmark_provenance.verify_retention(wrapper, executable, retention_reference) + result['identity'] = observation_identity(result) + validate(result) + return result + + +def retain(root, record, source=None): + require(record['origin'] != 'ci' or source is not None, 'CI retention requires trusted workflow metadata') + validate(record, source) + if source is not None: + record = dict(record, publication_source={key: source[key] for key in + ('id', 'run_attempt', 'created_at', 'head_sha', 'head_branch', 'event', + 'name', 'path', 'status', 'repository', 'base_sha')}) + destination = root / 'extended' / 'observations' / record['identity'] / 'report.json' + if destination.exists() or destination.with_suffix('.json.gz').exists(): + require(observation_identity(read(destination)) == observation_identity(record), + 'retained extended observation cannot change') + return destination + destination.parent.parent.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory(prefix='.extended-', dir=destination.parent.parent) as temporary: + stage = Path(temporary) / 'complete' + stage.mkdir() + (stage / 'report.json').write_text(json.dumps(record, allow_nan=False) + '\n') + stage.rename(destination.parent) + return destination diff --git a/tools/benchmark_extended_records_test.py b/tools/benchmark_extended_records_test.py new file mode 100644 index 00000000000..708610010ae --- /dev/null +++ b/tools/benchmark_extended_records_test.py @@ -0,0 +1,87 @@ +# SPDX-FileCopyrightText: Copyright (c) M. Boerger, the MBO Works authors +# SPDX-License-Identifier: Apache-2.0 +"""Reject incomplete, changed, incompatible or incorrectly attributed observations.""" + +import copy +import gzip +import json +import unittest + +import benchmark_extended_records as records +from benchmark_extended_test_support import ExtendedFixture + + +class BenchmarkExtendedRecordsTest(unittest.TestCase): + def setUp(self): + self.fixture = ExtendedFixture() + self.addCleanup(self.fixture.close) + + def test_real_retained_bytes_bind_both_views_without_claiming_ci(self): + value = self.fixture.record() + records.validate(value) + for view in ('traditional', 'anchor'): + wrapper = records.view_report(value, view) + self.assertEqual(records.benchmark_provenance.describe(wrapper)['state'], 'Verified against Retention') + self.assertEqual(wrapper['source']['event'], 'local') + directory = self.fixture.root / 'binaries' / value['revision']['sha'] + (directory / 'xff_full').write_bytes(b'changed') + with self.assertRaisesRegex(ValueError, 'frozen binary changed'): + records.build_proof(directory, value['revision']) + + def test_changed_rows_oracle_commands_environment_and_sampling_are_rejected(self): + value = self.fixture.record() + mutations = [lambda r: r['rows'].pop(), + lambda r: r['rows'][0]['samples'].pop(), + lambda r: r['rows'][0].update(expected_records=999), + lambda r: r['rows'][0]['environment'].update(TZ='wrong'), + lambda r: r['rows'][0]['commands']['xff'].append('--wrong'), + lambda r: r['rows'][0]['samples'][0].update(stdout_bytes=1), + lambda r: r['rows'][0]['samples'][0].update(exit_codes=[1]), + lambda r: r['contract'].update(keep=8), + lambda r: r['builds'][r['revision']['sha']]['link_arguments'].remove('-flto=thin')] + for mutate in mutations: + with self.subTest(mutation=mutate): + changed = copy.deepcopy(value) + mutate(changed) + changed['identity'] = records.observation_identity(changed) + with self.assertRaises(ValueError): + records.validate(changed) + + def test_parent_label_is_stable_and_concrete_parent_is_retained(self): + value = self.fixture.record(origin='ci', comparison='parent') + records.validate(value, self.fixture.source()) + view = records.view_report(value, 'anchor') + self.assertEqual(set(view['tool_comparisons']['tasks'][0]['participants']), {'xff', 'xff-parent'}) + self.assertEqual(view['base'], self.fixture.revisions[-1]['sha']) + + def test_ci_requires_actual_run_attempt_repository_and_first_parent(self): + value = self.fixture.record(origin='ci', comparison='parent') + for change in ({'head_sha': 'f' * 40}, {'base_sha': 'f' * 40}, {'run_attempt': 2}, + {'event': 'pull_request'}, {'repository': {'full_name': 'other/repo'}}): + with self.subTest(change=change), self.assertRaises(ValueError): + records.validate(value, dict(self.fixture.source(), **change)) + + def test_idempotent_retention_preserves_original_proof_and_supports_packed_records(self): + value = self.fixture.record() + root = self.fixture.root / 'site' + path = records.retain(root, value) + before = path.read_bytes() + repeated = self.fixture.record() + self.assertEqual(repeated['identity'], value['identity']) + records.retain(root, repeated) + self.assertEqual(path.read_bytes(), before) + path.with_suffix('.json.gz').write_bytes(gzip.compress(before, mtime=0)) + path.unlink() + records.retain(root, repeated) + self.assertEqual(records.read(path), json.loads(before)) + + def test_complete_head_can_be_retained_from_failed_workflow(self): + value = self.fixture.record(origin='ci', comparison='parent') + path = records.retain(self.fixture.root / 'site', value, self.fixture.source()) + saved = records.read(path) + self.assertEqual(saved['publication_source']['id'], 12) + self.assertEqual(saved['origin'], 'ci') + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/benchmark_extended_site.py b/tools/benchmark_extended_site.py new file mode 100644 index 00000000000..24448d4b7ca --- /dev/null +++ b/tools/benchmark_extended_site.py @@ -0,0 +1,161 @@ +# SPDX-FileCopyrightText: Copyright (c) M. Boerger, the MBO Works authors +# SPDX-License-Identifier: Apache-2.0 +"""Import measured extended history and expose it in the existing benchmark viewer.""" + +import argparse +from datetime import datetime, timezone +import html +import json +import os +from pathlib import Path + +import benchmark_datasets +import benchmark_extended as suite +import benchmark_extended_records as records +import benchmark_landscape as landscape +import benchmark_matrix as matrix +import benchmark_normalization + + +def import_campaign(campaign, builds_root, root, series, platform, machine): + records.require(not (campaign / 'invalid.json').exists(), 'campaign integrity check failed') + progress = records.read(campaign / 'progress.json') + records.require(progress['state'] == 'complete' and progress['completed'] == progress['total'], + 'campaign is incomplete') + contract = records.read(campaign / 'contract.json') + records.require(progress['total'] == len(contract['cases']) * len(contract['revisions']), 'campaign grid changed') + anchor = contract['anchor'] + anchor_build = records.build_proof(builds_root / 'binaries' / anchor['sha'], anchor) + result = [] + for revision in contract['revisions']: + rows = [records.read(campaign / 'cells' / revision['sha'] / suite.Case(**case).key / 'complete.json') + for case in contract['cases']] + builds = {anchor['sha']: anchor_build, + revision['sha']: records.build_proof(builds_root / 'binaries' / revision['sha'], revision)} + record = records.make_record(contract, revision, rows, builds, platform, machine, series, 'fixed-anchor', + retention_reference=str(builds_root / 'binaries' / revision['sha'] / 'xff_full') + + ' (original retained production executable)') + path = records.retain(root, record) + result.append(path) + print(f'IMPORTED {len(result)}/{len(contract["revisions"])} {revision["sha"]} {path}', flush=True) + return result + + +def descriptor(record, view): + return {'series': record['series'], 'origin': record['origin'], 'platform': record['platform'], + 'machine': record['machine'], 'recipe': record['contract']['recipe'], 'view': view, + 'comparison': record['comparison'], + 'fixed_anchor': record['contract']['anchor']['sha'] if record['comparison'] == 'fixed-anchor' else None, + 'target': '//xff/cli:xff_full', 'repetitions': 9, 'keep': 7, + 'storage': 'tmpfs' if record['platform'] == 'linux' else 'case-sensitive HFS+ RAM', + 'accounting': {key: record['contract']['launcher'][key] for key in ('wall_method', 'memory_method')}} + + +def retained(root): + paths = {*root.glob('extended/observations/*/report.json'), + *(path.with_suffix('') for path in root.glob('extended/observations/*/report.json.gz'))} + selected = {} + for path in sorted(paths): + record = records.read(path) + records.require(record['origin'] != 'ci' or record.get('publication_source'), + 'CI publication requires trusted workflow metadata') + records.validate(record, record.get('publication_source')) + for view in ('traditional', 'anchor'): + description = descriptor(record, view) + series_id = records.identity(description) + key = series_id, record['revision']['sha'] + collection = record.get('collection') or {} + rank = (record['completed_at'], int(collection.get('run_id', 0)), int(collection.get('run_attempt', 0)), record['identity']) + if key not in selected or rank > selected[key][0]: + selected[key] = (rank, path, record, view, description) + return sorted(selected.values(), key=lambda item: (item[2]['revision']['date'], item[2]['revision']['sha'], item[3])) + + +def publish(root, repository='mboworks/xff'): + selected = retained(root) + catalog_path = root / 'catalog.json' + catalog = [item for item in records.read(catalog_path) if not item.get('extended')] if catalog_path.exists() else [] + links_path = root / 'version-links.json' + version_links = records.read(links_path) if links_path.exists() else {} + groups = {} + for _, path, record, view, description in selected: + series_id = records.identity(description) + wrapper = records.view_report(record, view) + groups.setdefault(series_id, []).append((path, record, view, description, wrapper)) + for series_id, group in groups.items(): + order = landscape.common_task_order([matrix.relative_results(wrapper['tool_comparisons']) for *_, wrapper in group]) + windows = benchmark_normalization.reference_windows([(record['identity'], wrapper) for _, record, _, _, wrapper in group]) + for path, record, view, description, wrapper in group: + revision = record['revision'] + anchor = record['contract']['anchor'] + directory = root / 'extended' / 'views' / series_id / revision['sha'] + directory.mkdir(parents=True, exist_ok=True) + (directory / 'report.json').write_text(json.dumps(wrapper, allow_nan=False) + '\n') + report = wrapper['tool_comparisons'] + normalization = windows.get(record['identity'], {}) + data = landscape.figures(report, normalization, order) + (directory / 'landscape.json').write_text(json.dumps(data, allow_nan=False) + '\n') + (directory / 'normalization.json').write_text(json.dumps(normalization, allow_nan=False) + '\n') + document = landscape.render(report, '', normalization, order) + asset = html.escape(os.path.relpath(root / 'assets/three-landscape.js', directory), quote=True) + document = document.replace('', f'') + raw = html.escape(os.path.relpath(path, directory), quote=True) + title = ('Traditional tools' if view == 'traditional' else 'Parent XFF' if record['comparison'] == 'parent' + else 'Fixed XFF ' + anchor['sha'][:12]) + introduction = ('

Extended production suite: ' + suite.RECIPE + '; full production CLI. ' + + html.escape(title) + '. Original observations and provenance.

') + document = document.replace('', '' + introduction, 1) + (directory / 'index.html').write_text(document) + local = record['origin'] == 'local' + experiment = local and revision['pr'] <= 0 + label = ('Experiment ' + revision['sha'][:12] if experiment else + ('PR #' + str(revision['pr']) if revision['pr'] > 0 else 'main') + ' / ' + revision['sha'][:10]) + links = version_links.get(revision['sha'], []) + if not links and revision['pr'] > 0: + links = [{'label': 'PR #' + str(revision['pr']), 'href': f'https://github.com/{repository}/pull/{revision["pr"]}'}] + links = [*links, {'label': ('Parent ' if record['comparison'] == 'parent' else 'Fixed reference ') + anchor['sha'][:12], + 'href': f'https://github.com/{repository}/commit/{anchor["sha"]}'}] + origin_label = 'Local / ' + record['series'] if local else 'CI / ' + record['platform'] + collection = record.get('collection') or {} + catalog.append({'platform': origin_label + ' / ' + suite.RECIPE + ' / ' + title, + 'platform_name': record['platform'] + ' / ' + record['machine'], + 'dataset_type': suite.RECIPE + ' / ' + title, 'commit': revision['sha'], + 'label': label, 'date': datetime.fromisoformat(revision['date']).astimezone(timezone.utc).isoformat(), + 'measurement_date': record['completed_at'], + 'source': 'experiment' if experiment else 'merged', + 'source_label': 'Unmerged experiments' if experiment else 'Merged history', + 'comparison_role': ('Parent ' if record['comparison'] == 'parent' else 'Fixed reference ') + anchor['sha'][:12], + 'local': local, 'extended': True, 'series_id': series_id, + 'measured': record['completed_at'], 'run': collection.get('run_id', 0), + 'attempt': collection.get('run_attempt', 0), 'links': links, + 'binary': landscape.binary_evidence(directory / 'report.json', wrapper), + 'report': directory.relative_to(root).as_posix() + '/', + 'figures': (directory / 'landscape.json').relative_to(root).as_posix(), + 'identity': record['contract']['platform'] + ' / ' + suite.RECIPE, + 'platform_details': record['contract']['platform']}) + catalog_path.write_text(json.dumps(catalog, indent=2, allow_nan=False) + '\n') + landscape.publish_history(root, catalog) + benchmark_datasets.publish(root) + return len(selected) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + modes = parser.add_subparsers(dest='mode', required=True) + importer = modes.add_parser('import') + for name in ('campaign', 'build-root', 'root'): + importer.add_argument('--' + name, type=Path, required=True) + for name in ('series', 'platform', 'machine'): + importer.add_argument('--' + name, required=True) + renderer = modes.add_parser('publish') + renderer.add_argument('--root', type=Path, required=True) + renderer.add_argument('--repository', default='mboworks/xff') + args = parser.parse_args() + if args.mode == 'import': + import_campaign(args.campaign, args.build_root, args.root, args.series, args.platform, args.machine) + else: + print(f'Published {publish(args.root, args.repository)} extended views') + + +if __name__ == '__main__': + main() diff --git a/tools/benchmark_extended_site_test.py b/tools/benchmark_extended_site_test.py new file mode 100644 index 00000000000..7cc5de3430a --- /dev/null +++ b/tools/benchmark_extended_site_test.py @@ -0,0 +1,112 @@ +# SPDX-FileCopyrightText: Copyright (c) M. Boerger, the MBO Works authors +# SPDX-License-Identifier: Apache-2.0 +"""Keep extended observations separate, measured, discoverable and reproducible.""" + +import gzip +import json +import unittest + +import benchmark_extended as suite +import benchmark_extended_records as records +import benchmark_extended_site as site +from benchmark_extended_test_support import ExtendedFixture + + +class BenchmarkExtendedSiteTest(unittest.TestCase): + def setUp(self): + self.fixture = ExtendedFixture() + self.addCleanup(self.fixture.close) + self.root = self.fixture.root / 'site' + self.root.mkdir() + (self.root / 'index.html').write_text('

Benchmarks

') + self.core = {'platform': 'CI core linux', 'commit': 'c' * 40, 'date': '2026-11-01T00:00:00Z', + 'report': 'runs/core/linux/', 'figures': 'runs/core/linux/landscape.json', + 'label': 'Core fixture', 'source': 'merged'} + (self.root / 'catalog.json').write_text(json.dumps([self.core])) + + def test_both_views_join_existing_browser_without_replacing_core_or_inventing_points(self): + value = self.fixture.record() + records.retain(self.root, value) + self.assertEqual(site.publish(self.root), 2) + catalog = records.read(self.root / 'catalog.json') + self.assertEqual(catalog[0], self.core) + self.assertEqual(len(catalog), 3) + self.assertIn('XffBenchmarkHistory', (self.root / 'index.html').read_text()) + for entry in catalog[1:]: + self.assertTrue(entry['local']) + self.assertTrue(entry['extended']) + self.assertEqual(entry['binary']['state'], 'Verified against Retention') + wrapper = records.read(self.root / entry['report'] / 'report.json') + self.assertEqual(len(wrapper['tool_comparisons']['tasks']), len(self.fixture.cases)) + data = records.read(self.root / entry['figures']) + surfaces = [trace for trace in data['alphabetical']['percent']['data'] if trace['type'] == 'surface'] + heights = [height for trace in surfaces for row in trace['y'] for height in row if height is not None] + expected = 50 if 'Traditional tools' in entry['platform'] else -100 + self.assertEqual(heights, [expected] * len(self.fixture.cases)) + datasets = records.read(self.root / 'datasets.json')['datasets'] + self.assertEqual(len(datasets), 2) + self.assertEqual({item['kind'] for item in datasets}, {'local'}) + self.assertTrue(all(item['recipe']['suite'] == suite.RECIPE for item in datasets)) + + def test_repeated_publication_and_packed_observations_are_idempotent(self): + path = records.retain(self.root, self.fixture.record()) + site.publish(self.root) + before = records.read(self.root / 'catalog.json') + path.with_suffix('.json.gz').write_bytes(gzip.compress(path.read_bytes(), mtime=0)) + path.unlink() + site.publish(self.root) + self.assertEqual(records.read(self.root / 'catalog.json'), before) + page = (self.root / 'index.html').read_text() + self.assertEqual(page.count(''), 1) + self.assertEqual(page.count(''), 1) + + def test_changing_ci_parent_keeps_same_dataset_and_exposes_actual_parent_per_version(self): + first = self.fixture.record(origin='ci', comparison='parent') + records.retain(self.root, first, self.fixture.source()) + self.fixture.revisions.reverse() + self.fixture.contract['anchor'] = self.fixture.revisions[-1] + second = self.fixture.record(origin='ci', comparison='parent') + records.retain(self.root, second, self.fixture.source()) + self.assertEqual(site.publish(self.root), 4) + catalog = [item for item in records.read(self.root / 'catalog.json') if item.get('extended')] + self.assertEqual(len({item['series_id'] for item in catalog}), 2) + self.assertEqual({item['comparison_role'] for item in catalog}, {'Parent ' + 'a' * 12, 'Parent ' + 'b' * 12}) + for entry in catalog: + self.assertFalse(entry['local']) + self.assertEqual(entry['source'], 'merged') + self.assertIn('github.com/mboworks/xff/commit/', entry['links'][-1]['href']) + self.assertEqual(len(records.read(self.root / 'datasets.json')['datasets']), 2) + + def test_publication_rejects_untrusted_ci_and_modified_raw_rows(self): + value = self.fixture.record(origin='ci', comparison='parent') + with self.assertRaisesRegex(ValueError, 'trusted workflow metadata'): + records.retain(self.root, value) + path = records.retain(self.root, value, self.fixture.source()) + changed = records.read(path) + changed['publication_source']['event'] = 'pull_request' + path.write_text(json.dumps(changed)) + with self.assertRaisesRegex(ValueError, 'untrusted extended workflow'): + site.publish(self.root) + + def test_import_uses_complete_original_cells_and_actual_retained_builds(self): + campaign = self.fixture.root / 'campaign' + campaign.mkdir() + (campaign / 'contract.json').write_text(json.dumps(self.fixture.contract)) + total = len(self.fixture.cases) * len(self.fixture.revisions) + progress = {'state': 'complete', 'completed': total, 'total': total} + (campaign / 'progress.json').write_text(json.dumps(progress)) + for revision in self.fixture.revisions: + for row in self.fixture.rows(revision): + path = campaign / 'cells' / revision['sha'] / suite.Case(**row['case']).key / 'complete.json' + path.parent.mkdir(parents=True) + path.write_text(json.dumps(row)) + imported = site.import_campaign(campaign, self.fixture.root, self.root, 'synthetic-original', 'linux', 'x86_64') + self.assertEqual(len(imported), len(self.fixture.revisions)) + self.assertEqual(site.publish(self.root), 4) + (campaign / 'progress.json').write_text(json.dumps(dict(progress, completed=total - 1))) + with self.assertRaisesRegex(ValueError, 'campaign is incomplete'): + site.import_campaign(campaign, self.fixture.root, self.root, 'synthetic-original', 'linux', 'x86_64') + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/benchmark_extended_storage.py b/tools/benchmark_extended_storage.py new file mode 100644 index 00000000000..bbf746043f9 --- /dev/null +++ b/tools/benchmark_extended_storage.py @@ -0,0 +1,116 @@ +# SPDX-FileCopyrightText: Copyright (c) M. Boerger, the MBO Works authors +# SPDX-License-Identifier: Apache-2.0 +"""Own disposable memory fixtures and record their creation and release.""" + +from contextlib import contextmanager +from datetime import datetime, timezone +import json +import hashlib +from pathlib import Path +import plistlib +import re +import shutil +import subprocess +import sys + + +def write(path, value): + path.write_text(json.dumps(value, indent=2) + '\n') + + +def disk_info(mount): + return plistlib.loads(subprocess.check_output(['diskutil', 'info', '-plist', str(mount)])) + + +def verify_image(device, image): + images = plistlib.loads(subprocess.check_output(['hdiutil', 'info', '-plist']))['images'] + found = [entry for entry in images if entry.get('image-path') == image + and any(item.get('dev-entry') == device for item in entry.get('system-entities', []))] + if len(found) != 1: + raise ValueError('device does not match the newly owned RAM image') + + +def verify_volume(receipt): + verify_image(receipt['device'], receipt['image']) + info = disk_info(receipt['mount']) + if (info.get('DeviceNode') != receipt['device'] or info.get('VolumeUUID') != receipt['uuid'] + or info.get('MountPoint') != receipt['mount']): + raise ValueError('RAM volume ownership changed; refusing to detach') + + +@contextmanager +def fixture(root, existing=None): + """Create a new fixture; never reformat an existing volume or remove another fixture.""" + root = root.resolve() + root.mkdir(parents=True, exist_ok=True) + if existing is not None: + if sys.platform != 'linux': + raise ValueError('existing fixture reuse is Linux-only local validation') + existing = existing.resolve(strict=True) + filesystem = subprocess.check_output(['findmnt', '-n', '-o', 'FSTYPE', '-T', str(existing)], text=True).strip() + if filesystem != 'tmpfs' or not existing.is_dir(): + raise ValueError('existing extended fixture must be a verified tmpfs directory') + receipt = {'filesystem': filesystem, 'storage': 'memory', 'fixture': str(existing), 'borrowed': True} + write(root / 'storage.json', receipt) + try: + yield existing, receipt + finally: + write(root / 'storage-release.json', dict(receipt, preserved=True, + released_at=datetime.now(timezone.utc).isoformat())) + return + if sys.platform == 'linux': + filesystem = subprocess.check_output(['findmnt', '-n', '-o', 'FSTYPE', '-T', '/dev/shm'], text=True).strip() + if filesystem != 'tmpfs': + raise ValueError('extended Linux fixtures require verified tmpfs') + # Stable paths keep repeated CI invocations comparable. mkdir refuses a + # collision; only this context's newly created directory may be removed. + parent = Path('/dev/shm') / ('xff-extended-' + hashlib.sha256(str(root).encode()).hexdigest()[:12]) + parent.mkdir() + owned = parent.stat() + receipt = {'filesystem': filesystem, 'storage': 'memory', 'parent': str(parent)} + write(root / 'storage.json', receipt) + try: + yield parent / 'fixture', receipt + finally: + current = parent.lstat() + if parent.is_symlink() or (current.st_dev, current.st_ino) != (owned.st_dev, owned.st_ino): + raise ValueError('fixture ownership changed; refusing removal') + shutil.rmtree(parent) + write(root / 'storage-release.json', dict(receipt, released_at=datetime.now(timezone.utc).isoformat())) + return + if sys.platform != 'darwin': + raise ValueError('extended fixtures support Linux and macOS') + image = 'ram://4194304' # 2 GiB; the full recipe uses approximately 1 GiB. + result = subprocess.check_output(['hdiutil', 'attach', '-nomount', image], text=True).strip() + if not re.fullmatch(r'/dev/disk[0-9]+', result): + raise ValueError('ambiguous newly attached RAM device: ' + result) + device = result + verify_image(device, image) + # Record ownership before formatting; failure cleanup still checks the RAM image. + receipt = {'device': device, 'image': image, 'storage': 'memory', 'filesystem': 'case-sensitive HFS+', + 'created_at': datetime.now(timezone.utc).isoformat()} + write(root / 'storage.json', receipt) + try: + subprocess.run(['newfs_hfs', '-s', '-v', 'XffExtended', device], check=True) + mount = root / 'ram-volume' + mount.mkdir() + subprocess.run(['diskutil', 'mount', '-mountPoint', str(mount), device], check=True) + info = disk_info(mount) + if info.get('DeviceNode') != device or info.get('MountPoint') != str(mount) or not info.get('VolumeUUID'): + raise ValueError('new RAM volume identity is missing') + receipt.update(mount=str(mount), uuid=info['VolumeUUID']) + write(root / 'storage.json', receipt) + verify_volume(receipt) + yield mount / 'fixture', receipt + finally: + if 'uuid' in receipt: + verify_volume(receipt) + else: + verify_image(device, image) + subprocess.run(['hdiutil', 'detach', device], check=True) + images = plistlib.loads(subprocess.check_output(['hdiutil', 'info', '-plist']))['images'] + if any(any(item.get('dev-entry') == device for item in entry.get('system-entities', [])) for entry in images): + raise ValueError('owned RAM device remains after detach') + if 'mount' in receipt and Path(receipt['mount']).is_mount(): + raise ValueError('owned RAM mount remains after detach') + write(root / 'storage-release.json', dict(receipt, released_at=datetime.now(timezone.utc).isoformat())) diff --git a/tools/benchmark_extended_storage_test.py b/tools/benchmark_extended_storage_test.py new file mode 100644 index 00000000000..3fe805590c5 --- /dev/null +++ b/tools/benchmark_extended_storage_test.py @@ -0,0 +1,134 @@ +# SPDX-FileCopyrightText: Copyright (c) M. Boerger, the MBO Works authors +# SPDX-License-Identifier: Apache-2.0 +"""Memory fixtures release only resources whose ownership still matches.""" + +import hashlib +import json +from pathlib import Path +import plistlib +import tempfile +import unittest +from unittest import mock + +import benchmark_extended_storage as storage + + +class BenchmarkExtendedStorageTest(unittest.TestCase): + def setUp(self): + self.temporary = tempfile.TemporaryDirectory() + self.addCleanup(self.temporary.cleanup) + self.root = Path(self.temporary.name).resolve() / 'collection' + self.device = '/dev/disk987' + self.attached = False + self.image = 'ram://4194304' + self.uuid = 'synthetic-owned-uuid' + self.commands = [] + + def output(self, command, **kwargs): + self.commands.append(command) + if command[:2] == ['hdiutil', 'attach']: + self.attached = True + return self.device + '\n' + if command == ['hdiutil', 'info', '-plist']: + images = [{'image-path': self.image, 'system-entities': [{'dev-entry': self.device}]}] if self.attached else [] + return plistlib.dumps({'images': images}) + if command[:3] == ['diskutil', 'info', '-plist']: + return plistlib.dumps({'DeviceNode': self.device, 'MountPoint': str(self.root / 'ram-volume'), + 'VolumeUUID': self.uuid}) + raise AssertionError(command) + + def run_command(self, command, **kwargs): + self.commands.append(command) + if command == ['hdiutil', 'detach', self.device]: + self.attached = False + return mock.Mock(returncode=0) + + def mac_mocks(self): + return (mock.patch.object(storage.sys, 'platform', 'darwin'), + mock.patch.object(storage.subprocess, 'check_output', side_effect=self.output), + mock.patch.object(storage.subprocess, 'run', side_effect=self.run_command)) + + def test_mac_formats_only_the_new_owned_ram_image_and_records_verified_release(self): + platform, output, run = self.mac_mocks() + with platform, output, run: + with storage.fixture(self.root) as (fixture, receipt): + self.assertEqual(fixture, self.root / 'ram-volume/fixture') + self.assertEqual(receipt['uuid'], self.uuid) + self.assertTrue(self.attached) + self.assertFalse(self.attached) + commands = self.commands + self.assertLess(commands.index(['hdiutil', 'info', '-plist']), + commands.index(['newfs_hfs', '-s', '-v', 'XffExtended', self.device])) + self.assertEqual(json.loads((self.root / 'storage-release.json').read_text())['uuid'], self.uuid) + + def test_mac_body_failure_still_releases_owned_device(self): + platform, output, run = self.mac_mocks() + with platform, output, run, self.assertRaisesRegex(RuntimeError, 'measurement failed'): + with storage.fixture(self.root): + raise RuntimeError('measurement failed') + self.assertFalse(self.attached) + self.assertTrue((self.root / 'storage-release.json').is_file()) + + def test_mac_changed_volume_identity_refuses_detach(self): + platform, output, run = self.mac_mocks() + with platform, output, run, self.assertRaisesRegex(ValueError, 'ownership changed'): + with storage.fixture(self.root): + self.uuid = 'someone-else' + self.assertNotIn(['hdiutil', 'detach', self.device], self.commands) + self.assertFalse((self.root / 'storage-release.json').exists()) + + def test_mac_non_ram_device_is_never_formatted_or_detached(self): + platform, output, run = self.mac_mocks() + self.image = '/someone/elses/disk.dmg' + with platform, output, run, self.assertRaisesRegex(ValueError, 'newly owned RAM image'): + with storage.fixture(self.root): + self.fail('must not yield an unowned device') + self.assertFalse(any(command[0] == 'newfs_hfs' for command in self.commands)) + self.assertNotIn(['hdiutil', 'detach', self.device], self.commands) + + def test_linux_collision_preserves_existing_directory(self): + shm = Path(self.temporary.name) / 'shm' + shm.mkdir() + parent = shm / ('xff-extended-' + hashlib.sha256(str(self.root).encode()).hexdigest()[:12]) + parent.mkdir() + (parent / 'foreign').write_text('preserve') + path = lambda value: shm if value == '/dev/shm' else Path(value) + with mock.patch.object(storage.sys, 'platform', 'linux'), mock.patch.object(storage, 'Path', side_effect=path), \ + mock.patch.object(storage.subprocess, 'check_output', return_value='tmpfs\n'), \ + self.assertRaises(FileExistsError): + with storage.fixture(self.root): + self.fail('must not adopt an existing directory') + self.assertEqual((parent / 'foreign').read_text(), 'preserve') + + def test_linux_new_fixture_cleanup_and_borrowed_fixture_preservation_on_error(self): + shm = Path(self.temporary.name) / 'shm' + shm.mkdir() + path = lambda value: shm if value == '/dev/shm' else Path(value) + with mock.patch.object(storage.sys, 'platform', 'linux'), mock.patch.object(storage, 'Path', side_effect=path), \ + mock.patch.object(storage.subprocess, 'check_output', return_value='tmpfs\n'): + with self.assertRaisesRegex(RuntimeError, 'failed'): + with storage.fixture(self.root) as (fixture, _): + fixture.mkdir() + (fixture / 'owned').write_text('owned') + raise RuntimeError('failed') + self.assertFalse(fixture.parent.exists()) + borrowed = shm / 'borrowed' + borrowed.mkdir() + (borrowed / 'preserve').write_text('shared fixture') + with self.assertRaisesRegex(RuntimeError, 'failed'): + with storage.fixture(self.root, borrowed): + raise RuntimeError('failed') + self.assertEqual((borrowed / 'preserve').read_text(), 'shared fixture') + self.assertTrue(json.loads((self.root / 'storage-release.json').read_text())['preserved']) + + def test_disk_filesystems_cannot_be_used_as_memory_fixtures(self): + self.root.mkdir() + with mock.patch.object(storage.sys, 'platform', 'linux'), \ + mock.patch.object(storage.subprocess, 'check_output', return_value='ext4\n'), \ + self.assertRaisesRegex(ValueError, 'verified tmpfs'): + with storage.fixture(self.root, self.root): + self.fail('must not measure disk fixtures') + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/benchmark_extended_test_support.py b/tools/benchmark_extended_test_support.py new file mode 100644 index 00000000000..2a3c5ce0930 --- /dev/null +++ b/tools/benchmark_extended_test_support.py @@ -0,0 +1,98 @@ +# SPDX-FileCopyrightText: Copyright (c) M. Boerger, the MBO Works authors +# SPDX-License-Identifier: Apache-2.0 +"""Small synthetic observations for extended-pipeline tests; never performance data.""" + +import copy +from dataclasses import asdict +import hashlib +import json +from pathlib import Path +import tempfile +from unittest import mock + +import benchmark_extended as suite +import benchmark_extended_records as records + + +class ExtendedFixture: + def __init__(self): + self.temporary = tempfile.TemporaryDirectory() + self.root = Path(self.temporary.name) + self.cases = [suite.Case('files-exact', shape, files, cpus) + for shape in ('broad', 'deep') for files in (3, 6) for cpus in (1, 3)] + self.patch = mock.patch.object(suite, 'cases', return_value=self.cases) + self.patch.start() + self.revisions, self.builds = [], {} + for index, sha in enumerate(('a' * 40, 'b' * 40), 1): + directory = self.root / 'binaries' / sha + directory.mkdir(parents=True) + binary = directory / 'xff_full' + binary.write_bytes(('synthetic executable ' + sha).encode()) + artifact = 'bazel-out/test/bin/xff/cli/xff_full' + manifest = {'source': {'revision': sha, 'tree': sha, 'clean': True}, 'sha256': records.digest(binary), + 'bytes': binary.stat().st_size, 'artifact': artifact, + 'command': ['bazel', 'build', '//xff/cli:xff_full', '--config=clang_release', '--config=xff_full'], + 'configuration': {'.bazelrc': 'c' * 64}} + (directory / 'build.json').write_text(json.dumps(manifest, indent=2) + '\n') + (directory / 'link-actions.json').write_text(json.dumps({'actions': [ + {'arguments': ['clang++', '-flto=thin', '-o', artifact]}]}) + '\n') + revision = {'sha': sha, 'pr': index, 'date': f'2026-11-0{index}T00:00:00Z', + 'subject': 'Synthetic fixture', 'binary': str(binary), 'binary_sha256': manifest['sha256'], + 'build_sha256': records.digest(directory / 'build.json'), + 'link_actions_sha256': records.digest(directory / 'link-actions.json')} + self.revisions.append(revision) + self.builds[sha] = records.build_proof(directory, revision) + self.contract = {'recipe': suite.RECIPE, 'cases': [asdict(case) for case in self.cases], + 'revisions': self.revisions, 'anchor': self.revisions[-1], 'repetitions': 9, 'keep': 7, + 'estimator': 'mean-fastest', 'warmup_rounds': 1, 'fixture': '/synthetic-fixture', + 'filesystem': 'tmpfs', 'platform': 'Linux-test-x86_64', 'affinity': [0, 1, 2], + 'tools': {name: {'path': '/bin/' + name, 'sha256': 'd' * 64, 'version': 'synthetic ' + name} + for name in ('find', 'rg', 'tar')}, + 'launcher': {'path': '/synthetic-launcher', 'sha256': 'e' * 64, + 'wall_method': 'synthetic wall', 'memory_method': 'synthetic RSS'}} + + def close(self): + self.patch.stop() + self.temporary.cleanup() + + def rows(self, revision=None): + revision = revision or self.revisions[0] + rows = [] + for case in self.cases: + commands, cwd, separator = suite.commands(case, Path(self.contract['fixture']), revision['binary'], + {k: v['path'] for k, v in self.contract['tools'].items()}) + anchor, _, _ = suite.commands(case, Path(self.contract['fixture']), self.revisions[-1]['binary'], + {k: v['path'] for k, v in self.contract['tools'].items()}) + commands['anchor'] = anchor['xff'] + wanted = suite.expected(case, Path(self.contract['fixture'])) + output = separator.join(wanted) + separator + samples = [] + for round_number in range(10): + for label in commands: + seconds = 100 if round_number == 0 else {'xff': 2, 'anchor': 1, 'find': 4}[label] + samples.append({'label': label, 'round': round_number, + 'phase': 'warmup' if round_number == 0 else 'measured', + 'elapsed_seconds': seconds, 'user_cpu_seconds': seconds / 2, + 'system_cpu_seconds': seconds / 4, 'peak_child_rss_bytes': 1024, + 'first_stdout_seconds': .01, 'exit_codes': [0], 'stderr_bytes': 0, 'stderr': '', + 'stdout_bytes': len(output), 'stdout_sha256': hashlib.sha256(output).hexdigest(), + 'validated_records': len(wanted)}) + rows.append({'case': asdict(case), 'revision': revision, 'anchor': self.revisions[-1], + 'commands': commands, 'cwd': str(cwd), 'affinity': self.contract['affinity'][:case.cpus], + 'environment': {'PATH': '/usr/bin:/bin', 'HOME': '/synthetic-fixture/home', 'LC_ALL': 'C', 'TZ': 'UTC'}, + 'expected_records': len(wanted), 'expected_sha256': hashlib.sha256(b'\0'.join(sorted(wanted))).hexdigest(), + 'samples': samples, 'after': {'utc': '2026-11-08T12:00:00Z'}}) + return rows + + def record(self, origin='local', comparison='fixed-anchor'): + collection = {'run_id': 12, 'run_attempt': 1, 'repository': 'owner/repo', + 'head_sha': self.revisions[0]['sha'], 'base_sha': self.revisions[-1]['sha']} if origin == 'ci' else None + return records.make_record(copy.deepcopy(self.contract), self.revisions[0], self.rows(), self.builds, + 'linux', 'x86_64', 'synthetic-only', comparison, origin, collection) + + def source(self): + return {'id': 12, 'run_attempt': 1, 'head_sha': self.revisions[0]['sha'], + 'base_sha': self.revisions[-1]['sha'], 'name': 'Benchmarks', + 'path': '.github/workflows/benchmarks.yml', 'event': 'push', 'head_branch': 'main', + 'created_at': '2026-11-01T00:00:00Z', 'status': 'completed', 'conclusion': 'failure', + 'repository': {'full_name': 'owner/repo'}} diff --git a/tools/benchmark_history_test.py b/tools/benchmark_history_test.py index e2c4c5e426d..bcf90174ce6 100644 --- a/tools/benchmark_history_test.py +++ b/tools/benchmark_history_test.py @@ -248,7 +248,7 @@ def test_publication_coalesces_requests_and_reconciles_sources(self): self.assertIn('group: benchmark-publication-latest', workflow) reconcile = repository_file('tools/benchmark_reconcile_ci.py').read_text() self.assertIn('benchmark_preview_ci.collect', reconcile) - self.assertIn("('benchmarks.yml', 'push', 'success', retain_measurement)", reconcile) + self.assertIn("('benchmarks.yml', 'push', 'completed', retain_benchmarks)", reconcile) self.assertIn("('benchmark_backfill.yml', 'workflow_dispatch', 'completed', retain_backfill)", reconcile) def test_fifth_candidate_requires_unresolved_fourth_agreement(self): diff --git a/tools/benchmark_reconcile_ci.py b/tools/benchmark_reconcile_ci.py index 80fdad40c86..491fff134f8 100644 --- a/tools/benchmark_reconcile_ci.py +++ b/tools/benchmark_reconcile_ci.py @@ -13,6 +13,7 @@ import zipfile import benchmark_campaign +import benchmark_extended_records import benchmark_history import benchmark_preview_ci import benchmark_records @@ -55,13 +56,14 @@ def latest_artifacts(items): return list(selected.values()) -def retain_measurement(api, root, run): +def retain_measurement(api, root, run, inventory=None): destination = root / 'runs' / str(run['id']) / str(run['run_attempt']) missing = {platform for platform in ('linux', 'macos') if not benchmark_records.exists(destination / platform / 'report.json')} if not missing: return False - selected = [item for item in latest_artifacts(artifacts(api, run, 'benchmark-report-')) + selected = [item for item in latest_artifacts(artifacts(api, run, 'benchmark-report-') if inventory is None else + [item for item in inventory if item['name'].startswith('benchmark-report-')]) if item['created_at'] >= run['run_started_at']] reports = {} for item in selected: @@ -102,6 +104,72 @@ def retain_measurement(api, root, run): return retained +def retain_extended(api, root, run, inventory=None): + """Recover complete platforms independently, including successful earlier attempts.""" + selected = latest_artifacts(artifacts(api, run, 'benchmark-extended-report-') if inventory is None else + [item for item in inventory if item['name'].startswith('benchmark-extended-report-')]) + retained = False + for item in selected: + match = re.fullmatch(r'benchmark-extended-report-(linux|macos)', item['name']) + if not match: + continue + try: + retained = retain_extended_artifact(api, root, run, item, match.group(1)) or retained + except (ValueError, KeyError, OSError, zipfile.BadZipFile, + subprocess.CalledProcessError, subprocess.TimeoutExpired) as error: + print(f"::warning::extended run {run['id']} {item['name']}: {error}", flush=True) + return retained + + +def retain_extended_artifact(api, root, run, item, platform): + collections = root / 'extended' / 'collections' / str(run['id']) + markers = {*collections.glob('*/' + platform + '.json'), + *(path.with_suffix('') for path in collections.glob('*/' + platform + '.json.gz'))} + for marker in markers: + saved = benchmark_extended_records.read(marker) + observation = root / 'extended' / 'observations' / saved['identity'] / 'report.json' + if saved['artifact_id'] == item['id'] and benchmark_records.exists(observation): + return False + # A rerun of failed jobs does not recreate a successful platform's artifact. + # Its observation and source must keep the original attempt identity. + with tempfile.TemporaryDirectory(prefix='xff-extended-report-') as temporary: + directory = Path(temporary) + extract(api.request(f"actions/artifacts/{item['id']}/zip", raw=True), directory) + record = json.loads((directory / 'observation.json').read_text()) + collection = record['collection'] + if (record['origin'] != 'ci' or record['platform'] != platform + or collection['run_id'] != run['id'] + or type(collection['run_attempt']) is not int + or not 1 <= collection['run_attempt'] <= run['run_attempt']): + raise ValueError('extended artifact platform/run/attempt mismatch') + attempt = collection['run_attempt'] + source = dict(run if attempt == run['run_attempt'] else + api.request(f"actions/runs/{run['id']}/attempts/{attempt}")) + commit = api.request(f"commits/{source['head_sha']}") + if not commit.get('parents'): + raise ValueError('extended source has no first parent') + source['base_sha'] = commit['parents'][0]['sha'] + benchmark_extended_records.retain(root, record, source) + marker = collections / str(attempt) / (platform + '.json') + marker.parent.mkdir(parents=True, exist_ok=True) + marker.write_text(json.dumps({'identity': record['identity'], 'artifact_id': item['id']}) + '\n') + return True + + +def retain_benchmarks(api, root, run): + """A failed independent job must not discard other valid platform reports.""" + inventory = [item for item in api.pages(f"actions/runs/{run['id']}/artifacts", 'artifacts') + if not item.get('expired')] + retained = False + for name, retain in (('core', retain_measurement), ('extended', retain_extended)): + try: + retained = retain(api, root, run, inventory) or retained + except (ValueError, KeyError, OSError, zipfile.BadZipFile, + subprocess.CalledProcessError, subprocess.TimeoutExpired) as error: + print(f"::warning::benchmark run {run['id']} {name}: {error}", flush=True) + return retained + + def retain_backfill(api, root, run): destination = root / 'backfills' / str(run['id']) / str(run['run_attempt']) if benchmark_records.exists(destination / 'campaign.json'): @@ -132,7 +200,7 @@ def reconcile(api, root): since = (datetime.now(timezone.utc) - timedelta(days=30)).date().isoformat() failures = [] for workflow, event, status, retain in ( - ('benchmarks.yml', 'push', 'success', retain_measurement), + ('benchmarks.yml', 'push', 'completed', retain_benchmarks), ('benchmark_backfill.yml', 'workflow_dispatch', 'completed', retain_backfill)): runs = api.pages(f'actions/workflows/{workflow}/runs?status={status}&branch=main&created=%3E%3D{since}', 'workflow_runs') diff --git a/tools/benchmark_reconcile_ci_test.py b/tools/benchmark_reconcile_ci_test.py new file mode 100644 index 00000000000..93e1b055813 --- /dev/null +++ b/tools/benchmark_reconcile_ci_test.py @@ -0,0 +1,123 @@ +# SPDX-FileCopyrightText: Copyright (c) M. Boerger, the MBO Works authors +# SPDX-License-Identifier: Apache-2.0 +"""Recover independently valid reports without trusting workflow or artifact claims.""" + +import copy +import gzip +import io +import json +import unittest +from unittest import mock +import zipfile + +import benchmark_extended_records as records +import benchmark_reconcile_ci as reconcile +from benchmark_extended_test_support import ExtendedFixture + + +def archive(value): + output = io.BytesIO() + with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) as zipped: + zipped.writestr('observation.json', json.dumps(value)) + return output.getvalue() + + +class FakeAPI: + def __init__(self, fixture, value): + self.repository = 'owner/repo' + self.source = fixture.source() + self.commit = {'parents': [{'sha': self.source['base_sha']}], + 'commit': {'committer': {'date': '2026-11-01T00:00:00Z'}}} + self.items = [{'name': 'benchmark-extended-report-linux', 'id': 101, + 'created_at': '2026-11-08T00:00:00Z', 'expired': False}] + self.raw = {101: archive(value)} + self.requests = [] + + def request(self, path, raw=False): + self.requests.append(path) + if path.startswith('actions/artifacts/'): + return self.raw[int(path.split('/')[2])] + if path == 'actions/runs/12/attempts/1': + return copy.deepcopy(self.source) + if path.startswith('commits/'): + return copy.deepcopy(self.commit) + raise AssertionError(path) + + def pages(self, path, key=None): + self.requests.append(path) + if path == 'actions/runs/12/artifacts' and key == 'artifacts': + return copy.deepcopy(self.items) + raise AssertionError(path) + + +class BenchmarkReconcileCiTest(unittest.TestCase): + def setUp(self): + self.fixture = ExtendedFixture() + self.addCleanup(self.fixture.close) + self.record = self.fixture.record(origin='ci', comparison='parent') + self.api = FakeAPI(self.fixture, self.record) + self.root = self.fixture.root / 'site' + + def test_failed_workflow_keeps_valid_extended_head_even_when_core_retention_fails(self): + with mock.patch.object(reconcile, 'retain_measurement', side_effect=ValueError('no core agreement')): + self.assertTrue(reconcile.retain_benchmarks(self.api, self.root, self.api.source)) + path = self.root / 'extended/observations' / self.record['identity'] / 'report.json' + saved = records.read(path) + self.assertEqual(saved['publication_source']['status'], 'completed') + self.assertEqual(saved['publication_source']['base_sha'], self.fixture.revisions[-1]['sha']) + self.assertEqual(len(list(self.root.glob('extended/observations/*/report.json'))), 1) + + def test_successful_earlier_attempt_is_preserved_and_repeated_import_needs_no_download(self): + latest = dict(self.api.source, run_attempt=2) + self.assertTrue(reconcile.retain_extended(self.api, self.root, latest)) + self.assertIn('actions/runs/12/attempts/1', self.api.requests) + marker = self.root / 'extended/collections/12/1/linux.json' + self.assertTrue(marker.is_file()) + path = self.root / 'extended/observations' / self.record['identity'] / 'report.json' + for file in (marker, path): + file.with_suffix('.json.gz').write_bytes(gzip.compress(file.read_bytes(), mtime=0)) + file.unlink() + self.api.requests.clear() + self.assertFalse(reconcile.retain_extended(self.api, self.root, latest)) + self.assertEqual(self.api.requests, ['actions/runs/12/artifacts']) + self.assertEqual(records.read(path)['publication_source']['run_attempt'], 1) + + def test_invalid_platform_does_not_discard_other_valid_platform(self): + self.api.items.insert(0, dict(self.api.items[0], id=102, name='benchmark-extended-report-macos')) + self.api.raw[102] = archive(self.record) + self.assertTrue(reconcile.retain_extended(self.api, self.root, self.api.source)) + self.assertTrue((self.root / 'extended/collections/12/1/linux.json').exists()) + self.assertFalse((self.root / 'extended/collections/12/1/macos.json').exists()) + + def test_wrong_parent_head_repository_attempt_and_untrusted_workflow_are_rejected(self): + for key, replacement in (('base_sha', 'f' * 40), ('head_sha', 'f' * 40), + ('repository', {'full_name': 'other/repo'}), + ('run_attempt', 0), ('event', 'pull_request'), ('status', 'in_progress')): + with self.subTest(key=key): + source = dict(self.api.source, **{key: replacement}) + api = FakeAPI(self.fixture, self.record) + if key == 'base_sha': + api.commit['parents'][0]['sha'] = replacement + self.assertFalse(reconcile.retain_extended(api, self.root, source)) + self.assertFalse(list(self.root.glob('extended/observations/*/report.json'))) + + def test_invalid_extended_artifact_does_not_lose_valid_core_result(self): + self.api.raw[101] = b'not a zip' + with mock.patch.object(reconcile, 'retain_measurement', return_value=True) as core: + self.assertTrue(reconcile.retain_benchmarks(self.api, self.root, self.api.source)) + core.assert_called_once() + + def test_extract_rejects_escape_and_symlinks(self): + for name, mode in (('../outside', 0), ('/outside', 0), ('dir\\outside', 0), ('link', 0o120777)): + with self.subTest(name=name): + output = io.BytesIO() + with zipfile.ZipFile(output, 'w') as zipped: + info = zipfile.ZipInfo(name) + info.external_attr = mode << 16 + zipped.writestr(info, 'data') + with self.assertRaises(ValueError): + reconcile.extract(output.getvalue(), self.fixture.root / 'extracted') + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/benchmark_recovery.py b/tools/benchmark_recovery.py index ef8e2ab0d05..8b2cfcafd2d 100644 --- a/tools/benchmark_recovery.py +++ b/tools/benchmark_recovery.py @@ -33,7 +33,7 @@ def inventory(api, since, number=None): entries = catalog(api) covered = set() for entry in entries: - if entry.get('source') != 'merged' or entry.get('local'): + if entry.get('source') != 'merged' or entry.get('local') or entry.get('extended'): continue platform = entry.get('platform_name', '').split(' / ')[0].lower() sha = entry.get('commit', '') From 423d5b2043b4638d4a84326d7b247928e3bde151 Mon Sep 17 00:00:00 2001 From: helly25 <6420169+helly25@users.noreply.github.com> Date: Mon, 9 Nov 2026 00:28:26 +0000 Subject: [PATCH 2/4] Qualify extended collection and preserve independent valid reports --- .github/workflows/benchmarks.yml | 11 + docs/benchmark-extended-history-campaign.md | 28 ++ ...ory-continuous-qualification-20261109.json | 412 ++++++++++++++++++ release-site.json | 1 + tools/BUILD.bazel | 12 +- tools/benchmark_extended_build.py | 17 +- tools/benchmark_extended_build_test.py | 23 + tools/benchmark_extended_ci.py | 3 +- tools/benchmark_extended_ci_test.py | 31 ++ tools/benchmark_extended_records.py | 8 +- tools/benchmark_extended_records_test.py | 10 + tools/benchmark_extended_site.py | 7 +- tools/benchmark_extended_site_test.py | 22 + tools/benchmark_extended_test_support.py | 3 +- tools/benchmark_reconcile_ci.py | 26 +- tools/benchmark_reconcile_ci_test.py | 33 ++ tools/benchmark_recovery.py | 45 +- tools/benchmark_recovery_test.py | 48 +- 18 files changed, 723 insertions(+), 17 deletions(-) create mode 100644 docs/benchmarks/extended-history-continuous-qualification-20261109.json diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 504d20a8940..1956bb4062b 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -103,6 +103,17 @@ jobs: ${{ runner.temp }}/extended-measurements/storage-release.json retention-days: 30 if-no-files-found: warn + - name: Retain extended build diagnostics after success or failure + uses: actions/upload-artifact@v7 + if: always() + with: + name: benchmark-extended-build-logs-${{ matrix.platform }} + path: | + ${{ runner.temp }}/extended-build/binaries/*/build.log + ${{ runner.temp }}/extended-build/binaries/*/build.json + ${{ runner.temp }}/extended-build/binaries/*/link-actions.json + retention-days: 30 + if-no-files-found: warn build: strategy: diff --git a/docs/benchmark-extended-history-campaign.md b/docs/benchmark-extended-history-campaign.md index dc925c5123a..073d336b7c3 100644 --- a/docs/benchmark-extended-history-campaign.md +++ b/docs/benchmark-extended-history-campaign.md @@ -305,3 +305,31 @@ catalog entries remain intact. Tests cover known chart heights, warm-up exclusio stable parent datasets, local/CI separation, packed observations, idempotent publication, untrusted provenance, partial workflow recovery and RAM-resource ownership. Synthetic test observations are explicitly labeled and are never performance evidence. + +### Native pipeline and imported-view qualification (2026-11-09) + +The [qualification summary](benchmarks/extended-history-continuous-qualification-20261109.json) +records the actual import, compacted-browser and native collector checks. All 130 original +revisions and 7,800 cells passed validation against their original retained executables and link +evidence. Both 130-version datasets joined a copy of the published catalog without changing any +of its 588 existing entries. Every chart order reproduces the recorded ratios: 60 measured +coordinates and 20 unavailable coordinates per view. Chromium selected the oldest, middle and +latest versions in both datasets with correct binary evidence and no page errors. + +Each native collector completed 60 head cells and 60 identical-parent control cells on Linux +and macOS: 3,600 independently checked invocations per platform. Both source revisions produce +byte-identical production executables on their respective host. This validates the pipeline; +it is not evidence of a performance improvement. The frozen driver source is +`96765e1859c3d48ac0f04092c0613b4e8ad8ae21`, against first parent +`52950f937ad371e1450538faf7983024b324688d`. Subsequent changes tighten validation and improve +build logging/publication without changing native commands, sampling or C++ sources. + +Linux preserved its explicitly borrowed tmpfs fixture. The new macOS RAM volume recorded UUID +`A03B359F-E90E-38C0-96B4-439E7BF90389`; its image, device and volume identity were checked before +release, and device/mount absence was checked afterward. Its former device number is not an +authorization to detach any future device. Raw observations, builds and failed-attempt support +remain outside the source tree, with hashes and evidence locations in the summary. + +The original imported observations remain local history with a fixed reference. Publishing that +data and verifying the first actual post-merge extended CI run are separate final steps. +Changing workloads, sampling or resource accounting requires a separately identified contract. diff --git a/docs/benchmarks/extended-history-continuous-qualification-20261109.json b/docs/benchmarks/extended-history-continuous-qualification-20261109.json new file mode 100644 index 00000000000..5f6a69d1daf --- /dev/null +++ b/docs/benchmarks/extended-history-continuous-qualification-20261109.json @@ -0,0 +1,412 @@ +{ + "schema": 1, + "purpose": "Extended-history import and collector correctness qualification; not an optimization claim", + "import": { + "schema": 1, + "original_observations": 130, + "validated_original_cells": 7800, + "validated_original_invocations": 234000, + "retained_core_catalog_entries": 588, + "extended_catalog_entries": 260, + "series": { + "extended-production-v1 / Fixed XFF ae8c5af3eb11": 130, + "extended-production-v1 / Traditional tools": 130 + }, + "measured_points_per_view": 60, + "unavailable_points_per_view": 20, + "all_three_order_modes_match_actual_samples": true, + "normalization_cells": { + "available": 15600 + }, + "archive_sha256": "985d4c471bb91b917e54202406e662e5c19c0b5825e06e095b4b754a42b62ecc", + "binary_evidence": "Verified against Retention (original frozen executable bytes, both views)", + "published_snapshot_commit": "08a0e7ad53e559f672035e0adcbfd5ee39b6bcec" + }, + "browser": { + "url": "http://127.0.0.1:63790/benchmarks/", + "checked": [ + { + "dataset": "Local / linux-zen5-extended-20261107 / extended-production-v1 / Fixed XFF ae8c5af3eb11", + "index": 0, + "commit": "21c7f3656b005b23113d936c1cd2ca0fd2221ab2", + "binary": "f8577e4753dc90640bd926ac80ce7277be9791491bf4e15bfdcd1a3777ef8e47" + }, + { + "dataset": "Local / linux-zen5-extended-20261107 / extended-production-v1 / Fixed XFF ae8c5af3eb11", + "index": 65, + "commit": "09e6bbfff3275ed36eba7b98052cf5d0877d94f7", + "binary": "c21c424a876f82db3ac4de2458c8f47b5161dd49271c1b739f2d1ac6ee0f689b" + }, + { + "dataset": "Local / linux-zen5-extended-20261107 / extended-production-v1 / Fixed XFF ae8c5af3eb11", + "index": 129, + "commit": "ae8c5af3eb11dc8571f11db9b307b0db948f78c6", + "binary": "c47c904b693c8c114bb87284497b98a10e4925f25afdc3f5ec4f26896ad1a2e1" + }, + { + "dataset": "Local / linux-zen5-extended-20261107 / extended-production-v1 / Traditional tools", + "index": 0, + "commit": "21c7f3656b005b23113d936c1cd2ca0fd2221ab2", + "binary": "f8577e4753dc90640bd926ac80ce7277be9791491bf4e15bfdcd1a3777ef8e47" + }, + { + "dataset": "Local / linux-zen5-extended-20261107 / extended-production-v1 / Traditional tools", + "index": 65, + "commit": "09e6bbfff3275ed36eba7b98052cf5d0877d94f7", + "binary": "c21c424a876f82db3ac4de2458c8f47b5161dd49271c1b739f2d1ac6ee0f689b" + }, + { + "dataset": "Local / linux-zen5-extended-20261107 / extended-production-v1 / Traditional tools", + "index": 129, + "commit": "ae8c5af3eb11dc8571f11db9b307b0db948f78c6", + "binary": "c47c904b693c8c114bb87284497b98a10e4925f25afdc3f5ec4f26896ad1a2e1" + } + ], + "page_errors": [] + }, + "native": { + "linux": { + "source": "96765e1859c3d48ac0f04092c0613b4e8ad8ae21", + "parent": "52950f937ad371e1450538faf7983024b324688d", + "origin": "local", + "platform": "Linux-7.0.0-38-generic-x86_64-with-glibc2.43", + "target": "//xff/cli:xff_full", + "configuration": ["clang_release", "xff_full"], + "builds": { + "96765e1859c3d48ac0f04092c0613b4e8ad8ae21": { + "source": { + "revision": "96765e1859c3d48ac0f04092c0613b4e8ad8ae21", + "tree": "28719926a988f24150f67f9eb7947066d2488071", + "clean": true + }, + "sha256": "4fd6b2f73f912a5f6f50b958e1a75842bcce57fcb35bbcdce13833b2ffc16877", + "bytes": 18473632, + "command": [ + "bazel", + "--nosystem_rc", + "--nohome_rc", + "build", + "//xff/cli:xff_full", + "--config=clang_release", + "--config=xff_full", + "--disk_cache=/home/marcus/.cache/bazel-disk", + "--jobs=3" + ], + "query": [ + "bazel", + "--nosystem_rc", + "--nohome_rc", + "cquery", + "config(//xff/cli:xff_full, target)", + "--config=clang_release", + "--config=xff_full", + "--disk_cache=/home/marcus/.cache/bazel-disk", + "--jobs=3", + "--output=starlark", + "--starlark:expr=target.files_to_run.executable.path" + ], + "artifact": "bazel-out/k8-opt/bin/xff/cli/xff_full", + "configuration": { + ".bazelrc": "d194a75cb8f8e5d39dd3dc8a853aae4d1215a4539aafa92fb044713ef1ddbea1", + ".bazelversion": "4d9dfd02f463b487bd0bff68cf9292b3626b31c81febb54e8911e493f64793d0", + "MODULE.bazel": "14ac2e29445ee90680e1ec7d6debaeb0b2fb22ef29326eb74acf9d5ac52ce75d", + "MODULE.bazel.lock": "c5448fb90798589c71e18295711c61ca471d8088e05e194245905bc5dcc68e71", + "bazelmod/llvm.MODULE.bazel": "91fe1ae8bc722dc8af130906212774bfc28ade948d265cd3e6c72f67c50e08aa" + } + }, + "52950f937ad371e1450538faf7983024b324688d": { + "source": { + "revision": "52950f937ad371e1450538faf7983024b324688d", + "tree": "f99e563e2f1d6762190aee03c9ed0fd2c3e846f1", + "clean": true + }, + "sha256": "4fd6b2f73f912a5f6f50b958e1a75842bcce57fcb35bbcdce13833b2ffc16877", + "bytes": 18473632, + "command": [ + "bazel", + "--nosystem_rc", + "--nohome_rc", + "build", + "//xff/cli:xff_full", + "--config=clang_release", + "--config=xff_full", + "--disk_cache=/home/marcus/.cache/bazel-disk", + "--jobs=3" + ], + "query": [ + "bazel", + "--nosystem_rc", + "--nohome_rc", + "cquery", + "config(//xff/cli:xff_full, target)", + "--config=clang_release", + "--config=xff_full", + "--disk_cache=/home/marcus/.cache/bazel-disk", + "--jobs=3", + "--output=starlark", + "--starlark:expr=target.files_to_run.executable.path" + ], + "artifact": "bazel-out/k8-opt/bin/xff/cli/xff_full", + "configuration": { + ".bazelrc": "d194a75cb8f8e5d39dd3dc8a853aae4d1215a4539aafa92fb044713ef1ddbea1", + ".bazelversion": "4d9dfd02f463b487bd0bff68cf9292b3626b31c81febb54e8911e493f64793d0", + "MODULE.bazel": "14ac2e29445ee90680e1ec7d6debaeb0b2fb22ef29326eb74acf9d5ac52ce75d", + "MODULE.bazel.lock": "c5448fb90798589c71e18295711c61ca471d8088e05e194245905bc5dcc68e71", + "bazelmod/llvm.MODULE.bazel": "91fe1ae8bc722dc8af130906212774bfc28ade948d265cd3e6c72f67c50e08aa" + } + } + }, + "head_cells": 60, + "control_cells": 60, + "validated_invocations": 3600, + "sampling": { + "warmup": 1, + "measured": 9, + "retained": 7 + }, + "control_error": null, + "fixture": "/dev/shm/xff-per-entry-followup-20261108", + "storage_release": { + "filesystem": "tmpfs", + "storage": "memory", + "fixture": "/dev/shm/xff-per-entry-followup-20261108", + "borrowed": true, + "preserved": true, + "released_at": "2026-11-08T23:59:03.795281+00:00" + }, + "binary_evidence": { + "traditional": { + "schema": 1, + "method": "sha256-retained-executable", + "sha256": "4fd6b2f73f912a5f6f50b958e1a75842bcce57fcb35bbcdce13833b2ffc16877", + "measurement_identity": "f8ff95dfcaf473df6ba5fdd2b7110b731e08c062bc824cbb541670c65d9cda70", + "retention_reference": "original frozen full-production executable used for local paired validation", + "verified_at": "2026-11-08T23:59:03.960422+00:00" + }, + "anchor": { + "schema": 1, + "method": "sha256-retained-executable", + "sha256": "4fd6b2f73f912a5f6f50b958e1a75842bcce57fcb35bbcdce13833b2ffc16877", + "measurement_identity": "74d24587d2d8b9ce656166758b7db15a084a56628d97a1fe0018290129ed3688", + "retention_reference": "original frozen full-production executable used for local paired validation", + "verified_at": "2026-11-08T23:59:03.975087+00:00" + } + }, + "raw_observation_sha256": "71fa57acbc15958232e2c245343bb470b99c55d8eafa965b1d5fa5820d635f3d", + "raw_evidence": "/home/marcus/Documents/xff-per-entry-followup-20261108/extended-native-validation/collection/observation.json" + }, + "macos": { + "source": "96765e1859c3d48ac0f04092c0613b4e8ad8ae21", + "parent": "52950f937ad371e1450538faf7983024b324688d", + "origin": "local", + "platform": "macOS-26.6-arm64-arm-64bit-Mach-O", + "target": "//xff/cli:xff_full", + "configuration": ["clang_release", "xff_full"], + "builds": { + "96765e1859c3d48ac0f04092c0613b4e8ad8ae21": { + "source": { + "revision": "96765e1859c3d48ac0f04092c0613b4e8ad8ae21", + "tree": "28719926a988f24150f67f9eb7947066d2488071", + "clean": true + }, + "sha256": "b014fc984a68514826c2e32c3be7816baa32ac441753a18e12c75ed7e721b8b3", + "bytes": 8278888, + "command": [ + "bazel", + "--nosystem_rc", + "--nohome_rc", + "build", + "//xff/cli:xff_full", + "--config=clang_release", + "--config=xff_full", + "--disk_cache=/Users/marcus/.cache/bazel-disk", + "--jobs=3" + ], + "query": [ + "bazel", + "--nosystem_rc", + "--nohome_rc", + "cquery", + "config(//xff/cli:xff_full, target)", + "--config=clang_release", + "--config=xff_full", + "--disk_cache=/Users/marcus/.cache/bazel-disk", + "--jobs=3", + "--output=starlark", + "--starlark:expr=target.files_to_run.executable.path" + ], + "artifact": "bazel-out/darwin_arm64-opt/bin/xff/cli/xff_full", + "configuration": { + ".bazelrc": "d194a75cb8f8e5d39dd3dc8a853aae4d1215a4539aafa92fb044713ef1ddbea1", + ".bazelversion": "4d9dfd02f463b487bd0bff68cf9292b3626b31c81febb54e8911e493f64793d0", + "MODULE.bazel": "14ac2e29445ee90680e1ec7d6debaeb0b2fb22ef29326eb74acf9d5ac52ce75d", + "MODULE.bazel.lock": "585f641b4aa3c00c77634b2ef3d8f73da48cf99e03ea6c3727b5d7b89fcda26f", + "bazelmod/llvm.MODULE.bazel": "91fe1ae8bc722dc8af130906212774bfc28ade948d265cd3e6c72f67c50e08aa" + } + }, + "52950f937ad371e1450538faf7983024b324688d": { + "source": { + "revision": "52950f937ad371e1450538faf7983024b324688d", + "tree": "f99e563e2f1d6762190aee03c9ed0fd2c3e846f1", + "clean": true + }, + "sha256": "b014fc984a68514826c2e32c3be7816baa32ac441753a18e12c75ed7e721b8b3", + "bytes": 8278888, + "command": [ + "bazel", + "--nosystem_rc", + "--nohome_rc", + "build", + "//xff/cli:xff_full", + "--config=clang_release", + "--config=xff_full", + "--disk_cache=/Users/marcus/.cache/bazel-disk", + "--jobs=3" + ], + "query": [ + "bazel", + "--nosystem_rc", + "--nohome_rc", + "cquery", + "config(//xff/cli:xff_full, target)", + "--config=clang_release", + "--config=xff_full", + "--disk_cache=/Users/marcus/.cache/bazel-disk", + "--jobs=3", + "--output=starlark", + "--starlark:expr=target.files_to_run.executable.path" + ], + "artifact": "bazel-out/darwin_arm64-opt/bin/xff/cli/xff_full", + "configuration": { + ".bazelrc": "d194a75cb8f8e5d39dd3dc8a853aae4d1215a4539aafa92fb044713ef1ddbea1", + ".bazelversion": "4d9dfd02f463b487bd0bff68cf9292b3626b31c81febb54e8911e493f64793d0", + "MODULE.bazel": "14ac2e29445ee90680e1ec7d6debaeb0b2fb22ef29326eb74acf9d5ac52ce75d", + "MODULE.bazel.lock": "585f641b4aa3c00c77634b2ef3d8f73da48cf99e03ea6c3727b5d7b89fcda26f", + "bazelmod/llvm.MODULE.bazel": "91fe1ae8bc722dc8af130906212774bfc28ade948d265cd3e6c72f67c50e08aa" + } + } + }, + "head_cells": 60, + "control_cells": 60, + "validated_invocations": 3600, + "sampling": { + "warmup": 1, + "measured": 9, + "retained": 7 + }, + "control_error": null, + "fixture": "/private/tmp/xff-extended-native-validation-macos-20261108/collection/ram-volume/fixture", + "storage_release": { + "device": "/dev/disk4", + "image": "ram://4194304", + "storage": "memory", + "filesystem": "case-sensitive HFS+", + "created_at": "2026-11-08T23:57:15.208935+00:00", + "mount": "/private/tmp/xff-extended-native-validation-macos-20261108/collection/ram-volume", + "uuid": "A03B359F-E90E-38C0-96B4-439E7BF90389", + "released_at": "2026-11-09T00:20:53.956904+00:00" + }, + "binary_evidence": { + "traditional": { + "schema": 1, + "method": "sha256-retained-executable", + "sha256": "b014fc984a68514826c2e32c3be7816baa32ac441753a18e12c75ed7e721b8b3", + "measurement_identity": "d24dba4764229a0b5e7e2f60c1e57aabc635b33efd89639736acf226fec126dd", + "retention_reference": "original frozen full-production executable used for local paired validation", + "verified_at": "2026-11-09T00:20:54.189423+00:00" + }, + "anchor": { + "schema": 1, + "method": "sha256-retained-executable", + "sha256": "b014fc984a68514826c2e32c3be7816baa32ac441753a18e12c75ed7e721b8b3", + "measurement_identity": "8ea324153b17ab9845c7cc56278ac9ecfe80faaee85707943fb0464732097c8b", + "retention_reference": "original frozen full-production executable used for local paired validation", + "verified_at": "2026-11-09T00:20:54.203892+00:00" + } + }, + "raw_observation_sha256": "4ee36606de59184ca94d958a3dd0b4ba16fb57a45271e05d4e83835f4aaa6786", + "raw_evidence": "/private/tmp/xff-extended-native-validation-macos-20261108/collection/observation.json" + } + }, + "limits": [ + "Native pipeline qualification uses byte-identical head/parent production executables and makes no performance-improvement claim.", + "Frozen native driver source is 96765e1859c3d48ac0f04092c0613b4e8ad8ae21; follow-ups tighten validation and improve build logging/publication without changing measured commands, sampling, or C++ sources.", + "Original imported observations remain local fixed-reference history; GitHub collection must establish CI provenance separately.", + "Historical data publication and the first real post-merge extended CI run remain required." + ], + "tests": { + "macos_bazel": { + "benchmark_extended_ci_test": { + "tests": 1, + "failures": 0, + "errors": 0 + }, + "benchmark_extended_records_test": { + "tests": 1, + "failures": 0, + "errors": 0 + }, + "benchmark_extended_site_test": { + "tests": 1, + "failures": 0, + "errors": 0 + }, + "benchmark_extended_storage_test": { + "tests": 1, + "failures": 0, + "errors": 0 + }, + "benchmark_extended_build_test": { + "tests": 1, + "failures": 0, + "errors": 0 + }, + "benchmark_extended_history_test": { + "tests": 1, + "failures": 0, + "errors": 0 + }, + "benchmark_extended_report_test": { + "tests": 1, + "failures": 0, + "errors": 0 + }, + "benchmark_extended_test": { + "tests": 1, + "failures": 0, + "errors": 0 + }, + "benchmark_reconcile_ci_test": { + "tests": 1, + "failures": 0, + "errors": 0 + }, + "benchmark_datasets_test": { + "tests": 1, + "failures": 0, + "errors": 0 + }, + "benchmark_history_test": { + "tests": 1, + "failures": 0, + "errors": 0 + }, + "benchmark_recovery_test": { + "tests": 1, + "failures": 0, + "errors": 0 + } + }, + "linux_direct": { + "extended": 53, + "reconcile": 8, + "recovery": 8, + "failures": 0 + }, + "real_build_logging_smoke": { + "source": "52950f937ad371e1450538faf7983024b324688d", + "sha256": "b014fc984a68514826c2e32c3be7816baa32ac441753a18e12c75ed7e721b8b3", + "result": "passed" + } + } +} diff --git a/release-site.json b/release-site.json index 535b340171c..b4b53fefbd0 100644 --- a/release-site.json +++ b/release-site.json @@ -177,6 +177,7 @@ } ], "files": { + "docs/benchmarks/extended-history-continuous-qualification-20261109.json": "docs/benchmarks/extended-history-continuous-qualification-20261109.json", "docs/benchmarks/transactional-rg-records-20261107.json.gz": "docs/benchmarks/transactional-rg-records-20261107.json.gz", "docs/benchmarks/language-json-storage-20261107.json.gz": "docs/benchmarks/language-json-storage-20261107.json.gz", "docs/benchmarks/archive-ancestor-hint-20261107.json.gz": "docs/benchmarks/archive-ancestor-hint-20261107.json.gz", diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel index 13651de1829..d2bd4547358 100644 --- a/tools/BUILD.bazel +++ b/tools/BUILD.bazel @@ -1373,9 +1373,11 @@ py_test( name = "benchmark_extended_ci_test", size = "small", srcs = ["benchmark_extended_ci_test.py"], + data = ["//:python_workflow_test_data"], deps = [ ":benchmark_extended_ci_lib", ":benchmark_extended_test_support_lib", + ":test_paths_lib", ], ) @@ -1539,13 +1541,19 @@ py_library( name = "benchmark_recovery_lib", srcs = ["benchmark_recovery.py"], imports = ["."], - deps = [":benchmark_preview_ci_lib"], + deps = [ + ":benchmark_history_lib", + ":benchmark_preview_ci_lib", + ], ) py_binary( name = "benchmark_recovery", srcs = ["benchmark_recovery.py"], - deps = [":benchmark_preview_ci_lib"], + deps = [ + ":benchmark_history_lib", + ":benchmark_preview_ci_lib", + ], ) py_test( diff --git a/tools/benchmark_extended_build.py b/tools/benchmark_extended_build.py index c359eed7172..13686a71244 100644 --- a/tools/benchmark_extended_build.py +++ b/tools/benchmark_extended_build.py @@ -10,6 +10,7 @@ from pathlib import Path import shutil import subprocess +import sys import time @@ -35,6 +36,18 @@ def clean_source(checkout, sha): return {'revision': sha, 'tree': git(checkout, 'rev-parse', 'HEAD^{tree}'), 'clean': True} +def build_logged(command, checkout, log): + """Expose native build progress and keep the exact diagnostic output.""" + with subprocess.Popen(command, cwd=checkout, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, bufsize=1) as process: + for line in process.stdout: + log.write(line) + log.flush() + print(line, end='', file=sys.stderr, flush=True) + if process.wait(): + raise subprocess.CalledProcessError(process.returncode, command) + + def freeze_build(checkout, directory, sha, disk_cache, jobs=6, build_library_path=None): """Build and immediately freeze one exact checkout without selecting another revision.""" directory.mkdir(parents=True, exist_ok=True) @@ -52,8 +65,9 @@ def freeze_build(checkout, directory, sha, disk_cache, jobs=6, build_library_pat if build_library_path: options.append('--action_env=LD_LIBRARY_PATH=' + str(build_library_path.resolve())) command = [*bazel, 'build', '//xff/cli:xff_full', *options] + print(f'BUILD {sha} //xff/cli:xff_full --config=clang_release --config=xff_full ({checkout})', flush=True) with (directory / 'build.log').open('w') as log: - subprocess.run(command, cwd=checkout, stdout=log, stderr=subprocess.STDOUT, check=True) + build_logged(command, checkout, log) query = [*bazel, 'cquery', 'config(//xff/cli:xff_full, target)', *options, '--output=starlark', '--starlark:expr=target.files_to_run.executable.path'] artifact = subprocess.check_output(query, cwd=checkout, stderr=log, text=True).strip() @@ -66,6 +80,7 @@ def freeze_build(checkout, directory, sha, disk_cache, jobs=6, build_library_pat shutil.copy2(built, executable) if digest(executable) != binary_hash: raise ValueError('frozen executable copy mismatch') + print(f'FROZEN {sha} SHA256={binary_hash} bytes={executable.stat().st_size} {executable}', flush=True) # Link actions record the actual production LTO command for this revision. with (directory / 'link-actions.json').open('w') as actions: subprocess.run([*bazel, 'aquery', 'mnemonic("CppLink", //xff/cli:xff_full)', diff --git a/tools/benchmark_extended_build_test.py b/tools/benchmark_extended_build_test.py index 854bffb61fc..af15cee97bb 100644 --- a/tools/benchmark_extended_build_test.py +++ b/tools/benchmark_extended_build_test.py @@ -4,6 +4,7 @@ from pathlib import Path import json +import io import shutil import tempfile import unittest @@ -63,6 +64,9 @@ def run(command, **kwargs): {'arguments': ['clang++', '-flto=thin', '-o', artifact]}]})) built.write_bytes(b'alias changed after the frozen copy') + def logged(command, checkout, log): + run(command) + def query(command, **kwargs): events.append('configured query') self.assertIn('--config=clang_release', command) @@ -76,6 +80,7 @@ def freeze(source, destination): with mock.patch.object(build, 'clean_source', return_value={'revision': 'revision', 'tree': 'tree', 'clean': True}), \ mock.patch.object(build.subprocess, 'run', side_effect=run), \ + mock.patch.object(build, 'build_logged', side_effect=logged), \ mock.patch.object(build.subprocess, 'check_output', side_effect=query), \ mock.patch.object(build.shutil, 'copy2', side_effect=freeze): saved = build.freeze_build(checkout, directory, 'revision', root / 'cache') @@ -83,6 +88,24 @@ def freeze(source, destination): self.assertEqual(saved['sha256'], build.digest(directory / 'xff_full')) self.assertNotEqual(saved['sha256'], build.digest(built)) + def test_build_progress_is_visible_retained_and_failure_propagates(self): + for status in (0, 1): + with self.subTest(status=status): + process = mock.Mock(stdout=io.StringIO('building source.cc\nlinking xff_full\n'), returncode=status) + process.wait.return_value = status + process.__enter__ = mock.Mock(return_value=process) + process.__exit__ = mock.Mock(return_value=False) + output, log = io.StringIO(), io.StringIO() + with mock.patch.object(build.subprocess, 'Popen', return_value=process), \ + mock.patch.object(build.sys, 'stderr', output): + if status: + with self.assertRaises(build.subprocess.CalledProcessError): + build.build_logged(['bazel', 'build'], Path('.'), log) + else: + build.build_logged(['bazel', 'build'], Path('.'), log) + self.assertEqual(log.getvalue(), 'building source.cc\nlinking xff_full\n') + self.assertEqual(output.getvalue(), log.getvalue()) + if __name__ == '__main__': unittest.main() diff --git a/tools/benchmark_extended_ci.py b/tools/benchmark_extended_ci.py index 1f723467329..71dcd9042d6 100644 --- a/tools/benchmark_extended_ci.py +++ b/tools/benchmark_extended_ci.py @@ -120,7 +120,8 @@ def collect(build_root, root, launcher_path, collection=None, series=None, exist ('https://github.com/' + collection['repository'] + '/actions/runs/' + str(collection['run_id']) + '/attempts/' + str(collection['run_attempt']) + ' (benchmark-extended-build-' + platform_name + ')') if collection is not None - else 'original frozen full-production executable used for local paired validation') + else 'original frozen full-production executable used for local paired validation', + control_error=control_error) record['storage_release'] = records.read(root / 'storage-release.json') record['control_error'] = control_error record['identity'] = records.observation_identity(record) diff --git a/tools/benchmark_extended_ci_test.py b/tools/benchmark_extended_ci_test.py index 0c196b22e9a..c677704c062 100644 --- a/tools/benchmark_extended_ci_test.py +++ b/tools/benchmark_extended_ci_test.py @@ -10,6 +10,7 @@ import benchmark_extended_ci as ci from benchmark_extended_test_support import ExtendedFixture +from test_paths import repository_file class BenchmarkExtendedCiTest(unittest.TestCase): @@ -94,6 +95,17 @@ def test_head_failure_never_creates_publishable_observation(self): self.assertFalse((self.root / 'observation.json').exists()) self.assertTrue((self.root / 'storage-release.json').exists()) + def test_ci_control_failure_retains_head_with_explicit_error_and_valid_workflow_binding(self): + self.fail_at = len(self.fixture.cases) * 30 + 1 + collection = self.fixture.record(origin='ci', comparison='parent')['collection'] + with self.assertRaisesRegex(ValueError, 'head measurements retained'): + self.collect(collection) + value = ci.records.read(self.root / 'observation.json') + self.assertEqual(value['origin'], 'ci') + self.assertEqual(value['controls'], []) + self.assertIn('synthetic failed output', value['control_error']) + ci.records.validate(value, self.fixture.source()) + def test_changed_reference_invalidates_whole_collection_after_successful_samples(self): self.mutate_at = len(self.fixture.cases) * 30 * 2 with self.assertRaisesRegex(ValueError, 'reference executable changed'): @@ -109,6 +121,25 @@ def test_ci_cannot_reuse_a_local_fixture_or_measure_a_different_head(self): self.collect(dict(collection, head_sha='f' * 40)) self.assertEqual(self.calls, 0) + def test_workflow_keeps_collection_independent_and_measures_downloaded_production_bytes(self): + workflow = repository_file('.github/workflows/benchmarks.yml').read_text() + extended = workflow.split(' extended:\n', 1)[1].split('\n build:', 1)[0] + self.assertNotIn('needs:', extended) + self.assertIn('fail-fast: false', extended) + helper = extended.index('cp "${launcher}" "${RUNNER_TEMP}/extended-resource-launcher"') + head = extended.index('--role=head') + parent = extended.index('--role=base') + upload = extended.index('name: benchmark-extended-build-${{ matrix.platform }}') + download = extended.index('actions/download-artifact@') + restore = extended.index('tar -xf "${RUNNER_TEMP}/extended-retention/benchmark-extended-build.tar"') + measure = extended.index('benchmark_extended_ci.py collect') + self.assertEqual(sorted((helper, head, parent, upload, download, restore, measure)), + [helper, head, parent, upload, download, restore, measure]) + self.assertIn('--build-root="${RUNNER_TEMP}/extended-retained-build"', extended) + diagnostics = extended.split('name: Retain extended build diagnostics', 1)[1] + self.assertIn('if: always()', diagnostics) + self.assertIn('/extended-build/binaries/*/build.log', diagnostics) + if __name__ == '__main__': unittest.main() diff --git a/tools/benchmark_extended_records.py b/tools/benchmark_extended_records.py index 770c6ed1f11..24efbb18411 100644 --- a/tools/benchmark_extended_records.py +++ b/tools/benchmark_extended_records.py @@ -148,6 +148,9 @@ def validate(record, source=None): and all(flag in manifest['command'] for flag in ('//xff/cli:xff_full', '--config=clang_release', '--config=xff_full')), 'production link/configuration evidence missing') if record['origin'] == 'ci': + require(bool(record.get('controls')) or + isinstance(record.get('control_error'), str) and bool(record['control_error']), + 'CI controls require a complete grid or an explicit failure') collection = record['collection'] require(all(type(collection[key]) is int and collection[key] > 0 for key in ('run_id', 'run_attempt')), 'invalid extended workflow identity') @@ -213,11 +216,14 @@ def view_report(record, view): def make_record(contract, revision, rows, builds, platform, machine, series, comparison, - origin='local', collection=None, controls=None, retention_reference='original frozen executable'): + origin='local', collection=None, controls=None, retention_reference='original frozen executable', + control_error=None): result = {'schema': 1, 'kind': 'extended-production', 'contract': contract, 'revision': revision, 'rows': rows, 'builds': builds, 'platform': platform, 'machine': machine, 'series': series, 'comparison': comparison, 'origin': origin, 'collection': collection, 'controls': controls or [], 'completed_at': max(row['after']['utc'] for row in rows)} + if control_error is not None: + result['control_error'] = control_error result['view_verification'] = {} for view in ('traditional', 'anchor'): wrapper = view_report(result, view) diff --git a/tools/benchmark_extended_records_test.py b/tools/benchmark_extended_records_test.py index 708610010ae..de377c989f8 100644 --- a/tools/benchmark_extended_records_test.py +++ b/tools/benchmark_extended_records_test.py @@ -82,6 +82,16 @@ def test_complete_head_can_be_retained_from_failed_workflow(self): self.assertEqual(saved['publication_source']['id'], 12) self.assertEqual(saved['origin'], 'ci') + def test_ci_control_omission_requires_an_explicit_diagnostic_failure(self): + value = self.fixture.record(origin='ci', comparison='parent') + value['controls'] = [] + value['identity'] = records.observation_identity(value) + with self.assertRaisesRegex(ValueError, 'explicit failure'): + records.validate(value, self.fixture.source()) + value['control_error'] = 'synthetic control failure; complete head retained' + value['identity'] = records.observation_identity(value) + records.validate(value, self.fixture.source()) + if __name__ == '__main__': unittest.main() diff --git a/tools/benchmark_extended_site.py b/tools/benchmark_extended_site.py index 24448d4b7ca..c4d2fc9df1a 100644 --- a/tools/benchmark_extended_site.py +++ b/tools/benchmark_extended_site.py @@ -8,6 +8,7 @@ import json import os from pathlib import Path +import re import benchmark_datasets import benchmark_extended as suite @@ -65,7 +66,8 @@ def retained(root): series_id = records.identity(description) key = series_id, record['revision']['sha'] collection = record.get('collection') or {} - rank = (record['completed_at'], int(collection.get('run_id', 0)), int(collection.get('run_attempt', 0)), record['identity']) + rank = (record['completed_at'], int(collection.get('run_id', 0)), int(collection.get('run_attempt', 0)), + bool(record.get('controls')), record['identity']) if key not in selected or rank > selected[key][0]: selected[key] = (rank, path, record, view, description) return sorted(selected.values(), key=lambda item: (item[2]['revision']['date'], item[2]['revision']['sha'], item[3])) @@ -113,6 +115,9 @@ def publish(root, repository='mboworks/xff'): links = version_links.get(revision['sha'], []) if not links and revision['pr'] > 0: links = [{'label': 'PR #' + str(revision['pr']), 'href': f'https://github.com/{repository}/pull/{revision["pr"]}'}] + pr_labels = [link['label'] for link in links if re.fullmatch(r'PR #[0-9]+', link.get('label', ''))] + if not experiment and pr_labels: + label = ', '.join(pr_labels) + ' / ' + revision['sha'][:10] links = [*links, {'label': ('Parent ' if record['comparison'] == 'parent' else 'Fixed reference ') + anchor['sha'][:12], 'href': f'https://github.com/{repository}/commit/{anchor["sha"]}'}] origin_label = 'Local / ' + record['series'] if local else 'CI / ' + record['platform'] diff --git a/tools/benchmark_extended_site_test.py b/tools/benchmark_extended_site_test.py index 7cc5de3430a..33549582efa 100644 --- a/tools/benchmark_extended_site_test.py +++ b/tools/benchmark_extended_site_test.py @@ -3,6 +3,7 @@ """Keep extended observations separate, measured, discoverable and reproducible.""" import gzip +import copy import json import unittest @@ -88,6 +89,27 @@ def test_publication_rejects_untrusted_ci_and_modified_raw_rows(self): with self.assertRaisesRegex(ValueError, 'untrusted extended workflow'): site.publish(self.root) + def test_same_head_observations_prefer_completed_controls_when_measurement_times_match(self): + value = self.fixture.record() + records.retain(self.root, value) + complete = copy.deepcopy(value) + complete['controls'] = self.fixture.rows(self.fixture.revisions[-1]) + complete['identity'] = records.observation_identity(complete) + records.retain(self.root, complete) + selected = site.retained(self.root) + self.assertEqual(len(selected), 2) + self.assertTrue(all(item[2]['controls'] for item in selected)) + + def test_ci_pr_labels_use_trusted_version_links_when_subject_has_no_pr_suffix(self): + self.fixture.revisions[0]['pr'] = 0 + value = self.fixture.record(origin='ci', comparison='parent') + records.retain(self.root, value, self.fixture.source()) + (self.root / 'version-links.json').write_text(json.dumps({value['revision']['sha']: [ + {'label': 'PR #999', 'href': 'https://github.com/mboworks/xff/pull/999'}]})) + site.publish(self.root) + entries = [item for item in records.read(self.root / 'catalog.json') if item.get('extended')] + self.assertTrue(all(item['label'] == 'PR #999 / ' + value['revision']['sha'][:10] for item in entries)) + def test_import_uses_complete_original_cells_and_actual_retained_builds(self): campaign = self.fixture.root / 'campaign' campaign.mkdir() diff --git a/tools/benchmark_extended_test_support.py b/tools/benchmark_extended_test_support.py index 2a3c5ce0930..d0f2d00b90f 100644 --- a/tools/benchmark_extended_test_support.py +++ b/tools/benchmark_extended_test_support.py @@ -88,7 +88,8 @@ def record(self, origin='local', comparison='fixed-anchor'): collection = {'run_id': 12, 'run_attempt': 1, 'repository': 'owner/repo', 'head_sha': self.revisions[0]['sha'], 'base_sha': self.revisions[-1]['sha']} if origin == 'ci' else None return records.make_record(copy.deepcopy(self.contract), self.revisions[0], self.rows(), self.builds, - 'linux', 'x86_64', 'synthetic-only', comparison, origin, collection) + 'linux', 'x86_64', 'synthetic-only', comparison, origin, collection, + controls=self.rows(self.revisions[-1]) if origin == 'ci' else None) def source(self): return {'id': 12, 'run_attempt': 1, 'head_sha': self.revisions[0]['sha'], diff --git a/tools/benchmark_reconcile_ci.py b/tools/benchmark_reconcile_ci.py index 491fff134f8..34a4379d2d6 100644 --- a/tools/benchmark_reconcile_ci.py +++ b/tools/benchmark_reconcile_ci.py @@ -56,6 +56,27 @@ def latest_artifacts(items): return list(selected.values()) +def run_artifacts(api, root, run): + """Avoid re-enumerating settled runs on every coalesced publication request.""" + now = datetime.now(timezone.utc) + source = {key: run.get(key) for key in ('id', 'run_attempt', 'head_sha', 'updated_at')} + marker = root / 'reconciliation' / str(run['id']) / str(run['run_attempt']) / 'artifacts.json' + if marker.exists(): + saved = json.loads(marker.read_text()) + if saved['source'] == source and now - datetime.fromisoformat(saved['checked_at']) < timedelta(hours=24): + return saved['artifacts'] + inventory = api.pages(f"actions/runs/{run['id']}/artifacts", 'artifacts') + # Retry attempts get a different key. Recent completions are always refreshed + # to allow for artifact-index propagation; external deletion/expiry is refreshed daily. + if (run.get('status') == 'completed' and run.get('updated_at') + and now - datetime.fromisoformat(run['updated_at']) >= timedelta(minutes=15)): + marker.parent.mkdir(parents=True, exist_ok=True) + temporary = marker.with_suffix('.json.tmp') + temporary.write_text(json.dumps({'source': source, 'checked_at': now.isoformat(), 'artifacts': inventory}) + '\n') + temporary.replace(marker) + return inventory + + def retain_measurement(api, root, run, inventory=None): destination = root / 'runs' / str(run['id']) / str(run['run_attempt']) missing = {platform for platform in ('linux', 'macos') @@ -158,8 +179,9 @@ def retain_extended_artifact(api, root, run, item, platform): def retain_benchmarks(api, root, run): """A failed independent job must not discard other valid platform reports.""" - inventory = [item for item in api.pages(f"actions/runs/{run['id']}/artifacts", 'artifacts') - if not item.get('expired')] + inventory = [item for item in run_artifacts(api, root, run) + if not item.get('expired') and (not item.get('expires_at') + or datetime.fromisoformat(item['expires_at']) > datetime.now(timezone.utc))] retained = False for name, retain in (('core', retain_measurement), ('extended', retain_extended)): try: diff --git a/tools/benchmark_reconcile_ci_test.py b/tools/benchmark_reconcile_ci_test.py index 93e1b055813..eb94aad094d 100644 --- a/tools/benchmark_reconcile_ci_test.py +++ b/tools/benchmark_reconcile_ci_test.py @@ -3,6 +3,7 @@ """Recover independently valid reports without trusting workflow or artifact claims.""" import copy +from datetime import datetime, timezone import gzip import io import json @@ -118,6 +119,38 @@ def test_extract_rejects_escape_and_symlinks(self): with self.assertRaises(ValueError): reconcile.extract(output.getvalue(), self.fixture.root / 'extracted') + def test_settled_inventory_is_cached_but_retry_and_recent_completion_are_rechecked(self): + source = dict(self.api.source, updated_at='2026-11-01T00:00:00Z') + self.assertEqual(reconcile.run_artifacts(self.api, self.root, source), self.api.items) + self.api.requests.clear() + self.assertEqual(reconcile.run_artifacts(self.api, self.root, source), self.api.items) + self.assertEqual(self.api.requests, []) + reconcile.run_artifacts(self.api, self.root, dict(source, run_attempt=2)) + self.assertEqual(self.api.requests, ['actions/runs/12/artifacts']) + self.api.requests.clear() + recent = dict(source, run_attempt=3, updated_at=datetime.now(timezone.utc).isoformat()) + reconcile.run_artifacts(self.api, self.root, recent) + reconcile.run_artifacts(self.api, self.root, recent) + self.assertEqual(self.api.requests, ['actions/runs/12/artifacts', 'actions/runs/12/artifacts']) + + def test_expired_cached_artifacts_are_not_downloaded_and_old_cache_is_refreshed(self): + source = dict(self.api.source, updated_at='2026-11-01T00:00:00Z') + self.api.items[0]['expires_at'] = '2000-01-01T00:00:00Z' + reconcile.run_artifacts(self.api, self.root, source) + self.api.requests.clear() + with mock.patch.object(reconcile, 'retain_measurement', return_value=False) as core, \ + mock.patch.object(reconcile, 'retain_extended', return_value=False) as extended: + self.assertFalse(reconcile.retain_benchmarks(self.api, self.root, source)) + self.assertEqual(core.call_args.args[-1], []) + self.assertEqual(extended.call_args.args[-1], []) + self.assertEqual(self.api.requests, []) + marker = self.root / 'reconciliation/12/1/artifacts.json' + cached = json.loads(marker.read_text()) + cached['checked_at'] = '2000-01-01T00:00:00Z' + marker.write_text(json.dumps(cached)) + reconcile.run_artifacts(self.api, self.root, source) + self.assertEqual(self.api.requests, ['actions/runs/12/artifacts']) + if __name__ == '__main__': unittest.main() diff --git a/tools/benchmark_recovery.py b/tools/benchmark_recovery.py index 8b2cfcafd2d..a88935182f2 100644 --- a/tools/benchmark_recovery.py +++ b/tools/benchmark_recovery.py @@ -8,15 +8,43 @@ from datetime import date import io import json +from pathlib import Path import re import subprocess +import sys +import tempfile import zipfile +import benchmark_history import benchmark_preview_ci PLATFORMS = ('linux', 'macos') +def report_available(api, run, artifact, platform): + # Retrieval failures propagate: an API timeout is not evidence that costly + # measurements must be repeated. Only received, invalid data is rejected. + raw = api.request(f"actions/artifacts/{artifact['id']}/zip", raw=True) + try: + if len(raw) > 128 * 1024 * 1024: + raise ValueError('benchmark report archive exceeds 128 MiB') + with zipfile.ZipFile(io.BytesIO(raw)) as archive: + entry = archive.getinfo(artifact['name'] + '.json') + if entry.file_size > 128 * 1024 * 1024: + raise ValueError('benchmark report exceeds 128 MiB') + report = json.loads(archive.read(entry)) + if report.get('platform') != platform or report.get('head') != run['head_sha'] or not report.get('tool_comparisons'): + raise ValueError('report lacks matching head/platform/viewer measurements') + # Use the publisher's actual completeness, command and binary-identity + # checks, writing only into a disposable local directory. + with tempfile.TemporaryDirectory(prefix='xff-recovery-report-') as temporary: + benchmark_history.retain(Path(temporary), report, run) + return True + except (ValueError, KeyError, zipfile.BadZipFile) as error: + print(f"::warning::unpublishable run {run['id']} {platform}: {error}", file=sys.stderr, flush=True) + return False + + def catalog(api): metadata = api.request('contents/benchmarks/catalog.json?ref=coverage-pages') if not metadata.get('content') and metadata.get('sha'): @@ -104,12 +132,13 @@ def inventory(api, since, number=None): active = any(run['status'] != 'completed' for run in source_runs) available = {platform for platform in PLATFORMS if (sha, platform) in pending_backfills} for run in source_runs: - if run.get('conclusion') != 'success': + if run.get('status') != 'completed': continue artifacts = api.pages(f"actions/runs/{run['id']}/artifacts", 'artifacts') for artifact in artifacts: match = re.fullmatch(r'benchmark-report-(linux|macos)-[A-Za-z0-9_.-]+', artifact['name']) - if match and not artifact.get('expired') and artifact['created_at'] >= run['run_started_at']: + if (match and not artifact.get('expired') and artifact['created_at'] >= run['run_started_at'] + and report_available(api, run, artifact, match.group(1))): available.add(match.group(1)) state = ('measuring' if active else 'awaiting publication' if set(missing) <= available else 'needs backfill') @@ -142,8 +171,16 @@ def main(): campaigns = api.pages('actions/workflows/benchmark_backfill.yml/runs?branch=main', 'workflow_runs') if any(run['status'] != 'completed' for run in campaigns): raise ValueError('a backfill campaign is already active or queued; retry after it finishes') - selected = candidates[:args.batch_size] - platforms = sorted({platform for row in selected for platform in row['missing']}) + # A cross-product of different missing-platform sets would repeat successful + # configurations. Recover the oldest gap and batch only identical requests. + def requested(row): + return tuple(platform for platform in PLATFORMS if platform in row['missing'] + and platform not in row.get('available_artifacts', [])) + + platforms = requested(candidates[0]) + if not platforms: + raise ValueError('backfill candidate has no unavailable platform') + selected = [row for row in candidates if requested(row) == platforms][:args.batch_size] # The deployed workflow must support the revisions input added by PR #1147. subprocess.run(['gh', 'workflow', 'run', 'benchmark_backfill.yml', '--repo', api.repository, '--ref', 'main', '-f', 'revisions=' + ','.join(row['commit'] for row in selected), diff --git a/tools/benchmark_recovery_test.py b/tools/benchmark_recovery_test.py index 2434643b7c9..58d17530c86 100644 --- a/tools/benchmark_recovery_test.py +++ b/tools/benchmark_recovery_test.py @@ -4,6 +4,7 @@ import base64 import io import json +import subprocess import unittest from unittest import mock import zipfile @@ -47,15 +48,38 @@ def test_pr_missing_one_platform_is_reported(self): api.entries = [{'source': 'merged', 'commit': SHA, 'platform_name': 'linux / x86_64'}] self.assertEqual(recovery.inventory(api, '2026-09-19')[0]['missing'], ['macos']) + def test_extended_views_do_not_hide_missing_core_history(self): + api = FakeGitHub() + api.entries = [{'source': 'merged', 'commit': SHA, 'platform_name': 'linux / x86_64', 'extended': True}, + {'source': 'merged', 'commit': SHA, 'platform_name': 'macos / arm64', 'local': True}] + self.assertEqual(recovery.inventory(api, '2026-09-19')[0]['missing'], ['linux', 'macos']) + def test_active_run_and_available_reports_are_not_backfilled(self): api = FakeGitHub() api.runs = [{'id': 1, 'head_sha': SHA, 'event': 'push', 'status': 'in_progress', 'conclusion': None}] self.assertEqual(recovery.inventory(api, '2026-09-19')[0]['state'], 'measuring') - api.runs[0].update(status='completed', conclusion='success', run_started_at='2026-10-01T12:00:00Z') + api.runs[0].update(status='completed', conclusion='failure', run_started_at='2026-10-01T12:00:00Z') api.artifacts['actions/runs/1/artifacts'] = [ - {'name': f'benchmark-report-{platform}-arch', 'created_at': '2026-10-01T13:00:00Z', 'expired': False} - for platform in recovery.PLATFORMS] - self.assertEqual(recovery.inventory(api, '2026-09-19')[0]['state'], 'awaiting publication') + {'id': index, 'name': f'benchmark-report-{platform}-arch', + 'created_at': '2026-10-01T13:00:00Z', 'expired': False} + for index, platform in enumerate(recovery.PLATFORMS, 10)] + for artifact, platform in zip(api.artifacts['actions/runs/1/artifacts'], recovery.PLATFORMS): + archive = io.BytesIO() + with zipfile.ZipFile(archive, 'w') as output: + output.writestr(artifact['name'] + '.json', json.dumps( + {'platform': platform, 'head': SHA, 'tool_comparisons': {'synthetic-only': True}})) + api.archives[f"actions/artifacts/{artifact['id']}/zip"] = archive.getvalue() + with mock.patch.object(recovery.benchmark_history, 'retain') as retain: + self.assertEqual(recovery.inventory(api, '2026-09-19')[0]['state'], 'awaiting publication') + self.assertEqual(retain.call_count, 2) + with mock.patch.object(recovery.benchmark_history, 'retain', side_effect=ValueError('incomplete report')): + self.assertEqual(recovery.inventory(api, '2026-09-19')[0]['state'], 'needs backfill') + + def test_api_observation_timeout_never_becomes_a_request_to_repeat_measurements(self): + api = FakeGitHub() + with mock.patch.object(api, 'request', side_effect=subprocess.TimeoutExpired('gh', 120)), \ + self.assertRaises(subprocess.TimeoutExpired): + recovery.report_available(api, {'id': 1, 'head_sha': SHA}, {'id': 2}, 'linux') def test_retained_completed_campaign_is_not_dispatched_again(self): api = FakeGitHub() @@ -100,6 +124,22 @@ def test_dispatch_uses_the_targeted_main_workflow(self): 'owner/repo', '--ref', 'main', '-f', 'revisions=' + SHA, '-f', 'platforms=linux'], check=True) + def test_dispatch_batches_equal_gaps_without_repeating_successful_or_available_platforms(self): + api = FakeGitHub() + rows = [{'state': 'needs backfill', 'pr': 7, 'commit': SHA, 'missing': ['linux', 'macos'], + 'available_artifacts': ['macos']}, + {'state': 'needs backfill', 'pr': 8, 'commit': 'c' * 40, 'missing': ['linux', 'macos']}, + {'state': 'needs backfill', 'pr': 9, 'commit': OTHER, 'missing': ['linux']}] + with mock.patch.object(recovery.benchmark_preview_ci, 'GitHub', return_value=api), \ + mock.patch.object(recovery, 'inventory', return_value=rows), \ + mock.patch.object(api, 'pages', return_value=[]), \ + mock.patch.object(recovery.subprocess, 'run') as run, \ + mock.patch('sys.argv', ['benchmark_recovery', '--repository', 'owner/repo', '--dispatch-next']): + recovery.main() + run.assert_called_once_with(['gh', 'workflow', 'run', 'benchmark_backfill.yml', '--repo', + 'owner/repo', '--ref', 'main', '-f', 'revisions=' + SHA + ',' + OTHER, + '-f', 'platforms=linux'], check=True) + if __name__ == '__main__': unittest.main() From d7dee8aca41fea4c9d287800946a546377e5a85e Mon Sep 17 00:00:00 2001 From: helly25 <6420169+helly25@users.noreply.github.com> Date: Mon, 9 Nov 2026 00:39:07 +0000 Subject: [PATCH 3/4] Check CI logical CPU capacity before production builds --- .github/workflows/benchmarks.yml | 8 ++++++++ docs/benchmark-extended-history-campaign.md | 5 +++++ ...history-continuous-qualification-20261109.json | 8 +++++++- tools/benchmark_extended_ci.py | 15 ++++++++++++++- tools/benchmark_extended_ci_test.py | 15 +++++++++++++++ tools/benchmark_extended_records.py | 2 ++ 6 files changed, 51 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 1956bb4062b..1334833fc7f 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -41,6 +41,14 @@ jobs: - uses: actions/setup-python@v7 with: python-version: "3.13" + - name: Check extended runner capacity before building + run: | + python3 - <<'PY' + import sys + sys.path.insert(0, 'tools') + from benchmark_extended_ci import cpu_allocation + print('Extended allocation for three workers:', cpu_allocation(3, ci=True)) + PY - uses: ./.github/actions/bazel-cache-restore with: namespace: default diff --git a/docs/benchmark-extended-history-campaign.md b/docs/benchmark-extended-history-campaign.md index 073d336b7c3..b8c6c8381c6 100644 --- a/docs/benchmark-extended-history-campaign.md +++ b/docs/benchmark-extended-history-campaign.md @@ -261,6 +261,11 @@ launcher are checked again after collection. No timing result is accepted after failure. Local Linux validation can reuse an existing verified fixture with `--fixture`; this does not remove that fixture and is forbidden for CI collections. +CI Linux budgets three allowed logical CPUs, including hosts where those logical CPUs share +guest-topology cores. The original local-history collector retains its distinct-core policy. +The workflow checks capacity before production builds and records the allocation policy and +logical CPU count. Actual post-merge CI collection still needs to be verified on its runner. + Head cells rotate head XFF, first-parent XFF and the traditional tool through execution order. A second complete grid measures the parent against itself as a diagnostic control. All successful outputs must match the independent oracle. Raw warm-ups, measured samples, command lines, diff --git a/docs/benchmarks/extended-history-continuous-qualification-20261109.json b/docs/benchmarks/extended-history-continuous-qualification-20261109.json index 5f6a69d1daf..01f2529bdd6 100644 --- a/docs/benchmarks/extended-history-continuous-qualification-20261109.json +++ b/docs/benchmarks/extended-history-continuous-qualification-20261109.json @@ -398,7 +398,7 @@ } }, "linux_direct": { - "extended": 53, + "extended": 54, "reconcile": 8, "recovery": 8, "failures": 0 @@ -408,5 +408,11 @@ "sha256": "b014fc984a68514826c2e32c3be7816baa32ac441753a18e12c75ed7e721b8b3", "result": "passed" } + }, + "cpu_allocation_policy": { + "ci_linux": "Three allowed logical CPUs, including sibling threads; capacity checked before builds.", + "local_linux": "Distinct allowed guest-topology cores, preserving original history policy.", + "macos": "Worker requests without CPU affinity.", + "verification": "Allocation policy is unit-tested on Linux and macOS; the first actual extended GitHub run remains required." } } diff --git a/tools/benchmark_extended_ci.py b/tools/benchmark_extended_ci.py index 71dcd9042d6..66de7b4c938 100644 --- a/tools/benchmark_extended_ci.py +++ b/tools/benchmark_extended_ci.py @@ -5,6 +5,7 @@ import argparse from dataclasses import asdict import json +import os from pathlib import Path import platform import re @@ -20,6 +21,15 @@ import process_resources +def cpu_allocation(count, ci): + """CI budgets logical CPUs; local history keeps distinct guest-topology cores.""" + if ci and sys.platform == 'linux': + allowed = sorted(os.sched_getaffinity(0)) + records.require(len(allowed) >= count, 'not enough allowed logical CPUs') + return allowed[:count] + return history.cpu_allocation(count) + + def revision(repo): sha = build.git(repo, 'rev-parse', 'HEAD') subject = build.git(repo, 'show', '-s', '--format=%s', sha) @@ -62,7 +72,7 @@ def collect(build_root, root, launcher_path, collection=None, series=None, exist collection = dict(collection, base_sha=anchor['sha']) series = series or ('github-ci-' + platform_name) cases = suite.cases() - affinity = history.cpu_allocation(max(case.cpus for case in cases)) + affinity = cpu_allocation(max(case.cpus for case in cases), origin == 'ci') tools = {} for name in ('find', 'rg', 'tar'): executable = shutil.which({'find': 'gfind', 'tar': 'gtar'}.get(name, name) if sys.platform == 'darwin' else name) @@ -80,6 +90,9 @@ def collect(build_root, root, launcher_path, collection=None, series=None, exist 'anchor': anchor, 'fixture': str(fixture), 'filesystem': 'tmpfs' if sys.platform == 'linux' else filesystem, 'tools': tools, 'launcher': launcher, 'affinity': affinity, 'platform': platform.platform(), + 'logical_cpu_count': os.cpu_count(), + 'cpu_policy': ('allowed-logical-cpus' if origin == 'ci' else 'guest-topology-cores') + if sys.platform == 'linux' else 'unpinned-workers', 'comparison': 'parent', 'python': sys.version, 'drivers': {Path(module).name: build.digest(Path(module)) for module in (__file__, suite.__file__, history.__file__, storage.__file__, process_resources.__file__)}, diff --git a/tools/benchmark_extended_ci_test.py b/tools/benchmark_extended_ci_test.py index c677704c062..4238010e52d 100644 --- a/tools/benchmark_extended_ci_test.py +++ b/tools/benchmark_extended_ci_test.py @@ -55,6 +55,7 @@ def collect(self, collection=None, existing=None): with mock.patch.object(ci.sys, 'platform', 'linux'), \ mock.patch.object(ci.platform, 'platform', return_value='Linux-synthetic-x86_64'), \ mock.patch.object(ci.platform, 'machine', return_value='x86_64'), \ + mock.patch.object(ci.os, 'sched_getaffinity', return_value={0, 1, 2}, create=True), \ mock.patch.object(ci.shutil, 'which', side_effect=self.tools.get), \ mock.patch.object(ci.subprocess, 'check_output', return_value='synthetic tool'), \ mock.patch.object(ci.history, 'cpu_allocation', return_value=[0, 1, 2]), \ @@ -121,12 +122,26 @@ def test_ci_cannot_reuse_a_local_fixture_or_measure_a_different_head(self): self.collect(dict(collection, head_sha='f' * 40)) self.assertEqual(self.calls, 0) + def test_ci_accepts_allowed_logical_cpus_without_requiring_three_distinct_cores(self): + with mock.patch.object(ci.sys, 'platform', 'linux'), \ + mock.patch.object(ci.os, 'sched_getaffinity', return_value={0, 1, 2, 3}, create=True), \ + mock.patch.object(ci.history, 'cpu_allocation', side_effect=ValueError('not enough distinct cores')) as physical: + self.assertEqual(ci.cpu_allocation(3, ci=True), [0, 1, 2]) + physical.assert_not_called() + with self.assertRaisesRegex(ValueError, 'not enough distinct cores'): + ci.cpu_allocation(3, ci=False) + with mock.patch.object(ci.sys, 'platform', 'linux'), \ + mock.patch.object(ci.os, 'sched_getaffinity', return_value={0, 1}, create=True), \ + self.assertRaisesRegex(ValueError, 'not enough allowed logical CPUs'): + ci.cpu_allocation(3, ci=True) + def test_workflow_keeps_collection_independent_and_measures_downloaded_production_bytes(self): workflow = repository_file('.github/workflows/benchmarks.yml').read_text() extended = workflow.split(' extended:\n', 1)[1].split('\n build:', 1)[0] self.assertNotIn('needs:', extended) self.assertIn('fail-fast: false', extended) helper = extended.index('cp "${launcher}" "${RUNNER_TEMP}/extended-resource-launcher"') + self.assertLess(extended.index('Check extended runner capacity before building'), helper) head = extended.index('--role=head') parent = extended.index('--role=base') upload = extended.index('name: benchmark-extended-build-${{ matrix.platform }}') diff --git a/tools/benchmark_extended_records.py b/tools/benchmark_extended_records.py index 24efbb18411..809c35c4cc3 100644 --- a/tools/benchmark_extended_records.py +++ b/tools/benchmark_extended_records.py @@ -186,6 +186,8 @@ def view_report(record, view): for task in adapted['tasks']: task['participants'][name] = task['participants'].pop(old) adapted['tools'] = tools + if 'cpu_policy' in record['contract']: + adapted['contract']['cpu_policy'] = record['contract']['cpu_policy'] for task in adapted['tasks']: task['fixture_identity'] = fixture_identity(task['dataset'], task['files'], task['name'] == 'archive-tar') adapted['contract'].update(comparison=record['comparison'] if view == 'anchor' else 'traditional', From a1579a797c394a929827d0fc1a3f8bfcea707b33 Mon Sep 17 00:00:00 2001 From: helly25 <6420169+helly25@users.noreply.github.com> Date: Mon, 9 Nov 2026 10:10:53 +0000 Subject: [PATCH 4/4] docs: refresh merged benchmark history after verified publication (cherry picked from commit 5f4c4c534b4320a74456b8d538dfa3a53f887193) --- docs/benchmark-merged-pr-coverage.md | 322 +++++++++++++++------------ 1 file changed, 181 insertions(+), 141 deletions(-) diff --git a/docs/benchmark-merged-pr-coverage.md b/docs/benchmark-merged-pr-coverage.md index 73e53e9c005..a0cc51797bb 100644 --- a/docs/benchmark-merged-pr-coverage.md +++ b/docs/benchmark-merged-pr-coverage.md @@ -1,156 +1,196 @@ # Missing merged PR benchmark inventory -Snapshot: November 9, 2026, 01:02 UTC. Scope: PRs merged directly into `main` on or after -September 19, 2026. This is a dated inventory; publishing and active measurements may change it. - -A platform is covered only when the exact merge commit is selectable in the published merged-CI -catalog. Local and extended-contract datasets are excluded. A retained artifact awaiting publication -is recorded separately from a catalog entry. - -## Current coverage - -- 237 directly merged PRs: 162 published on both Linux and macOS; 75 absent on both. -- Of the 75, PR #1150 has validated reports for both platforms awaiting publication. -- The remaining 74 require recovery according to the read-only planner. Two already have an active - targeted campaign: #868 and #876 have successful Linux jobs and macOS jobs still running in - [run 42538554174](https://github.com/mboworks/xff/actions/runs/42538554174). Do not repeat those jobs. -- PRs #914 and #986 are now published on both platforms. Their Linux recovery completed successfully - in [run 42534282665](https://github.com/mboworks/xff/actions/runs/42534282665). PR #1143 is also present - on both platforms. Live browser selection verification is recorded below. - -The November 8 total of 252 included PRs merged into other development branches. This refresh -counts only direct merges into `main`; merges into development branches are excluded from this audit. -The present metadata contains 256 merges across all base branches, of which 19 target other branches. - -## Missing PRs - -Every row currently lacks both Linux and macOS catalog entries. Workflow conclusions are observed -states, not established root causes. A canceled run may have been canceled manually or superseded; -a failed run requires its logs to establish the cause. No validated report was discovered for a -row unless the table explicitly identifies one. - -| PR | Merged (UTC) | Merge commit | Observed state | -| -------------------------------------------------- | ------------ | -------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| [#1150](https://github.com/mboworks/xff/pull/1150) | 2026-11-08 | `52950f937ad3` | Validated Linux/macOS reports awaiting publication | -| [#1136](https://github.com/mboworks/xff/pull/1136) | 2026-11-07 | `1f25ba6d4ae0` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42398990564)) | -| [#1132](https://github.com/mboworks/xff/pull/1132) | 2026-11-07 | `5d413a35cbec` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42378502027)) | -| [#1130](https://github.com/mboworks/xff/pull/1130) | 2026-11-07 | `ae8c5af3eb11` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42367122125)) | -| [#1128](https://github.com/mboworks/xff/pull/1128) | 2026-11-07 | `1278c8943a6a` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42354657340)) | -| [#1121](https://github.com/mboworks/xff/pull/1121) | 2026-11-06 | `d9bf84b04784` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42265512655)) | -| [#1118](https://github.com/mboworks/xff/pull/1118) | 2026-11-06 | `c7bb70cc6f3e` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42234848791)) | -| [#1115](https://github.com/mboworks/xff/pull/1115) | 2026-11-06 | `ab909839da7d` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42218900004)) | -| [#1113](https://github.com/mboworks/xff/pull/1113) | 2026-11-06 | `5729026a8482` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42200480427)) | -| [#1111](https://github.com/mboworks/xff/pull/1111) | 2026-11-06 | `5cade08b6701` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42184472882)) | -| [#1106](https://github.com/mboworks/xff/pull/1106) | 2026-11-06 | `e7e42d01e067` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42146475737)) | -| [#1099](https://github.com/mboworks/xff/pull/1099) | 2026-11-05 | `76185cb37fa2` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41999670147)) | -| [#1098](https://github.com/mboworks/xff/pull/1098) | 2026-11-05 | `7c8cd64e02a8` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41986760505)) | -| [#1097](https://github.com/mboworks/xff/pull/1097) | 2026-11-05 | `910e2a7bbae5` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41977457900)) | -| [#1095](https://github.com/mboworks/xff/pull/1095) | 2026-11-05 | `6792b5030de7` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41962227281)) | -| [#1092](https://github.com/mboworks/xff/pull/1092) | 2026-11-04 | `a89459ed819a` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41920310179)) | -| [#1088](https://github.com/mboworks/xff/pull/1088) | 2026-11-04 | `9bd70fc9b460` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41851695947)) | -| [#1084](https://github.com/mboworks/xff/pull/1084) | 2026-11-04 | `f06cea15f5b1` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41798644066)) | -| [#1082](https://github.com/mboworks/xff/pull/1082) | 2026-11-04 | `6e6b25ffc869` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41783344161)) | -| [#1080](https://github.com/mboworks/xff/pull/1080) | 2026-11-04 | `669f68a3e263` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41771864734)) | -| [#1078](https://github.com/mboworks/xff/pull/1078) | 2026-11-03 | `76054b3a9c60` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41711975639)) | -| [#1073](https://github.com/mboworks/xff/pull/1073) | 2026-11-03 | `6db2de9e49f1` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41628320126)) | -| [#1067](https://github.com/mboworks/xff/pull/1067) | 2026-11-03 | `fb726c946063` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41573418793)) | -| [#1059](https://github.com/mboworks/xff/pull/1059) | 2026-11-03 | `22702c34a0bc` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41559497818)) | -| [#1057](https://github.com/mboworks/xff/pull/1057) | 2026-11-02 | `6bebe940ba36` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41524744291)) | -| [#1053](https://github.com/mboworks/xff/pull/1053) | 2026-11-02 | `dc4e70237eff` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41451327523)) | -| [#1050](https://github.com/mboworks/xff/pull/1050) | 2026-11-02 | `2d615ea1e447` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41425575717)) | -| [#1044](https://github.com/mboworks/xff/pull/1044) | 2026-11-02 | `d546fcb3d864` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41378115229)) | -| [#1042](https://github.com/mboworks/xff/pull/1042) | 2026-11-02 | `2b21790502fd` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41367091220)) | -| [#1027](https://github.com/mboworks/xff/pull/1027) | 2026-10-29 | `d406f4132d06` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/40816218296)) | -| [#1011](https://github.com/mboworks/xff/pull/1011) | 2026-10-23 | `801087f3e9a8` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/39886813900)) | -| [#1007](https://github.com/mboworks/xff/pull/1007) | 2026-10-14 | `33a01e13d7cd` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/38653097649)) | -| [#1006](https://github.com/mboworks/xff/pull/1006) | 2026-10-14 | `20095dd53c31` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/38644322359)) | -| [#994](https://github.com/mboworks/xff/pull/994) | 2026-10-10 | `88d4facd15eb` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/38049489783)) | -| [#993](https://github.com/mboworks/xff/pull/993) | 2026-10-10 | `dac3747d63ca` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/38045263735)) | -| [#976](https://github.com/mboworks/xff/pull/976) | 2026-10-06 | `73b2cfcd7101` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/37393233041)) | -| [#979](https://github.com/mboworks/xff/pull/979) | 2026-10-05 | `378d3540691e` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/37388497650)) | -| [#973](https://github.com/mboworks/xff/pull/973) | 2026-10-05 | `dfd5447644f0` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/37388437775)) | -| [#954](https://github.com/mboworks/xff/pull/954) | 2026-10-03 | `2817247f59cb` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/37138060568)) | -| [#950](https://github.com/mboworks/xff/pull/950) | 2026-10-03 | `c262c0a71360` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/37114542638)) | -| [#939](https://github.com/mboworks/xff/pull/939) | 2026-10-01 | `cc01d2edbab6` | No main-push benchmark run found; no retained report discovered | -| [#935](https://github.com/mboworks/xff/pull/935) | 2026-10-01 | `481c32aaf2bd` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/36831896372)) | -| [#936](https://github.com/mboworks/xff/pull/936) | 2026-10-01 | `be954012e048` | No main-push benchmark run found; no retained report discovered | -| [#928](https://github.com/mboworks/xff/pull/928) | 2026-09-27 | `f0dfc3edefc7` | No main-push benchmark run found; no retained report discovered | -| [#916](https://github.com/mboworks/xff/pull/916) | 2026-09-27 | `ce5ee3c40579` | No main-push benchmark run found; no retained report discovered | -| [#902](https://github.com/mboworks/xff/pull/902) | 2026-09-22 | `be2f400392dc` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35677724823)) | -| [#904](https://github.com/mboworks/xff/pull/904) | 2026-09-21 | `80d75576c32b` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35667805574)) | -| [#905](https://github.com/mboworks/xff/pull/905) | 2026-09-21 | `77c00ec8cbea` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35666152368)) | -| [#903](https://github.com/mboworks/xff/pull/903) | 2026-09-21 | `9b04999acb79` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35664420733)) | -| [#906](https://github.com/mboworks/xff/pull/906) | 2026-09-21 | `79278f51e63c` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35662475460)) | -| [#901](https://github.com/mboworks/xff/pull/901) | 2026-09-21 | `f76015ff96f8` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35653591054)) | -| [#899](https://github.com/mboworks/xff/pull/899) | 2026-09-21 | `98d31e1616a7` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35564630217)) | -| [#898](https://github.com/mboworks/xff/pull/898) | 2026-09-20 | `c0c0b6d39e26` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35538423416)) | -| [#897](https://github.com/mboworks/xff/pull/897) | 2026-09-20 | `6e882aea6d2c` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35536193393)) | -| [#896](https://github.com/mboworks/xff/pull/896) | 2026-09-20 | `ced00ac473e6` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35529346711)) | -| [#895](https://github.com/mboworks/xff/pull/895) | 2026-09-20 | `a185ae12d70d` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35521619198)) | -| [#894](https://github.com/mboworks/xff/pull/894) | 2026-09-20 | `42febc0e8d0a` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35510379808)) | -| [#893](https://github.com/mboworks/xff/pull/893) | 2026-09-20 | `29c75be40529` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35503333615)) | -| [#892](https://github.com/mboworks/xff/pull/892) | 2026-09-20 | `c9b56c2f009e` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35502743136)) | -| [#891](https://github.com/mboworks/xff/pull/891) | 2026-09-20 | `a39758eff8ac` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35499899635)) | -| [#890](https://github.com/mboworks/xff/pull/890) | 2026-09-19 | `1f568ceecd28` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35474269925)) | -| [#888](https://github.com/mboworks/xff/pull/888) | 2026-09-19 | `b7d28411a9cc` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35473674929)) | -| [#887](https://github.com/mboworks/xff/pull/887) | 2026-09-19 | `0bcb461dbb08` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35470455554)) | -| [#886](https://github.com/mboworks/xff/pull/886) | 2026-09-19 | `2bd76d6e682f` | No main-push benchmark run found; no retained report discovered | -| [#885](https://github.com/mboworks/xff/pull/885) | 2026-09-19 | `2448257a8d4d` | No main-push benchmark run found; no retained report discovered | -| [#884](https://github.com/mboworks/xff/pull/884) | 2026-09-19 | `4fc594246067` | No main-push benchmark run found; no retained report discovered | -| [#883](https://github.com/mboworks/xff/pull/883) | 2026-09-19 | `b1df434503a3` | No main-push benchmark run found; no retained report discovered | -| [#882](https://github.com/mboworks/xff/pull/882) | 2026-09-19 | `9282e3d1da7c` | No main-push benchmark run found; no retained report discovered | -| [#881](https://github.com/mboworks/xff/pull/881) | 2026-09-19 | `990045bf2d3b` | No main-push benchmark run found; no retained report discovered | -| [#880](https://github.com/mboworks/xff/pull/880) | 2026-09-19 | `c8b35dfa8249` | No main-push benchmark run found; no retained report discovered | -| [#879](https://github.com/mboworks/xff/pull/879) | 2026-09-19 | `7f48d0d888cb` | No main-push benchmark run found; no retained report discovered | -| [#878](https://github.com/mboworks/xff/pull/878) | 2026-09-19 | `889c652ec8cf` | No main-push benchmark run found; no retained report discovered | -| [#877](https://github.com/mboworks/xff/pull/877) | 2026-09-19 | `99b78c8f1bad` | No main-push benchmark run found; no retained report discovered | -| [#876](https://github.com/mboworks/xff/pull/876) | 2026-09-19 | `e1cc1f72605e` | Linux backfill succeeded; macOS measuring ([campaign](https://github.com/mboworks/xff/actions/runs/42538554174)) | -| [#868](https://github.com/mboworks/xff/pull/868) | 2026-09-19 | `c092e8245ff8` | Linux backfill succeeded; macOS measuring ([campaign](https://github.com/mboworks/xff/actions/runs/42538554174)) | +Snapshot: November 9, 2026, 09:37 UTC. Scope: PRs merged directly into main on or after +September 19, 2026, by that snapshot. Later merges, including #1155, are outside this dated +inventory. Refresh before selecting another backfill campaign. + +A platform is covered only when the exact merge commit appears in the published merged-CI +catalog. Local and extended-contract datasets do not fill gaps in this contract. A validated +report awaiting publication is recorded separately from a selectable catalog entry. + +## Current coverage at the snapshot + +- 240 directly merged PRs: 171 published on both platforms; 69 missing from both viewer datasets. +- PR #1154 has validated Linux and macOS reports awaiting publication. It needs publication, + not another measurement run. +- The other 68 missing PRs lack validated retained reports at the snapshot. Four of them, + #881-884, already have an active bounded backfill campaign; do not dispatch them again. +- PRs #877-880 are now published and browser-verified on both platforms. All eight selections + loaded their exact merge commit and chart data at 09:35 UTC, without page errors. +- PR #1151 is also published on both platforms. PRs #868/#876 were browser-verified at 03:58 UTC; + #914/#986/#1143 were verified at 01:14 UTC. + +The earlier 05:10 snapshot counted 239 direct merges, 166 covered and 73 missing. Since then, +#1154 merged while #1151 and #877-880 were published, producing 240/171/69. The November 8 +total of 252 included PRs targeting other development branches; those are outside this scope. + +## Missing PRs at the snapshot + +Every row lacks both published catalog entries. Historical workflow conclusions are observations, +not established root causes: a successful workflow does not prove that usable reports remain, +and a cancellation needs its own logs to establish why. Available reports and active backfills +are identified separately. + +| PR | Merged (UTC) | Merge commit | Observed state | +| -------------------------------------------------- | ------------ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [#1154](https://github.com/mboworks/xff/pull/1154) | 2026-11-09 | `ad3be8a12e05` | Both validated reports retained; awaiting publication ([main run](https://github.com/mboworks/xff/actions/runs/42583205104)) | +| [#1136](https://github.com/mboworks/xff/pull/1136) | 2026-11-07 | `1f25ba6d4ae0` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42398990564)) | +| [#1132](https://github.com/mboworks/xff/pull/1132) | 2026-11-07 | `5d413a35cbec` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42378502027)) | +| [#1130](https://github.com/mboworks/xff/pull/1130) | 2026-11-07 | `ae8c5af3eb11` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42367122125)) | +| [#1128](https://github.com/mboworks/xff/pull/1128) | 2026-11-07 | `1278c8943a6a` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42354657340)) | +| [#1121](https://github.com/mboworks/xff/pull/1121) | 2026-11-06 | `d9bf84b04784` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42265512655)) | +| [#1118](https://github.com/mboworks/xff/pull/1118) | 2026-11-06 | `c7bb70cc6f3e` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42234848791)) | +| [#1115](https://github.com/mboworks/xff/pull/1115) | 2026-11-06 | `ab909839da7d` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42218900004)) | +| [#1113](https://github.com/mboworks/xff/pull/1113) | 2026-11-06 | `5729026a8482` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42200480427)) | +| [#1111](https://github.com/mboworks/xff/pull/1111) | 2026-11-06 | `5cade08b6701` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42184472882)) | +| [#1106](https://github.com/mboworks/xff/pull/1106) | 2026-11-06 | `e7e42d01e067` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/42146475737)) | +| [#1099](https://github.com/mboworks/xff/pull/1099) | 2026-11-05 | `76185cb37fa2` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41999670147)) | +| [#1098](https://github.com/mboworks/xff/pull/1098) | 2026-11-05 | `7c8cd64e02a8` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41986760505)) | +| [#1097](https://github.com/mboworks/xff/pull/1097) | 2026-11-05 | `910e2a7bbae5` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41977457900)) | +| [#1095](https://github.com/mboworks/xff/pull/1095) | 2026-11-05 | `6792b5030de7` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41962227281)) | +| [#1092](https://github.com/mboworks/xff/pull/1092) | 2026-11-04 | `a89459ed819a` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41920310179)) | +| [#1088](https://github.com/mboworks/xff/pull/1088) | 2026-11-04 | `9bd70fc9b460` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41851695947)) | +| [#1084](https://github.com/mboworks/xff/pull/1084) | 2026-11-04 | `f06cea15f5b1` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41798644066)) | +| [#1082](https://github.com/mboworks/xff/pull/1082) | 2026-11-04 | `6e6b25ffc869` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41783344161)) | +| [#1080](https://github.com/mboworks/xff/pull/1080) | 2026-11-04 | `669f68a3e263` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41771864734)) | +| [#1078](https://github.com/mboworks/xff/pull/1078) | 2026-11-03 | `76054b3a9c60` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41711975639)) | +| [#1073](https://github.com/mboworks/xff/pull/1073) | 2026-11-03 | `6db2de9e49f1` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41628320126)) | +| [#1067](https://github.com/mboworks/xff/pull/1067) | 2026-11-03 | `fb726c946063` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41573418793)) | +| [#1059](https://github.com/mboworks/xff/pull/1059) | 2026-11-03 | `22702c34a0bc` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41559497818)) | +| [#1057](https://github.com/mboworks/xff/pull/1057) | 2026-11-02 | `6bebe940ba36` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41524744291)) | +| [#1053](https://github.com/mboworks/xff/pull/1053) | 2026-11-02 | `dc4e70237eff` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41451327523)) | +| [#1050](https://github.com/mboworks/xff/pull/1050) | 2026-11-02 | `2d615ea1e447` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41425575717)) | +| [#1044](https://github.com/mboworks/xff/pull/1044) | 2026-11-02 | `d546fcb3d864` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41378115229)) | +| [#1042](https://github.com/mboworks/xff/pull/1042) | 2026-11-02 | `2b21790502fd` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/41367091220)) | +| [#1027](https://github.com/mboworks/xff/pull/1027) | 2026-10-29 | `d406f4132d06` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/40816218296)) | +| [#1011](https://github.com/mboworks/xff/pull/1011) | 2026-10-23 | `801087f3e9a8` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/39886813900)) | +| [#1007](https://github.com/mboworks/xff/pull/1007) | 2026-10-14 | `33a01e13d7cd` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/38653097649)) | +| [#1006](https://github.com/mboworks/xff/pull/1006) | 2026-10-14 | `20095dd53c31` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/38644322359)) | +| [#994](https://github.com/mboworks/xff/pull/994) | 2026-10-10 | `88d4facd15eb` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/38049489783)) | +| [#993](https://github.com/mboworks/xff/pull/993) | 2026-10-10 | `dac3747d63ca` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/38045263735)) | +| [#979](https://github.com/mboworks/xff/pull/979) | 2026-10-05 | `378d3540691e` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/37388497650)) | +| [#976](https://github.com/mboworks/xff/pull/976) | 2026-10-06 | `73b2cfcd7101` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/37393233041)) | +| [#973](https://github.com/mboworks/xff/pull/973) | 2026-10-05 | `dfd5447644f0` | Main run cancelled; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/37388437775)) | +| [#954](https://github.com/mboworks/xff/pull/954) | 2026-10-03 | `2817247f59cb` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/37138060568)) | +| [#950](https://github.com/mboworks/xff/pull/950) | 2026-10-03 | `c262c0a71360` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/37114542638)) | +| [#939](https://github.com/mboworks/xff/pull/939) | 2026-10-01 | `cc01d2edbab6` | No main-push benchmark run found; no validated retained report discovered | +| [#936](https://github.com/mboworks/xff/pull/936) | 2026-10-01 | `be954012e048` | No main-push benchmark run found; no validated retained report discovered | +| [#935](https://github.com/mboworks/xff/pull/935) | 2026-10-01 | `481c32aaf2bd` | Main run failure; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/36831896372)) | +| [#928](https://github.com/mboworks/xff/pull/928) | 2026-09-27 | `f0dfc3edefc7` | No main-push benchmark run found; no validated retained report discovered | +| [#916](https://github.com/mboworks/xff/pull/916) | 2026-09-27 | `ce5ee3c40579` | No main-push benchmark run found; no validated retained report discovered | +| [#906](https://github.com/mboworks/xff/pull/906) | 2026-09-21 | `79278f51e63c` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35662475460)) | +| [#905](https://github.com/mboworks/xff/pull/905) | 2026-09-21 | `77c00ec8cbea` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35666152368)) | +| [#904](https://github.com/mboworks/xff/pull/904) | 2026-09-21 | `80d75576c32b` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35667805574)) | +| [#903](https://github.com/mboworks/xff/pull/903) | 2026-09-21 | `9b04999acb79` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35664420733)) | +| [#902](https://github.com/mboworks/xff/pull/902) | 2026-09-22 | `be2f400392dc` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35677724823)) | +| [#901](https://github.com/mboworks/xff/pull/901) | 2026-09-21 | `f76015ff96f8` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35653591054)) | +| [#899](https://github.com/mboworks/xff/pull/899) | 2026-09-21 | `98d31e1616a7` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35564630217)) | +| [#898](https://github.com/mboworks/xff/pull/898) | 2026-09-20 | `c0c0b6d39e26` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35538423416)) | +| [#897](https://github.com/mboworks/xff/pull/897) | 2026-09-20 | `6e882aea6d2c` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35536193393)) | +| [#896](https://github.com/mboworks/xff/pull/896) | 2026-09-20 | `ced00ac473e6` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35529346711)) | +| [#895](https://github.com/mboworks/xff/pull/895) | 2026-09-20 | `a185ae12d70d` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35521619198)) | +| [#894](https://github.com/mboworks/xff/pull/894) | 2026-09-20 | `42febc0e8d0a` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35510379808)) | +| [#893](https://github.com/mboworks/xff/pull/893) | 2026-09-20 | `29c75be40529` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35503333615)) | +| [#892](https://github.com/mboworks/xff/pull/892) | 2026-09-20 | `c9b56c2f009e` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35502743136)) | +| [#891](https://github.com/mboworks/xff/pull/891) | 2026-09-20 | `a39758eff8ac` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35499899635)) | +| [#890](https://github.com/mboworks/xff/pull/890) | 2026-09-19 | `1f568ceecd28` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35474269925)) | +| [#888](https://github.com/mboworks/xff/pull/888) | 2026-09-19 | `b7d28411a9cc` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35473674929)) | +| [#887](https://github.com/mboworks/xff/pull/887) | 2026-09-19 | `0bcb461dbb08` | Main run success; no validated retained report discovered ([run](https://github.com/mboworks/xff/actions/runs/35470455554)) | +| [#886](https://github.com/mboworks/xff/pull/886) | 2026-09-19 | `2bd76d6e682f` | No main-push benchmark run found; no validated retained report discovered | +| [#885](https://github.com/mboworks/xff/pull/885) | 2026-09-19 | `2448257a8d4d` | No main-push benchmark run found; no validated retained report discovered | +| [#884](https://github.com/mboworks/xff/pull/884) | 2026-09-19 | `4fc594246067` | No main-push benchmark run found; no validated retained report discovered; already in [active backfill](https://github.com/mboworks/xff/actions/runs/42589939294) | +| [#883](https://github.com/mboworks/xff/pull/883) | 2026-09-19 | `b1df434503a3` | No main-push benchmark run found; no validated retained report discovered; already in [active backfill](https://github.com/mboworks/xff/actions/runs/42589939294) | +| [#882](https://github.com/mboworks/xff/pull/882) | 2026-09-19 | `9282e3d1da7c` | No main-push benchmark run found; no validated retained report discovered; already in [active backfill](https://github.com/mboworks/xff/actions/runs/42589939294) | +| [#881](https://github.com/mboworks/xff/pull/881) | 2026-09-19 | `990045bf2d3b` | No main-push benchmark run found; no validated retained report discovered; already in [active backfill](https://github.com/mboworks/xff/actions/runs/42589939294) | + +## Completed backfill and browser publication + +[Campaign 42566817872](https://github.com/mboworks/xff/actions/runs/42566817872) measured +PRs #877-880 on Linux and macOS. All eight attempt-1 jobs failed on HTTP 504 dependency +downloads before compiling or measuring. The exact failure logs and download preflights remain. +After downloads recovered, one failed-only rerun was requested. Attempt 2 completed successfully +on every configuration. No successful configuration needs another measurement run. + +The first browser check at 08:47 UTC found none of these eight selections. The subsequent +09:35 UTC check loaded all eight exact commits and landscape payloads, without page errors. +Both observations remain. Successful measurement was verified separately from publication; +the publication delay did not require repeating the measurements. + +## Active bounded campaign + +After a fresh check confirmed that no backfill was active, exactly PRs #881-884 were dispatched +for both platforms at 08:59 UTC in +[campaign 42589939294](https://github.com/mboworks/xff/actions/runs/42589939294). +The 09:37 inventory still observes this original campaign as active. PRs #877-880 were excluded +because their successful artifacts were already available. The dispatch receipt retains the +selected commits, inventory hash, live campaign preflight, exact command, timestamp and result. +Observe the original campaign to completion; preserve successes and retry failed configurations +only after investigating terminal failures. Its dispatch is not proof of published viewer entries. ## Reproduction and retained evidence -Refresh the inventory without starting measurements: +Refresh the read-only planner with: -```sh -bazel run //tools:benchmark_recovery -- --repository mboworks/xff -``` + bazel run //tools:benchmark_recovery -- --repository mboworks/xff -The read-only planner reports missing platforms, live main measurements and validated retained -reports awaiting publication. Active backfill campaigns require an additional workflow-status -check; their incomplete plans are not yet included in the planner's completed-campaign inventory. +The planner distinguishes missing platforms, active measurements and validated reports awaiting +publication. Check active backfill workflows separately. PR #1153 contains the planner fix that +subtracts available platforms before grouping requests, and propagates API observation errors; +it remains unmerged at this snapshot. Every dispatch must preserve available configurations. -This snapshot retains the complete responses locally as: +The complete 09:37 inventory, request index and raw API responses are under +`/home/marcus/Documents/xff-coverage-refresh-post-backfill-20261109/post-browser-877-880/` +on drogon. The audit rejects non-GET API requests and identifies source revision +`d7dee8aca41fea4c9d287800946a546377e5a85e`. The 94-response index records content hashes. +Document generation independently checked all three main-run response pages against that index. +The complete missing rows and artifact classifications come from the retained inventory. -- `/private/tmp/xff-missing-merged-prs-20261109-refresh.json` and its diagnostic log; -- `/private/tmp/xff-coverage-pulls-20261109.json`: all 1,139 closed PRs across 12 API pages; -- `/private/tmp/xff-coverage-runs-20261109.json`: all 225 main benchmark runs across three pages; -- `/private/tmp/xff-published-catalog-live-20261109.json`: 590 published entries, catalog Git blob - `854b5bf4cf46515b5d0ca70dd0fa6f83de384233`. - -These are observation snapshots, not pending instructions to dispatch a new recovery campaign. +Earlier snapshots remain separate under the same root's `snapshot/` and +`post-campaign-42566817872/` directories. Failed attempt-1 logs and the rerun receipt remain +under `campaign-42566817872/`. The next dispatch is recorded in +`dispatch-881-884-receipt.json`. Browser evidence for #877-880 is retained as +`xff-recovered-877-880-browser-20261109.{json,png,log}` for 08:47 and +`xff-recovered-877-880-after-publisher-20261109.{json,png,log}` for 09:35. ## Preservation and remaining work -- Preserve successful backfills and let the active #868/#876 jobs finish. Reconciliation publishes - valid configurations independently; a failed sibling must not force successful work to rerun. -- Retry only a genuinely missing or failed configuration. PR #1147 added targeted revision inputs; - #1148 added the reusable inventory/recovery command. #1149/#1150 fixed historical Linux runtime - preparation. The additional planner fixes in #1153 avoid repeating available platforms across - different missing-platform sets and propagate API observation failures. -- The publisher coalesces pending requests and reconciles available artifacts under the shared site - lock, with a six-hour scheduled reconciliation. This republishes existing evidence; it does not - automatically measure every missing merge. -- A durable per-PR/platform retry queue, pending/failed viewer entries and automatic completeness - enforcement remain proposed work. A finite GitHub concurrency queue alone cannot prove coverage. -- Historical backfill entries currently report `Not verified` with a workflow-provenance mismatch. - Their workflow driver commit differs from the measured historical commit. Validating that relation - requires the retained campaign/build provenance; do not bypass the check or label them verified. +- Preserve all successful reports; retry only missing or failed configurations after observing + the original run's terminal state and investigating its failure. Do not cancel active work + merely because observation timed out. +- The coalescing publisher and six-hour scheduled reconciliation can publish retained reports. + They do not automatically measure every missing merge. A durable retry queue, pending/failed + viewer entries and automatic completeness enforcement remain proposed work. +- Historical entries conservatively show Not verified when workflow-driver provenance differs + from the measured source. Do not retrofit unsupported binary verification. +- The 130-revision extended-production-v1 history is a separate contract and does not fill these + original merged-CI gaps. Its continuous integration and live publication remain separate work. + +### Earlier historical Linux compatibility and timeouts + +The original #914/#986 Linux campaign failed on missing historical linker libraries. Selecting +Ubuntu 22.04 then allowed compilation but produced binaries that required a newer glibc +(`GLIBC_ABI_DT_RELR`). The deployed follow-up uses Ubuntu 24.04 and obtains `libicu70` from signed +Jammy package indexes without changing the runner's configured sources. Runtime receipts record +package and loaded-library hashes, OS, glibc and preparer identity. Historical compiler/source/LTO +configurations remain fixed. See +[the retained runtime smoke proof](benchmarks/historical-runtime-smoke-20261108.json). +The successful [Linux retry](https://github.com/mboworks/xff/actions/runs/42534282665) has now been +published; the obsolete Ubuntu 22.04 campaign requires no further retry. + +The original macOS #914 warmup exceeded a 60-second command limit. New backfill plans record a +300-second command limit; [its macOS retry](https://github.com/mboworks/xff/actions/runs/42519073200) +succeeded. Preserve that report and the original successful macOS #986 report. Timeouts terminate +the worker process group, and no timed-out sample is retained. These historical failures are +resolved for those two PRs; the #877-880 attempt-1 HTTP 504 failures occurred before that runtime was exercised. ## Browser verification -Chromium selected PRs #914, #986 and #1143 in both CI datasets on the -[published viewer](https://mboworks.github.io/xff/benchmarks/) at 01:14 UTC on November 9. -All six selections loaded their actual chart payloads and displayed the expected merge commit, -with no page errors. Their binary state remains `Not verified`, matching the conservative -provenance limitation above. The retained check is -`/private/tmp/xff-recovered-browser-20261109.json` and its companion screenshot. +The published viewer loaded #914, #986 and #1143 in both CI datasets at 01:14 UTC, and #868 and +#876 on both at 03:58 UTC. Those checks retained exact merge commits, actual loaded landscape +payloads, conservative binary states, screenshots and absence of page errors. + +The 09:35 check for #877-880 retained all eight successful selections. Each loaded its own +merge commit and landscape in both `linux / x86_64` and `macos / arm64`. Binary state remained +Not verified; publication does not manufacture provenance. The catalog had 570 entries, and +the browser reported neither missing selections nor page errors. Future recovered entries +require the same actual selection and payload checks.