Skip to content

Instantly share code, notes, and snippets.

@tolleiv
Last active October 20, 2023 08:09
Show Gist options
  • Save tolleiv/a602ecea838f96599fd3e907d173d52e to your computer and use it in GitHub Desktop.
Save tolleiv/a602ecea838f96599fd3e907d173d52e to your computer and use it in GitHub Desktop.
Small utility to extract used metrics from promql queries
module prom-stat
go 1.21.1
require github.com/prometheus/prometheus v0.47.2
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dennwc/varint v1.0.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.16.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.11.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect
golang.org/x/sys v0.10.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
)
package main
import (
"bufio"
"fmt"
"github.com/prometheus/prometheus/promql/parser"
"os"
)
func main() {
metricSet := make(map[string]struct{}) // To remove duplicates
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := scanner.Text()
extractMetrics(line, metricSet)
}
if err := scanner.Err(); err != nil {
fmt.Println("Error reading from stdin:", err)
}
for metric := range metricSet {
fmt.Println(metric)
}
}
func extractMetrics(query string, metricSet map[string]struct{}) {
expr, err := parser.ParseExpr(query)
if err != nil {
fmt.Printf("Error parsing query: %s, error: %v\n", query, err)
return
}
parser.Inspect(expr, func(node parser.Node, path []parser.Node) error {
switch n := node.(type) {
case *parser.VectorSelector:
metricSet[n.Name] = struct{}{}
}
return nil
})
}
package main
import "testing"
func TestExtractMetrics(t *testing.T) {
tests := []struct {
query string
expected []string
}{
{
query: `max by (name, exported_namespace, namespace, condition) (certmanager_certificate_ready_status{condition!="True"} == 1)`,
expected: []string{"certmanager_certificate_ready_status"},
},
{
query: `min by (cluster, namespace, priority, poddisruptionbudget) (kube_poddisruptionbudget_status_pod_disruptions_allowed) == 0`,
expected: []string{"kube_poddisruptionbudget_status_pod_disruptions_allowed"},
},
{
query: `sum by (destination_service_name, destination_service_namespace, priority) (rate(istio_requests_total{reporter="source"}[5m]))`,
expected: []string{"istio_requests_total"},
},
{
query: `sum without (app_kubernetes_io_name, kubernetes_namespace, annotation_service_bare_id_priority) (label_replace(label_replace(probe_success < 1, "ingress", "$1", "app_kubernetes_io_name", "(.*)"), "namespace", "$1", "kubernetes_namespace", "(.*)") * on (namespace, ingress) group_left (priority) topk by (namespace, ingress) (1, label_replace(kube_ingress_annotations, "priority", "$1", "annotation_service_bare_id_priority", "(.*)")))`,
expected: []string{"probe_success", "kube_ingress_annotations"},
},
// ... add more tests as needed
}
for _, test := range tests {
metricSet := make(map[string]struct{})
extractMetrics(test.query, metricSet)
if len(metricSet) != len(test.expected) {
t.Errorf("Query: %s, Expected %d metrics, got %d", test.query, len(test.expected), len(metricSet))
continue
}
for _, exp := range test.expected {
if _, exists := metricSet[exp]; !exists {
t.Errorf("Query: %s, Expected metric %s not found", test.query, exp)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment