1
16
17 package testutil
18
19 import (
20 "fmt"
21 "io"
22 "math"
23 "reflect"
24 "sort"
25 "strings"
26
27 dto "github.com/prometheus/client_model/go"
28 "github.com/prometheus/common/expfmt"
29 "github.com/prometheus/common/model"
30
31 "k8s.io/component-base/metrics"
32 )
33
34 var (
35
36 MetricNameLabel model.LabelName = model.MetricNameLabel
37
38 QuantileLabel model.LabelName = model.QuantileLabel
39 )
40
41
42 type Metrics map[string]model.Samples
43
44
45 func (m *Metrics) Equal(o Metrics) bool {
46 var leftKeySet []string
47 var rightKeySet []string
48 for k := range *m {
49 leftKeySet = append(leftKeySet, k)
50 }
51 for k := range o {
52 rightKeySet = append(rightKeySet, k)
53 }
54 if !reflect.DeepEqual(leftKeySet, rightKeySet) {
55 return false
56 }
57 for _, k := range leftKeySet {
58 if !(*m)[k].Equal(o[k]) {
59 return false
60 }
61 }
62 return true
63 }
64
65
66 func NewMetrics() Metrics {
67 result := make(Metrics)
68 return result
69 }
70
71
72 func ParseMetrics(data string, output *Metrics) error {
73 dec := expfmt.NewDecoder(strings.NewReader(data), expfmt.FmtText)
74 decoder := expfmt.SampleDecoder{
75 Dec: dec,
76 Opts: &expfmt.DecodeOptions{},
77 }
78
79 for {
80 var v model.Vector
81 if err := decoder.Decode(&v); err != nil {
82 if err == io.EOF {
83
84 return nil
85 }
86 continue
87 }
88 for _, metric := range v {
89 name := string(metric.Metric[MetricNameLabel])
90 (*output)[name] = append((*output)[name], metric)
91 }
92 }
93 }
94
95
96
97
98
99 func TextToMetricFamilies(in io.Reader) (map[string]*dto.MetricFamily, error) {
100 var textParser expfmt.TextParser
101 return textParser.TextToMetricFamilies(in)
102 }
103
104
105 func PrintSample(sample *model.Sample) string {
106 buf := make([]string, 0)
107
108
109
110
111 _, normalContainer := sample.Metric["kubernetes_container_name"]
112 for k, v := range sample.Metric {
113 if strings.HasPrefix(string(k), "__") {
114 continue
115 }
116
117 if string(k) == "id" && normalContainer {
118 continue
119 }
120 buf = append(buf, fmt.Sprintf("%v=%v", string(k), v))
121 }
122 return fmt.Sprintf("[%v] = %v", strings.Join(buf, ","), sample.Value)
123 }
124
125
126
127 func ComputeHistogramDelta(before, after model.Samples, label model.LabelName) {
128 beforeSamplesMap := make(map[string]*model.Sample)
129 for _, bSample := range before {
130 beforeSamplesMap[makeKey(bSample.Metric[label], bSample.Metric["le"])] = bSample
131 }
132 for _, aSample := range after {
133 if bSample, found := beforeSamplesMap[makeKey(aSample.Metric[label], aSample.Metric["le"])]; found {
134 aSample.Value = aSample.Value - bSample.Value
135 }
136 }
137 }
138
139 func makeKey(a, b model.LabelValue) string {
140 return string(a) + "___" + string(b)
141 }
142
143
144 func GetMetricValuesForLabel(ms Metrics, metricName, label string) map[string]int64 {
145 samples, found := ms[metricName]
146 result := make(map[string]int64, len(samples))
147 if !found {
148 return result
149 }
150 for _, sample := range samples {
151 count := int64(sample.Value)
152 dimensionName := string(sample.Metric[model.LabelName(label)])
153 result[dimensionName] = count
154 }
155 return result
156 }
157
158
159 func ValidateMetrics(metrics Metrics, metricName string, expectedLabels ...string) error {
160 samples, ok := metrics[metricName]
161 if !ok {
162 return fmt.Errorf("metric %q was not found in metrics", metricName)
163 }
164 for _, sample := range samples {
165 for _, l := range expectedLabels {
166 if _, ok := sample.Metric[model.LabelName(l)]; !ok {
167 return fmt.Errorf("metric %q is missing label %q, sample: %q", metricName, l, sample.String())
168 }
169 }
170 }
171 return nil
172 }
173
174
175 type Histogram struct {
176 *dto.Histogram
177 }
178
179
180
181 type HistogramVec []*Histogram
182
183
184 func (vec HistogramVec) GetAggregatedSampleCount() uint64 {
185 var count uint64
186 for _, hist := range vec {
187 count += hist.GetSampleCount()
188 }
189 return count
190 }
191
192
193 func (vec HistogramVec) GetAggregatedSampleSum() float64 {
194 var sum float64
195 for _, hist := range vec {
196 sum += hist.GetSampleSum()
197 }
198 return sum
199 }
200
201
202
203 func (vec HistogramVec) Quantile(q float64) float64 {
204 var buckets []bucket
205
206 for i, hist := range vec {
207 for j, bckt := range hist.Bucket {
208 if i == 0 {
209 buckets = append(buckets, bucket{
210 count: float64(bckt.GetCumulativeCount()),
211 upperBound: bckt.GetUpperBound(),
212 })
213 } else {
214 buckets[j].count += float64(bckt.GetCumulativeCount())
215 }
216 }
217 }
218
219 if len(buckets) == 0 || buckets[len(buckets)-1].upperBound != math.Inf(+1) {
220
221
222 buckets = append(buckets, bucket{
223 count: float64(vec.GetAggregatedSampleCount()),
224 upperBound: math.Inf(+1),
225 })
226 }
227
228 return bucketQuantile(q, buckets)
229 }
230
231
232 func (vec HistogramVec) Average() float64 {
233 return vec.GetAggregatedSampleSum() / float64(vec.GetAggregatedSampleCount())
234 }
235
236
237 func (vec HistogramVec) Validate() error {
238 bucketSize := 0
239 for i, hist := range vec {
240 if err := hist.Validate(); err != nil {
241 return err
242 }
243 if i == 0 {
244 bucketSize = len(hist.GetBucket())
245 } else if bucketSize != len(hist.GetBucket()) {
246 return fmt.Errorf("found different bucket size: expect %v, but got %v at index %v", bucketSize, len(hist.GetBucket()), i)
247 }
248 }
249 return nil
250 }
251
252
253
254
255 func GetHistogramVecFromGatherer(gatherer metrics.Gatherer, metricName string, lvMap map[string]string) (HistogramVec, error) {
256 var metricFamily *dto.MetricFamily
257 m, err := gatherer.Gather()
258 if err != nil {
259 return nil, err
260 }
261 for _, mFamily := range m {
262 if mFamily.GetName() == metricName {
263 metricFamily = mFamily
264 break
265 }
266 }
267
268 if metricFamily == nil {
269 return nil, fmt.Errorf("metric %q not found", metricName)
270 }
271
272 if len(metricFamily.GetMetric()) == 0 {
273 return nil, fmt.Errorf("metric %q is empty", metricName)
274 }
275
276 vec := make(HistogramVec, 0)
277 for _, metric := range metricFamily.GetMetric() {
278 if LabelsMatch(metric, lvMap) {
279 if hist := metric.GetHistogram(); hist != nil {
280 vec = append(vec, &Histogram{hist})
281 }
282 }
283 }
284 return vec, nil
285 }
286
287 func uint64Ptr(u uint64) *uint64 {
288 return &u
289 }
290
291
292 type bucket struct {
293 upperBound float64
294 count float64
295 }
296
297 func bucketQuantile(q float64, buckets []bucket) float64 {
298 if q < 0 {
299 return math.Inf(-1)
300 }
301 if q > 1 {
302 return math.Inf(+1)
303 }
304
305 if len(buckets) < 2 {
306 return math.NaN()
307 }
308
309 rank := q * buckets[len(buckets)-1].count
310 b := sort.Search(len(buckets)-1, func(i int) bool { return buckets[i].count >= rank })
311
312 if b == 0 {
313 return buckets[0].upperBound * (rank / buckets[0].count)
314 }
315
316 if b == len(buckets)-1 && math.IsInf(buckets[b].upperBound, 1) {
317 return buckets[len(buckets)-2].upperBound
318 }
319
320
321 brank := rank - buckets[b-1].count
322 bSize := buckets[b].upperBound - buckets[b-1].upperBound
323 bCount := buckets[b].count - buckets[b-1].count
324
325 return buckets[b-1].upperBound + bSize*(brank/bCount)
326 }
327
328
329
330 func (hist *Histogram) Quantile(q float64) float64 {
331 var buckets []bucket
332
333 for _, bckt := range hist.Bucket {
334 buckets = append(buckets, bucket{
335 count: float64(bckt.GetCumulativeCount()),
336 upperBound: bckt.GetUpperBound(),
337 })
338 }
339
340 if len(buckets) == 0 || buckets[len(buckets)-1].upperBound != math.Inf(+1) {
341
342
343 buckets = append(buckets, bucket{
344 count: float64(hist.GetSampleCount()),
345 upperBound: math.Inf(+1),
346 })
347 }
348
349 return bucketQuantile(q, buckets)
350 }
351
352
353 func (hist *Histogram) Average() float64 {
354 return hist.GetSampleSum() / float64(hist.GetSampleCount())
355 }
356
357
358 func (hist *Histogram) Validate() error {
359 if hist.SampleCount == nil || hist.GetSampleCount() == 0 {
360 return fmt.Errorf("nil or empty histogram SampleCount")
361 }
362
363 if hist.SampleSum == nil || hist.GetSampleSum() == 0 {
364 return fmt.Errorf("nil or empty histogram SampleSum")
365 }
366
367 for _, bckt := range hist.Bucket {
368 if bckt == nil {
369 return fmt.Errorf("empty histogram bucket")
370 }
371 if bckt.UpperBound == nil || bckt.GetUpperBound() < 0 {
372 return fmt.Errorf("nil or negative histogram bucket UpperBound")
373 }
374 }
375
376 return nil
377 }
378
379
380 func GetGaugeMetricValue(m metrics.GaugeMetric) (float64, error) {
381 metricProto := &dto.Metric{}
382 if err := m.Write(metricProto); err != nil {
383 return 0, fmt.Errorf("error writing m: %v", err)
384 }
385 return metricProto.Gauge.GetValue(), nil
386 }
387
388
389 func GetCounterMetricValue(m metrics.CounterMetric) (float64, error) {
390 metricProto := &dto.Metric{}
391 if err := m.(metrics.Metric).Write(metricProto); err != nil {
392 return 0, fmt.Errorf("error writing m: %v", err)
393 }
394 return metricProto.Counter.GetValue(), nil
395 }
396
397
398 func GetHistogramMetricValue(m metrics.ObserverMetric) (float64, error) {
399 metricProto := &dto.Metric{}
400 if err := m.(metrics.Metric).Write(metricProto); err != nil {
401 return 0, fmt.Errorf("error writing m: %v", err)
402 }
403 return metricProto.Histogram.GetSampleSum(), nil
404 }
405
406
407 func GetHistogramMetricCount(m metrics.ObserverMetric) (uint64, error) {
408 metricProto := &dto.Metric{}
409 if err := m.(metrics.Metric).Write(metricProto); err != nil {
410 return 0, fmt.Errorf("error writing m: %v", err)
411 }
412 return metricProto.Histogram.GetSampleCount(), nil
413 }
414
415
416 func LabelsMatch(metric *dto.Metric, labelFilter map[string]string) bool {
417 metricLabels := map[string]string{}
418
419 for _, labelPair := range metric.Label {
420 metricLabels[labelPair.GetName()] = labelPair.GetValue()
421 }
422
423
424 if len(labelFilter) > len(metricLabels) {
425 return false
426 }
427
428 for labelName, labelValue := range labelFilter {
429 if value, ok := metricLabels[labelName]; !ok || value != labelValue {
430 return false
431 }
432 }
433
434 return true
435 }
436
View as plain text