Skip to content

feat(grafana): add PostgreSQL Exporter 12485 curated pack - #386

Merged
giorgi-imerlishvili-elastic merged 8 commits into
mainfrom
feat/grafana-12485-postgresql-exporter-pack
Sep 1, 2026
Merged

feat(grafana): add PostgreSQL Exporter 12485 curated pack#386
giorgi-imerlishvili-elastic merged 8 commits into
mainfrom
feat/grafana-12485-postgresql-exporter-pack

Conversation

@shmsr

@shmsr shmsr commented Aug 31, 2026

Copy link
Copy Markdown
Member

Summary

  • Add a curated pack for Grafana dashboard 12485 (PostgreSQL Exporter) so a modern prometheuscommunity/postgres-exporter v0.15 scrape maps correctly under prometheus_native (metric_map for the four v0.15 renames, metric_kinds so _count gauges are not rated, plugin rewrites Instance/Database populate queries and drops the inert Interval control).
  • Extend the shared curated rig with stat_statements + postmaster collectors and the pg_stat_statements extension so Query rate / Average query runtime / Uptime render on real data.
  • Polish both 12485 and 14114 for Kibana: section-scoped layouts with no 48-col holes, Read/Write I/O legends, visible gauge chrome titles, stacked-bar composition charts, and a per-database connections series that no longer legends as (null).

Test plan

  • Offline: pytest tests/test_curated_packs.py -k "12485 or 14114 or hide_title or layout_overrides or stacked_bar or section_match"
  • Live migrate + upload 12485 and 14114 (prometheus_native, metrics-postgres.prometheus-default)
  • grafana-validate-uploaded: 12485 35/35 pass, 14114 6/6 pass, 0 empty, 0 overlaps, 0 runtime errors
  • Kibana view-mode check: I/O legends Read/Write; gauge titles visible; KPI row fills 48 cols; Database section expanded and hole-free; 14114 connections legend is datname not (null); QPS matches Rows height
  • CI make test / make lint on this PR

Fixes #391

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.
@shmsr
shmsr requested a balanced review from Copilot August 31, 2026 20:39
@shmsr shmsr self-assigned this Aug 31, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.
@shmsr shmsr added program:curated-packs Curated Grafana community dashboard packs source:grafana Grafana source migration asset:dashboards Dashboard migration assets labels Sep 1, 2026
@shmsr

shmsr commented Sep 1, 2026

Copy link
Copy Markdown
Member Author

Tracking: closes #391 under https://github.com/elastic/obs-integration-team/issues/1200. Please add Fixes #391 to the PR body.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found two correctness issues that should be addressed before merge:

  1. observability_migration/adapters/source/grafana/curated_packs/grafana_14114_postgres_exporter_quickstart/pack.yaml:103 collapses the source panel's instance dimension. The source query is the bare selector pg_stat_database_numbackends{datname=~\"$db\",instance=~\"$instance\"}, and instance defaults to All, so Prometheus returns a distinct series for every (instance, datname). The override groups only by datname and takes MAX, 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 new PERFECT classification is not accurate for multi-instance targets. Preserve both dimensions (for example, build a single synthetic instance / datname breakdown 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.

  2. observability_migration/adapters/source/grafana/panels.py:11327-11338 evaluates a child layout override's section_match against the parent title after parent layout overrides have mutated it. A pack that renames Section 1 to Overview and scopes a child override with section_match: Section 1 silently skips the child override, even though the docs define section_match against 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.
@shmsr

shmsr commented Sep 1, 2026

Copy link
Copy Markdown
Member Author

Both points are in 72100af:

  1. 14114 Number of active connections now groups by instance and datname. _native_esql_panel_spec composites those into series_group (instance / datname) so Lens XY keeps one series per source identity. test_14114_numbackends_override_drops_name_breakdown asserts both labels in the STATS BY and breakdown.field == series_group. Fidelity stays PERFECT.

  2. _apply_panel_layout_overrides_recursively captures the parent title before applying layout overrides and passes that Grafana row title to children. test_panel_layout_overrides_section_match_uses_source_title_after_rename covers a section rename plus a child override scoped to the original title.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

Comment thread observability_migration/adapters/source/grafana/rules.py Outdated
Comment thread observability_migration/adapters/source/grafana/rules.py Outdated
Comment thread observability_migration/adapters/source/grafana/panels.py Outdated
Comment thread docs/design/curated-pack-12485-postgresql-exporter.md Outdated
Comment thread docs/design/curated-pack-12485-postgresql-exporter.md Outdated
…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.
@shmsr

shmsr commented Sep 1, 2026

Copy link
Copy Markdown
Member Author

Copilot's five review notes are in 69ead30:

  • layout merge uses the same stripped (title_match, section_match) key as query overrides
  • standalone xy_mode applies to the effective esql.type
  • 12485 design doc no longer claims a collapsed Database row / 32-element audit, or that 14114 needed no pack changes

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 in curated_packs/grafana_14114_postgres_exporter_quickstart/pack.yaml:97-109, and matched child overrides against the source row title before a layout rename in panels.py:11410-11429; regressions are pinned at tests/test_curated_packs.py:2304-2345,3237-3280.
  • 69ead30: made override layering use normalized (title_match, section_match) keys in rules.py:413-462, applied standalone xy_mode, and refreshed the final 35-panel/14114 design record; tests cover the merge and stacking boundaries at tests/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 at tests/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 Kibana user_activity/refresh endpoint.
  • Post-push CI: 14/14 checks passed on 27f3192.

Concerns probed and dropped

  • Templated Database: $Database section matching did not fail: the documented stable-prefix matcher selected Global vs Database overrides correctly.
  • The suspected hide_title duplicate-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-postgres data gap; selecting active rigdb populated the scoped panels and did not reveal a translation defect.

Remaining optional minors: none. Pre-existing base failures attributable to this PR: none.

@giorgi-imerlishvili-elastic
giorgi-imerlishvili-elastic merged commit b2dbf78 into main Sep 1, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

asset:dashboards Dashboard migration assets program:curated-packs Curated Grafana community dashboard packs source:grafana Grafana source migration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[pack] Grafana 12485 PostgreSQL Exporter

3 participants