Skip to content

Commit f6ea899

Browse files
authored
Move analyse command logic to pkg, and export it, allowing it to be used as a module in other projects (#214)
1 parent 7f63659 commit f6ea899

File tree

8 files changed

+223
-205
lines changed

8 files changed

+223
-205
lines changed

pkg/analyse/grafana.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
package analyse
22

3+
import (
4+
"fmt"
5+
"regexp"
6+
"sort"
7+
"strings"
8+
9+
log "github.com/sirupsen/logrus"
10+
11+
"github.com/grafana-tools/sdk"
12+
"github.com/pkg/errors"
13+
"github.com/prometheus/prometheus/promql/parser"
14+
)
15+
316
type MetricsInGrafana struct {
417
MetricsUsed []string `json:"metricsUsed"`
518
OverallMetrics map[string]struct{} `json:"-"`
@@ -13,3 +26,138 @@ type DashboardMetrics struct {
1326
Metrics []string `json:"metrics"`
1427
ParseErrors []string `json:"parse_errors"`
1528
}
29+
30+
func ParseMetricsInBoard(mig *MetricsInGrafana, board sdk.Board) {
31+
var parseErrors []error
32+
metrics := make(map[string]struct{})
33+
34+
// Iterate through all the panels and collect metrics
35+
for _, panel := range board.Panels {
36+
parseErrors = append(parseErrors, metricsFromPanel(*panel, metrics)...)
37+
if panel.RowPanel != nil {
38+
for _, subPanel := range panel.RowPanel.Panels {
39+
parseErrors = append(parseErrors, metricsFromPanel(subPanel, metrics)...)
40+
}
41+
}
42+
}
43+
44+
// Iterate through all the rows and collect metrics
45+
for _, row := range board.Rows {
46+
for _, panel := range row.Panels {
47+
parseErrors = append(parseErrors, metricsFromPanel(panel, metrics)...)
48+
}
49+
}
50+
51+
// Process metrics in templating
52+
parseErrors = append(parseErrors, metricsFromTemplating(board.Templating, metrics)...)
53+
54+
var parseErrs []string
55+
for _, err := range parseErrors {
56+
parseErrs = append(parseErrs, err.Error())
57+
}
58+
59+
var metricsInBoard []string
60+
for metric := range metrics {
61+
if metric == "" {
62+
continue
63+
}
64+
65+
metricsInBoard = append(metricsInBoard, metric)
66+
mig.OverallMetrics[metric] = struct{}{}
67+
}
68+
sort.Strings(metricsInBoard)
69+
70+
mig.Dashboards = append(mig.Dashboards, DashboardMetrics{
71+
Slug: board.Slug,
72+
UID: board.UID,
73+
Title: board.Title,
74+
Metrics: metricsInBoard,
75+
ParseErrors: parseErrs,
76+
})
77+
78+
}
79+
80+
func metricsFromTemplating(templating sdk.Templating, metrics map[string]struct{}) []error {
81+
parseErrors := []error{}
82+
for _, templateVar := range templating.List {
83+
if templateVar.Type != "query" {
84+
continue
85+
}
86+
if query, ok := templateVar.Query.(string); ok {
87+
// label_values
88+
if strings.Contains(query, "label_values") {
89+
re := regexp.MustCompile(`label_values\(([a-zA-Z0-9_]+)`)
90+
sm := re.FindStringSubmatch(query)
91+
// In case of really gross queries, like - https://github.com/grafana/jsonnet-libs/blob/e97ab17f67ab40d5fe3af7e59151dd43be03f631/hass-mixin/dashboard.libsonnet#L93
92+
if len(sm) > 0 {
93+
query = sm[1]
94+
}
95+
}
96+
// query_result
97+
if strings.Contains(query, "query_result") {
98+
re := regexp.MustCompile(`query_result\((.+)\)`)
99+
query = re.FindStringSubmatch(query)[1]
100+
}
101+
err := parseQuery(query, metrics)
102+
if err != nil {
103+
parseErrors = append(parseErrors, errors.Wrapf(err, "query=%v", query))
104+
log.Debugln("msg", "promql parse error", "err", err, "query", query)
105+
continue
106+
}
107+
} else {
108+
err := fmt.Errorf("templating type error: name=%v", templateVar.Name)
109+
parseErrors = append(parseErrors, err)
110+
log.Debugln("msg", "templating parse error", "err", err)
111+
continue
112+
}
113+
}
114+
return parseErrors
115+
}
116+
117+
func metricsFromPanel(panel sdk.Panel, metrics map[string]struct{}) []error {
118+
var parseErrors []error
119+
120+
if panel.GetTargets() == nil {
121+
return parseErrors
122+
}
123+
124+
for _, target := range *panel.GetTargets() {
125+
// Prometheus has this set.
126+
if target.Expr == "" {
127+
continue
128+
}
129+
query := target.Expr
130+
err := parseQuery(query, metrics)
131+
if err != nil {
132+
parseErrors = append(parseErrors, errors.Wrapf(err, "query=%v", query))
133+
log.Debugln("msg", "promql parse error", "err", err, "query", query)
134+
continue
135+
}
136+
}
137+
138+
return parseErrors
139+
}
140+
141+
func parseQuery(query string, metrics map[string]struct{}) error {
142+
query = strings.ReplaceAll(query, `$__interval`, "5m")
143+
query = strings.ReplaceAll(query, `$interval`, "5m")
144+
query = strings.ReplaceAll(query, `$resolution`, "5s")
145+
query = strings.ReplaceAll(query, "$__rate_interval", "15s")
146+
query = strings.ReplaceAll(query, "$__range", "1d")
147+
query = strings.ReplaceAll(query, "${__range_s:glob}", "30")
148+
query = strings.ReplaceAll(query, "${__range_s}", "30")
149+
expr, err := parser.ParseExpr(query)
150+
if err != nil {
151+
return err
152+
}
153+
154+
parser.Inspect(expr, func(node parser.Node, path []parser.Node) error {
155+
if n, ok := node.(*parser.VectorSelector); ok {
156+
metrics[n.Name] = struct{}{}
157+
}
158+
159+
return nil
160+
})
161+
162+
return nil
163+
}

pkg/analyse/ruler.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
package analyse
22

3+
import (
4+
"sort"
5+
6+
"github.com/grafana/cortex-tools/pkg/rules/rwrulefmt"
7+
"github.com/pkg/errors"
8+
"github.com/prometheus/prometheus/promql/parser"
9+
log "github.com/sirupsen/logrus"
10+
)
11+
312
type MetricsInRuler struct {
413
MetricsUsed []string `json:"metricsUsed"`
514
OverallMetrics map[string]struct{} `json:"-"`
@@ -12,3 +21,63 @@ type RuleGroupMetrics struct {
1221
Metrics []string `json:"metrics"`
1322
ParseErrors []string `json:"parse_errors"`
1423
}
24+
25+
func ParseMetricsInRuleGroup(mir *MetricsInRuler, group rwrulefmt.RuleGroup, ns string) error {
26+
var (
27+
ruleMetrics = make(map[string]struct{})
28+
refMetrics = make(map[string]struct{})
29+
parseErrors []error
30+
)
31+
32+
for _, rule := range group.Rules {
33+
if rule.Record.Value != "" {
34+
ruleMetrics[rule.Record.Value] = struct{}{}
35+
}
36+
37+
query := rule.Expr.Value
38+
expr, err := parser.ParseExpr(query)
39+
if err != nil {
40+
parseErrors = append(parseErrors, errors.Wrapf(err, "query=%v", query))
41+
log.Debugln("msg", "promql parse error", "err", err, "query", query)
42+
continue
43+
}
44+
45+
parser.Inspect(expr, func(node parser.Node, path []parser.Node) error {
46+
if n, ok := node.(*parser.VectorSelector); ok {
47+
refMetrics[n.Name] = struct{}{}
48+
}
49+
50+
return nil
51+
})
52+
}
53+
54+
// remove defined recording rule metrics in same RG
55+
for ruleMetric := range ruleMetrics {
56+
delete(refMetrics, ruleMetric)
57+
}
58+
59+
var metricsInGroup []string
60+
var parseErrs []string
61+
62+
for metric := range refMetrics {
63+
if metric == "" {
64+
continue
65+
}
66+
metricsInGroup = append(metricsInGroup, metric)
67+
mir.OverallMetrics[metric] = struct{}{}
68+
}
69+
sort.Strings(metricsInGroup)
70+
71+
for _, err := range parseErrors {
72+
parseErrs = append(parseErrs, err.Error())
73+
}
74+
75+
mir.RuleGroups = append(mir.RuleGroups, RuleGroupMetrics{
76+
Namespace: ns,
77+
GroupName: group.Name,
78+
Metrics: metricsInGroup,
79+
ParseErrors: parseErrs,
80+
})
81+
82+
return nil
83+
}

pkg/commands/analyse_dashboards.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func (cmd *DashboardAnalyseCommand) run(k *kingpin.ParseContext) error {
3030
fmt.Fprintf(os.Stderr, "%s for %s\n", err, file)
3131
continue
3232
}
33-
parseMetricsInBoard(output, board)
33+
analyse.ParseMetricsInBoard(output, board)
3434
}
3535

3636
err := writeOut(output, cmd.outputFile)

0 commit comments

Comments
 (0)