1 package promassert
2
3 import (
4 "errors"
5 "fmt"
6 "math"
7 "regexp"
8 "testing"
9
10 promi "github.com/prometheus/client_model/go"
11 "github.com/prometheus/common/expfmt"
12 )
13
14
15 var Verbose = true
16
17
18 var ErrEmptyMetric = errors.New("metric is empty")
19
20
21 type ParsedMetrics []ParsedMetric
22
23
24 type ParsedMetric struct {
25 Value float64
26 Labels map[string]string
27 }
28
29
30
31
32
33
34
35
36
37
38 func Counter(name string) ParsedMetrics {
39 return parseMetric(name, promi.MetricType_COUNTER)
40 }
41
42
43
44
45
46
47
48
49
50
51 func Gauge(name string) ParsedMetrics {
52 return parseMetric(name, promi.MetricType_GAUGE)
53 }
54
55
56
57
58 func parseMetric(name string, mtype promi.MetricType) ParsedMetrics {
59 var ret []ParsedMetric
60
61 var prombody, err = ScrapePrometheusMetrics()
62 if err != nil {
63 panic(fmt.Sprintf("got error when scraping metrics: %v", err))
64 }
65
66 var tp expfmt.TextParser
67 mf, err := tp.TextToMetricFamilies(prombody)
68 if err != nil {
69
70 panic(fmt.Sprintf("could not parse metrics from prometheus handler: %v", err))
71 }
72
73 metf, found := mf[name]
74 if !found {
75
76
77 return ret
78 } else if mtype != metf.GetType() {
79 panic(fmt.Sprintf("expected metric %q to be type %q but got type %q", name, mtype, metf.GetType()))
80 }
81
82 for _, met := range metf.GetMetric() {
83 var pm = ParsedMetric{Labels: make(map[string]string)}
84 switch mtype {
85 case promi.MetricType_COUNTER:
86 pm.Value = met.GetCounter().GetValue()
87 case promi.MetricType_GAUGE:
88 pm.Value = met.GetGauge().GetValue()
89 }
90
91 for _, lp := range met.GetLabel() {
92 pm.Labels[lp.GetName()] = lp.GetValue()
93 }
94 ret = append(ret, pm)
95 }
96 return ret
97 }
98
99 var ErrFoldedNaN = errors.New("folded float values cannot contain NaN")
100 var ErrFoldedInf = errors.New("folded float values cannot contain Inf")
101
102
103
104
105
106
107
108
109 func (p ParsedMetrics) Fold() (float64, error) {
110 if len(p) == 0 {
111 return 0, ErrEmptyMetric
112 }
113 var sum float64
114 for _, pm := range p {
115 if math.IsNaN(pm.Value) {
116 return 0, ErrFoldedNaN
117 } else if math.IsInf(pm.Value, 0) {
118 return 0, ErrFoldedInf
119 }
120 sum += pm.Value
121 }
122 return sum, nil
123 }
124
125
126 func (p ParsedMetrics) TryFold() float64 {
127 value, err := p.Fold()
128 if err != nil {
129 return 0
130 }
131 return value
132 }
133
134
135
136
137 func (p ParsedMetrics) IsNaN(t *testing.T) bool {
138 if len(p) == 0 {
139 if t != nil {
140 t.Error(ErrEmptyMetric)
141 }
142 return false
143 }
144 var passed = true
145 for _, pm := range p {
146 if !math.IsNaN(pm.Value) {
147 passed = false
148 if t != nil {
149 t.Errorf("value %f is not NaN", pm.Value)
150 }
151 }
152 }
153 if t != nil && Verbose && passed {
154 t.Logf("assertion passed: the values of the metric are NaN")
155 }
156 return passed
157 }
158
159
160
161
162
163
164
165 func (p ParsedMetrics) IsInf(t *testing.T, sign int) bool {
166 if len(p) == 0 {
167 if t != nil {
168 t.Error(ErrEmptyMetric)
169 }
170 return false
171 }
172
173
174 var s string
175 if sign > 0 {
176 s = "+"
177 } else if sign < 0 {
178 s = "-"
179 }
180
181 var passed = true
182 for _, pm := range p {
183 if !math.IsInf(pm.Value, sign) {
184 passed = false
185 if t != nil {
186 t.Errorf("value %f is not %sInf", pm.Value, s)
187 }
188 }
189 }
190 if t != nil && Verbose && passed {
191 t.Logf("assertion passed: the values of the metric are %sInf", s)
192 }
193 return passed
194 }
195
196
197
198
199
200
201
202
203
204 func (p ParsedMetrics) Exists(t *testing.T) bool {
205 if len(p) != 0 {
206 if t != nil && Verbose {
207 t.Logf("assertion passed. the metric exists")
208 }
209 return true
210 }
211
212 if t != nil {
213 t.Errorf("the metric does not exist")
214 }
215 return false
216 }
217
218
219 func (p ParsedMetrics) NotExists(t *testing.T) bool {
220 if len(p) == 0 {
221 if t != nil && Verbose {
222 t.Logf("assertion passed: the metric does not exist")
223 }
224 return true
225 }
226
227 if t != nil {
228 t.Errorf("the metric exists")
229 }
230 return false
231 }
232
233
234
235
236 func (p ParsedMetrics) Equals(t *testing.T, total float64) bool {
237 result, err := p.Fold()
238 if err != nil {
239 if t != nil {
240 t.Error(err)
241 }
242 return false
243 } else if result != total {
244 if t != nil {
245 t.Errorf("folded metric value %f does not equal expected total %f", result, total)
246 }
247 return false
248 }
249
250 if t != nil && Verbose {
251 t.Logf("assertion passed. folded metric value %f equals expected total %f", result, total)
252 }
253 return true
254 }
255
256
257
258
259 func (p ParsedMetrics) NotEquals(t *testing.T, total float64) bool {
260 result, err := p.Fold()
261 if err != nil {
262 if t != nil {
263 t.Error(err)
264 }
265 return false
266 } else if result == total {
267 if t != nil {
268 t.Errorf("folded metric value %f equals provided total %f", result, total)
269 }
270 return false
271 }
272
273 if t != nil && Verbose {
274 t.Logf("assertion passed. folded metric value %f does not equal provided total %f", result, total)
275 }
276 return true
277 }
278
279
280
281
282 func (p ParsedMetrics) GreaterThan(t *testing.T, total float64) bool {
283 result, err := p.Fold()
284 if err != nil {
285 if t != nil {
286 t.Error(err)
287 }
288 return false
289 } else if result <= total {
290 if t != nil {
291 t.Errorf("folded metric value %f is not greater than expected total %f", result, total)
292 }
293 return false
294 }
295
296 if t != nil && Verbose {
297 t.Logf("assertion passed. folded metric value %f is greater than provided total %f", result, total)
298 }
299 return true
300 }
301
302
303
304
305 func (p ParsedMetrics) GreaterThanOrEquals(t *testing.T, total float64) bool {
306 result, err := p.Fold()
307 if err != nil {
308 if t != nil {
309 t.Error(err)
310 }
311 return false
312 } else if result < total {
313 if t != nil {
314 t.Errorf("folded metric value %f is not greater than or equal to expected total %f", result, total)
315 }
316 return false
317 }
318
319 if t != nil && Verbose {
320 t.Logf("assertion passed. folded metric value %f is greater than or equal to provided total %f", result, total)
321 }
322 return true
323 }
324
325
326
327
328 func (p ParsedMetrics) LessThan(t *testing.T, total float64) bool {
329 result, err := p.Fold()
330 if err != nil {
331 if t != nil {
332 t.Error(err)
333 }
334 return false
335 } else if result >= total {
336 if t != nil {
337 t.Errorf("folded metric value %f is not less than expected total %f", result, total)
338 }
339 return false
340 }
341
342 if t != nil && Verbose {
343 t.Logf("assertion passed. folded metric value %f is less than provided total %f", result, total)
344 }
345 return true
346 }
347
348
349
350
351 func (p ParsedMetrics) LessThanOrEquals(t *testing.T, total float64) bool {
352 result, err := p.Fold()
353 if err != nil {
354 if t != nil {
355 t.Error(err)
356 }
357 return false
358 } else if result > total {
359 if t != nil {
360 t.Errorf("folded metric value %f is not less than or equal to expected total %f", result, total)
361 }
362 return false
363 }
364
365 if t != nil && Verbose {
366 t.Logf("assertion passed. folded metric value %f is less than or equal to provided total %f", result, total)
367 }
368 return true
369 }
370
371
372 func (p ParsedMetrics) LabelKeysExist(t *testing.T, keys ...string) bool {
373 if len(p) == 0 {
374 if t != nil {
375 t.Error(ErrEmptyMetric)
376 }
377 return false
378 }
379 var missing = make(map[string]bool)
380 for _, pm := range p {
381 for _, k := range keys {
382 if _, found := pm.Labels[k]; !found {
383 missing[k] = true
384 }
385 }
386 }
387 if len(missing) != 0 {
388
389 var mk []string
390 for k := range missing {
391 mk = append(mk, k)
392 }
393 if t != nil {
394 t.Errorf("the metric is missing the following keys: %v", mk)
395 }
396 } else if t != nil && Verbose {
397 t.Logf("assertion passed: the keys %v are present in the metric", keys)
398 }
399 return len(missing) == 0
400 }
401
402
403 func (p ParsedMetrics) WithRationalValues() ParsedMetrics {
404 var filtered []ParsedMetric
405 for _, pm := range p {
406 if !math.IsNaN(pm.Value) && !math.IsInf(pm.Value, 0) {
407 filtered = append(filtered, pm)
408 }
409 }
410 return filtered
411 }
412
413
414
415
416 func (p ParsedMetrics) With(labels map[string]string) ParsedMetrics {
417 var filtered []ParsedMetric
418 for _, pm := range p {
419 var didNotMatch bool
420 for k, v := range labels {
421 if pmv, found := pm.Labels[k]; !found || pmv != v {
422 didNotMatch = true
423 break
424 }
425 }
426 if !didNotMatch {
427 filtered = append(filtered, pm)
428 }
429 }
430 return filtered
431 }
432
433
434
435
436
437
438
439
440
441
442
443
444 func (p ParsedMetrics) WithValues(labelValues map[string][]string) ParsedMetrics {
445 var filtered []ParsedMetric
446
447
448 var lm = make(map[string]map[string]bool)
449 for k, vs := range labelValues {
450 if 0 == len(vs) {
451 panic("the label values must not be an empty slice")
452 }
453 lm[k] = make(map[string]bool)
454 for _, v := range vs {
455 lm[k][v] = true
456 }
457 }
458
459 for _, pm := range p {
460 var didNotMatch bool
461 for k, vm := range lm {
462 if pmv, found := pm.Labels[k]; !found || !vm[pmv] {
463 didNotMatch = true
464 break
465 }
466 }
467 if !didNotMatch {
468 filtered = append(filtered, pm)
469 }
470 }
471 return filtered
472 }
473
474
475
476
477
478
479
480
481
482 func (p ParsedMetrics) WithRegexp(lrxp map[string]*regexp.Regexp) ParsedMetrics {
483 var filtered []ParsedMetric
484 for _, pm := range p {
485 var didNotMatch bool
486 for k, rxpv := range lrxp {
487 if pmv, found := pm.Labels[k]; !found || !rxpv.MatchString(pmv) {
488 didNotMatch = true
489 break
490 }
491 }
492 if !didNotMatch {
493 filtered = append(filtered, pm)
494 }
495 }
496 return filtered
497 }
498
View as plain text