feat(grafana): add PostgreSQL Exporter 12485 curated pack - #386
Conversation
Grafana dashboard 12485 was authored against an older postgres_exporter
lineage, so on a modern prometheuscommunity/postgres-exporter v0.15 scrape
several panels resolve to the wrong field, mistype the two `_count` gauges as
counters, and leave the Instance/Database controls empty. Read the real
exporter output on the parity rig and pin only what the engine cannot infer:
- metric_map for four v0.15 renames (pg_database_size -> _bytes,
pg_replication_lag -> _seconds, pg_stat_statements_calls -> _calls_total,
..._total_time_seconds -> _seconds_total).
- metric_kinds forcing pg_stat_activity_count / pg_locks_count /
pg_stat_database_numbackends to gauge so rate() is never applied to a gauge.
- plugin repopulates Instance from label_values(pg_up, instance) (the source
up{job="postgres-exporter"} filter never matches an Elastic scrape), anchors
the bare Database control on pg_stat_database_numbackends, and drops the
Interval interval-variable instead of emitting an inert control.
Extend the shared curated rig to enable the postgres_exporter stat_statements
and postmaster collectors plus the pg_stat_statements extension so Query rate /
Average query runtime / Uptime render on real data.
Live-validated on ES 9.5 + Kibana: 35 panels, 0 Red / 0 not-feasible, uploaded;
render audit 0 render_error (32/32 rendered, correct viz types); both controls
populate and bind (?Instance x57, ?Database x23). 14114 re-validated on the same
rig with no regression (6/6, render PASS). make test green (5921 passed).
Grafana 12485 duplicates blk_read_time on both I/O series and leaves
holes on the 48-col grid; 14114's {{__name__}} legend GROKs to (null)
under native PROMQL. Add section-scoped layout overrides, Read/Write
I/O names, stacked-bar locks, and a per-database connections series so
the uploaded dashboards match Kibana chart conventions without empty
bands.
… lock legend Native PROMQL leftover series names and LAST(delta/delta) N/A on 12485; query_overrides now take section_match so Global vs Database stay distinct.
Issue 382's bargauge case unpacked two STATS lines; lastNotNull now adds a LAST collapse after MAX(inner_val). Keep the grouping invariant.
Resolve the bargauge nested-aggregation test against #387's three-stage TS + LAST collapse so lastNotNull and the 12485 pack can land together.
|
Tracking: closes #391 under https://github.com/elastic/obs-integration-team/issues/1200. Please add |
giorgi-imerlishvili-elastic
left a comment
There was a problem hiding this comment.
I found two correctness issues that should be addressed before merge:
-
observability_migration/adapters/source/grafana/curated_packs/grafana_14114_postgres_exporter_quickstart/pack.yaml:103collapses the source panel'sinstancedimension. The source query is the bare selectorpg_stat_database_numbackends{datname=~\"$db\",instance=~\"$instance\"}, andinstancedefaults to All, so Prometheus returns a distinct series for every(instance, datname). The override groups only bydatnameand takesMAX, meaning two servers that expose the same database become one series and one server's value is discarded. That is a value/cardinality regression, and the newPERFECTclassification is not accurate for multi-instance targets. Preserve both dimensions (for example, build a single syntheticinstance / datnamebreakdown as required by the Lens XY schema), or explicitly choose and document an aggregate with an APPROXIMATE classification. Please add a multi-instance regression test. -
observability_migration/adapters/source/grafana/panels.py:11327-11338evaluates a child layout override'ssection_matchagainst the parent title after parent layout overrides have mutated it. A pack that renamesSection 1toOverviewand scopes a child override withsection_match: Section 1silently skips the child override, even though the docs definesection_matchagainst the Grafana row title and query overrides use that original title. Capture the parent section title before applying its overrides (or otherwise keep the source title for matching) and add a regression test that combines a section rename with a section-scoped child layout override.
The linked #391 criteria for the 12485 pack are otherwise covered: it is registered and auto-resolves, the 35-entry fidelity manifest has no UNKNOWN values, operator-facing docs are updated, the provenance pin verifies, and CI plus the full local suite are green.
MAX-by-datname collapsed two exporters that share a database name, and layout section_match ran against renamed Kibana titles instead of the Grafana row title. Group connections by instance+datname (Lens composites series_group) and match child layout overrides against the source title.
|
Both points are in
|
There was a problem hiding this comment.
🟡 Changes recommended
Section-scoped layout merging and standalone xy_mode handling are incorrect, and the documented final render audit is stale.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 15/15 changed files
- Comments generated: 5
- Review effort level: Balanced
…xy_mode User --rules-file layout entries keyed only on title_match dropped curated same-title panels in other sections, and xy_mode was ignored unless the override also changed kibana_type. Match query-override merge keys (stripped title+section) and apply stacking to the effective chart type.
|
Copilot's five review notes are in 69ead30:
Stacked PRs 388 / 389 / 430 were rebased onto this tip. #430 is mergeable again (it had been sitting on the pre-rewrite 315/6417 SHAs). |
Restrict late chart presentation changes to compatible XY shapes so valid rule packs cannot emit dashboard payloads that fail Kibana schema validation. Surface skipped requests and the 12485 repeated-row fidelity gap instead of hiding operator-visible behavior loss.
giorgi-imerlishvili-elastic
left a comment
There was a problem hiding this comment.
Verdict: fixed, then approved.
What I verified
- The diff now delivers the PR contract: 12485 resolves through the curated registry, its metric kinds/renames, controls, panel-query exceptions, layout, provenance, and 35-entry fidelity manifest are covered; the accompanying 14114 cardinality/layout correction is also present.
- I exercised absent/generic/section-scoped override selection, whitespace-normalized user layering, standalone and incompatible
xy_mode, cross-shape type requests, valid stacked XY output, missing controls, repeated-row loss, and runtime warning/error paths. Existing valid 12485 output stayed byte-identical through the schema-safety fix. - Added tests failed against the unmodified merge-base for the registry/rename/query behavior, while the reproduced pre-fix layout probes emitted schema-invalid dashboard JSON. No panel/widget support matrix or fidelity baseline update was needed.
Fix record
72100af: preserved 14114(instance, datname)cardinality incurated_packs/grafana_14114_postgres_exporter_quickstart/pack.yaml:97-109, and matched child overrides against the source row title before a layout rename inpanels.py:11410-11429; regressions are pinned attests/test_curated_packs.py:2304-2345,3237-3280.69ead30: made override layering use normalized(title_match, section_match)keys inrules.py:413-462, applied standalonexy_mode, and refreshed the final 35-panel/14114 design record; tests cover the merge and stacking boundaries attests/test_curated_packs.py:2659-2778,2927-2984.27f3192: valid rule-pack input could still inject XY presentation fields into incompatible panel shapes and emit Kibana-schema-invalid output. Config validators now constrain type/mode combinations and runtime application skips incompatible requests with explicit panel warnings (extension_schema.py:28,146-177;panels.py:11263-11455,11923). Schema regressions are covered attests/test_curated_packs.py:2987-3158.27f3192: strengthened the core Database-control assertion so a missing/unanchored control cannot pass (tests/test_curated_packs.py:1898-1968).27f3192: disclosed that Grafana's repeated Database row becomes one single-select Kibana section (fidelity_manifest.yaml:140-143;docs/sources/grafana.md:189-196) and pinned both the emitted warning/control and manifest disclosure (tests/test_curated_packs.py:2056-2117).
Gate evidence
- Targeted:
tests/test_curated_packs.py— 164 passed. - Repository:
make test— 6101 passed, 3 skipped;make lint— Ruff passed and source-header checks 3/3;make typecheck— 0 errors in 9 source files. - Generated/uploaded artifact: 0 schema errors; uploaded 35-panel validation reported 0 runtime errors, 0 empty panels, 0 overlaps, and 0 out-of-bounds panels.
- Visual gate: Chrome DevTools against an isolated ES 9.5/Kibana rig, cache-bypassing dashboard view load, affected-panel and nearby-panel snapshots/screenshots, console/network inspection, and Database control interaction. Both global and
rigdb-scoped Locks panels rendered as stacked bars with right-side legends; scoped KPI/series values changed; Lens/ES|QL requests returned 200. The only console failure was the unrelated no-security Kibanauser_activity/refreshendpoint. - Post-push CI: 14/14 checks passed on
27f3192.
Concerns probed and dropped
- Templated
Database: $Databasesection matching did not fail: the documented stable-prefix matcher selected Global vs Database overrides correctly. - The suspected
hide_titleduplicate-title issue did not reproduce because the title rename is applied before duplicate-label clearing. - Initial empty UI panels were a warm-up/time-window/default-
postgresdata gap; selecting activerigdbpopulated the scoped panels and did not reveal a translation defect.
Remaining optional minors: none. Pre-existing base failures attributable to this PR: none.
Summary
prometheuscommunity/postgres-exporterv0.15 scrape maps correctly underprometheus_native(metric_mapfor the four v0.15 renames,metric_kindsso_countgauges are not rated, plugin rewrites Instance/Database populate queries and drops the inert Interval control).stat_statements+postmastercollectors and thepg_stat_statementsextension so Query rate / Average query runtime / Uptime render on real data.(null).Test plan
pytest tests/test_curated_packs.py -k "12485 or 14114 or hide_title or layout_overrides or stacked_bar or section_match"prometheus_native,metrics-postgres.prometheus-default)grafana-validate-uploaded: 12485 35/35 pass, 14114 6/6 pass, 0 empty, 0 overlaps, 0 runtime errorsdatnamenot(null); QPS matches Rows heightmake test/make linton this PRFixes #391