Skip to content

Commit

Permalink
Support Grafana 'timeseries' panel type for analyse grafana. (#224)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
stevesg authored Nov 16, 2021
1 parent c9664fe commit bcb2d50
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
42 changes: 39 additions & 3 deletions pkg/analyse/grafana.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package analyse

import (
"encoding/json"
"fmt"
"regexp"
"sort"
Expand Down Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions pkg/commands/analyse_grafana_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
147 changes: 147 additions & 0 deletions pkg/commands/testdata/timeseries.json
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit bcb2d50

Please sign in to comment.