Skip to content

Commit

Permalink
Support Grafana 'timeseries' panel type for analyse grafana.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
stevesg committed Nov 16, 2021
1 parent c9664fe commit e956ee3
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 2 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
46 changes: 44 additions & 2 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,55 @@ 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 getPanelTargets(panel sdk.Panel) *[]sdk.Target {
targets := panel.GetTargets()
if targets == nil {
targets = getCustomPanelTargets(panel)
}

return targets
}

func metricsFromPanel(panel sdk.Panel, metrics map[string]struct{}) []error {
var parseErrors []error

if panel.GetTargets() == nil {
targets := getPanelTargets(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
19 changes: 19 additions & 0 deletions pkg/commands/analyse_grafana_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,22 @@ func TestParseMetricsInBoard(t *testing.T) {
analyse.ParseMetricsInBoard(output, board)
assert.Equal(t, dashboardMetrics, output.Dashboards[0].Metrics)
}

var timeseriesPanelMetrics = []string{
"my_lovely_metric",
}

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, timeseriesPanelMetrics, 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 e956ee3

Please sign in to comment.