github robintra/perf-sentinel v0.9.5

latest release: chart-v0.9.5
3 hours ago

What's new in v0.9.5

v0.9.5 opens two file-based ingestion paths and adds an opt-in memory guard for the daemon. analyze --input now auto-detects OpenTelemetry OTLP JSON, so a Collector file exporter dump feeds perf-sentinel directly, with no Tempo or Jaeger backend. A new mysql-stat subcommand ranks MySQL hotspots from a performance_schema digest export, the MySQL counterpart to pg-stat, with a matching tab in the self-contained HTML dashboard. And an opt-in cgroup memory-pressure guard lets the daemon reject OTLP ingest before it decodes, bounding RSS under a saturation flood. The pipeline stages, the OTLP wire protocol, and the existing daemon HTTP routes are unchanged. The pinned build toolchain moves to Rust 1.96.1, the minimum supported Rust version stays 1.96.0.

Ingestion: OTLP JSON in batch input

analyze --input (and explain, diff, calibrate, and report --input) now auto-detect OpenTelemetry OTLP JSON alongside the native, Jaeger, and Zipkin formats. A single pretty-printed ExportTraceServiceRequest, the newline-delimited form a Collector file exporter writes, and concatenated request objects are all read through one streaming path. Detection keys on the top-level resourceSpans structure rather than a substring match, so a payload's content can never be mistaken for a different format, and a truncated final NDJSON line fails closed rather than silently dropping a partial record. This is what lets a team bridging dd-trace or any other tracer through an OpenTelemetry Collector run perf-sentinel in batch on a dumped file, not only against a live Tempo or Jaeger backend.

Analysis: mysql-stat for performance_schema digests

A new mysql-stat subcommand reads a MySQL performance_schema.events_statements_summary_by_digest export (CSV or JSON) and ranks the heaviest statement digests, the MySQL counterpart to pg-stat for teams with no distributed tracing on the MySQL side. It ranks by total execution time, call count, mean execution time, and rows examined, converts the picosecond timers to milliseconds, and handles backtick-quoted identifiers and a NULL schema. An optional --traces cross-reference marks which digests already appear in a trace file. A malformed export, for example an all-NULL digest column, fails fast rather than emitting empty rows.

Report: mysql_stat dashboard tab

report --mysql-stat <file> (with an optional --mysql-stat-top N) embeds the digest rankings as a mysql_stat tab in the self-contained HTML dashboard, at parity with the existing pg_stat tab, with a sub-switcher over the four rankings and CSV export. The demo --html showcase populates it from an embedded fixture.

Daemon: memory-pressure ingest guard

An opt-in [daemon] memory_high_water_pct (default 0, off) makes the daemon poll its cgroup v2 memory and reject OTLP ingest with a retryable status once the working-set ratio crosses the configured high-water mark, bounding RSS independently of the queue-depth shed path. It keys on the working set (memory.current minus the reclaimable inactive_file page cache, the same figure kubelet reports), so an archive-heavy pod hovering on page cache does not trip it, and it fails open if the cgroup becomes unreadable or unlimited. The rejection fires at every ingest door before the body is buffered or decoded, HTTP, gRPC, and the JSON socket, so a flood cannot materialize up to the payload limit per request into memory. A new perf_sentinel_ingest_memory_pressure gauge and a memory_high_water_pct field on GET /api/config expose the state, and the settings advisor flags sustained rejections. The guard is Linux and cgroup-v2 only, inert on other hosts.

Operator-visible behavior change

An OTLP JSON file that analyze --input previously rejected is now auto-detected and analyzed. Nothing else changes by default: the memory guard is off unless memory_high_water_pct is set, and the new perf_sentinel_ingest_memory_pressure gauge sits at 0 until it is enabled. When the guard is enabled and memory crosses the mark, OTLP exporters receive a retryable 503 (HTTP) or UNAVAILABLE (gRPC) and should back off, which is the standard OTLP contract. No existing daemon HTTP route, OTLP wire shape, or output format is removed or changed.

Why this is a patch and not a minor

Every addition is additive and opt-in. The OTLP JSON format is auto-detected next to the existing ones, mysql-stat is a new subcommand that touches nothing else, the dashboard tab appears only when --mysql-stat is passed, and the memory guard defaults to off. A pipeline, a CLI invocation, or a daemon deployment that does not opt into the new surface behaves exactly as before. No existing CLI flag, configuration default, output format, daemon HTTP route, or OTLP wire shape changes. The minimum supported Rust version stays 1.96.0.

Validation

The simulation-lab release-gate passed for v0.9.5 (lab commit f756744, 2026-07-04), exercising the OTLP JSON batch path and the mysql-stat subcommand, and re-validating the memory-pressure guard end to end after review: the original saturation scenario still OOM-kills with the guard off, the guard caps RSS with it on, ingest is rejected before decode, and a page-cache-heavy pod does not trip. CI covers the build, the full test suite (cargo test --workspace), clippy at -D warnings, cargo fmt, and both the default and --no-default-features builds.

Verifying this release

# Binary integrity via SLSA Build L3 attestation
gh attestation verify perf-sentinel-linux-amd64 \
  --owner robintra --repo perf-sentinel

# A periodic disclosure produced by this binary
perf-sentinel verify-hash --report perf-sentinel-report.json \
  --expected-identity "https://github.com/robintra/perf-sentinel/.github/workflows/release.yml@refs/tags/v0.9.5" \
  --expected-issuer "https://token.actions.githubusercontent.com"

gh CLI 2.49 or newer required for gh attestation verify.

Full Changelog: v0.9.4...v0.9.5

Don't miss a new perf-sentinel release

NewReleases is sending notifications on new releases.