Skip to content

Add nightly automation and statistical regression detection#16

Open
vito1317 wants to merge 3 commits into
alamb:mainfrom
vito1317:nightly-automation
Open

Add nightly automation and statistical regression detection#16
vito1317 wants to merge 3 commits into
alamb:mainfrom
vito1317:nightly-automation

Conversation

@vito1317

Copy link
Copy Markdown

This PR implements the two remaining TODOs in the README ("rerun the benchmarks on a regular basis (cron job?)" / "Automation") and adds the analysis layer discussed in apache/datafusion#5504 and the "Trackable over time" goal of apache/datafusion#21165.

What's included (new nightly/ directory, python is stdlib-only, 3.9+):

  • nightly.py — orchestrator: build recent commits (via the existing ensure_datafusion_cli.py) → run benchmark.py → detect regressions → regenerate the report → optionally commit/push. Ownership-checked lockfile, per-step logs, status.json for monitoring, --dry-run, and effect assertions so a night where binaries were built but no results landed fails loudly instead of reporting green. Publishing is config-gated and off by default, with a branch guard.
  • detect.py — two detectors over per-query median series: (1) per-query significance thresholds learned from each query's own night-over-night noise history (IQR fence, the approach used by rustc-perf), and (2) E-divisive-means changepoint detection with a permutation significance test (the approach used by MongoDB). Emits alerts.json / alerts.md and detected_events.json, which report.py now renders as dashboard annotations. Exit code 2 signals regressions, so cron wrappers can alert.
  • nightly.sh (cron entry, with a NIGHTLY_NOTIFY_CMD hook that fires on failures and on regression alerts), systemd units, tune.sh/check_env.sh (governor/turbo/SMT/ASLR, following LLVM & rustc-perf benchmarking practice), and a schedule-only self-hosted GitHub Actions example with the security caveats documented.
  • 100 unit/integration tests (python -m unittest discover -s nightly/tests), including a real-data fixture carved from results_metal.

Validation on real data: running the detector over the results_metal history (383 per-commit revisions) finds the collect_statistics default change (2d7ae0926, already hand-curated in events.json) as a statistically significant changepoint on 7 queries — without being told where to look.

Also fixes three dashboard issues in report.py: mostRecentRelease picked the oldest release (releases.json is newest-first), breaking the "last week/months" filters; the Plotly CDN script is now pinned to 2.35.2 instead of plotly-latest; detected events degrade gracefully when the JSON is missing or corrupt.

See nightly/README.md for setup and nightly/DESIGN.md for design rationale. Happy to adjust anything — especially the default detection thresholds and the alert output format.

vito1317 added 2 commits July 14, 2026 02:43
…nnotations

- load_release_data picked releases_info[-1] as mostRecentRelease, but
  releases.json is ordered newest-first, so the oldest release (41.0.0)
  was pinned and the last week / last months time filters anchored to
  the wrong revision. Now select the release with the latest
  git_revision_timestamp among those present in the loaded data,
  falling back to releases_info[0].
- Pin the Plotly CDN script to 2.35.2 instead of plotly-latest, so a
  future Plotly major release cannot break the published dashboard.
- Merge nightly/out/detected_events.json (written by the new regression
  detector) into the chart annotations: rendered orange dash-dot with
  their own 'Show detected changes' toggle, deduplicated by revision
  with hand-curated events.json taking precedence. A missing or corrupt
  file degrades to no detected annotations instead of failing the
  report.
Closes the automation TODOs: the pipeline (build recent commits, run
clickbench, regenerate the dashboard) can now run unattended every
night from cron or a systemd timer, and regressions are detected
statistically instead of by eyeballing charts.

New tooling under nightly/ (python is stdlib-only, 3.9+):

- nightly.py: orchestrator running build -> bench -> detect -> report
  -> publish with per-step logs, a portable ownership-checked lockfile,
  effect assertions (a night where binaries were built but no new
  revisions landed in results.csv fails loudly instead of reporting a
  silent green), builds/ pruning, status.json for monitoring, and
  --dry-run for safe inspection. Publishing is config-gated and off by
  default, with a branch guard before any commit/rebase/push.
- detect.py: two detectors over per-query median series -
  (1) per-query significance thresholds learned from each query's own
  historical night-over-night noise (IQR fence, as used by rustc-perf),
  (2) E-divisive-means changepoint detection with a permutation
  significance test (as used by MongoDB), pure stdlib. Emits
  alerts.json / alerts.md and detected_events.json, which report.py
  renders as dashboard annotations. Exit code 2 signals regressions.
  Validated against the results_metal history: the collect_statistics
  change (2d7ae0926) is found as a changepoint on 7 queries.
- nightly.sh: cron entry point - flock guard, dirty-tree-aware
  git pull, failure and regression-alert notifications via a
  configurable NIGHTLY_NOTIFY_CMD hook.
- tune.sh / check_env.sh: benchmark-box tuning (cpu governor, turbo,
  SMT, ASLR, irqbalance) and pre-run assertions, following LLVM and
  rustc-perf benchmarking practice.
- systemd units, crontab example, and a self-hosted GitHub Actions
  example (schedule-only, with security caveats documented).
- tests: 100 unit/integration tests, including a real-data fixture
  carved from results_metal.

See nightly/README.md for setup and nightly/DESIGN.md for the design.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new nightly/ automation pipeline to regularly build and run benchmarks, detect statistically significant performance regressions, and optionally publish updated dashboards—completing the “Automation” TODOs in the top-level README and integrating regression annotations into the report dashboard.

Changes:

  • Introduces nightly/nightly.py + nightly/nightly.sh orchestration (lockfile, logging, status.json, optional publish).
  • Adds stdlib-only statistical regression detection (nightly/detect.py) with alerts and detected event outputs plus extensive unit/integration tests.
  • Updates report.py / generated docs/index.html to pin Plotly, fix most-recent-release selection, and render/toggle detected regression annotations.

Reviewed changes

Copilot reviewed 18 out of 19 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
report.py Pins Plotly JS, adds detected-regression annotations/toggle, and adjusts most-recent-release logic.
docs/index.html Regenerated dashboard output reflecting the report changes (Plotly pin + detected toggle + release selection).
README.md Updates Automation/TODO sections to point to nightly/ pipeline docs.
nightly/nightly.py New stdlib-only orchestrator (build → bench → detect → report → optional publish) with locking/logging/status.
nightly/detect.py New stdlib-only regression detection (threshold + e-divisive changepoints) emitting alerts + detected events.
nightly/nightly.sh Cron/systemd entry wrapper with flock guard, optional pull, logging, and notification hook.
nightly/tune.sh Linux benchmark-box tuning helper (governor/turbo/SMT/ASLR/irqbalance) + reset mode.
nightly/check_env.sh Read-only environment assertions for tuned benchmark stability (optional pipeline gate).
nightly/config.json Default nightly configuration (publish off by default).
nightly/README.md Operator manual: install/schedule/configure/run/interpret alerts, plus methodology details.
nightly/DESIGN.md Design rationale and interface/contract documentation for the nightly pipeline.
nightly/crontab.example Example crontab entry for scheduling nightly runs.
nightly/systemd/datafusion-nightly.service Example systemd service unit for running the nightly pipeline.
nightly/systemd/datafusion-nightly.timer Example systemd timer unit to schedule the service daily (UTC).
nightly/github-workflow.example.yml Optional self-hosted Actions workflow example with explicit security caveats.
nightly/tests/test_nightly.py Extensive tests for orchestrator config merge, locking, step dependencies, publish guard, dry-run, etc.
nightly/tests/test_detect.py Detector tests (synthetic + real-data fixture, parsing robustness, determinism, exit codes).
nightly/.gitignore Ignores runtime artifacts for nightly runs while keeping nightly/out/ publishable.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread report.py
Comment on lines +1173 to +1177
releases_in_data = [rev for rev in releases if rev in rev_ts]
if releases_in_data:
most_recent_release = max(releases_in_data, key=lambda rev: pd.to_datetime(rev_ts[rev], utc=True))
else:
most_recent_release = releases_info[0]['revision']
Comment thread report.py Outdated
Comment on lines +1171 to +1172
except:
rev_ts = {}
Comment thread report.py Outdated
Comment on lines +779 to +780
with open(detected_path, 'r') as f:
detected_labels = {item['revision']: item['label'] for item in json.load(f) if item['revision'] not in event_labels}
Comment thread report.py Outdated
Comment on lines +977 to +978
with open(detected_path, 'r') as f:
detected_labels = {item['revision']: item['label'] for item in json.load(f) if item['revision'] not in event_labels}
Comment thread nightly/detect.py Outdated
Comment on lines +102 to +105
with open(path, 'r', newline='', encoding='utf-8') as fh:
reader = csv.reader(fh)
first = True
for record in reader:
- report.py: expose mostRecentReleaseLabel alongside mostRecentRelease so
  the dashboard's <=30-day annotation filter matches release labels
  (e.g. "54.0.0") instead of revisions (e.g. "branch-54")
- report.py: replace the bare except in load_release_data with Exception
  plus a warning so fallback reasons show up in generation logs
- report.py: open detected_events.json with explicit UTF-8 encoding
- nightly/detect.py: make load_results tolerate NUL bytes, undecodable
  bytes, and oversized fields (csv.Error) so a single corrupted results
  file cannot abort the nightly run
- add regression tests for both corruption modes
@vito1317 vito1317 force-pushed the nightly-automation branch from 5147c57 to 4fa6e7c Compare July 14, 2026 08:25
@vito1317 vito1317 requested a review from Copilot July 14, 2026 08:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants