From bcb2d50c2f0cc8bb206546d3b2fcd123ee6f2b40 Mon Sep 17 00:00:00 2001 From: Steve Simpson Date: Tue, 16 Nov 2021 19:56:28 +0100 Subject: [PATCH] Support Grafana 'timeseries' panel type for `analyse grafana`. (#224) * Support Grafana 'timeseries' panel type for `analyse grafana`. This adds a workaround to parse the 'targets' out of a panel with type 'timeseries', not currently supported upstream. It also improves the error handling when an unknown panel type is encountered. When the functionality is merged upstream, this change can be removed. * Review comments. --- CHANGELOG.md | 1 + pkg/analyse/grafana.go | 42 +++++++- pkg/commands/analyse_grafana_test.go | 15 +++ pkg/commands/testdata/timeseries.json | 147 ++++++++++++++++++++++++++ 4 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 pkg/commands/testdata/timeseries.json diff --git a/CHANGELOG.md b/CHANGELOG.md index df5e7abf1..e10a13553 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Order should be `CHANGE`, `FEATURE`, `ENHANCEMENT`, and `BUGFIX` ## unreleased * [ENHANCEMENT] Benchtool: add `-bench.write.proxy-url` argument for configuring the Prometheus remote-write client with a HTTP proxy URL. #223 +* [ENHANCEMENT] Analyse: support Grafana 'timeseries' panel type for `cortextool analyse grafana` command. #224 ## v0.10.6 diff --git a/pkg/analyse/grafana.go b/pkg/analyse/grafana.go index b86048f1c..74b53a182 100644 --- a/pkg/analyse/grafana.go +++ b/pkg/analyse/grafana.go @@ -1,6 +1,7 @@ package analyse import ( + "encoding/json" "fmt" "regexp" "sort" @@ -114,14 +115,49 @@ func metricsFromTemplating(templating sdk.Templating, metrics map[string]struct{ return parseErrors } +// Workaround to support Grafana "timeseries" panel. This should +// be implemented in grafana/tools-sdk, and removed from here. +func getCustomPanelTargets(panel sdk.Panel) *[]sdk.Target { + if panel.CommonPanel.Type != "timeseries" { + return nil + } + + // Heavy handed approach to re-marshal the panel and parse it again + // so that we can extract the 'targets' field in the right format. + + bytes, err := json.Marshal(panel.CustomPanel) + if err != nil { + log.Debugln("msg", "panel re-marshalling error", "err", err) + return nil + } + + type panelType struct { + Targets []sdk.Target `json:"targets,omitempty"` + } + + var parsedPanel panelType + err = json.Unmarshal(bytes, &parsedPanel) + if err != nil { + log.Debugln("msg", "panel parsing error", "err", err) + return nil + } + + return &parsedPanel.Targets +} + func metricsFromPanel(panel sdk.Panel, metrics map[string]struct{}) []error { var parseErrors []error - if panel.GetTargets() == nil { - return parseErrors + targets := panel.GetTargets() + if targets == nil { + targets = getCustomPanelTargets(panel) + if targets == nil { + parseErrors = append(parseErrors, fmt.Errorf("unsupported panel type: %q", panel.CommonPanel.Type)) + return parseErrors + } } - for _, target := range *panel.GetTargets() { + for _, target := range *targets { // Prometheus has this set. if target.Expr == "" { continue diff --git a/pkg/commands/analyse_grafana_test.go b/pkg/commands/analyse_grafana_test.go index 76184318f..4a82ffc23 100644 --- a/pkg/commands/analyse_grafana_test.go +++ b/pkg/commands/analyse_grafana_test.go @@ -38,3 +38,18 @@ func TestParseMetricsInBoard(t *testing.T) { analyse.ParseMetricsInBoard(output, board) assert.Equal(t, dashboardMetrics, output.Dashboards[0].Metrics) } + +func TestParseMetricsInBoardWithTimeseriesPanel(t *testing.T) { + var board sdk.Board + output := &analyse.MetricsInGrafana{} + output.OverallMetrics = make(map[string]struct{}) + + buf, err := loadFile("testdata/timeseries.json") + require.NoError(t, err) + + err = json.Unmarshal(buf, &board) + require.NoError(t, err) + + analyse.ParseMetricsInBoard(output, board) + assert.Equal(t, []string{"my_lovely_metric"}, output.Dashboards[0].Metrics) +} diff --git a/pkg/commands/testdata/timeseries.json b/pkg/commands/testdata/timeseries.json new file mode 100644 index 000000000..dc115ca4e --- /dev/null +++ b/pkg/commands/testdata/timeseries.json @@ -0,0 +1,147 @@ +{ + "__inputs": [ ], + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "8.2.3-40566" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": "$datasource", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "1+2+3+count(my_lovely_metric)", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Panel Title", + "type": "timeseries" + } + ], + "schemaVersion": 32, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Dashboard with timeseries panel", + "uid": "TX7rYEc7k", + "version": 2 +}