Compare commits

...

18 Commits

Author SHA1 Message Date
William Banfield
2b7f46206e change output and related tests 2022-05-11 13:37:02 -04:00
William Banfield
ad577e93d4 sort data and merge 2022-05-11 13:27:50 -04:00
William Banfield
5b5dc2ea95 use strings.Builder 2022-05-11 10:12:54 -04:00
William Banfield
81cac78702 field renames 2022-05-11 10:10:23 -04:00
William Banfield
4eb6bdaee7 use fmt.Print 2022-05-11 10:04:29 -04:00
William Banfield
0048bfe5a9 use flag.Arg 2022-05-11 10:03:05 -04:00
William Banfield
8ebbc52b97 close files 2022-05-11 10:00:47 -04:00
William Banfield
ace89bf523 simplify open error 2022-05-11 09:59:51 -04:00
William Banfield
4257b0762e non allocating decl 2022-05-11 09:58:19 -04:00
William Banfield
e0badc0b2f rename diff function 2022-05-11 09:57:26 -04:00
William Banfield
7947e9ea5f change struct name 2022-05-11 09:56:28 -04:00
William Banfield
f3550cea47 use want as name in test 2022-05-11 09:54:50 -04:00
William Banfield
dc1d9d48e0 use NArg 2022-05-11 09:52:49 -04:00
William Banfield
f834edea56 Update scripts/metricsgen/metricsdiff/metricsdiff.go
Co-authored-by: M. J. Fromberger <fromberger@interchain.io>
2022-05-11 09:50:53 -04:00
William Banfield
8c5f42949f Update scripts/metricsgen/metricsdiff/metricsdiff.go
Co-authored-by: M. J. Fromberger <fromberger@interchain.io>
2022-05-11 09:49:15 -04:00
William Banfield
035356df64 Update scripts/metricsgen/metricsdiff/metricsdiff.go
Co-authored-by: M. J. Fromberger <fromberger@interchain.io>
2022-05-11 09:48:19 -04:00
William Banfield
672a26a3cf Update scripts/metricsgen/metricsdiff/metricsdiff.go
Co-authored-by: M. J. Fromberger <fromberger@interchain.io>
2022-05-11 09:47:51 -04:00
William Banfield
118b48fba0 scripts/metricsgen: add metricsdiff tool 2022-05-11 00:06:04 -04:00
2 changed files with 259 additions and 0 deletions

View File

@@ -0,0 +1,197 @@
// 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"
"sort"
"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
}
type parsedMetric struct {
name string
labels []string
}
type metricsList []parsedMetric
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{}
aList := toList(amf)
bList := toList(bmf)
i, j := 0, 0
for i < len(aList) || j < len(bList) {
for j < len(bList) && (i >= len(aList) || bList[j].name < aList[i].name) {
md.Adds = append(md.Adds, bList[j].name)
j++
}
for i < len(aList) && j < len(bList) && aList[i].name == bList[j].name {
adds, removes := listDiff(aList[i].labels, bList[j].labels)
if len(adds) > 0 || len(removes) > 0 {
md.Changes = append(md.Changes, LabelDiff{
Metric: aList[i].name,
Adds: adds,
Removes: removes,
})
}
i++
j++
}
for i < len(aList) && (j >= len(bList) || aList[i].name < bList[j].name) {
md.Removes = append(md.Removes, aList[i].name)
i++
}
}
return md, nil
}
func toList(l map[string]*dto.MetricFamily) metricsList {
r := make([]parsedMetric, len(l))
var idx int
for name, family := range l {
r[idx] = parsedMetric{
name: name,
labels: labelsToStringList(family.Metric[0].Label),
}
idx++
}
sort.Sort(metricsList(r))
return r
}
func labelsToStringList(ls []*dto.LabelPair) []string {
r := make([]string, len(ls))
for i, l := range ls {
r[i] = *l.Name
}
return sort.StringSlice(r)
}
func listDiff(a, b []string) ([]string, []string) {
adds, removes := []string{}, []string{}
i, j := 0, 0
for i < len(a) || j < len(b) {
for j < len(b) && (i >= len(a) || b[j] < a[i]) {
adds = append(adds, b[j])
j++
}
for i < len(a) && j < len(b) && a[i] == b[j] {
i++
j++
}
for i < len(a) && (j >= len(b) || a[i] < b[j]) {
removes = append(removes, a[i])
i++
}
}
return adds, removes
}
func (m metricsList) Len() int { return len(m) }
func (m metricsList) Less(i, j int) bool { return m[i].name < m[j].name }
func (m metricsList) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m Diff) String() string {
var s strings.Builder
if len(m.Adds) > 0 || len(m.Removes) > 0 {
fmt.Fprintln(&s, "Metric changes:")
}
if len(m.Adds) > 0 {
for _, add := range m.Adds {
fmt.Fprintf(&s, "+++ %s\n", add)
}
}
if len(m.Removes) > 0 {
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, "Metric: %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()
}

View File

@@ -0,0 +1,62 @@
package main_test
import (
"bytes"
"io"
"testing"
"github.com/stretchr/testify/require"
metricsdiff "github.com/tendermint/tendermint/scripts/metricsgen/metricsdiff"
)
func TestDiff(t *testing.T) {
for _, tc := range []struct {
name string
aContents string
bContents string
want string
}{
{
name: "labels",
aContents: `
metric_one{label_one="content", label_two="content"} 0
`,
bContents: `
metric_one{label_three="content", label_four="content"} 0
`,
want: `Label changes:
Metric: metric_one
+++ label_three
+++ label_four
--- label_one
--- label_two
`,
},
{
name: "metrics",
aContents: `
metric_one{label_one="content"} 0
`,
bContents: `
metric_two{label_two="content"} 0
`,
want: `Metric changes:
+++ metric_two
--- metric_one
`,
},
} {
t.Run(tc.name, func(t *testing.T) {
bufA := bytes.NewBuffer([]byte{})
bufB := bytes.NewBuffer([]byte{})
_, err := io.WriteString(bufA, tc.aContents)
require.NoError(t, err)
_, err = io.WriteString(bufB, tc.bContents)
require.NoError(t, err)
md, err := metricsdiff.DiffFromReaders(bufA, bufB)
require.NoError(t, err)
require.Equal(t, tc.want, md.String())
})
}
}