Skip to content

Releases: Institut-du-Numerique-Responsable/perf-sentinel

perf-sentinel chart v0.26.3

Choose a tag to compare

@github-actions github-actions released this 11 Oct 10:04
chart-v0.26.3
9e2d8c6

What's new in chart-v0.26.3

A chart-only release that changes where the chart and its image are published, and nothing else. The repository joined the Institut du Numérique Responsable, so the chart is now pushed to oci://ghcr.io/institut-du-numerique-responsable/charts/perf-sentinel and image.repository defaults to ghcr.io/institut-du-numerique-responsable/perf-sentinel. appVersion is intentionally held at 0.26.2 because the binary did not change. The chart version advances to 0.26.3 on its own, and the next application release is 0.26.4.

The same bytes at a new address

Every image and chart version published under ghcr.io/robintra was copied to the new namespace with its digest unchanged, and the chart signatures and the image attestations came along. A tag or digest pinned today resolves at both addresses. Only the new one receives new releases, the old namespace stays at 0.26.2. The Docker Hub image keeps its name, robintrassard/perf-sentinel.

Changed

  • image.repository defaults to ghcr.io/institut-du-numerique-responsable/perf-sentinel.
  • home, sources, icon and the Artifact Hub links point at the repository's new home.

Upgrade impact

  • Pods roll once, on the image reference alone. The image: field moves from ghcr.io/robintra/perf-sentinel:0.26.2 to ghcr.io/institut-du-numerique-responsable/perf-sentinel:0.26.2. Both resolve to the same digest, sha256:908c9b31…, so the daemon that comes back is the one that left.
  • A values file that sets image.repository keeps pulling from where it points. Point it at the new namespace, or at a mirror synced from it, to receive the next releases.
  • Registry mirrors and allow-lists that name ghcr.io/robintra need the new namespace.
  • checksum/config moves, and not because your configuration changed. The rendered perf-sentinel.toml is identical, but the ConfigMap carries a helm.sh/chart label that bumps with the chart.
  • No values.yaml key is added or removed, no template changes, and the shipped PrometheusRule is untouched.

Verifying this chart

Charts signed before the move carry the original account's identity, so the regex accepts both owners:

cosign verify \
  --certificate-identity-regexp '^https://github.com/(robintra|Institut-du-Numerique-Responsable)/perf-sentinel/\.github/workflows/helm-release\.yml@refs/tags/chart-v' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/institut-du-numerique-responsable/charts/perf-sentinel:0.26.3

helm pull oci://ghcr.io/institut-du-numerique-responsable/charts/perf-sentinel --version 0.26.3
gh attestation verify perf-sentinel-0.26.3.tgz \
  --repo Institut-du-Numerique-Responsable/perf-sentinel

cosign 3.0 or newer is required, the signature is an OCI 1.1 referrer bundle.

Install

The chart is published as an OCI artifact on GHCR, install it directly with no helm repo add step:

helm install perf-sentinel oci://ghcr.io/institut-du-numerique-responsable/charts/perf-sentinel --version 0.26.3

Upgrade an existing release:

helm upgrade perf-sentinel oci://ghcr.io/institut-du-numerique-responsable/charts/perf-sentinel --version 0.26.3

Read docs/HELM-DEPLOYMENT.md for the ServiceMonitor section, sizing and Ingress postures.

If you are upgrading from chart-v0.23.0 or earlier, read the chart-v0.24.0 notes first: that release raises the default workload.statefulset.persistence.size to 2Gi, which an existing StatefulSet does not pick up on its own. From chart-v0.18.0 or earlier, read the chart-v0.19.0 notes as well: that release adds a grouping label to five metrics and is breaking for an unaggregated alert on any of them. From chart-v0.16.0 or earlier, the chart-v0.17.0 notes change the shipped PrometheusRule.

Full Changelog: chart-v0.26.2...chart-v0.26.3

v0.26.2

Choose a tag to compare

@github-actions github-actions released this 09 Oct 13:59
v0.26.2
86b3223

What's new in v0.26.2

v0.26.2 is about getting the same answer twice. On one input, the 0.26.1 binary could print a different report on every run: the findings in another order, CO2 figures that differed in their last digits, another pair of calls in a chatty suggestion, another trace, endpoint and signature on a tied cross-trace slow finding. The daemon did the same across restarts. The cause was one pattern in 29 places: an order, a tie-break or a floating-point sum taken from a hash map, whose iteration order changes with every map the process builds. Fixing them also surfaced two undercounts of avoidable I/O, and one configuration mistake that only showed as randomness.

One input, one report

Batch runs now walk traces in the order their first span appears in the input, so the carbon pipeline sums its figures in a fixed order, and a service whose traces carry several regions reports the region of its first trace. Findings are sorted on a key that runs from type and severity down to the grouping, the timestamps, the service, the occurrence count and the code location, and the detectors that can still tie emit in input order. A chatty_service suggestion names its two most frequent calls with ties broken on the template, where it named whichever two a hash map listed first.

Against the 0.26.1 binary, ten runs of analyze on tests/fixtures/demo.json with a default_region gave 10 distinct JSON reports, 9 distinct SARIF files and 10 distinct terminal outputs, and ten runs of perf-sentinel demo gave 10 distinct outputs. With 0.26.2 every one of these gives a single output. On tests/fixtures/tied_findings.json, a new fixture that packs the ties, 0.26.1 gave 10 distinct outputs in each format and 0.26.2 one.

A tied slow finding names one trace

When several calls of a cross-trace slow_sql or slow_http finding tie on duration, the finding names the smallest trace ID, then the earliest timestamp, then the span ID. It took whichever tied trace a hash map listed last, and since the signature hashes the service and the endpoint, the signature moved with it. On four traces running the same 900 ms query from four endpoints, 0.26.1 produced 4 different signatures in 12 runs, so an acknowledgment matched some runs and not others. 0.26.2 names the same trace and signature every time. The daemon's windowed slow findings take the same tie-break for their service, endpoint and code location, and keep the most recent episode's trace so Explain can open it.

The daemon, restart after restart

/api/correlations, the correlations of /api/export/report, query correlations and the TUI panel sorted on confidence and co-occurrence count only. On 0.26.1, six restarts fed the same traces returned the same 53 correlations in six orders. They now continue on the source then the target, and the pairs evicted at max_tracked_pairs among tied ones go in pair order instead of map order. GET /api/acks and ack list list by signature, and the ack JSONL file is rewritten at startup in acknowledgment order, so it keeps its bytes across restarts. Findings the cross-batch slow window emits together come out sorted, and past max_events_per_trace the source endpoints of one batch are admitted in service then span ID order, so a span no longer resolves to its route on one run and to unknown on the next.

Avoidable I/O counts every grouping and every parameter set

The dedup that keeps one avoidable count per pattern was keyed on trace, template and endpoint. A template run in several groupings of one trace counted for one grouping only, and one template called with several parameter sets kept the largest redundant group instead of their sum: 3 calls with id = 1 and 4 with id = 2 counted 3 avoidable operations, not 5. The key now carries the grouping, redundant findings at one key add up, and the key keeps the larger of its N+1 figure and that sum. On tests/fixtures/tied_findings.json the avoidable count moves from 11 to 29 of 201 operations and the I/O waste ratio from 5.5% to 14.4%. The 20 other trace fixtures of the repository keep their figures.

A region key spelled twice

[green.service_regions] and [green.electricity_maps] region_map match case-insensitively and are lowercased at load, so two keys that differ only in case collapsed into one, and which value won followed a hash map. With "order-svc" = "eu-north-1" and "Order-Svc" = "us-east-1", 0.26.1 placed the service in eu-north-1 on 6 of 8 runs and in us-east-1 on the other 2, and its CO2 figures moved with it. 0.26.2 refuses that configuration at load with exit code 75 and an error that names both keys. Keys that agree on the value still load, and a region_map is only checked when a token enables the Electricity Maps section.

Upgrade impact

  • Avoidable I/O and the figures built on it rise on traces that span several groupings or repeat one query with several parameter sets: green_summary.avoidable_io_ops and its SQL and messaging parts, io_waste_ratio, avoidable CO2, perf_sentinel_avoidable_io_ops, perf_sentinel_service_avoidable_io_ops_total and the canonical figures of periodic disclosures. They are not comparable across the upgrade for such traffic, and an analyze --ci run whose io_waste_ratio_max rule passed near its threshold can now fail.
  • A configuration whose region keys differ only in case and map to different values fails to load. Keep one of the two keys before upgrading.
  • Output order changes once. Findings, correlations and acks may list in another order than 0.26.1 printed on a given run, then stay put. A tied cross-trace slow finding may settle on another signature than the one acknowledged, in which case the acknowledgment needs renewing once.
  • perf-sentinel-core gains API: CorrelationEndpoint and CodeLocation derive PartialOrd and Ord.
  • No finding appears or disappears, no detector verdict changes, no configuration key is added or removed, no route, metric name or wire format changes, the embedded reference data keeps its vintages, and MSRV stays 1.99.0.

Full detail in CHANGELOG.md.

Verifying this release

# Binary integrity via SLSA build provenance attestation (Build Level 2)
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.26.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.

v0.26.1

Choose a tag to compare

@github-actions github-actions released this 09 Oct 12:28
v0.26.1
4b25e85

What's new in v0.26.1

v0.26.1 is a maintenance release of the binary. It is built with Rust 1.99.0 instead of 1.98.1, against the newest compatible releases of its dependencies, and it behaves as 0.26.0 did.

The 1.99.0 compiler

The workspace pins Rust 1.99.0 in rust-toolchain.toml and in rust-version, so every release binary, including the one the container image copies, comes out of the 1.99.0 compiler. No source line of the binary changes for it.

Newer dependencies, same requirements

Cargo.lock moves to the newest releases the existing version requirements allow, and no requirement in the Cargo.toml files changes: hyper 1.12.0, hyper-rustls 0.27.10, hyper-util 0.1.21, which brings in base64 0.23.1, libc 0.2.190, lru 0.18.5, thiserror 2.0.21, tokio 1.53.2, tokio-rustls 0.26.6, toml 1.1.8, uuid 1.27.0 and want 0.3.2. cargo audit reports no advisory on the lockfile of either release, so none of these updates closes a known vulnerability.

Against the 0.26.0 binary, analyze on the 20 trace fixtures of the repository, in JSON and in SARIF, produced the same output apart from the version string: the same 32 findings, with the same signatures. The one line that differed is the pair of example calls a chatty-service suggestion names when every call ties at x1, a pair that already changes from one run to the next on 0.26.0.

Upgrade impact

  • MSRV moves to 1.99.0. perf-sentinel-core declares rust-version = "1.99.0", so a crate that depends on it needs Rust 1.99.0 or newer to build.
  • Nothing a finding is keyed on moves. Findings, signatures, endpoints and SARIF locations are unchanged, so acknowledgments keep matching and no finding appears or disappears.
  • No configuration key is added or removed, no route, metric name or wire format changes, no public signature in perf-sentinel-core moves, and the embedded reference data keeps its vintages.

Full detail in CHANGELOG.md.

Verifying this release

# Binary integrity via SLSA build provenance attestation (Build Level 2)
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.26.1" \
  --expected-issuer "https://token.actions.githubusercontent.com" \
  --verify-binary ./perf-sentinel-linux-amd64

gh CLI 2.49 or newer required for gh attestation verify.

perf-sentinel chart v0.26.2

Choose a tag to compare

@github-actions github-actions released this 09 Oct 14:03
chart-v0.26.2
86b3223

What's new in chart-v0.26.2

A metadata-only bump. appVersion moves to 0.26.2 and image.tag follows it to :0.26.2. No template, values.yaml key or schema changes. Under the default values the rendered perf-sentinel.toml is byte-for-byte what chart-v0.26.1 rendered, same 5036 bytes and the same digest. The other manifest changes are the image, the version labels and checksum/config.

What moves inside the pod

The daemon's outputs no longer change from one restart to the next on the same traffic. The order of /api/findings, /api/correlations, /api/acks and /api/export/report, the trace and signature a tied cross-trace slow finding names, the pair of calls a chatty suggestion names, and the source endpoints kept past max_events_per_trace all followed hash maps that change at every restart. The ack JSONL file the daemon rewrites at startup keeps its acknowledgment order, so its bytes stay the same across restarts.

Avoidable I/O now counts every grouping of a trace and adds up the redundant findings of one query called with several parameter sets, where it kept one of them. Traffic that spans several grouping_attributes values or repeats a query with different parameters sees perf_sentinel_avoidable_io_ops, perf_sentinel_service_avoidable_io_ops_total and the periodic disclosures rise.

A config or fragments entry whose [green.service_regions], or a [green.electricity_maps] region_map that a token enables, holds two keys that differ only in case and map to different values is rejected at load. The 0.26.1 daemon picked one of the two values at random, and the service's CO2 figures moved with it.

The v0.26.2 binary notes describe the changes and the measurements.

Upgrade impact

  • Pods roll on the image. helm upgrade replaces them because appVersion and image.tag move together.
  • The avoidable I/O counters step up for traffic that spans several groupings or repeats one query with several parameter sets, so a Grafana panel on perf_sentinel_avoidable_io_ops or perf_sentinel_service_avoidable_io_ops_total shows a step, and the figures are not comparable across the upgrade.
  • A daemon whose region keys differ only in case and map to different values does not start. Keep one of the two keys in config or fragments before upgrading. Keys that agree on the value still load.
  • A tied cross-trace slow finding may settle on another signature once, in which case its acknowledgment needs renewing.
  • checksum/config moves, and not because your configuration changed. The rendered perf-sentinel.toml is identical, but the ConfigMap carries helm.sh/chart and app.kubernetes.io/version labels that bump with the chart.
  • No values.yaml key is added or removed, no template changes, and the shipped PrometheusRule is untouched.

Install

The chart is published as an OCI artifact on GHCR, install it directly with no helm repo add step:

helm install perf-sentinel oci://ghcr.io/robintra/charts/perf-sentinel --version 0.26.2

Upgrade an existing release:

helm upgrade perf-sentinel oci://ghcr.io/robintra/charts/perf-sentinel --version 0.26.2

Read docs/HELM-DEPLOYMENT.md for the ServiceMonitor section, sizing and Ingress postures.

If you are upgrading from chart-v0.23.0 or earlier, read the chart-v0.24.0 notes first: that release raises the default workload.statefulset.persistence.size to 2Gi, which an existing StatefulSet does not pick up on its own. From chart-v0.18.0 or earlier, read the chart-v0.19.0 notes as well: that release adds a grouping label to five metrics and is breaking for an unaggregated alert on any of them. From chart-v0.16.0 or earlier, the chart-v0.17.0 notes change the shipped PrometheusRule.

Full Changelog: chart-v0.26.1...chart-v0.26.2

perf-sentinel chart v0.26.1

Choose a tag to compare

@github-actions github-actions released this 09 Oct 12:37
chart-v0.26.1
4b25e85

What's new in chart-v0.26.1

A metadata-only bump. appVersion moves to 0.26.1 and image.tag follows it to :0.26.1. No template, values.yaml key or schema changes. Under the default values the rendered perf-sentinel.toml is byte-for-byte what chart-v0.26.0 rendered, same 5036 bytes and the same digest. The other manifest changes are the image, the version labels and checksum/config.

What moves inside the pod

Nothing the daemon serves. The binary in the image is built with Rust 1.99.0 and against the newest compatible releases of its dependencies, among them hyper 1.12.0, tokio 1.53.2 and toml 1.1.8. Its API, its metrics and the findings it stores and exports keep the same fields and values, and no finding changes signature. Against the 0.26.0 binary, analyze on the 20 trace fixtures of the repository produced the same findings and signatures, and the same output apart from the version string.

The v0.26.1 binary notes describe the changes.

Upgrade impact

  • Pods roll on the image. helm upgrade replaces them because appVersion and image.tag move together.
  • Acknowledgments keep matching. No finding appears, disappears or changes signature, so perf_sentinel_findings_total reads as before.
  • The example dashboards live outside this chart and gain French variants under examples/FR/, with the same queries, panels, uid and version as the English files, so importing one replaces its English counterpart. Import it where the dashboards are provisioned.
  • checksum/config moves, and not because your configuration changed. The rendered perf-sentinel.toml is identical, but the ConfigMap carries helm.sh/chart and app.kubernetes.io/version labels that bump with the chart.
  • No values.yaml key is added or removed, no template changes, and the shipped PrometheusRule is untouched.

Install

The chart is published as an OCI artifact on GHCR, install it directly with no helm repo add step:

helm install perf-sentinel oci://ghcr.io/robintra/charts/perf-sentinel --version 0.26.1

Upgrade an existing release:

helm upgrade perf-sentinel oci://ghcr.io/robintra/charts/perf-sentinel --version 0.26.1

Read docs/HELM-DEPLOYMENT.md for the ServiceMonitor section, sizing and Ingress postures.

If you are upgrading from chart-v0.23.0 or earlier, read the chart-v0.24.0 notes first: that release raises the default workload.statefulset.persistence.size to 2Gi, which an existing StatefulSet does not pick up on its own. From chart-v0.18.0 or earlier, read the chart-v0.19.0 notes as well: that release adds a grouping label to five metrics and is breaking for an unaggregated alert on any of them. From chart-v0.16.0 or earlier, the chart-v0.17.0 notes change the shipped PrometheusRule.

Full Changelog: chart-v0.26.0...chart-v0.26.1

v0.26.0

Choose a tag to compare

@github-actions github-actions released this 04 Oct 19:11
v0.26.0
939b579

What's new in v0.26.0

v0.26.0 is about energy figures that read more than the hardware measured, and labels that named the wrong source. The Kepler scraper summed every zone Kepler exports for a workload, although the zones overlap, so Kepler-fed energy and carbon came out too high. The Redfish scraper took the first PowerControl entry of the legacy /Power resource as the chassis, and could not read firmware that serves neither /Power nor a PowerWatts reading. An Alumet series in millijoules or milliwatts was read as joules without a word. And inspect and query monitor printed the window's model tag as the energy source, which reads electricity_maps_api on a daemon with Electricity Maps, an intensity source.

One Kepler zone instead of their sum

Kepler 0.10 and later export kepler_container_cpu_joules_total and kepler_process_cpu_joules_total once per zone, and the zones overlap: package already contains core and uncore, and Kepler's metrics documentation says not to sum them. The scraper now keeps one zone, set by the new [green.kepler] zone key, package by default as that documentation advises. package covers the CPU socket only, so the dram zone, which the old sum also added, is no longer counted unless zone names it. Rows without a zone label are still read. A blank zone, one with control characters or surrounding whitespace, or one longer than 256 characters is rejected at load.

Where Kepler reads hwmon, zones are named after the host's sensors, so zone has to name one of them. Otherwise the zero-sample warning fires, and it now names the zone as a possible cause. The Kepler scraper started line names the zone too. A test fixture comes from a real Kepler v0.12.0 /metrics endpoint: each workload carries a package, a core and a dram row, and 0.26.0 reads the package one where 0.25.5 added all three.

The chassis entry, and a chassis power sensor

DMTF does not require entry 0 of PowerControl to cover the whole chassis, and a BMC may list CPU or memory subsystems next to it. With schema = "legacy_power", the daemon now reads PowerConsumedWatts from the entry whose PhysicalContext is Chassis, and keeps entry 0 only when no entry says so. A null reading on the chassis entry fails the scrape as invalid_value instead of falling back to a subsystem.

The new schema = "sensor" reads Reading from a DMTF Sensor resource, /redfish/v1/Chassis/{id}/Sensors/{sensorId}, for firmware that serves neither /Power nor PowerWatts in /EnvironmentMetrics. Upstream OpenBMC has not served /Power by default since 2025-08-26. The chassis sensor is the one PowerWatts.DataSourceUri names. The reading counts only when ReadingUnits is W and the sensor's PhysicalContext, when present, is Chassis, so a temperature sensor, or the power sensor of one supply or one CPU that says so, fails the scrape as path_missing instead of feeding a wrong figure. Against the files of the DMTF public-rackmount1 mockup, the daemon read 344 W through legacy_power and 374 W through environment_metrics and sensor, and counted the PS1InputPower sensor as path_missing. Every schema keeps the redfish_bmc tag.

When ca_bundle_path is set, the startup error now points at SSL_CERT_FILE, which covers a BMC certificate from a private CA, or a self-signed one that is not itself a CA certificate.

A warning on non-RAPL Alumet series

The scraper reads joules per energy_interval_secs and tags the reading alumet_rapl. A GPU, Grace or Jetson series lands in the wrong unit, 1000x too high for millijoules, and the energy-estimation-tdp estimate is published as measured RAPL. At start, the daemon now warns once when [green.alumet] metric_name names such a source (grace_, nvml_, amd_gpu_, the Jetson input_power gauge, estimated_consumed_energy) or carries a unit suffix other than joules, such as _millijoules or _watts, which Alumet's development branch appends after v0.9.5. rapl_consumed_energy, an energy-attribution output and a _joules suffix do not warn. The scraper still reads the series.

The energy source, named the same way everywhere

analyze, inspect, query monitor and the HTML report name where the energy figure comes from, from per-service coverage instead of the window tag:

  • modeled from I/O counts when no energy backend covered a service,
  • source <backends> when every service was fully measured,
  • source <backends> on N of M services · rest modeled from I/O counts when they mix.

A service counts as covered once any of its operations was measured. The modeled part is followed by · calibrated when calibration factors rescaled it. A measured or Electricity Maps window tag drops the +cal suffix, so the reports carry a new green_summary.energy_calibrated field, omitted when false, and the label reads it. Tags outside [A-Za-z0-9_+-] or longer than 64 characters are not displayed.

analyze prints the label on a new Energy: line after the CO₂ block. The inspect panel's Energy: line and the query monitor Window energy: line carry it, and the monitor's By service table reads I/O counts for a service none of whose operations was measured. The HTML report's Carbon tab gains an Energy card after Total CO2, greyed out when no span resolved to a region, and its help texts no longer claim a model tag on every figure. In the simulation lab, measured-energy-chain attributed the Redfish mock's chassis energy to order-service under redfish_bmc, and alumet-db-waste found the database waste card intact next to the new card.

Calibration in periodic disclosures

disclose set methodology.calibration_inputs.calibration_applied only from a +cal suffix on a window's energy_model, which measured and Electricity Maps tags never carry. A calibrated daemon with Electricity Maps or an energy backend therefore published false, in the report and in its attestation predicate. The aggregator now also reads green_summary.energy_calibrated. Periods archived before 0.26.0 carry no such field and keep their published value.

Upgrade impact

  • Kepler-fed energy and carbon drop, by a factor that depends on the zones the host exposes, in the JSON report, the perf_sentinel_energy_kwh and perf_sentinel_carbon_gco2 gauges, /api/energy, query monitor and the periodic disclosures built from archived reports. Figures are not comparable across the upgrade. Part of the drop is DRAM leaving the default scope rather than an error removed. On a host whose hwmon zones carry other names, a Kepler-fed service falls back to the next configured backend or the I/O proxy until zone names one of them.
  • A Redfish BMC whose first PowerControl entry is a subsystem now reports its chassis entry under legacy_power, so its figures move too.
  • Two configuration keys are added, [green.kepler] zone and schema = "sensor" under [green.redfish], and an older binary rejects them at load, so drop them before rolling back.
  • The energy label replaces the model tag in inspect and query monitor, and analyze gains an Energy: line, so a parser of that text output needs updating.
  • The JSON report gains one optional field, green_summary.energy_calibrated, and a calibrated daemon behind a measured or Electricity Maps tag now publishes calibration_applied: true.
  • Breaking, perf-sentinel-core only: KeplerConfig gains the public field zone, GreenSummary the public field energy_calibrated and RedfishSchema the Sensor variant, none of them #[non_exhaustive], so a struct literal or an exhaustive match stops compiling. GreenSummary::energy_source_label, GreenSummary::service_energy_source and prom_parser::parse_metric_samples_where are new.
  • No detector, normalizer or signature changes, so no finding appears, disappears or changes signature and acknowledgments keep matching. No route or metric name changes, 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 provenance attestation (Build Level 2)
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.26.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.

perf-sentinel chart v0.26.0

Choose a tag to compare

@github-actions github-actions released this 04 Oct 19:26
chart-v0.26.0
939b579

What's new in chart-v0.26.0

appVersion moves to 0.26.0 and image.tag follows it to :0.26.0. No template, values.yaml key or schema changes. Under the default values the rendered perf-sentinel.toml is byte-for-byte what chart-v0.25.5 rendered, same 5036 bytes and the same digest. The other manifest changes are the image, the version labels and checksum/config.

What moves inside the pod

The default values enable no energy backend, so a release that keeps them sees no figure move. The changes below reach a release that configures [green.kepler], [green.redfish] or [green.alumet] through config or fragments.

The daemon now reads one Kepler zone instead of summing the overlapping ones, set by the new [green.kepler] zone key, package by default, which also leaves DRAM out unless zone names it. Kepler-fed energy and carbon drop in perf_sentinel_energy_kwh, perf_sentinel_carbon_gco2, /api/energy and the periodic disclosures. On a host where Kepler reads hwmon, zone has to name one of the host's sensor zones, otherwise the zero-sample warning fires and those services fall back to the next configured backend or the I/O proxy. PerfSentinelEnergyScraperStale stays silent then, since each scrape still answers HTTP 200.

Under legacy_power, [green.redfish] reads the PowerControl entry whose PhysicalContext is Chassis, where it read entry 0, which a BMC may give to a CPU or memory subsystem. The new schema = "sensor" reads a chassis power Sensor resource, for firmware that serves neither /Power nor a PowerWatts reading, and examples/helm/values-green-redfish.yaml shows it commented out. At start, the daemon warns once when [green.alumet] metric_name names a GPU, Grace, Jetson or TDP-estimate series, or a unit other than joules.

The perf-sentinel CLI in the image names the energy source in query monitor and query inspect where it printed the window's model tag, and the reports the daemon serves gain the optional green_summary.energy_calibrated field. A calibrated daemon behind a measured or Electricity Maps tag now publishes calibration_applied: true in its periodic disclosures.

The v0.26.0 binary notes describe the changes.

Upgrade impact

  • Pods roll on the image. helm upgrade replaces them because appVersion and image.tag move together.
  • Kepler-fed figures are not comparable across the upgrade, in the energy and carbon gauges, /api/energy and the periodic disclosures. A Grafana panel on perf_sentinel_energy_kwh or perf_sentinel_carbon_gco2 shows a step down.
  • A 0.25.5 daemon rejects [green.kepler] zone and schema = "sensor" at load, so drop them from config or fragments before rolling back.
  • checksum/config moves, and not because your configuration changed. The rendered perf-sentinel.toml is identical, but the ConfigMap carries helm.sh/chart and app.kubernetes.io/version labels that bump with the chart.
  • No values.yaml key is added or removed, no template changes, and the shipped PrometheusRule is untouched.

Install

The chart is published as an OCI artifact on GHCR, install it directly with no helm repo add step:

helm install perf-sentinel oci://ghcr.io/robintra/charts/perf-sentinel --version 0.26.0

Upgrade an existing release:

helm upgrade perf-sentinel oci://ghcr.io/robintra/charts/perf-sentinel --version 0.26.0

Read docs/HELM-DEPLOYMENT.md for the ServiceMonitor section, sizing and Ingress postures.

If you are upgrading from chart-v0.23.0 or earlier, read the chart-v0.24.0 notes first: that release raises the default workload.statefulset.persistence.size to 2Gi, which an existing StatefulSet does not pick up on its own. From chart-v0.18.0 or earlier, read the chart-v0.19.0 notes as well: that release adds a grouping label to five metrics and is breaking for an unaggregated alert on any of them. From chart-v0.16.0 or earlier, the chart-v0.17.0 notes change the shipped PrometheusRule.

Full Changelog: chart-v0.25.5...chart-v0.26.0

v0.25.5

Choose a tag to compare

@github-actions github-actions released this 03 Oct 10:47
v0.25.5
1a53d87

What's new in v0.25.5

v0.25.5 is about perf-sentinel reaching what sits behind a corporate network, and about two cases where it read less than it should. Outbound https:// calls ignored HTTPS_PROXY, verify-hash --url aside, and trusted only the bundled Mozilla roots, so a daemon or a CI job behind a proxy, or facing a TLS-inspecting one, could not reach Electricity Maps, Tempo, Jaeger, Prometheus, the Hub export or a Redfish BMC. jaeger-query --service failed with HTTP 404 against Jaeger 2.21, which removed the search endpoint it called. And on MySQL and MariaDB, a value between double quotes reached the templates, the reports and PerfSentinelHub in clear, because the tokenizer read it as an identifier.

One client, one proxy rule

Every https:// call of the daemon and of the CLI now goes through HTTPS_PROXY, or ALL_PROXY when HTTPS_PROXY is unset, empty or not a usable URL, over an HTTP CONNECT tunnel. NO_PROXY exempts host names, domains with their subdomains, IP addresses, CIDR ranges or *, and loopback only when it is listed. Credentials in the proxy URL become a Basic proxy authorization. Only an http:// proxy URL, or one without a scheme, is used: a SOCKS or https:// proxy URL logs one warning and the call connects directly, so it never receives a plaintext CONNECT or the credentials. http:// calls always connect directly and HTTP_PROXY is ignored, so a cluster-wide proxy does not divert internal endpoints.

The PEM certificates of SSL_CERT_FILE are trusted next to the bundled roots, for a proxy that inspects TLS or an internal PKI. An unreadable file, or one that holds no certificate, logs a warning and leaves the bundled roots alone.

verify-hash --url now downloads through the same client instead of ureq, which leaves the dependency tree. It still accepts only https://, refuses redirects and stops at 10 MiB. ureq read the proxy variables too, with other rules: ALL_PROXY before HTTPS_PROXY, HTTP_PROXY as a last resort, and https:// proxies. verify-hash --url now follows the rules above.

In the simulation lab, an origin serving a certificate from a throwaway CA sat behind a tinyproxy, on a Docker network the host cannot resolve, so a fetch could only succeed through the tunnel. verify-hash --url with HTTPS_PROXY and SSL_CERT_FILE verified the report's content hash, with the CONNECT in the proxy log, and so did ALL_PROXY alone. Without SSL_CERT_FILE it failed on the certificate, and with NO_PROXY naming the origin it connected directly and failed on DNS. It refused the 302 and stopped at the 10 MiB cap. The 0.25.4 binary went through the proxy too but failed on UnknownIssuer. A daemon serving TLS with a certificate from the same CA answered query --daemon https://localhost:<port> status only with SSL_CERT_FILE set.

The v3 search behind Jaeger 2.21

Jaeger 2.21 removed the v1 search, /api/traces?service=, and kept the per-trace read. When the v1 search answers 404, jaeger-query now retries it once through /api/v3/traces, with the same window as RFC 3339 bounds and --max-traces as query.search_depth, and parses the OTLP JSON results through the same path as an OTLP JSON file. A v3 search that matches nothing reports no traces found. A 404 without Jaeger's error body stays an HTTP error, after one extra request. Victoria Traces and Jaeger 2.20 and earlier keep the v1 search, and --trace-id is unchanged.

The lab replays the same Spring Boot capture into Jaeger 2.20.0 and 2.21.0. On 2.21.0, 0.25.4 failed with HTTP 404 on /api/traces, and 0.25.5 logged the v3 retry and found the same two n_plus_one_http findings as through 2.20.0 and the daemon, POST x6 and GET x7, with the same signatures across the six ingestion paths.

Double quotes are strings on MySQL and MariaDB

Both engines read "..." as a string literal in their default mode, where PostgreSQL and the SQL standard read an identifier. The tokenizer took the standard reading for every engine, so WHERE email = "alice@example.com" kept the address in the template. It now masks a double-quoted value like a single-quoted one when the engine is MySQL or MariaDB, taken from db.system.name or db.system, from db.type for dd-trace over OTLP, or from the operation field of native JSON. mysql-stat normalizes as MySQL. Every other engine, and an event that names no engine, keeps "..." as an identifier.

Detection groups by template, so the findings on these queries change too. Queries that split into one template per value now group, and an N+1 that only varied by such a value now shows up. A sanitized statement whose only literal was double-quoted no longer looks sanitized, so a heuristic N+1 on it can disappear. On a lab fixture of six-query loops, 0.25.4 found no N+1 on the MySQL and MariaDB traces and left the address 9 times in the JSON report and 9 times in the SARIF. 0.25.5 found one N+1 on each MySQL and MariaDB trace and no address, and kept SELECT "Name" FROM "Users" WHERE "Id" = ? verbatim on PostgreSQL.

An accurate non-loopback warning

A daemon listening on a non-loopback address warned that its endpoints have no authentication, which the ack and incident routes contradict once their API keys are set. The warning now reads OTLP ingest, /metrics and most read endpoints are never authenticated. Put a reverse proxy or a network policy in front.

Upgrade impact

  • MySQL and MariaDB findings whose SQL held a double-quoted value change template and signature, so their acknowledgments stop matching and PerfSentinelHub files them as new findings. Findings on such queries can appear or disappear, as described above. A MySQL server in ANSI_QUOTES mode loses its double-quoted identifiers to ?.
  • A process that inherits HTTPS_PROXY now uses it for its https:// calls. An https:// endpoint the proxy cannot reach, such as an in-cluster PerfSentinelHub, a Redfish BMC or a daemon queried over https://localhost, belongs in NO_PROXY.
  • verify-hash --url changes proxy rules: HTTPS_PROXY wins over ALL_PROXY, and an environment that only sets HTTP_PROXY, or names an https:// proxy, connects directly. A build without the daemon, tempo or jaeger-query feature can no longer fetch --url, it exits 4 and asks for --report. The released binaries carry all three.
  • The non-loopback listen warning changes wording, so a log rule that matches its old text needs updating.
  • perf-sentinel-core gains API: sentinel_core::http_client::ProxyConnector, fetch_get_limited and sentinel_core::normalize::sql::normalize_sql_for. The connector type of HttpClient and HttpClientWithBody becomes ProxyConnector, which only breaks code that spells the full client type.
  • No configuration key is added or removed, no route, metric name or wire format changes, 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 provenance attestation (Build Level 2)
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.5" \
  --expected-issuer "https://token.actions.githubusercontent.com" \
  --verify-binary ./perf-sentinel-linux-amd64

gh CLI 2.49 or newer required for gh attestation verify.

perf-sentinel chart v0.25.5

Choose a tag to compare

@github-actions github-actions released this 03 Oct 10:59
chart-v0.25.5
1a53d87

What's new in chart-v0.25.5

appVersion moves to 0.25.5 and image.tag follows it to :0.25.5. The NetworkPolicy gains per-port peer lists, under four new values.yaml keys, and stays off by default. Under the default values the rendered perf-sentinel.toml is byte-for-byte what chart-v0.25.4 rendered, same 5036 bytes and the same digest. A values.yaml comment and a configmap-fragments.yaml comment change wording, and neither renders into a manifest.

Per-port NetworkPolicy peers

Port 4317 serves OTLP gRPC only. Port 4318 serves OTLP HTTP, and also /metrics, /health and the query API, which returns findings with their SQL templates and takes ack writes. Until now every peer allowed by the policy reached both ports, so a collector that only sends OTLP gRPC could also read the API.

networkPolicy.ingress.otlpGrpc and networkPolicy.ingress.http take the same fromNamespaceSelectors and fromPodSelectors lists as networkPolicy.ingress, and each renders its own rule on port 4317 or port 4318 alone. Allow-list a gRPC-only collector under otlpGrpc, and Prometheus, PerfSentinelHub or Grafana under http. The lists directly under networkPolicy.ingress still open both ports, and a release that only sets them renders the same policy as chart-v0.25.4. With no selector anywhere the policy still denies all ingress. values.schema.json validates the new keys.

What moves inside the pod

The daemon routes its https:// calls, the PerfSentinelHub export, Electricity Maps, Redfish, the energy scrapers and the cloud energy Prometheus source, through HTTPS_PROXY, or ALL_PROXY, minus NO_PROXY, and trusts the PEM certificates of SSL_CERT_FILE next to its bundled roots. Set them through the existing extraEnv, extraVolumes and extraVolumeMounts, as docs/CONFIGURATION.md shows. http:// calls stay direct, so the in-cluster scrapes are unaffected.

On MySQL and MariaDB the daemon now masks a value between double quotes like one between single quotes, where it used to keep it in the template, in clear. The default configuration listens on 0.0.0.0, so every pod logs the non-loopback listen advisory, and its wording changes: it now reads OTLP ingest, /metrics and most read endpoints are never authenticated. The perf-sentinel CLI in the image falls back to the v3 search when jaeger-query --service meets Jaeger 2.21 or later.

In the simulation lab, the 0.25.5 daemon image detected the 12 anti-patterns of the reference workload, and, run as 65534 on 0.0.0.0, logged the new advisory once where the 0.25.4 image logged the old one.

The v0.25.5 binary notes describe the changes.

Upgrade impact

  • Pods roll on the image. helm upgrade replaces them because appVersion and image.tag move together.
  • MySQL and MariaDB findings whose SQL held a double-quoted value change template and signature, so their acknowledgments stop matching and PerfSentinelHub files them as new findings. Findings on such queries can appear or disappear.
  • A pod that already carries HTTPS_PROXY now uses it, whether it comes from extraEnv, extraEnvFrom or an injecting webhook. An in-cluster https:// endpoint, such as PerfSentinelHub, belongs in NO_PROXY.
  • A log rule that matches the old non-loopback advisory needs updating.
  • checksum/config moves, and not because your configuration changed. The rendered perf-sentinel.toml is identical, but the ConfigMap carries helm.sh/chart and app.kubernetes.io/version labels that bump with the chart.
  • Four values.yaml keys are added, networkPolicy.ingress.otlpGrpc and networkPolicy.ingress.http with their two selector lists each, all empty by default. None is removed, and the shipped PrometheusRule is untouched.

Install

The chart is published as an OCI artifact on GHCR, install it directly with no helm repo add step:

helm install perf-sentinel oci://ghcr.io/robintra/charts/perf-sentinel --version 0.25.5

Upgrade an existing release:

helm upgrade perf-sentinel oci://ghcr.io/robintra/charts/perf-sentinel --version 0.25.5

Read docs/HELM-DEPLOYMENT.md for the ServiceMonitor section, sizing and Ingress postures.

If you are upgrading from chart-v0.23.0 or earlier, read the chart-v0.24.0 notes first: that release raises the default workload.statefulset.persistence.size to 2Gi, which an existing StatefulSet does not pick up on its own. From chart-v0.18.0 or earlier, read the chart-v0.19.0 notes as well: that release adds a grouping label to five metrics and is breaking for an unaggregated alert on any of them. From chart-v0.16.0 or earlier, the chart-v0.17.0 notes change the shipped PrometheusRule.

Full Changelog: chart-v0.25.4...chart-v0.25.5

v0.25.4

Choose a tag to compare

@github-actions github-actions released this 01 Oct 09:01
v0.25.4

What's new in v0.25.4

v0.25.4 is about the code location perf-sentinel prints under a finding. An instrumentation that follows the stable OpenTelemetry code conventions sends code.function.name already qualified, as in com.foo.OrderService.findItems, and perf-sentinel derives the namespace from that name. The text outputs then joined the two back together, so the line read com.foo.OrderService.com.foo.OrderService.findItems. The same line joined every namespace to its function with a dot, which spells a PHP method App\Jobs\PurgeJob.handle, and an empty attribute left a stray separator behind. This is the Source: line of analyze, the Location: line of diff, the location: line of explain and the detail panel of the TUI.

A qualified name prints once

When the function starts with the namespace followed by a separator (., \, : or #), it now prints alone. The endpoint fallback, which names a job or a consumer without HTTP attributes after its code frame, already applied that rule, and both now go through the same function. A legacy code.namespace and code.function pair collapses the same way when its function already carries the namespace, and is joined as before otherwise: a namespace and a function both named handler still print handler.handler.

:: where the language writes it

A namespace that holds \ or :: now joins its function with ::, the separator the endpoint fallback already used. PHP qualifies its namespaces with \ but attaches a method with ::, so the legacy pair App\Jobs\PurgeJob and handle prints App\Jobs\PurgeJob::handle. Rust, C++ and Ruby namespaces join the same way, crate::db::load where it read crate::db.load. Every other namespace still joins with a dot.

An empty attribute is an absent one

An empty code.function, code.namespace or code.filepath no longer prints as an empty part. A namespace sent with a file but no function printed com.foo.OrderService. (OrderService.java:42), an empty namespace .find and an empty file path com.foo.Svc.find (:42). They now print com.foo.OrderService (OrderService.java:42), find and com.foo.Svc.find.

Compared with the 0.25.3 binary on eleven code location shapes, from the stable name to an empty file path, seven printed one of the spellings above and now print the corrected one, and the other four print as they did. perf-sentinel demo changes four of its eighteen Source: lines, diesel::query_builder::OrderService::create_order where it read diesel::query_builder.OrderService::create_order. On the eleven shapes, both versions emit the same findings, signatures, endpoints and SARIF locations.

Upgrade impact

  • The code location of some findings prints differently in the text outputs and the TUI. A script that reads the Source: line of analyze sees the new spelling. The JSON report keeps code_location as the four fields the span sent.
  • Nothing a finding is keyed on moves. Signatures, endpoints and SARIF locations are unchanged, so acknowledgments keep matching and no finding appears or disappears.
  • 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.4" \
  --expected-issuer "https://token.actions.githubusercontent.com" \
  --verify-binary ./perf-sentinel-linux-amd64

gh CLI 2.49 or newer required for gh attestation verify.