feat(observability): add the operator overview and conditions browser dashboards - #190
Conversation
…series protection (#186) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JPihvXVfS997iGmGabTGsd
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JPihvXVfS997iGmGabTGsd
…eal metrics (#186) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JPihvXVfS997iGmGabTGsd
There was a problem hiding this comment.
🟡 Changes recommended
Dashboard filters and aggregation keys currently produce incorrect results for namespaces and same-named resources.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds Grafana dashboards for end-to-end operator health and CRD condition exploration.
Changes:
- Adds operator overview and conditions-browser dashboards.
- Adds template and metric-reference validation.
- Runs dashboard linting in CI.
File summaries
| File | Description |
|---|---|
.github/workflows/test.yml |
Runs dashboard linting in CI. |
Makefile |
Adds lint-dashboards. |
observability/dashboards/crd_conditions_browser.tpl.json |
Adds the condition browser. |
observability/dashboards/ocf_operator.tpl.json |
Adds the operator overview. |
observability/observability_test.go |
Validates dashboard and alert templates. |
Review details
Suppressed comments (3)
observability/dashboards/crd_conditions_browser.tpl.json:389
- The False count ignores the dashboard's namespace selection, so after choosing one namespace it still reports False conditions from all namespaces. Add the namespace matcher to both sides of the stale-series filter.
"expr": "count(\n {{operator_namespace}}controller_condition{kind=\"$kind\", condition=~\"$condition\", status=\"False\", reason=~\"$reason\", id=~\"$resource_id\"} > 0\n and\n topk by (id, condition) (1, {{operator_namespace}}controller_condition{kind=\"$kind\", condition=~\"$condition\"})\n)",
observability/dashboards/crd_conditions_browser.tpl.json:458
- The Unknown count ignores
$namespace, so its value does not correspond to the namespace-filtered table. Apply the namespace matcher to both selectors.
"expr": "count(\n {{operator_namespace}}controller_condition{kind=\"$kind\", condition=~\"$condition\", status=\"Unknown\", reason=~\"$reason\", id=~\"$resource_id\"} > 0\n and\n topk by (id, condition) (1, {{operator_namespace}}controller_condition{kind=\"$kind\", condition=~\"$condition\"})\n)",
observability/dashboards/crd_conditions_browser.tpl.json:531
- The True count ignores
$namespace, so selecting a namespace leaves this panel showing the global count. Apply the namespace matcher to both selectors.
"expr": "count(\n {{operator_namespace}}controller_condition{kind=\"$kind\", condition=~\"$condition\", status=\"True\", reason=~\"$reason\", id=~\"$resource_id\"} > 0\n and\n topk by (id, condition) (1, {{operator_namespace}}controller_condition{kind=\"$kind\", condition=~\"$condition\"})\n)",
- Files reviewed: 4/5 changed files
- Comments generated: 7
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ent (#186) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JPihvXVfS997iGmGabTGsd
|
Review round 1, disposition of the suppressed findings:
|
…#186) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JPihvXVfS997iGmGabTGsd
There was a problem hiding this comment.
🟡 Changes recommended
The browser’s default “All” filters exclude cluster-scoped resources and create owner-count-dependent query sizes.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
observability/dashboards/crd_conditions_browser.tpl.json:713
- With no custom All value, Grafana expands “All” into every resource ID returned by this high-cardinality variable. The default dashboard therefore sends a regex whose size grows with the owner count on every panel query, which can become very large for production operators. Use the Prometheus wildcard as the All value so these queries stay constant-size.
"includeAll": true,
- Files reviewed: 4/5 changed files
- Comments generated: 1
- Review effort level: Balanced
…ers and instant stats (#186) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JPihvXVfS997iGmGabTGsd
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JPihvXVfS997iGmGabTGsd
|
Review round 2, disposition of the suppressed finding:
|
… world (#186) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JPihvXVfS997iGmGabTGsd
There was a problem hiding this comment.
🟡 Changes recommended
The lint permits critical severity despite the stated warning-only contract, and one dashboard description has a grammatical error.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
observability/dashboards/crd_conditions_browser.tpl.json:413
- Use “an” before “Unknown” in this user-visible panel description.
"description": "Number of $kind $condition Conditions in a Unknown state",
- Files reviewed: 8/9 changed files
- Comments generated: 1
- Review effort level: Balanced
…er (#186) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JPihvXVfS997iGmGabTGsd
|
Review round 3, disposition of the suppressed finding:
|
Towards #186
Summary
Adds the two Grafana dashboard templates and the Go template test.
observability/dashboards/crd_conditions_browser.tpl.json(uidcrd_conditions_browser) ports the upstream go-crd-condition-metrics browser and improves it:{{namespace_label}}templated everywhere upstream hardcodesexported_namespace(namespace variable, table query, organize transformation keys).<selector> and topk by (id, condition) (1, <selector without status/reason matchers>), so a former-leader pod still exporting old series cannot show a stale status. The topk keys on(id, condition)rather than(id)alone because the browser's queries can span multiple condition types (condition=All); keying onidalone would collapse each owner to its single most recently transitioned condition.resource_idvariable is made visible (label Resource) and wired asid=~"$resource_id"into the table and all four count stats, so the alerts'dashboard_urldeep links (var-kind,var-condition,var-status,var-resource_id) actually narrow the view to the one owner.dateTimeFromNow) in the table, column order Namespace, Name, Condition, Status, Reason, Since.refresh: "", tags["ocf"], and the${datasource}variable on every panel, target and the ad hoc filter (upstream had one panel pinned to a private datasource uid).observability/dashboards/ocf_operator.tpl.json(uidocf_operator) is the operator overview: variablesdatasource,job,controller(multi, All), rowsobservability/observability_test.gorenders every dashboard and alert template the way the Makefile does and checks: valid JSON/YAML, no placeholder left behind, uid matches the file name,refreshoff, every alert carries exactly oneseveritylabel with valuewarningorcriticaland no routing labels, every referenced metric name exists (framework collectors and the condition gauge are interrogated viaprometheus.Collector.Describe, the controller-runtime/workqueue/client-go/process families are a fixed list), and every alert has a promtool unit test. A newmake lint-dashboardstarget runs it and the CI observability job gained aLint dashboardsstep.Verification
make all,make test-alerts,make lint-dashboardsandgolangci-lint run ./...(pinned 2.12.2) all pass:Visual verification against the dev stack (
make observability-up, simulator running for several minutes, checks through Grafana's HTTP API and Prometheus):GET /api/searchlists both dashboards provisioned in folder OCF;GET /api/dashboards/uid/ocf_operatorreturns the model;POST /api/ds/queryexecutes panel queries through the provisioned datasource.$job=demo-operator,$controller=.*,$kind=Database, remaining variables.*,$__rate_interval=1m: all 37 overview targets and all 5 browser targets returned at least one series, and every query variable resolved values (job 1, controller 2, kind 2, condition 3, status 3, reason 3, namespace 2, resource_id 5).shop/orders-db8h old,analytics/reports-db), Unknown 1 (shop/users-db); the not-Ready table shows exactly those three rows with reasons Failing/Unknown and a Since in the past; the updated-rate table haswebapp/server/configmapon top at 4/s; the workers query showsdatabasesaturated at 2 of 2; selectingcontroller=webappempties the not-Ready table and the workqueue backlog whiledatabaseshows a depth of 40.id=~"shop/orders-db"narrows the table to that one owner, so the alert deep links work.🤖 Generated with Claude Code
https://claude.ai/code/session_01JPihvXVfS997iGmGabTGsd