github robintra/perf-sentinel v0.5.1

latest releases: chart-v0.2.58, v0.8.12, chart-v0.2.57...
one month ago

What's new in v0.5.1

Consolidation release focused on the HTML dashboard introduced in v0.5.0. The report subcommand gains six polish items and a new flag, the daemon correlator emits a representative trace id per pair so Correlations rows become jumpable, the dashboard ships a full WAI-ARIA upgrade and an auto theme mode following prefers-color-scheme, and the Correlations / pg_stat / Diff tabs all pick up end-user ergonomics. A Playwright smoke suite now exercises the dashboard in a real Chromium browser (clipboard, CSV blob, hashchange, deep-link state, ARIA) so DOM-level regressions surface in CI, and two dashboard_{dark,light}.gif tours plus twelve per-tab still frames land under docs/img/report/ for the README. Security hardening around the report sink (CSP, input_label sanitization, cross-trace id cap), a rustls-webpki bump against RUSTSEC-2026-0104, and clearing all eleven open SonarCloud findings close the release.

Added

  • --pg-stat-top N flag on report. Override the default 10-row ranking cut. Clap-level range guard (1..=10_000) rejects zero, negatives, and implausible values before any filesystem or network I/O. When the pg_stat source is Prometheus (--pg-stat-prometheus), the scrape size widens to max(top_n, 200) so the secondary rankings (by_calls / by_mean_time / by_io_blocks) still see the full hot-spot distribution instead of the top-N by seconds_total only. Documented in docs/INTEGRATION.md + FR and docs/design/07-CLI-CONFIG-RELEASE.md + FR.
  • Dashboard: Copy link button next to Export CSV on every listable tab (Findings, pg_stat, Diff, Correlations). Writes location.href to the clipboard via navigator.clipboard.writeText, with a document.execCommand('copy') fallback for non-HTTPS origins. The button flashes "Copied" for 1500 ms, double-click-safe via a per-button timer id. Paired with the existing deep-link hash so a shared URL restores the exact filtered view.
  • Dashboard: filename in the browser tab title. <title> now reads perf-sentinel: <filename> when --input points at a file, falling back to perf-sentinel report for stdin or when input_label is missing. Makes multi-report tabs distinguishable in the browser tab bar and history.
  • Dashboard: clickable resolved Diff findings. Clicking a resolved row in the Diff tab jumps to Explain with an empty-state banner explaining the trace lives in the baseline Report (not the current run), so the user knows to open the baseline dashboard separately instead of blaming the renderer.
  • Dashboard: clickable Correlations rows when a sample trace is available. The daemon's correlator now stores the most recent target-side trace_id observed per pair (PairState.last_trace_id, capped at 128 bytes, truncated on a UTF-8 boundary). The dashboard exposes this via sample_trace_id on each CrossTraceCorrelation. Rows whose sample_trace_id resolves to an embedded trace become clickable and open Explain on that trace; rows without a sample or with a non-embedded trace stay non-interactive (same behavior as before) instead of producing broken-link UX.
  • Dashboard: auto theme mode following prefers-color-scheme. The theme toggle cycles auto -> dark -> light -> auto. auto (the new default) reads the OS preference via window.matchMedia('(prefers-color-scheme: dark)') and reapplies on system change in real time. Forced dark / light from prior v0.5.0 sessions keep their meaning via the existing sessionStorage persistence, so no user-visible regression on upgrade.
  • Dashboard: Powered by perf-sentinel credit centered under the footer, linking to the GitHub repo. target="_blank" + rel="noopener noreferrer" to block reverse-tabnabbing. CSP-compatible (top-level navigation is unaffected by default-src 'none').

Changed

  • Dashboard tabs and chips carry WAI-ARIA-compliant markup. <div role="tablist" aria-label="Dashboard sections"> wraps the tab strip, each tab button carries role="tab" + aria-selected + aria-controls + roving tabindex (0 active, -1 otherwise). Left / Right arrow keys cycle focus through the visible tabs (skipping conditional tabs that are not registered), Home / End jump to first / last, Space / Enter activates. Severity filter chips form an aria-label="Finding severity" radiogroup with aria-checked, service chips become plain toggle buttons with aria-pressed. pg_stat ranking chips are a aria-label="pg_stat ranking" radiogroup. The existing g f / g e / ... shortcut state machine is untouched and co-exists on a separate input path.
  • Daemon correlator: sample_trace_id on CrossTraceCorrelation. PairState grows a last_trace_id: Option<String>, updated on every co-occurrence by PairState::update_sample_trace_id (extracted helper, short-circuits on empty strings and on values equal to the current record to avoid needless allocations on batched ingest). Serialized with #[serde(default, skip_serializing_if = "Option::is_none")] so older Report consumers keep parsing without schema changes. Ignored for the memory budget: adds ~320 KB at the 10k-pair cap, well under 2%.

Fixed

  • Dashboard: URL hashchange handler. An in-page location.hash = "#pgstat&ranking=calls" now applies state, not just a full-page navigation. Internal writes via writeHash are tracked via a module-scoped _lastWrittenHash so the listener ignores self-triggered events and only responds to user paste / anchor clicks.
  • Dashboard sink: input_label sanitization + Content-Security-Policy + placeholder ordering. The <title>{{PAGE_TITLE}}</title> and the embedded input_label both now pass through sanitize_input_label, which strips Unicode Cc (control) and Cf (format: BiDi overrides, LSEP, BOM) characters before the HTML-escape pass. A <meta http-equiv="Content-Security-Policy"> tag locks the dashboard to default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none', closing the class of defects where a future sink change might leak an external reference. The placeholder injection order in html::inject now goes title first, JSON second, so a malicious title cannot shift the JSON payload boundary.
  • CLI: --pg-stat-top sanity. Capped at 10_000 at the clap level and decoupled from the scrape size on the Prometheus path (see Added). Without the cap, a user passing --pg-stat-top 2_000_000_000 would OOM the process on ingest.
  • Daemon correlator: sample_trace_id length and identical-write short-circuit. Truncated at 128 bytes on a UTF-8 character boundary so a pathological emitter cannot blow up the PairState memory budget. The update path short-circuits when the incoming trace_id already matches the recorded value, avoiding a to_string() allocation on the common case of a single trace emitting multiple findings in one ingest batch.
  • Browser test suite: http-server binds 127.0.0.1 explicitly. Without -a 127.0.0.1, http-server defaults to 0.0.0.0 and would expose the dashboard fixture to every interface on the CI runner for the duration of the suite. The browser README now documents the constraint in both EN and FR.
  • Dashboard demo pipeline hardening (cycleTo + themeFor + build-gif.sh). The demo's cycleTo throws when the theme cycle never reaches the requested target, themeFor throws on a project name that does not end with -dark or -light (instead of silently writing a light still under a dark name), and the case in build-gif.sh matches the exact Playwright output directory (not a loose glob that could catch the stills projects).
  • All eleven SonarCloud findings cleared. Cognitive complexity on Correlator::record_co_occurrences drops below the 15-point cap by extracting update_sample_trace_id and truncate_to_utf8_boundary. Six tabindex="0" attributes drop from role="tabpanel" containers (WAI-ARIA APG says a tabpanel with focusable children should NOT be focusable itself). Array.indexOf >= 0 -> Array.includes, parentNode.removeChild -> childNode.remove, window.addEventListener -> globalThis.addEventListener, and an explicit return 0 in the convert_webm bash helper.
  • Dependency: rustls-webpki bumped to 0.103.13 against RUSTSEC-2026-0104 (reachable panic in certificate revocation list parsing). rustls-webpki lands transitively through rustls 0.23 -> tokio-rustls + hyper-rustls used by the daemon's TLS stack. Patch-level bump, semver-compatible, cargo audit green.

Tests

  • Playwright browser smoke suite under crates/sentinel-cli/tests/browser/. Ten specs cover page-title filename, g p keyboard tab switch, Findings -> Explain cross-nav, Explain -> pg_stat drill-in, / search filter, Export CSV blob content (with full RFC 4180 quoting assertion on embedded commas), hash deep-link on fresh load, hashchange on in-page paste, Copy link clipboard write, and ARIA tablist shape. Runs in a separate browser-tests GitHub Actions job with Node 24 + Chromium + Playwright, parallel with the Rust check job so the Rust-only path is not slowed. The hand-written Rust twin of the JS csvEscape function retires now that Playwright covers the same contract in a real browser.

Docs

  • docs/img/report/ grows a dashboard tour via npm run demo in the browser-tests dir: dashboard_dark.gif and dashboard_light.gif (one Playwright tour per primary theme, ~28 s each, ffmpeg palette-optimised at 1000 px / 15 fps) plus per-tab still frames (findings, explain, pg-stat, diff, correlations, cheatsheet in both light and dark). The root README + README-FR serve the correct variant via a <picture> tag with prefers-color-scheme, matching the pattern already used for the logo and architecture diagram. The browser-tests README documents the regen cadence so casual doc tweaks do not balloon the repo with fresh ~5 MB blobs.
  • tsconfig.json + @types/node land under crates/sentinel-cli/tests/browser/ so IDE TypeScript language servers no longer flag TS2705 on the async spec functions. target pinned to ES2024 (the newest stable target shipped by TypeScript 5.9, Node 24 supports it natively). Playwright's own CLI transpiler is unaffected.

Install

Prebuilt binaries (Linux amd64 / arm64, macOS arm64, Windows amd64):

curl -LO https://github.com/robintra/perf-sentinel/releases/download/v0.5.1/perf-sentinel-linux-amd64
chmod +x perf-sentinel-linux-amd64
sudo mv perf-sentinel-linux-amd64 /usr/local/bin/perf-sentinel

Linux binaries are statically linked against musl and run on any distribution (Alpine, Debian, RHEL, Ubuntu any version) regardless of glibc version, and inside FROM scratch images.

From crates.io:

cargo install perf-sentinel

Docker:

docker run --rm -p 4317:4317 -p 4318:4318 \
  ghcr.io/robintra/perf-sentinel:0.5.1 watch --listen-address 0.0.0.0

Also available on Docker Hub: robintrassard/perf-sentinel:0.5.1.

Verify the binary against SHA256SUMS.txt:

curl -LO https://github.com/robintra/perf-sentinel/releases/download/v0.5.1/SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt --ignore-missing

Full diff: v0.5.0...v0.5.1

Don't miss a new perf-sentinel release

NewReleases is sending notifications on new releases.