Skip to content

Commit

Permalink
Update mimirtool grafana analyze to support more panel types (#10669)
Browse files Browse the repository at this point in the history
* add support for custom panel types

* add support for bar chart, pie chart, state timeline, status history, histogram, candlestick, canvas, flame graph, geomap, node graph, trend and XY chart panel types

* add license header

* update CHANGELOG

* remove white noise

* fix typo
  • Loading branch information
LasseHels authored and ying-jeanne committed Feb 19, 2025
1 parent 92eb9f2 commit d785d32
Show file tree
Hide file tree
Showing 7 changed files with 612 additions and 393 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@

* [BUGFIX] Fix issue where `MIMIR_HTTP_PREFIX` environment variable was ignored and the value from `MIMIR_MIMIR_HTTP_PREFIX` was used instead. #10207
* [ENHANCEMENT] Unify mimirtool authentication options and add extra-headers support for commands that depend on MimirClient. #10178
* [ENHANCEMENT] `mimirtool grafana analyze` now supports custom panels. #10669
* [ENHANCEMENT] `mimirtool grafana analyze` now supports bar chart, pie chart, state timeline, status history,
histogram, candlestick, canvas, flame graph, geomap, node graph, trend, and XY chart panels. #10669

### Mimir Continuous Test

Expand Down
47 changes: 6 additions & 41 deletions pkg/mimirtool/analyze/grafana.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
package analyze

import (
"encoding/json"
"fmt"
"slices"
"strings"
Expand Down Expand Up @@ -61,8 +60,8 @@ func ParseMetricsInBoard(mig *MetricsInGrafana, board minisdk.Board) {
// Iterate through all the panels and collect metrics
for _, panel := range board.Panels {
parseErrors = append(parseErrors, metricsFromPanel(*panel, metrics)...)
if panel.RowPanel != nil {
for _, subPanel := range panel.RowPanel.Panels {
if panel.SubPanels != nil {
for _, subPanel := range panel.SubPanels {
parseErrors = append(parseErrors, metricsFromPanel(subPanel, metrics)...)
}
}
Expand Down Expand Up @@ -157,48 +156,14 @@ func metricsFromTemplating(templating minisdk.Templating, metrics map[string]str
return parseErrors
}

// Workaround to support Grafana "timeseries" panel. This should
// be implemented in grafana/tools-sdk, and removed from here.
func getCustomPanelTargets(panel minisdk.Panel) *[]minisdk.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 []minisdk.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 minisdk.Panel, metrics map[string]struct{}) []error {
var parseErrors []error
if !panel.SupportsTargets() {
return []error{fmt.Errorf("unsupported panel type: %q", panel.Type)}
}

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
}
}

var parseErrors []error
for _, target := range *targets {
// Prometheus has this set.
if target.Expr == "" {
Expand Down
29 changes: 14 additions & 15 deletions pkg/mimirtool/commands/analyse_grafana_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,29 @@ import (
var dashboardMetrics = []string{
"apiserver_request:availability30d",
"apiserver_request_total",
"bar_chart_metric",
"candlestick_metric",
"canvas_metric",
"cluster_quantile:apiserver_request_duration_seconds:histogram_quantile",
"code_resource:apiserver_request_total:rate5m",
"flame_graph_metric",
"geomap_metric",
"go_goroutines",
"histogram_metric",
"kube_pod_info",
"my_lovely_metric",
"node_graph_metric",
"pie_chart_metric",
"polystat_panel_metric",
"process_cpu_seconds_total",
"process_resident_memory_bytes",
"state_timeline_metric",
"status_history_metric",
"trend_metric",
"workqueue_adds_total",
"workqueue_depth",
"workqueue_queue_duration_seconds_bucket",
"xy_chart_metric",
}

var expectedParseErrors = []string{
Expand Down Expand Up @@ -71,18 +85,3 @@ func BenchmarkParseMetricsInBoard(b *testing.B) {
analyze.ParseMetricsInBoard(output, board)
}
}

func TestParseMetricsInBoardWithTimeseriesPanel(t *testing.T) {
var board minisdk.Board
output := &analyze.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)

analyze.ParseMetricsInBoard(output, board)
assert.Equal(t, []string{"my_lovely_metric"}, output.Dashboards[0].Metrics)
}
Loading

0 comments on commit d785d32

Please sign in to comment.