github robintra/perf-sentinel v0.22.2

latest release: chart-v0.22.2
10 hours ago

What's new in v0.22.2

v0.22.2 is about a finding that could not say where it came from. A trace that begins in a message consumer has no inbound HTTP route to name, and under the OpenTelemetry Java agent it has no application frame above the I/O either: the consumer span is the root, and the repository calls that do the work are children of it rather than descendants of anything that names the listener. So every N+1 behind every listener in a service reported the same endpoint, unknown. They folded into one row, and acknowledging that row hid all of them at once.

The nearest consumer, not the outermost one

The endpoint now falls back to the destination of the nearest consumer span, <messaging.system> <destination>, read from messaging.destination.template, then messaging.destination.name, then the legacy messaging.destination. It sits last in the order, after the inbound route and after the code frame, so nothing that already resolved changes.

The nearest one and not the outermost, because consumer spans nest. When an amqp-client delivery span wraps a framework listener span, the outer one names the exchange and the inner one names the queue the listener actually drained, which is the one the operator is looking for.

A destination that cannot identify anything stays unknown rather than becoming a plausible-looking row: a destination flagged temporary or anonymous, a server-generated amq.gen-* or spring.gen-* queue, a placeholder in angle brackets such as <default>, which instrumentations emit for a default exchange, and one holding ?, # or @, which the endpoint sanitizer would truncate anyway. The flags are checked case-insensitively and accept a stringified boolean, because Zipkin carries tags as strings where Jaeger keeps the JSON type. An instrumentation may set neither flag, the OpenTelemetry Java agent among them, which is why the server-generated names are filtered on their shape as well. OTLP, Jaeger and Zipkin all resolve it the same way.

A span that closes after the children it wraps

On the streaming path this is not a lookup, it is a retention problem. A consumer span ends after everything it wraps, so it usually arrives in a later export than the queries it explains, and by then the window has already seen those queries. The daemon keeps consumer destinations in the trace window apart from route roots, under their own max_events_per_trace cap and with their parent links, and reads them only where no route and no resolved ancestor answers.

Keeping them meant being stricter about what counts as resolved. An endpoint an I/O span carries that no retained route confirmed, a code frame or a destination alike, is now cached as unproven, so a listener calling its own API cannot outrank the route of the SERVER span below it. At an ancestry cap of one, a retained destination stops the single retained route from being guessed as the root.

One related fix came out of the same work: the ancestor walk used to stamp the entries above its outermost route with that route, so I/O spans on a sibling branch, a listener's own queries sitting next to a self-call, inherited a route they never served. Only the entries below the outermost resolution are compressed now.

Measured in the simulation lab on a real @RabbitListener under the Java agent 2.30.0, where the N+1 behind the listener reports rabbitmq order-service, the routing key it received, instead of unknown. A synthetic family of 14 consumer shapes covering the accepted and the rejected destinations passes 53 of 53 assertions on this release against 47 of 53 on 0.22.1.

One dependency moved

rustls goes to 0.23.45 against RUSTSEC-2026-0285, where a TLS 1.3 handshake message that followed a key-changing message inside the same record was accepted at the wrong encryption level, which RFC 8446 section 5.1 requires a peer to reject. The transcript stays authenticated, so this is not a path to altering or completing a handshake, and the practical effect is that a peer could send in plaintext what should have been encrypted. It reaches the binary through the daemon's own TLS listeners and through the shared outbound client every scraper uses. Patch-level bump, semver-compatible, cargo audit green.

Upgrade impact

  • A consumer-rooted finding changes endpoint, and its acknowledgment with it. An acknowledgment matches on the finding type, the service, the endpoint and a digest of the template, so a finding that read unknown and now reads rabbitmq order-service is a different signature. Acknowledgments on those findings, in the .perf-sentinel-acknowledgments.toml baseline and in the runtime store alike, have to be taken again. A finding whose endpoint already resolved keeps the signature it had.
  • Listeners in one service stop sharing a row. Where they all folded into a single unknown, each destination is now its own signature, so the listing shows one row per listener and acknowledging one no longer hides the others. Expect the row count to rise on a service that runs several consumers, without the underlying detections changing.
  • Nothing resolves differently where a route or a code frame was already found. The fallback is last in the order, and a destination that cannot identify anything still reads unknown.
  • No configuration key is added or removed, no route, metric or wire format changes, no public signature in perf-sentinel-core moves, the detection verdicts are untouched, and MSRV stays 1.98.1.

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.22.2" \
  --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.