github robintra/perf-sentinel v0.21.0

latest release: chart-v0.21.0
3 hours ago

What's new in v0.21.0

Ten tenants, nine services, more than 4300 distinct signatures on one production daemon. GET /api/findings caps a page at 1000 rows, so that fleet could only ever read its newest thousand, and nothing on a row said which tenant it belonged to. The overview dashboard had been filtering on exactly that value since 0.19.0, two clicks away, and the findings listing could not take it.

v0.21.0 hands the listing the same filter and a way to walk past the cap, then makes the read that serves it cheaper.

The value the label already carries

GET /api/findings takes grouping, an exact match on the finding's effective grouping: the first attribute present from [detection] grouping_attributes, which on Kubernetes is the namespace the analysed traffic runs in, and the value the grouping Prometheus label has carried since 0.19.0. So label_values(perf_sentinel_findings_total, grouping) feeds this API with no conversion, and one Grafana variable drives both shipped dashboards. It screens during the buffer pass, like service.

offset skips folded rows, after the severity screen and the delta bound, on the newest-first order, so a listing past the cap is read page by page. The ring keeps evicting and inserting between two requests, so a row can cross a page boundary, the caveat /api/incidents already carries. A reader that needs a stable set narrows with grouping and service first, which is also what makes most pages fit under the cap.

An empty filter value is an absent one

This one is visible to an existing client. ?severity= used to be an exact match on the empty string and returned nothing. It now means no filter, on all four string filters, and a value is trimmed.

The reason is Grafana: it ignores an empty allValue, so a dashboard whose All must reach an exact-match API sends a single space instead. The server reads a blank as no filter, and the shipped findings dashboard gains Grouping, Service and Skip rows variables plus a Grouping column on that contract. Dashboard version 4, and the overview moves to version 7, its I/O waste ratio and Avoidable I/O ops rate panels now naming the five finding types that feed them, so a reader watching the ratio climb while only slow_sql fires can tell the two are unrelated.

A suggestion that names the block instead of carrying it

A serialized_calls suggestion listed every call of the sequence. On a run of 121 sequential Hibernate selects of four kilobytes each the sentence weighed 480 KB, and three such rows were 72 % of a 5 MB page, which is what made the page cap look too small before the cap was.

It now names the first three distinct templates, each cut at 120 characters, and ends in -> ... when the block holds more. The count, the total and the parallel estimate are unchanged, and every template is whole in the trace the row points at.

Only the page a read keeps

The fold cloned the first instance of every distinct signature, template and suggestion included, applied the severity screen, the delta bound, offset and limit to those clones, then dropped the rest. A one-row read of a ring holding 100 000 instances folding into 4300 signatures paid for 4300 clones, all of it under the read lock the write path waits behind.

It now accumulates the group metadata against a borrowed representative and materialises only the rows the page keeps, with the same representative, the same counts and the same order. Measured by the new benches/findings_store.rs on a 100 000-instance ring with production-sized strings, 800-byte templates with one in twenty at 16 KB and 600-byte suggestions:

first page of 1000      9.1 ms -> 6.3 ms
page at offset 3000     8.9 ms -> 6.6 ms
one-row read            9.2 ms -> 5.9 ms

What remains is the pass over the ring itself, hashing every instance's fold key. The store sits behind the daemon feature, so the bench needs --features daemon.

Upgrade impact

  • ?severity=, ?service=, ?type= and ?grouping= with an empty or blank value now list everything, where an empty value was an exact match that returned nothing. A client sending one gets the whole listing.
  • A short page is not the last page. Acknowledged findings are dropped after offset and limit have cut the page, so with the default include_acked=false a page can come back short with more pages behind it. Page until a request returns nothing.
  • MSRV moves from 1.97.1 to 1.98.1, with every declared dependency raised to its newest release. The one major is dirs 6 to 7, whose only breaking change is preference_dir on Windows, which nothing here calls.
  • Additive, perf-sentinel-core included. FindingsFilter gains grouping and offset and is #[non_exhaustive], so no external construction breaks. No configuration key is added or removed, no route or wire format changes, the response shape is untouched, and the acknowledgment signatures and the detection verdicts are unchanged.

Full detail in CHANGELOG.md.

Verifying this release

# Binary integrity via SLSA Build L3 attestation
gh attestation verify perf-sentinel-linux-amd64 \
  --repo robintra/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.21.0" \
  --expected-issuer "https://token.actions.githubusercontent.com" \
  --verify-binary ./perf-sentinel-linux-amd64

gh CLI 2.49 or newer required for gh attestation verify.

Don't miss a new perf-sentinel release

NewReleases is sending notifications on new releases.