Skip to content

Commit 07608d4

Browse files
committed
Added tests and options for RT.
Signed-off-by: bwplotka <[email protected]>
1 parent 4e2a055 commit 07608d4

File tree

5 files changed

+216
-74
lines changed

5 files changed

+216
-74
lines changed

prometheus/promhttp/instrument_client.go

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
3838
//
3939
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
4040
func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripper) RoundTripperFunc {
41-
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
41+
return func(r *http.Request) (*http.Response, error) {
4242
gauge.Inc()
4343
defer gauge.Dec()
4444
return next.RoundTrip(r)
45-
})
45+
}
4646
}
4747

4848
// InstrumentRoundTripperCounter is a middleware that wraps the provided
@@ -59,22 +59,29 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp
5959
// If the wrapped RoundTripper panics or returns a non-nil error, the Counter
6060
// is not incremented.
6161
//
62+
// Use with WithExemplarFromContext to instrument the exemplars on the counter of requests.
63+
//
6264
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
6365
func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper, opts ...Option) RoundTripperFunc {
64-
rtOpts := &option{}
66+
rtOpts := defaultOptions()
6567
for _, o := range opts {
66-
o(rtOpts)
68+
o.apply(rtOpts)
6769
}
6870

6971
code, method := checkLabels(counter)
7072

71-
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
73+
return func(r *http.Request) (*http.Response, error) {
7274
resp, err := next.RoundTrip(r)
7375
if err == nil {
76+
exemplarAdd(
77+
counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)),
78+
1,
79+
rtOpts.getExemplarFn(r.Context()),
80+
)
7481
counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)).Inc()
7582
}
7683
return resp, err
77-
})
84+
}
7885
}
7986

8087
// InstrumentRoundTripperDuration is a middleware that wraps the provided
@@ -94,24 +101,30 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou
94101
// If the wrapped RoundTripper panics or returns a non-nil error, no values are
95102
// reported.
96103
//
104+
// Use with WithExemplarFromContext to instrument the exemplars on the duration histograms.
105+
//
97106
// Note that this method is only guaranteed to never observe negative durations
98107
// if used with Go1.9+.
99108
func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper, opts ...Option) RoundTripperFunc {
100-
rtOpts := &option{}
109+
rtOpts := defaultOptions()
101110
for _, o := range opts {
102-
o(rtOpts)
111+
o.apply(rtOpts)
103112
}
104113

105114
code, method := checkLabels(obs)
106115

107-
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
116+
return func(r *http.Request) (*http.Response, error) {
108117
start := time.Now()
109118
resp, err := next.RoundTrip(r)
110119
if err == nil {
111-
obs.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)).Observe(time.Since(start).Seconds())
120+
exemplarObserve(
121+
obs.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)),
122+
time.Since(start).Seconds(),
123+
rtOpts.getExemplarFn(r.Context()),
124+
)
112125
}
113126
return resp, err
114-
})
127+
}
115128
}
116129

117130
// InstrumentTrace is used to offer flexibility in instrumenting the available
@@ -149,7 +162,7 @@ type InstrumentTrace struct {
149162
//
150163
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
151164
func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc {
152-
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
165+
return func(r *http.Request) (*http.Response, error) {
153166
start := time.Now()
154167

155168
trace := &httptrace.ClientTrace{
@@ -231,5 +244,5 @@ func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) Ro
231244
r = r.WithContext(httptrace.WithClientTrace(r.Context(), trace))
232245

233246
return next.RoundTrip(r)
234-
})
247+
}
235248
}

prometheus/promhttp/instrument_client_test.go

Lines changed: 102 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@ import (
1818
"log"
1919
"net/http"
2020
"net/http/httptest"
21+
"reflect"
22+
"sort"
2123
"strings"
2224
"testing"
2325
"time"
2426

2527
"github.com/prometheus/client_golang/prometheus"
28+
dto "github.com/prometheus/client_model/go"
29+
"google.golang.org/protobuf/proto"
2630
)
2731

28-
func makeInstrumentedClient() (*http.Client, *prometheus.Registry) {
32+
func makeInstrumentedClient(opts ...Option) (*http.Client, *prometheus.Registry) {
2933
client := http.DefaultClient
3034
client.Timeout = 1 * time.Second
3135

@@ -91,13 +95,91 @@ func makeInstrumentedClient() (*http.Client, *prometheus.Registry) {
9195
client.Transport = InstrumentRoundTripperInFlight(inFlightGauge,
9296
InstrumentRoundTripperCounter(counter,
9397
InstrumentRoundTripperTrace(trace,
94-
InstrumentRoundTripperDuration(histVec, http.DefaultTransport),
98+
InstrumentRoundTripperDuration(histVec, http.DefaultTransport, opts...),
9599
),
96-
),
100+
opts...),
97101
)
98102
return client, reg
99103
}
100104

105+
func labelsToLabelPair(l prometheus.Labels) []*dto.LabelPair {
106+
ret := make([]*dto.LabelPair, 0, len(l))
107+
for k, v := range l {
108+
ret = append(ret, &dto.LabelPair{Name: proto.String(k), Value: proto.String(v)})
109+
}
110+
sort.Slice(ret, func(i, j int) bool {
111+
return *ret[i].Name < *ret[j].Name
112+
})
113+
return ret
114+
}
115+
116+
func assetMetricAndExemplars(
117+
t *testing.T,
118+
reg *prometheus.Registry,
119+
expectedNumMetrics int,
120+
expectedExemplar []*dto.LabelPair,
121+
) {
122+
t.Helper()
123+
124+
mfs, err := reg.Gather()
125+
if err != nil {
126+
t.Fatal(err)
127+
}
128+
if want, got := expectedNumMetrics, len(mfs); want != got {
129+
t.Fatalf("unexpected number of metric families gathered, want %d, got %d", want, got)
130+
}
131+
132+
for _, mf := range mfs {
133+
if len(mf.Metric) == 0 {
134+
t.Errorf("metric family %s must not be empty", mf.GetName())
135+
}
136+
for _, m := range mf.GetMetric() {
137+
if c := m.GetCounter(); c != nil {
138+
if len(expectedExemplar) == 0 {
139+
if c.Exemplar != nil {
140+
t.Errorf("expected no exemplar on the counter %v%v, got %v", mf.GetName(), m.Label, c.Exemplar.String())
141+
}
142+
continue
143+
}
144+
145+
if c.Exemplar == nil {
146+
t.Errorf("expected exemplar %v on the counter %v%v, got none", expectedExemplar, mf.GetName(), m.Label)
147+
continue
148+
}
149+
if got := c.Exemplar.Label; !reflect.DeepEqual(expectedExemplar, got) {
150+
t.Errorf("expected exemplar %v on the counter %v%v, got %v", expectedExemplar, mf.GetName(), m.Label, got)
151+
}
152+
continue
153+
}
154+
if h := m.GetHistogram(); h != nil {
155+
found := false
156+
for _, b := range h.GetBucket() {
157+
if len(expectedExemplar) == 0 {
158+
if b.Exemplar != nil {
159+
t.Errorf("expected no exemplar on histogram %v%v bkt %v, got %v", mf.GetName(), m.Label, b.GetUpperBound(), b.Exemplar.String())
160+
}
161+
continue
162+
}
163+
164+
if b.Exemplar == nil {
165+
continue
166+
}
167+
if got := b.Exemplar.Label; !reflect.DeepEqual(expectedExemplar, got) {
168+
t.Errorf("expected exemplar %v on the histogram %v%v on bkt %v, got %v", expectedExemplar, mf.GetName(), m.Label, b.GetUpperBound(), got)
169+
continue
170+
}
171+
found = true
172+
break
173+
}
174+
175+
if len(expectedExemplar) > 0 && !found {
176+
t.Errorf("expected exemplar %v on at least one bucket of the histogram %v%v, got none", expectedExemplar, mf.GetName(), m.Label)
177+
}
178+
}
179+
}
180+
}
181+
}
182+
101183
func TestClientMiddlewareAPI(t *testing.T) {
102184
client, reg := makeInstrumentedClient()
103185
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -111,21 +193,28 @@ func TestClientMiddlewareAPI(t *testing.T) {
111193
}
112194
defer resp.Body.Close()
113195

114-
mfs, err := reg.Gather()
196+
assetMetricAndExemplars(t, reg, 3, nil)
197+
}
198+
199+
func TestClientMiddlewareAPI_WithExemplars(t *testing.T) {
200+
exemplar := prometheus.Labels{"traceID": "example situation observed by this metric"}
201+
202+
client, reg := makeInstrumentedClient(WithExemplarFromContext(func(_ context.Context) prometheus.Labels { return exemplar }))
203+
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
204+
w.WriteHeader(http.StatusOK)
205+
}))
206+
defer backend.Close()
207+
208+
resp, err := client.Get(backend.URL)
115209
if err != nil {
116210
t.Fatal(err)
117211
}
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-
}
212+
defer resp.Body.Close()
213+
214+
assetMetricAndExemplars(t, reg, 3, labelsToLabelPair(exemplar))
126215
}
127216

128-
func TestClientMiddlewareAPIWithRequestContext(t *testing.T) {
217+
func TestClientMiddlewareAPI_WithRequestContext(t *testing.T) {
129218
client, reg := makeInstrumentedClient()
130219
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
131220
w.WriteHeader(http.StatusOK)

0 commit comments

Comments
 (0)