/
promtest.go
146 lines (123 loc) · 4.12 KB
/
promtest.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Package promtest provides helpers for parsing and extracting prometheus metrics.
// These functions are only intended to be called from test files,
// as there is a dependency on the standard library testing package.
package promtest
import (
"fmt"
"io"
"net/http"
"strings"
"testing"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/expfmt"
)
// FromHTTPResponse parses the prometheus metrics from the given *http.Response.
// It relies on properly set response headers to correctly parse.
// It will unconditionally close the response body.
//
// This is particularly helpful when testing the output of the /metrics endpoint of a service.
// However, for comprehensive testing of metrics, it usually makes more sense to
// add collectors to a registry and call Registry.Gather to get the metrics without involving HTTP.
func FromHTTPResponse(r *http.Response) ([]*dto.MetricFamily, error) {
defer r.Body.Close()
dec := expfmt.NewDecoder(r.Body, expfmt.ResponseFormat(r.Header))
var mfs []*dto.MetricFamily
for {
mf := new(dto.MetricFamily)
if err := dec.Decode(mf); err != nil {
if err == io.EOF {
break
} else {
return nil, err
}
}
mfs = append(mfs, mf)
}
return mfs, nil
}
// FindMetric iterates through mfs to find the first metric family matching name.
// If a metric family matches, then the metrics inside the family are searched,
// and the first metric whose labels match the given labels are returned.
// If no matches are found, FindMetric returns nil.
//
// FindMetric assumes that the labels on the metric family are well formed,
// i.e. there are no duplicate label names, and the label values are not empty strings.
func FindMetric(mfs []*dto.MetricFamily, name string, labels map[string]string) *dto.Metric {
_, m := findMetric(mfs, name, labels)
return m
}
// MustFindMetric returns the matching metric, or if no matching metric could be found,
// it calls tb.Log with helpful output of what was actually available, before calling tb.FailNow.
func MustFindMetric(tb testing.TB, mfs []*dto.MetricFamily, name string, labels map[string]string) *dto.Metric {
tb.Helper()
fam, m := findMetric(mfs, name, labels)
if fam == nil {
tb.Logf("metric family with name %q not found", name)
tb.Log("available names:")
for _, mf := range mfs {
tb.Logf("\t%s", mf.GetName())
}
tb.FailNow()
return nil // Need an explicit return here for test.
}
if m == nil {
tb.Logf("found metric family with name %q, but metric with labels %v not found", name, labels)
tb.Logf("available labels on metric family %q:", name)
for _, m := range fam.Metric {
pairs := make([]string, len(m.Label))
for i, l := range m.Label {
pairs[i] = fmt.Sprintf("%q: %q", l.GetName(), l.GetValue())
}
tb.Logf("\t%s", strings.Join(pairs, ", "))
}
tb.FailNow()
return nil // Need an explicit return here for test.
}
return m
}
// findMetric is a helper that returns the matching family and the matching metric.
// The exported FindMetric function specifically only finds the metric, not the family,
// but for test it is more helpful to identify whether the family was matched.
func findMetric(mfs []*dto.MetricFamily, name string, labels map[string]string) (*dto.MetricFamily, *dto.Metric) {
var fam *dto.MetricFamily
for _, mf := range mfs {
if mf.GetName() == name {
fam = mf
break
}
}
if fam == nil {
// No family matching the name.
return nil, nil
}
for _, m := range fam.Metric {
if len(m.Label) != len(labels) {
continue
}
match := true
for _, l := range m.Label {
if labels[l.GetName()] != l.GetValue() {
match = false
break
}
}
if !match {
continue
}
// All labels matched.
return fam, m
}
// Didn't find a metric whose labels all matched.
return fam, nil
}
// MustGather calls g.Gather and calls tb.Fatal if there was an error.
func MustGather(tb testing.TB, g prometheus.Gatherer) []*dto.MetricFamily {
tb.Helper()
mfs, err := g.Gather()
if err != nil {
tb.Fatalf("error while gathering metrics: %v", err)
return nil // Need an explicit return here for test.
}
return mfs
}