v0.5.11
What's new in v0.5.11
Electricity Maps published their v4 API, with v3 still served but considered legacy. v0.5.10 was hard-coded against /v3. The v4 doc reference also documents two query parameters that perf-sentinel was not surfacing as TOML knobs: emissionFactorType (lifecycle vs direct) and temporalGranularity (hourly, 5-minute, 15-minute). v0.5.11 closes the version migration and exposes the two knobs.
The default endpoint flips to https://api.electricitymaps.com/v4. The response schema on carbon-intensity/latest is byte-identical between v3 and v4 (verified manually with the sandbox token and locked in by a new wire-parity test), so the migration is transparent for downstream consumers. green_summary.regions[] rows look exactly the same regardless of which API path the daemon was configured against. Existing configs that pin endpoint = "https://api.electricitymaps.com/v3" keep loading and continue to work, but the daemon now logs a tracing::warn! once at startup pointing the operator at the v4 migration. The endpoint string is sanitized through sanitize_for_terminal before logging, mirroring the 0.5.10 pattern on intensity_estimation_method, so a hostile TOML cannot inject ANSI escape sequences into the log stream. Trailing slashes on the configured endpoint are also normalized at config load, so a copy-paste like endpoint = "https://api.electricitymaps.com/v4/" no longer produces a double-slash URL.
The two new TOML knobs map to the v4 query parameters one-to-one:
[green.electricity_maps] emission_factor_type = "lifecycle" | "direct"defaults tolifecycle, matching the API default. Setdirectwhen the GHG Protocol Scope 2 Guidance applies and only the combustion-phase boundary is reportable.[green.electricity_maps] temporal_granularity = "hourly" | "5_minutes" | "15_minutes"defaults tohourly. Sub-hour values require a paidElectricity Mapsplan that exposes them, otherwise the API silently coarsens to hourly.
Both knobs use a fail-graceful parser: an unknown value (typo or unsupported) emits a tracing::warn! and falls back to the default. The unknown value is sanitized through sanitize_for_terminal before logging. The query parameters are only appended to the request URL when they differ from the API defaults, so users who do not opt into the knobs see byte-identical wire traffic relative to v0.5.10. The green_summary JSON, the GreenOps aggregates, the dashboard rendering, and the terminal output are all unchanged for default settings.
A small internal refactor splits the inline format! URL construction into a pure helper build_request_url(endpoint, zone, eft, tg) -> String that is unit-tested with all four combinations (default-only, each knob individually, both together). The EmissionFactorType and TemporalGranularity enums are public and re-exported from score::electricity_maps, with from_config(Option<&str>) -> Self (case-insensitive, graceful fallback) and as_query_value(self) -> &'static str (URL serialization).
Added
[green.electricity_maps] emission_factor_typeTOML knob. Maps to theemissionFactorTypev4 query parameter. Accepted:"lifecycle"(default) and"direct". Documented indocs/CONFIGURATION.md(EN+FR).[green.electricity_maps] temporal_granularityTOML knob. Maps to thetemporalGranularityv4 query parameter. Accepted:"hourly"(default),"5_minutes","15_minutes". Same docs.- Public
EmissionFactorTypeandTemporalGranularityenums incrates/sentinel-core/src/score/electricity_maps/config.rs, re-exported from the parent module. Both exposefrom_config(case-insensitive parsing with graceful fallback to default + sanitized warn on unknown) andas_query_value(URL serialization). - Public constant
DEFAULT_ELECTRICITY_MAPS_ENDPOINTin the same module, single source of truth used by both the production fallback inconvert_electricity_maps_section_with_envand the test fixtures. - Pure helper
build_request_urlincrates/sentinel-core/src/score/electricity_maps/scraper.rs. Composes the full request URL from endpoint, zone, and the two knobs. Query params only appended when non-default, so the wire stays byte-identical to pre-0.5.11 for users who do not opt into the knobs.
Changed
- Default
Electricity Mapsendpoint flipped from v3 to v4. New configs (no explicitendpoint) targethttps://api.electricitymaps.com/v4. api_endpointis normalized at config load: trailing slashes are trimmed, soendpoint = "https://api.electricitymaps.com/v4/"no longer produces a double-slash URL when the scraper appends/carbon-intensity/latest?zone=....- BREAKING (
perf-sentinel-core, pre-1.0 so minor-bump allowed):ElectricityMapsConfiggains two new public fieldsemission_factor_type: EmissionFactorTypeandtemporal_granularity: TemporalGranularity. External consumers constructing the struct directly (no in-tree consumers do today) must add the two fields.
Deprecated
Electricity MapsAPI v3 endpoint. Configs that pinendpoint = "https://api.electricitymaps.com/v3"keep loading and continue to work, but the daemon logs atracing::warn!once at startup pointing the operator at the v4 migration. To silence the warning, switch to the v4 endpoint. To deliberately stay on v3 (for example to A/B-validate against v4), keep the v3 URL and acknowledge the warning.Electricity Mapshas not announced a v3 retirement date, this is forward-defense.
Security
- Endpoint string sanitized at log time. The deprecation warn passes the endpoint through
sanitize_for_terminalbefore logging, so a hostile TOML config carrying ANSI / OSC 8 / control bytes in the URL cannot inject terminal escape sequences into the daemon's log stream. Mirrors the 0.5.10 fix onintensity_estimation_method. - Unknown knob values sanitized at log time. Same hardening applied to the fail-graceful warn path on
emission_factor_typeandtemporal_granularity. A typo liketemporal_granularity = "WIPE\x1b[2J"is logged with control bytes replaced by?.
Behavior
- Wire format unchanged for users not opting into the new knobs. URL shape is byte-identical to pre-0.5.11.
green_summary.regions[]rows are byte-identical between v3 and v4 (locked in by a newfetch_intensity_v3_and_v4_responses_parse_identicallyregression test).- GreenOps aggregates unchanged.
avoidable_io_ops, IIS, waste ratio,top_offendersare not touched. The new knobs change which carbon intensity value the API returns, but perf-sentinel treats the value as opaque and propagates it without branching. - Existing scraper integration tests (mock HTTP servers on
127.0.0.1:NNNNwithout/vNsuffix) are unaffected by the new deprecation detection. - Backward compatible with pre-0.5.11 JSON reports: the new fields are absent from older reports, the dashboard and terminal handle absence cleanly.
Install
Prebuilt binaries (Linux amd64 / arm64, macOS arm64, Windows amd64):
curl -LO https://github.com/robintra/perf-sentinel/releases/download/v0.5.11/perf-sentinel-linux-amd64
chmod +x perf-sentinel-linux-amd64
sudo mv perf-sentinel-linux-amd64 /usr/local/bin/perf-sentinelLinux binaries are statically linked against musl and run on any distribution (Alpine, Debian, RHEL, Ubuntu any version) regardless of glibc version, and inside FROM scratch images.
From crates.io:
cargo install perf-sentinelDocker:
docker run --rm -p 4317:4317 -p 4318:4318 \
ghcr.io/robintra/perf-sentinel:0.5.11 watch --listen-address 0.0.0.0Also available on Docker Hub: robintrassard/perf-sentinel:0.5.11.
Helm (chart 0.2.14 ships 0.5.11 as its appVersion default):
helm install perf-sentinel oci://ghcr.io/robintra/charts/perf-sentinel \
--version 0.2.14 \
--namespace observability --create-namespaceVerify the binary against SHA256SUMS.txt:
curl -LO https://github.com/robintra/perf-sentinel/releases/download/v0.5.11/SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt --ignore-missingFull diff: v0.5.10...v0.5.11