Skip to content

Commit bcb2d50

Browse files
authored
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.
1 parent c9664fe commit bcb2d50

File tree

4 files changed

+202
-3
lines changed

4 files changed

+202
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Order should be `CHANGE`, `FEATURE`, `ENHANCEMENT`, and `BUGFIX`
55
## unreleased
66

77
* [ENHANCEMENT] Benchtool: add `-bench.write.proxy-url` argument for configuring the Prometheus remote-write client with a HTTP proxy URL. #223
8+
* [ENHANCEMENT] Analyse: support Grafana 'timeseries' panel type for `cortextool analyse grafana` command. #224
89

910
## v0.10.6
1011

pkg/analyse/grafana.go

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package analyse
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"regexp"
67
"sort"
@@ -114,14 +115,49 @@ func metricsFromTemplating(templating sdk.Templating, metrics map[string]struct{
114115
return parseErrors
115116
}
116117

118+
// Workaround to support Grafana "timeseries" panel. This should
119+
// be implemented in grafana/tools-sdk, and removed from here.
120+
func getCustomPanelTargets(panel sdk.Panel) *[]sdk.Target {
121+
if panel.CommonPanel.Type != "timeseries" {
122+
return nil
123+
}
124+
125+
// Heavy handed approach to re-marshal the panel and parse it again
126+
// so that we can extract the 'targets' field in the right format.
127+
128+
bytes, err := json.Marshal(panel.CustomPanel)
129+
if err != nil {
130+
log.Debugln("msg", "panel re-marshalling error", "err", err)
131+
return nil
132+
}
133+
134+
type panelType struct {
135+
Targets []sdk.Target `json:"targets,omitempty"`
136+
}
137+
138+
var parsedPanel panelType
139+
err = json.Unmarshal(bytes, &parsedPanel)
140+
if err != nil {
141+
log.Debugln("msg", "panel parsing error", "err", err)
142+
return nil
143+
}
144+
145+
return &parsedPanel.Targets
146+
}
147+
117148
func metricsFromPanel(panel sdk.Panel, metrics map[string]struct{}) []error {
118149
var parseErrors []error
119150

120-
if panel.GetTargets() == nil {
121-
return parseErrors
151+
targets := panel.GetTargets()
152+
if targets == nil {
153+
targets = getCustomPanelTargets(panel)
154+
if targets == nil {
155+
parseErrors = append(parseErrors, fmt.Errorf("unsupported panel type: %q", panel.CommonPanel.Type))
156+
return parseErrors
157+
}
122158
}
123159

124-
for _, target := range *panel.GetTargets() {
160+
for _, target := range *targets {
125161
// Prometheus has this set.
126162
if target.Expr == "" {
127163
continue

pkg/commands/analyse_grafana_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,18 @@ func TestParseMetricsInBoard(t *testing.T) {
3838
analyse.ParseMetricsInBoard(output, board)
3939
assert.Equal(t, dashboardMetrics, output.Dashboards[0].Metrics)
4040
}
41+
42+
func TestParseMetricsInBoardWithTimeseriesPanel(t *testing.T) {
43+
var board sdk.Board
44+
output := &analyse.MetricsInGrafana{}
45+
output.OverallMetrics = make(map[string]struct{})
46+
47+
buf, err := loadFile("testdata/timeseries.json")
48+
require.NoError(t, err)
49+
50+
err = json.Unmarshal(buf, &board)
51+
require.NoError(t, err)
52+
53+
analyse.ParseMetricsInBoard(output, board)
54+
assert.Equal(t, []string{"my_lovely_metric"}, output.Dashboards[0].Metrics)
55+
}

pkg/commands/testdata/timeseries.json

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
{
2+
"__inputs": [ ],
3+
"__requires": [
4+
{
5+
"type": "grafana",
6+
"id": "grafana",
7+
"name": "Grafana",
8+
"version": "8.2.3-40566"
9+
},
10+
{
11+
"type": "datasource",
12+
"id": "prometheus",
13+
"name": "Prometheus",
14+
"version": "1.0.0"
15+
},
16+
{
17+
"type": "panel",
18+
"id": "timeseries",
19+
"name": "Time series",
20+
"version": ""
21+
}
22+
],
23+
"annotations": {
24+
"list": [
25+
{
26+
"builtIn": 1,
27+
"datasource": "-- Grafana --",
28+
"enable": true,
29+
"hide": true,
30+
"iconColor": "rgba(0, 211, 255, 1)",
31+
"name": "Annotations & Alerts",
32+
"target": {
33+
"limit": 100,
34+
"matchAny": false,
35+
"tags": [],
36+
"type": "dashboard"
37+
},
38+
"type": "dashboard"
39+
}
40+
]
41+
},
42+
"editable": true,
43+
"fiscalYearStartMonth": 0,
44+
"gnetId": null,
45+
"graphTooltip": 0,
46+
"id": null,
47+
"links": [],
48+
"liveNow": false,
49+
"panels": [
50+
{
51+
"datasource": "$datasource",
52+
"fieldConfig": {
53+
"defaults": {
54+
"color": {
55+
"mode": "palette-classic"
56+
},
57+
"custom": {
58+
"axisLabel": "",
59+
"axisPlacement": "auto",
60+
"barAlignment": 0,
61+
"drawStyle": "line",
62+
"fillOpacity": 0,
63+
"gradientMode": "none",
64+
"hideFrom": {
65+
"legend": false,
66+
"tooltip": false,
67+
"viz": false
68+
},
69+
"lineInterpolation": "linear",
70+
"lineWidth": 1,
71+
"pointSize": 5,
72+
"scaleDistribution": {
73+
"type": "linear"
74+
},
75+
"showPoints": "auto",
76+
"spanNulls": false,
77+
"stacking": {
78+
"group": "A",
79+
"mode": "none"
80+
},
81+
"thresholdsStyle": {
82+
"mode": "off"
83+
}
84+
},
85+
"mappings": [],
86+
"thresholds": {
87+
"mode": "absolute",
88+
"steps": [
89+
{
90+
"color": "green",
91+
"value": null
92+
},
93+
{
94+
"color": "red",
95+
"value": 80
96+
}
97+
]
98+
}
99+
},
100+
"overrides": []
101+
},
102+
"gridPos": {
103+
"h": 9,
104+
"w": 12,
105+
"x": 0,
106+
"y": 0
107+
},
108+
"id": 2,
109+
"options": {
110+
"legend": {
111+
"calcs": [],
112+
"displayMode": "list",
113+
"placement": "bottom"
114+
},
115+
"tooltip": {
116+
"mode": "single"
117+
}
118+
},
119+
"targets": [
120+
{
121+
"exemplar": true,
122+
"expr": "1+2+3+count(my_lovely_metric)",
123+
"interval": "",
124+
"legendFormat": "",
125+
"refId": "A"
126+
}
127+
],
128+
"title": "Panel Title",
129+
"type": "timeseries"
130+
}
131+
],
132+
"schemaVersion": 32,
133+
"style": "dark",
134+
"tags": [],
135+
"templating": {
136+
"list": []
137+
},
138+
"time": {
139+
"from": "now-6h",
140+
"to": "now"
141+
},
142+
"timepicker": {},
143+
"timezone": "",
144+
"title": "Dashboard with timeseries panel",
145+
"uid": "TX7rYEc7k",
146+
"version": 2
147+
}

0 commit comments

Comments
 (0)