Skip to content

Commit d300d5c

Browse files
stuartnelson3beorn7
authored andcommitted
Instrument RoundTripper via middleware (#295)
Instrument RoundTripper via middleware
1 parent 7d94842 commit d300d5c

File tree

6 files changed

+438
-2
lines changed

6 files changed

+438
-2
lines changed

.travis.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ language: go
44
go:
55
- 1.6.3
66
- 1.7
7+
- 1.8.1
78

89
script:
9-
- go test -short ./...
10+
- go test -short ./...

prometheus/promhttp/http.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
// via middleware. Middleware wrappers follow the naming scheme
2525
// InstrumentHandlerX, where X describes the intended use of the middleware.
2626
// See each function's doc comment for specific details.
27+
//
28+
// Finally, the package allows for an http.RoundTripper to be instrumented via
29+
// middleware. Middleware wrappers follow the naming scheme
30+
// InstrumentRoundTripperX, where X describes the intended use of the
31+
// middleware. See each function's doc comment for specific details.
2732
package promhttp
2833

2934
import (
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2017 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package promhttp
15+
16+
import (
17+
"net/http"
18+
"time"
19+
20+
"github.com/prometheus/client_golang/prometheus"
21+
)
22+
23+
// The RoundTripperFunc type is an adapter to allow the use of ordinary
24+
// functions as RoundTrippers. If f is a function with the appropriate
25+
// signature, RountTripperFunc(f) is a RoundTripper that calls f.
26+
type RoundTripperFunc func(req *http.Request) (*http.Response, error)
27+
28+
// RoundTrip implements the RoundTripper interface.
29+
func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
30+
return rt(r)
31+
}
32+
33+
// InstrumentRoundTripperInFlight is a middleware that wraps the provided
34+
// http.RoundTripper. It sets the provided prometheus.Gauge to the number of
35+
// requests currently handled by the wrapped http.RoundTripper.
36+
//
37+
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
38+
func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripper) RoundTripperFunc {
39+
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
40+
gauge.Inc()
41+
defer gauge.Dec()
42+
return next.RoundTrip(r)
43+
})
44+
}
45+
46+
// InstrumentRoundTripperCounter is a middleware that wraps the provided
47+
// http.RoundTripper to observe the request result with the provided CounterVec.
48+
// The CounterVec must have zero, one, or two labels. The only allowed label
49+
// names are "code" and "method". The function panics if any other instance
50+
// labels are provided. Partitioning of the CounterVec happens by HTTP status
51+
// code and/or HTTP method if the respective instance label names are present
52+
// in the CounterVec. For unpartitioned counting, use a CounterVec with
53+
// zero labels.
54+
//
55+
// If the wrapped RoundTripper panics or returns a non-nil error, the Counter
56+
// is not incremented.
57+
//
58+
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
59+
func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper) RoundTripperFunc {
60+
code, method := checkLabels(counter)
61+
62+
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
63+
resp, err := next.RoundTrip(r)
64+
if err == nil {
65+
counter.With(labels(code, method, r.Method, resp.StatusCode)).Inc()
66+
}
67+
return resp, err
68+
})
69+
}
70+
71+
// InstrumentRoundTripperDuration is a middleware that wraps the provided
72+
// http.RoundTripper to observe the request duration with the provided ObserverVec.
73+
// The ObserverVec must have zero, one, or two labels. The only allowed label
74+
// names are "code" and "method". The function panics if any other instance
75+
// labels are provided. The Observe method of the Observer in the ObserverVec
76+
// is called with the request duration in seconds. Partitioning happens by HTTP
77+
// status code and/or HTTP method if the respective instance label names are
78+
// present in the ObserverVec. For unpartitioned observations, use an
79+
// ObserverVec with zero labels. Note that partitioning of Histograms is
80+
// expensive and should be used judiciously.
81+
//
82+
// If the wrapped RoundTripper panics or returns a non-nil error, no values are
83+
// reported.
84+
func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper) RoundTripperFunc {
85+
code, method := checkLabels(obs)
86+
87+
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
88+
start := time.Now()
89+
resp, err := next.RoundTrip(r)
90+
if err == nil {
91+
obs.With(labels(code, method, r.Method, resp.StatusCode)).Observe(time.Since(start).Seconds())
92+
}
93+
return resp, err
94+
})
95+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright 2017 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
// +build go1.8
15+
16+
package promhttp
17+
18+
import (
19+
"context"
20+
"crypto/tls"
21+
"net/http"
22+
"net/http/httptrace"
23+
"time"
24+
)
25+
26+
// InstrumentTrace is used to offer flexibility in instrumenting the available
27+
// httptrace.ClientTrace hook functions. Each function is passed a float64
28+
// representing the time in seconds since the start of the http request. A user
29+
// may choose to use separately buckets Histograms, or implement custom
30+
// instance labels on a per function basis.
31+
type InstrumentTrace struct {
32+
GotConn func(float64)
33+
PutIdleConn func(float64)
34+
GotFirstResponseByte func(float64)
35+
Got100Continue func(float64)
36+
DNSStart func(float64)
37+
DNSDone func(float64)
38+
ConnectStart func(float64)
39+
ConnectDone func(float64)
40+
TLSHandshakeStart func(float64)
41+
TLSHandshakeDone func(float64)
42+
WroteHeaders func(float64)
43+
Wait100Continue func(float64)
44+
WroteRequest func(float64)
45+
}
46+
47+
// InstrumentRoundTripperTrace is a middleware that wraps the provided
48+
// RoundTripper and reports times to hook functions provided in the
49+
// InstrumentTrace struct. Hook functions that are not present in the provided
50+
// InstrumentTrace struct are ignored. Times reported to the hook functions are
51+
// time since the start of the request. Note that partitioning of Histograms
52+
// is expensive and should be used judiciously.
53+
//
54+
// For hook functions that receive an error as an argument, no observations are
55+
// made in the event of a non-nil error value.
56+
//
57+
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
58+
func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc {
59+
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
60+
start := time.Now()
61+
62+
trace := &httptrace.ClientTrace{
63+
GotConn: func(_ httptrace.GotConnInfo) {
64+
if it.GotConn != nil {
65+
it.GotConn(time.Since(start).Seconds())
66+
}
67+
},
68+
PutIdleConn: func(err error) {
69+
if err != nil {
70+
return
71+
}
72+
if it.PutIdleConn != nil {
73+
it.PutIdleConn(time.Since(start).Seconds())
74+
}
75+
},
76+
DNSStart: func(_ httptrace.DNSStartInfo) {
77+
if it.DNSStart != nil {
78+
it.DNSStart(time.Since(start).Seconds())
79+
}
80+
},
81+
DNSDone: func(_ httptrace.DNSDoneInfo) {
82+
if it.DNSStart != nil {
83+
it.DNSStart(time.Since(start).Seconds())
84+
}
85+
},
86+
ConnectStart: func(_, _ string) {
87+
if it.ConnectStart != nil {
88+
it.ConnectStart(time.Since(start).Seconds())
89+
}
90+
},
91+
ConnectDone: func(_, _ string, err error) {
92+
if err != nil {
93+
return
94+
}
95+
if it.ConnectDone != nil {
96+
it.ConnectDone(time.Since(start).Seconds())
97+
}
98+
},
99+
GotFirstResponseByte: func() {
100+
if it.GotFirstResponseByte != nil {
101+
it.GotFirstResponseByte(time.Since(start).Seconds())
102+
}
103+
},
104+
Got100Continue: func() {
105+
if it.Got100Continue != nil {
106+
it.Got100Continue(time.Since(start).Seconds())
107+
}
108+
},
109+
TLSHandshakeStart: func() {
110+
if it.TLSHandshakeStart != nil {
111+
it.TLSHandshakeStart(time.Since(start).Seconds())
112+
}
113+
},
114+
TLSHandshakeDone: func(_ tls.ConnectionState, err error) {
115+
if err != nil {
116+
return
117+
}
118+
if it.TLSHandshakeDone != nil {
119+
it.TLSHandshakeDone(time.Since(start).Seconds())
120+
}
121+
},
122+
WroteHeaders: func() {
123+
if it.WroteHeaders != nil {
124+
it.WroteHeaders(time.Since(start).Seconds())
125+
}
126+
},
127+
Wait100Continue: func() {
128+
if it.Wait100Continue != nil {
129+
it.Wait100Continue(time.Since(start).Seconds())
130+
}
131+
},
132+
WroteRequest: func(_ httptrace.WroteRequestInfo) {
133+
if it.WroteRequest != nil {
134+
it.WroteRequest(time.Since(start).Seconds())
135+
}
136+
},
137+
}
138+
r = r.WithContext(httptrace.WithClientTrace(context.Background(), trace))
139+
140+
return next.RoundTrip(r)
141+
})
142+
}

0 commit comments

Comments
 (0)