@@ -18,14 +18,19 @@ import (
18
18
"log"
19
19
"net/http"
20
20
"net/http/httptest"
21
+ "reflect"
22
+ "sort"
21
23
"strings"
22
24
"testing"
23
25
"time"
24
26
25
27
"github.com/prometheus/client_golang/prometheus"
28
+
29
+ dto "github.com/prometheus/client_model/go"
30
+ "google.golang.org/protobuf/proto"
26
31
)
27
32
28
- func makeInstrumentedClient () (* http.Client , * prometheus.Registry ) {
33
+ func makeInstrumentedClient (opts ... Option ) (* http.Client , * prometheus.Registry ) {
29
34
client := http .DefaultClient
30
35
client .Timeout = 1 * time .Second
31
36
@@ -91,13 +96,91 @@ func makeInstrumentedClient() (*http.Client, *prometheus.Registry) {
91
96
client .Transport = InstrumentRoundTripperInFlight (inFlightGauge ,
92
97
InstrumentRoundTripperCounter (counter ,
93
98
InstrumentRoundTripperTrace (trace ,
94
- InstrumentRoundTripperDuration (histVec , http .DefaultTransport ),
99
+ InstrumentRoundTripperDuration (histVec , http .DefaultTransport , opts ... ),
95
100
),
96
- ),
101
+ opts ... ),
97
102
)
98
103
return client , reg
99
104
}
100
105
106
+ func labelsToLabelPair (l prometheus.Labels ) []* dto.LabelPair {
107
+ ret := make ([]* dto.LabelPair , 0 , len (l ))
108
+ for k , v := range l {
109
+ ret = append (ret , & dto.LabelPair {Name : proto .String (k ), Value : proto .String (v )})
110
+ }
111
+ sort .Slice (ret , func (i , j int ) bool {
112
+ return * ret [i ].Name < * ret [j ].Name
113
+ })
114
+ return ret
115
+ }
116
+
117
+ func assetMetricAndExemplars (
118
+ t * testing.T ,
119
+ reg * prometheus.Registry ,
120
+ expectedNumMetrics int ,
121
+ expectedExemplar []* dto.LabelPair ,
122
+ ) {
123
+ t .Helper ()
124
+
125
+ mfs , err := reg .Gather ()
126
+ if err != nil {
127
+ t .Fatal (err )
128
+ }
129
+ if want , got := expectedNumMetrics , len (mfs ); want != got {
130
+ t .Fatalf ("unexpected number of metric families gathered, want %d, got %d" , want , got )
131
+ }
132
+
133
+ for _ , mf := range mfs {
134
+ if len (mf .Metric ) == 0 {
135
+ t .Errorf ("metric family %s must not be empty" , mf .GetName ())
136
+ }
137
+ for _ , m := range mf .GetMetric () {
138
+ if c := m .GetCounter (); c != nil {
139
+ if len (expectedExemplar ) == 0 {
140
+ if c .Exemplar != nil {
141
+ t .Errorf ("expected no exemplar on the counter %v%v, got %v" , mf .GetName (), m .Label , c .Exemplar .String ())
142
+ }
143
+ continue
144
+ }
145
+
146
+ if c .Exemplar == nil {
147
+ t .Errorf ("expected exemplar %v on the counter %v%v, got none" , expectedExemplar , mf .GetName (), m .Label )
148
+ continue
149
+ }
150
+ if got := c .Exemplar .Label ; ! reflect .DeepEqual (expectedExemplar , got ) {
151
+ t .Errorf ("expected exemplar %v on the counter %v%v, got %v" , expectedExemplar , mf .GetName (), m .Label , got )
152
+ }
153
+ continue
154
+ }
155
+ if h := m .GetHistogram (); h != nil {
156
+ found := false
157
+ for _ , b := range h .GetBucket () {
158
+ if len (expectedExemplar ) == 0 {
159
+ if b .Exemplar != nil {
160
+ t .Errorf ("expected no exemplar on histogram %v%v bkt %v, got %v" , mf .GetName (), m .Label , b .GetUpperBound (), b .Exemplar .String ())
161
+ }
162
+ continue
163
+ }
164
+
165
+ if b .Exemplar == nil {
166
+ continue
167
+ }
168
+ if got := b .Exemplar .Label ; ! reflect .DeepEqual (expectedExemplar , got ) {
169
+ t .Errorf ("expected exemplar %v on the histogram %v%v on bkt %v, got %v" , expectedExemplar , mf .GetName (), m .Label , b .GetUpperBound (), got )
170
+ continue
171
+ }
172
+ found = true
173
+ break
174
+ }
175
+
176
+ if len (expectedExemplar ) > 0 && ! found {
177
+ t .Errorf ("expected exemplar %v on at least one bucket of the histogram %v%v, got none" , expectedExemplar , mf .GetName (), m .Label )
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+
101
184
func TestClientMiddlewareAPI (t * testing.T ) {
102
185
client , reg := makeInstrumentedClient ()
103
186
backend := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
@@ -111,21 +194,28 @@ func TestClientMiddlewareAPI(t *testing.T) {
111
194
}
112
195
defer resp .Body .Close ()
113
196
114
- mfs , err := reg .Gather ()
197
+ assetMetricAndExemplars (t , reg , 3 , nil )
198
+ }
199
+
200
+ func TestClientMiddlewareAPI_WithExemplars (t * testing.T ) {
201
+ exemplar := prometheus.Labels {"traceID" : "example situation observed by this metric" }
202
+
203
+ client , reg := makeInstrumentedClient (WithExemplarFromContext (func (_ context.Context ) prometheus.Labels { return exemplar }))
204
+ backend := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
205
+ w .WriteHeader (http .StatusOK )
206
+ }))
207
+ defer backend .Close ()
208
+
209
+ resp , err := client .Get (backend .URL )
115
210
if err != nil {
116
211
t .Fatal (err )
117
212
}
118
- if want , got := 3 , len (mfs ); want != got {
119
- t .Fatalf ("unexpected number of metric families gathered, want %d, got %d" , want , got )
120
- }
121
- for _ , mf := range mfs {
122
- if len (mf .Metric ) == 0 {
123
- t .Errorf ("metric family %s must not be empty" , mf .GetName ())
124
- }
125
- }
213
+ defer resp .Body .Close ()
214
+
215
+ assetMetricAndExemplars (t , reg , 3 , labelsToLabelPair (exemplar ))
126
216
}
127
217
128
- func TestClientMiddlewareAPIWithRequestContext (t * testing.T ) {
218
+ func TestClientMiddlewareAPI_WithRequestContext (t * testing.T ) {
129
219
client , reg := makeInstrumentedClient ()
130
220
backend := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
131
221
w .WriteHeader (http .StatusOK )
0 commit comments