github robintra/perf-sentinel v0.25.3

latest release: chart-v0.25.3
5 hours ago

What's new in v0.25.3

v0.25.3 is about Java findings that suggested the wrong fix, or none. The most common N+1 of a JPA application, the load of a lazy collection, reaches perf-sentinel as a bare JDBC span: the OpenTelemetry Java agent wraps a Hibernate query or a Spring Data repository call in a span that names the ORM, but not a lazy load. With no Hibernate scope on the finding, it got the Java generic fix instead of the JPA one. A service traced through Micrometer Observation got no framework fix at all, since its only scope, org.springframework.boot, named no language. The release also removes two warnings the daemon printed at every start.

Hibernate signs the SQL it generates

Hibernate 6 and later alias each table of a query it generates as <stem><n>_<m>, as in select b1_0.id from book b1_0. A Java SELECT finding whose statement carries such an alias, also declared after its table, now reads java_jpa when no span names Hibernate. The declaration rules out a schema or a table merely named that way, which never stands alone. The block comment that hibernate.use_sql_comments puts in front of the statement is skipped before it is read.

A bulk UPDATE or DELETE carries the same aliases but is not a fetch, so it keeps the Java generic fix. So does a statement traced under the Vert.x SQL client, where Hibernate Reactive generates the same aliases but the blocking JPA advice does not apply. The rule is Java-only, the same aliases on a finding of another language change nothing.

org.springframework.boot names Java

spring-boot-starter-opentelemetry puts every span of the service under the org.springframework.boot scope and sets no code.namespace. That scope now marks the finding as Java, so an N+1 on a RestClient call gets the Java generic fix. The Brave bridge of spring-boot-starter-zipkin carries no scope, so its findings keep the generic suggestion alone.

Measured in the simulation lab on a Spring Boot 4.1.1 + Spring Data JPA application under the OpenTelemetry Java agent 2.31.1, under the agent with its Hibernate and Spring Data instrumentation off, and under the Micrometer bridge. On 0.25.2 the lazy loads read java_generic on every path, the daemon's included, the commented derived query does too once no Hibernate span wraps it, and the Micrometer n_plus_one_http carries no framework fix. On 0.25.3 the lazy loads and the derived query read java_jpa and the Micrometer finding java_generic, while the hand-written JdbcTemplate SELECT and the bulk JPQL UPDATE stay on java_generic. On one capture, both versions produce the same finding signatures.

A volume root the daemon cannot tighten

At start the daemon tightens the directory of its ack store to 0700. A Kubernetes volume mounted under a pod's fsGroup has its root owned by root, mode 2775, and the Helm chart puts the ack store there. The daemon runs as 65534 and cannot chmod a directory it does not own, so every start printed could not tighten ack store parent directory to 0700 at warn level, about something the operator cannot act on. When the chmod is refused on a directory other users cannot write into, the refusal now logs at debug. A world-writable directory still warns, as does any other chmod failure, and acks.jsonl keeps its 0600 mode.

One validation pass for watch

perf-sentinel watch validated its configuration as loaded, then again after applying --listen-address, --listen-port-http, --listen-port-grpc and --max-export-findings. Every warning from that validation printed twice, the non-loopback listen advisory among them, which a daemon listening on 0.0.0.0 meets at every start. The flags now join the configuration files as a last [daemon] layer before the one validation pass, so each advisory prints once and reads the address and limits the daemon runs with.

The lab starts the daemon image as 65534 on a Docker volume prepared like an fsGroup root, listening on 0.0.0.0. 0.25.2 prints the chmod warning, and the listen advisory twice when the address comes from the file. 0.25.3 prints no chmod warning, and the advisory once, whether the address comes from the file or from --listen-address.

Upgrade impact

  • Some Java findings suggest a different fix. A SELECT Hibernate generated, with no Hibernate span on the finding, moves from java_generic to java_jpa, and a finding traced through Micrometer gains the Java generic fix. The fix is not part of the signature, so acknowledgments keep matching, and no finding appears or disappears.
  • A watch flag that fails validation exits with code 75, like an invalid configuration file, instead of 1, and its error names the command-line flags. A value clap rejects, such as a port above 65535, still exits with code 2.
  • A flag now replaces the file value before validation. A file value that a flag overrides is no longer checked on its own, so an out-of-range max_export_findings in the file no longer stops a daemon started with --max-export-findings.
  • No configuration key is added or removed, no route, metric name or wire format changes, no public signature in perf-sentinel-core moves, the embedded reference data keeps its vintages, 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.25.3" \
  --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.