v0.5.28
What's new in v0.5.28
v0.5.28 lands one breaking signature change and three substantive payloads. The finding signature prefix moves from 16 hex chars (~64 bits) to 32 hex chars (~128 bits), so existing TOML acks no longer match findings and daemon JSONL store entries with the legacy width are dropped on replay with a startup WARN. Re-acknowledgement is required after upgrade. The embedded SPECpower table grows from 187 to 318 entries to cover the modern cloud lineup that was missing from the frozen Cloud Carbon Footprint snapshot. SpanEvent and NormalizedEvent migrate seven repeated string fields from String to Arc<str> so the daemon stops re-allocating identical service names and code locations across spans of the same Resource block. The TUI gains a Correlations to Detail jump and a small drill-down refactor. CLI and tests pick up the SonarCloud cleanup pass that was left over from the 0.5.27 release.
Breaking: signature width bumped to 32 hex chars
Finding::signature and the daemon ack store key are derived from the first hex prefix of a SHA-256 digest. The prefix moves from 16 hex chars (~64 bits) to 32 hex chars (~128 bits) for collision resistance. The change is breaking by construction. Pre-existing .perf-sentinel-acknowledgments.toml entries no longer match any finding produced by 0.5.28, so analyze and the daemon both treat them as orphan and skip them. The daemon JSONL ack store similarly drops legacy-width entries during the startup replay, emitting a single WARN with the dropped count and the new width. Operators must re-issue acknowledgements after upgrade. An ack-stability invariant locked by 11 tests (4 ack-store, 3 OTLP, 2 Jaeger, 2 Zipkin) prevents future drift in the canonical signature input shape.
SPECpower table refresh 2024-2026
crates/sentinel-core/src/score/cloud_energy/table.rs jumps from 187 to 318 entries. The previous baseline plateaued at Ice Lake and EPYC 3rd Gen because Cloud Carbon Footprint coefficients are frozen at 2023-05-01. v0.5.28 pulls SPECpower_ssj 2008 quarterly results from 2024 Q1 to 2026 Q2 directly, aggregates avg_watts / total_threads per architecture, and maps the coefficients to AWS, GCP, and Azure instance families. New coverage:
- AWS: m7i, c7i, r7i (Sapphire Rapids), m7a, c7a (Genoa), m6a, c6a (Milan), m7g, c7g (Graviton 3), m8g, c8g (Graviton 4).
- GCP: c3, c3d, c4, c4d, n2d, t2a.
- Azure: Standard_Dv6, Standard_Dadsv6, Standard_Dpsv6 (Cobalt 100), Standard_Ev6.
- Bare metal:
xeon-6780e(Sierra Forest, 1-chip system level, assumes full chip ownership, sanity-tested at a 50 W idle floor).
Graviton 3 and 4 are estimated as a documented mix of an Ampere Altra floor (Neoverse N1, SPECpower direct) and a Sapphire Rapids minus 25% upper bound (AWS public claim). Cobalt 100 uses the midpoint blend of N1 and V1 (0.60 / 2.20 W per vCPU) per a re-audit. AWS PROVIDER_DEFAULTS deliberately stays on m5.large (2.0 / 20.0) to preserve the waste signal. Bumping to m7i would silently drop reported energy ~3x because the legacy AWS entries are baseboard-inclusive while the new entries use per-vCPU coefficients. Azure default bumps to Standard_D2s_v6 (1.1 / 6.4) since v4 to v6 are methodologically homogeneous. The methodology shift between vintages is documented in docs/LIMITATIONS.md "Two data vintages, two methodologies" along with explicit +/-40% uncertainty bounds for Genoa (n=1), Graviton 3/4, Cobalt 100, and Ampere Altra.
Memory: Arc on repeated SpanEvent fields
Seven SpanEvent and NormalizedEvent fields with high cross-event repetition migrate from String to Arc<str>: service, cloud_region, code_function, code_filepath, code_namespace, instrumentation_scopes, plus template on NormalizedEvent. Per-event-unique fields (timestamp, trace_id, span_id, operation, target, params) stay String. The serde feature rc is activated so the wire format stays identical, deserialization routes through Arc::from(String) with no intermediate DTO. The OTLP ingest path now builds the per-Resource service_name and cloud_region Arcs once per resource_spans block and the Jaeger ingest builds a per-trace process_id to Arc<str> map, so each span clones the shared buffer rather than re-allocating. For 10K spans sharing one service in a single Resource block, this collapses 10K allocations to one. The Zipkin path keeps a per-span allocation since local_endpoint.service_name is per-span by protocol shape. The calibration loop in crates/sentinel-core/src/calibrate.rs switches ops_per_service to HashMap<Arc<str>, u64> so the per-event accumulator is one Arc::clone instead of one String::clone. Finding, FindingResponse, Report, the SARIF and HTML outputs, and the TOML ack store all keep String on purpose, the JSON wire format is byte-for-byte identical to 0.5.27 for already-clean inputs.
TUI: Correlations to Detail jump
perf-sentinel query inspect now binds Enter on a Correlations panel row to a Detail jump targeting the correlation's sample_trace_id. Three silent no-op cases are intentional: the row has no sample_trace_id, the trace is no longer in the active set, or no row is selected. A separate small refactor extracts the existing drill-down into an enter_detail helper so the new Correlations binding and the legacy Findings drill-down share a single code path. No public TUI API change, no key remapping for the existing keys.
Quality: SonarCloud findings
The 0.5.27 SonarCloud run flagged two cognitive complexity violations on crates/sentinel-core/src/daemon/ack.rs::AckStore::new (17/15) and replay_and_compact (16/15), plus eight Playwright tests in crates/sentinel-cli/tests/browser/demo/{stills,tour}.spec.ts without explicit assertions. v0.5.28 extracts two helpers (tighten_parent_dir_perms, apply_replay_entry) that drop both functions below the 15 threshold (~8 and ~7 respectively), and adds an expectScreenshotWritten helper plus a final cheatsheet visibility assertion so each Playwright test now has at least one explicit expect() call.
Added
- Embedded SPECpower entries for 2024-2026 cloud architectures (131 new rows in
crates/sentinel-core/src/score/cloud_energy/table.rs). AWS m7i, c7i, r7i, m7a, c7a, m6a, c6a, m7g, c7g, m8g, c8g. GCP c3, c3d, c4, c4d, n2d, t2a. Azure Standard_Dv6, Standard_Dadsv6, Standard_Dpsv6 (Cobalt 100), Standard_Ev6. Bare metalxeon-6780e(Sierra Forest). - TUI
Enteron Correlations panel jumps to Detail forsample_trace_id. Three silent no-op cases are intentional. enter_detailhelper in the TUI router so Correlations and Findings drill-downs share one code path.docs/LIMITATIONS.mdsections on the two data vintages, Graviton/Cobalt 100 estimated bounds, the Genoa n=1 caveat, and the legacy memory-optimized gap. FR mirror updated.
Changed
- Finding signature prefix width: 16 hex chars to 32 hex chars (~64 to ~128 bits collision resistance). Breaking. Existing TOML acks no longer match findings, daemon JSONL store legacy entries are dropped on replay with a startup
WARNcarrying the dropped count and the new width. Re-acknowledgement required. SpanEventandNormalizedEventrepeated string fields are nowArc<str>:service,cloud_region,code_function,code_filepath,code_namespace,instrumentation_scopes,template.serdefeaturercis activated, the JSON wire format is unchanged.- OTLP ingest hoists
service_nameandcloud_regionArc<str>to the resource_spans level, each span Arc-clones the shared buffer. Jaeger ingest builds theprocess_idtoArc<str>map once per trace. - Calibration
ops_per_serviceis nowHashMap<Arc<str>, u64>keyed viaArc::clone(&event.service). - AWS
PROVIDER_DEFAULTSstays onm5.large(2.0 / 20.0) to preserve the waste signal across the methodology shift between legacy CCF and modern SPECpower entries. - Azure
PROVIDER_DEFAULTSbumps toStandard_D2s_v6(1.1 / 6.4). v4 to v6 are methodologically homogeneous on Azure.
Performance
- OTLP and Jaeger ingest collapse N per-span service allocations to one per Resource or trace via
Arc::cloneof a hoistedArc<str>. For 10K spans sharing one service in one Resource block, this is 1 allocation instead of 10K. collect_instrumentation_scopesreturnsVec<Arc<str>>directly instead of building aVec<String>and converting at the boundary, removing one intermediate Vec alloc per span with scopes.- Calibration loop saves one
String::cloneper event by keyingops_per_serviceonArc<str>and routing the join withEnergyReading::servicethroughHashMap::get(s.as_str())(Borrow<str>).
Internal
- Two helpers extracted from
crates/sentinel-core/src/daemon/ack.rs:tighten_parent_dir_perms(Unix0700chmod, no-op on non-Unix) andapply_replay_entry(Ack/Unack match plus active-set cap), dropping cognitive complexity below the SonarCloud threshold (17 to ~8 onAckStore::new, 16 to ~7 onreplay_and_compact). expectScreenshotWrittenhelper incrates/sentinel-cli/tests/browser/demo/stills.spec.tsasserts each captured PNG is at least 1 KiB, satisfying SonarCloudtypescript:S2699.- Final cheatsheet visibility assertion in
crates/sentinel-cli/tests/browser/demo/tour.spec.tsso the tour test has at least one explicitexpect()call. - Eleven tests lock the canonical signature input shape (4 ack-store, 3 OTLP, 2 Jaeger, 2 Zipkin) so a future field rename or sanitization tweak that would change the digest input is caught at the test boundary.
Notes
- Re-acknowledgement is required after upgrade. Pre-existing TOML acks and daemon JSONL store entries with the legacy 16-hex width no longer match findings produced by 0.5.28. Plan a one-shot re-ack pass for environments that rely on persistent acknowledgements.
- JSON wire format is unchanged.
Finding,Report,FindingResponse, the SARIF output, the HTML report, and the TOML ack store all keepString. Tests that pin the JSON output remain green byte-for-byte, the only field that visibly changes for users issignaturewidth. - No public Rust surface change beyond
SpanEventandNormalizedEventfield types. Crates depending onperf-sentinel-core0.5.27 that readevent.serviceneed to switch from&Stringpatterns to&str(e.g. viaevent.service.as_ref()). The serde featurercis now activated unconditionally onperf-sentinel-core. - Behavior is preserved bit-for-bit for already-clean inputs beyond the documented breaking change on signature width and the documented methodology shift on the new SPECpower entries. Cloud energy values for instances already in the table at 0.5.27 are unchanged.
Install
Pre-built static binaries are attached to this release for linux-amd64, linux-arm64, macos-arm64, and windows-amd64. Verify the SHA256 from SHA256SUMS.txt before extracting. Crate consumers can cargo install perf-sentinel --version 0.5.28 once the workflow finishes propagating.
Plan the re-ack pass before rolling the daemon upgrade in environments that rely on persistent acknowledgements.
Full Changelog: v0.5.27...v0.5.28