github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/scripts/metricsgen/metricsdiff/metricsdiff.go (about) 1 // metricsdiff is a tool for generating a diff between two different files containing 2 // prometheus metrics. metricsdiff outputs which metrics have been added, removed, 3 // or have different sets of labels between the two files. 4 package main 5 6 import ( 7 "flag" 8 "fmt" 9 "io" 10 "log" 11 "os" 12 "path/filepath" 13 "sort" 14 "strings" 15 16 dto "github.com/prometheus/client_model/go" 17 "github.com/prometheus/common/expfmt" 18 ) 19 20 func init() { 21 flag.Usage = func() { 22 fmt.Fprintf(os.Stderr, `Usage: %[1]s <path1> <path2> 23 24 Generate the diff between the two files of Prometheus metrics. 25 The input should have the format output by a Prometheus HTTP endpoint. 26 The tool indicates which metrics have been added, removed, or use different 27 label sets from path1 to path2. 28 29 `, filepath.Base(os.Args[0])) 30 flag.PrintDefaults() 31 } 32 } 33 34 // Diff contains the set of metrics that were modified between two files 35 // containing prometheus metrics output. 36 type Diff struct { 37 Adds []string 38 Removes []string 39 40 Changes []LabelDiff 41 } 42 43 // LabelDiff describes the label changes between two versions of the same metric. 44 type LabelDiff struct { 45 Metric string 46 Adds []string 47 Removes []string 48 } 49 50 type parsedMetric struct { 51 name string 52 labels []string 53 } 54 55 type metricsList []parsedMetric 56 57 func main() { 58 flag.Parse() 59 if flag.NArg() != 2 { 60 log.Fatalf("Usage is '%s <path1> <path2>', got %d arguments", 61 filepath.Base(os.Args[0]), flag.NArg()) 62 } 63 fa, err := os.Open(flag.Arg(0)) 64 if err != nil { 65 log.Fatalf("Open: %v", err) 66 } 67 defer fa.Close() 68 fb, err := os.Open(flag.Arg(1)) 69 if err != nil { 70 log.Fatalf("Open: %v", err) 71 } 72 defer fb.Close() 73 md, err := DiffFromReaders(fa, fb) 74 if err != nil { 75 log.Fatalf("Generating diff: %v", err) 76 } 77 fmt.Print(md) 78 } 79 80 // DiffFromReaders parses the metrics present in the readers a and b and 81 // determines which metrics were added and removed in b. 82 func DiffFromReaders(a, b io.Reader) (Diff, error) { 83 var parser expfmt.TextParser 84 amf, err := parser.TextToMetricFamilies(a) 85 if err != nil { 86 return Diff{}, err 87 } 88 bmf, err := parser.TextToMetricFamilies(b) 89 if err != nil { 90 return Diff{}, err 91 } 92 93 md := Diff{} 94 aList := toList(amf) 95 bList := toList(bmf) 96 97 i, j := 0, 0 98 for i < len(aList) || j < len(bList) { 99 for j < len(bList) && (i >= len(aList) || bList[j].name < aList[i].name) { 100 md.Adds = append(md.Adds, bList[j].name) 101 j++ 102 } 103 for i < len(aList) && j < len(bList) && aList[i].name == bList[j].name { 104 adds, removes := listDiff(aList[i].labels, bList[j].labels) 105 if len(adds) > 0 || len(removes) > 0 { 106 md.Changes = append(md.Changes, LabelDiff{ 107 Metric: aList[i].name, 108 Adds: adds, 109 Removes: removes, 110 }) 111 } 112 i++ 113 j++ 114 } 115 for i < len(aList) && (j >= len(bList) || aList[i].name < bList[j].name) { 116 md.Removes = append(md.Removes, aList[i].name) 117 i++ 118 } 119 } 120 return md, nil 121 } 122 123 func toList(l map[string]*dto.MetricFamily) metricsList { 124 r := make([]parsedMetric, len(l)) 125 var idx int 126 for name, family := range l { 127 r[idx] = parsedMetric{ 128 name: name, 129 labels: labelsToStringList(family.Metric[0].Label), 130 } 131 idx++ 132 } 133 sort.Sort(metricsList(r)) 134 return r 135 } 136 137 func labelsToStringList(ls []*dto.LabelPair) []string { 138 r := make([]string, len(ls)) 139 for i, l := range ls { 140 r[i] = l.GetName() 141 } 142 return sort.StringSlice(r) 143 } 144 145 func listDiff(a, b []string) ([]string, []string) { 146 adds, removes := []string{}, []string{} 147 i, j := 0, 0 148 for i < len(a) || j < len(b) { 149 for j < len(b) && (i >= len(a) || b[j] < a[i]) { 150 adds = append(adds, b[j]) 151 j++ 152 } 153 for i < len(a) && j < len(b) && a[i] == b[j] { 154 i++ 155 j++ 156 } 157 for i < len(a) && (j >= len(b) || a[i] < b[j]) { 158 removes = append(removes, a[i]) 159 i++ 160 } 161 } 162 return adds, removes 163 } 164 165 func (m metricsList) Len() int { return len(m) } 166 func (m metricsList) Less(i, j int) bool { return m[i].name < m[j].name } 167 func (m metricsList) Swap(i, j int) { m[i], m[j] = m[j], m[i] } 168 169 func (m Diff) String() string { 170 var s strings.Builder 171 if len(m.Adds) > 0 || len(m.Removes) > 0 { 172 fmt.Fprintln(&s, "Metric changes:") 173 } 174 if len(m.Adds) > 0 { 175 for _, add := range m.Adds { 176 fmt.Fprintf(&s, "+++ %s\n", add) 177 } 178 } 179 if len(m.Removes) > 0 { 180 for _, rem := range m.Removes { 181 fmt.Fprintf(&s, "--- %s\n", rem) 182 } 183 } 184 if len(m.Changes) > 0 { 185 fmt.Fprintln(&s, "Label changes:") 186 for _, ld := range m.Changes { 187 fmt.Fprintf(&s, "Metric: %s\n", ld.Metric) 188 for _, add := range ld.Adds { 189 fmt.Fprintf(&s, "+++ %s\n", add) 190 } 191 for _, rem := range ld.Removes { 192 fmt.Fprintf(&s, "--- %s\n", rem) 193 } 194 } 195 } 196 return s.String() 197 }