Files
tendermint/scripts/metricsgen/metricsdiff/metricsdiff.go
William Banfield 5b5dc2ea95 use strings.Builder
2022-05-11 10:12:54 -04:00

161 lines
3.5 KiB
Go

// metricsdiff is a tool for generating a diff between two different files containing
// prometheus metrics. metricsdiff outputs which metrics have been added, removed,
// or have different sets of labels between the two files.
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/expfmt"
)
func init() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, `Usage: %[1]s <path1> <path2>
Generate the diff between the two files of Prometheus metrics.
The input should have the format output by a Prometheus HTTP endpoint.
The tool indicates which metrics have been added, removed, or use different
label sets from path1 to path2.
`, filepath.Base(os.Args[0]))
flag.PrintDefaults()
}
}
// Diff contains the set of metrics that were modified between two files
// containing prometheus metrics output.
type Diff struct {
Adds []string
Removes []string
Changes []LabelDiff
}
// LabelDiff describes the label changes between two versions of the same metric.
type LabelDiff struct {
Metric string
Adds []string
Removes []string
}
func main() {
flag.Parse()
if flag.NArg() != 2 {
log.Fatalf("Usage is '%s <path1> <path2>', got %d arguments",
filepath.Base(os.Args[0]), flag.NArg())
}
fa, err := os.Open(flag.Arg(0))
if err != nil {
log.Fatalf("Open: %v", err)
}
defer fa.Close()
fb, err := os.Open(flag.Arg(1))
if err != nil {
log.Fatalf("Open: %v", err)
}
defer fb.Close()
md, err := DiffFromReaders(fa, fb)
if err != nil {
log.Fatalf("Generating diff: %v", err)
}
fmt.Print(md)
}
// DiffFromReaders parses the metrics present in the readers a and b and
// determines which metrics were added and removed in b.
func DiffFromReaders(a, b io.Reader) (Diff, error) {
var parser expfmt.TextParser
amf, err := parser.TextToMetricFamilies(a)
if err != nil {
return Diff{}, err
}
bmf, err := parser.TextToMetricFamilies(b)
if err != nil {
return Diff{}, err
}
md := Diff{}
for name, afamily := range amf {
bfamily, ok := bmf[name]
if !ok {
md.Removes = append(md.Removes, name)
continue
}
labelsDiff := false
aLabelSet := toSet(afamily.Metric[0].Label)
bLabelSet := toSet(bfamily.Metric[0].Label)
ld := LabelDiff{
Metric: name,
}
for name := range aLabelSet {
_, ok := bLabelSet[name]
if !ok {
ld.Removes = append(ld.Removes, name)
labelsDiff = true
}
}
for name := range bLabelSet {
_, ok := aLabelSet[name]
if !ok {
ld.Adds = append(ld.Adds, name)
labelsDiff = true
}
}
if labelsDiff {
md.Changes = append(md.Changes, ld)
}
}
for name := range bmf {
if _, ok := amf[name]; !ok {
md.Adds = append(md.Adds, name)
}
}
return md, nil
}
func toSet(lps []*dto.LabelPair) map[string]struct{} {
m := make(map[string]struct{}, len(lps))
for _, pair := range lps {
m[*pair.Name] = struct{}{}
}
return m
}
func (m Diff) String() string {
var s strings.Builder
if len(m.Adds) > 0 {
fmt.Fprintln(&s, "Adds:")
for _, add := range m.Adds {
fmt.Fprintf(&s, "+++ %s\n", add)
}
}
if len(m.Removes) > 0 {
fmt.Fprintln(&s, "Removes:")
for _, rem := range m.Removes {
fmt.Fprintf(&s, "--- %s\n", rem)
}
}
if len(m.Changes) > 0 {
fmt.Fprintln(&s, "Label changes:")
for _, ld := range m.Changes {
fmt.Fprintf(&s, "Label: %s\n", ld.Metric)
for _, add := range ld.Adds {
fmt.Fprintf(&s, "+++ %s\n", add)
}
for _, rem := range ld.Removes {
fmt.Fprintf(&s, "--- %s\n", rem)
}
}
}
return s.String()
}