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  }