github.com/grafana/pyroscope@v1.18.0/pkg/test/integration/cluster/metrics.go (about) 1 package cluster 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "math" 8 "strings" 9 10 "github.com/prometheus/client_golang/prometheus" 11 pm "github.com/prometheus/client_model/go" 12 ) 13 14 type gatherCheck struct { 15 g prometheus.Gatherer 16 conditions []gatherCoditions 17 } 18 19 func matchValue(exp float64) func(float64) error { 20 return func(value float64) error { 21 if value == exp { 22 return nil 23 } 24 return fmt.Errorf("expected %f, got %f", exp, value) 25 } 26 } 27 28 //nolint:unparam 29 func (c *gatherCheck) addExpectValue(value float64, metricName string, labelPairs ...string) *gatherCheck { 30 c.conditions = append(c.conditions, gatherCoditions{ 31 metricName: metricName, 32 labelPairs: labelPairs, 33 valueCheck: matchValue(value), 34 }) 35 return c 36 } 37 38 func retrieveValue(ch chan float64) func(float64) error { 39 return func(value float64) error { 40 ch <- value 41 return nil 42 } 43 } 44 45 func (c *gatherCheck) addRetrieveValue(valueCh chan float64, metricName string, labelPairs ...string) *gatherCheck { 46 c.conditions = append(c.conditions, gatherCoditions{ 47 metricName: metricName, 48 labelPairs: labelPairs, 49 valueCheck: retrieveValue(valueCh), 50 }) 51 return c 52 } 53 54 type gatherCoditions struct { 55 metricName string 56 labelPairs []string 57 valueCheck func(float64) error 58 } 59 60 func (c *gatherCoditions) String() string { 61 b := strings.Builder{} 62 b.WriteString(c.metricName) 63 b.WriteRune('{') 64 for i := 0; i < len(c.labelPairs); i += 2 { 65 b.WriteString(c.labelPairs[i]) 66 b.WriteRune('=') 67 b.WriteString(c.labelPairs[i+1]) 68 b.WriteRune(',') 69 } 70 s := b.String() 71 return s[:len(s)-1] + "}" 72 } 73 74 func (c *gatherCoditions) matches(pairs []*pm.LabelPair) bool { 75 outer: 76 for i := 0; i < len(c.labelPairs); i += 2 { 77 for _, l := range pairs { 78 if l.GetName() != c.labelPairs[i] { 79 continue 80 } 81 if l.GetValue() == c.labelPairs[i+1] { 82 continue outer // match move to next pair 83 } 84 return false // value wrong 85 } 86 return false // label not found 87 } 88 return true 89 } 90 91 func (comp *Component) checkMetrics() *gatherCheck { 92 return &gatherCheck{ 93 g: comp.reg, 94 } 95 } 96 97 func (g *gatherCheck) run(ctx context.Context) error { 98 actualValues := make([]float64, len(g.conditions)) 99 100 // maps from metric name to condition index 101 nameMap := make(map[string][]int) 102 for idx, c := range g.conditions { 103 // not a number 104 actualValues[idx] = math.NaN() 105 nameMap[c.metricName] = append(nameMap[c.metricName], idx) 106 } 107 108 // now gather actual metrics 109 metrics, err := g.g.Gather() 110 if err != nil { 111 return err 112 } 113 114 for _, m := range metrics { 115 if ctx.Err() != nil { 116 return ctx.Err() 117 } 118 119 conditions, ok := nameMap[m.GetName()] 120 if !ok { 121 continue 122 } 123 124 // now iterate over all label pairs 125 for _, sm := range m.GetMetric() { 126 // check for each condition if it matches with he labels 127 for _, condIdx := range conditions { 128 if g.conditions[condIdx].matches(sm.Label) { 129 v := -1.0 // -1.0 is an invalid value, when metric type is not gauge or counter 130 if g := sm.GetGauge(); g != nil { 131 v = g.GetValue() 132 } else if c := sm.GetCounter(); c != nil { 133 v = c.GetValue() 134 } 135 actualValues[condIdx] = v 136 } 137 } 138 } 139 } 140 141 errs := make([]error, len(actualValues)) 142 for idx, actual := range actualValues { 143 cond := g.conditions[idx] 144 if math.IsNaN(actual) { 145 errs[idx] = fmt.Errorf("metric for %s not found", cond.String()) 146 continue 147 } 148 if err := cond.valueCheck(actual); err != nil { 149 errs[idx] = fmt.Errorf("unexpected value for %s: %w", cond.String(), err) 150 } 151 } 152 153 return errors.Join(errs...) 154 }