-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Instrument RoundTripper via middleware #295
Changes from 11 commits
78c294c
29ba5d1
ec79472
2c1b043
0fba3c1
890cefe
e7cbef5
3b0e2d1
60ff76b
1544107
51be061
63bb61a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ language: go | |
go: | ||
- 1.6.3 | ||
- 1.7 | ||
- 1.8.1 | ||
|
||
script: | ||
- go test -short ./... | ||
- go test -short ./... |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// Copyright 2017 The Prometheus Authors | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package promhttp | ||
|
||
import ( | ||
"net/http" | ||
"time" | ||
|
||
"github.com/prometheus/client_golang/prometheus" | ||
) | ||
|
||
// The RoundTripperFunc type is an adapter to allow the use of ordinary | ||
// functions as RoundTrippers. If f is a function with the appropriate | ||
// signature, RountTripperFunc(f) is a RoundTripper that calls f. | ||
type RoundTripperFunc func(req *http.Request) (*http.Response, error) | ||
|
||
// RoundTrip implements the RoundTripper interface. | ||
func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { | ||
return rt(r) | ||
} | ||
|
||
// InstrumentRoundTripperInFlight is a middleware that wraps the provided | ||
// http.RoundTripper. It sets the provided prometheus.Gauge to the number of | ||
// requests currently handled by the wrapped http.RoundTripper. | ||
// | ||
// See the example for ExampleInstrumentRoundTripperDuration for example usage. | ||
func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripper) RoundTripperFunc { | ||
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { | ||
gauge.Inc() | ||
defer gauge.Dec() | ||
resp, err := next.RoundTrip(r) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's better to just directly return here, i.e. |
||
if err != nil { | ||
return nil, err | ||
} | ||
return resp, err | ||
}) | ||
} | ||
|
||
// InstrumentRoundTripperCounter is a middleware that wraps the provided | ||
// http.RoundTripper to observe the request result with the provided CounterVec. | ||
// The CounterVec must have zero, one, or two labels. The only allowed label | ||
// names are "code" and "method". The function panics if any other instance | ||
// labels are provided. Partitioning of the CounterVec happens by HTTP status | ||
// code and/or HTTP method if the respective instance label names are present | ||
// in the CounterVec. For unpartitioned counting, use a CounterVec with | ||
// zero labels. | ||
// | ||
// If the wrapped RoundTripper panics or returns a non-nil error, the Counter | ||
// is not incremented. | ||
// | ||
// See the example for ExampleInstrumentRoundTripperDuration for example usage. | ||
func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper) RoundTripperFunc { | ||
code, method := checkLabels(counter) | ||
|
||
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { | ||
resp, err := next.RoundTrip(r) | ||
if err != nil { | ||
return nil, err | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, I think we want to return whatever RoundTrip returned in any case, i.e. Which means you can just increment the counter in a |
||
} | ||
counter.With(labels(code, method, r.Method, resp.StatusCode)).Inc() | ||
return resp, err | ||
}) | ||
} | ||
|
||
// InstrumentRoundTripperDuration is a middleware that wraps the provided | ||
// http.RoundTripper to observe the request duration with the provided ObserverVec. | ||
// The ObserverVec must have zero, one, or two labels. The only allowed label | ||
// names are "code" and "method". The function panics if any other instance | ||
// labels are provided. The Observe method of the Observer in the ObserverVec | ||
// is called with the request duration in seconds. Partitioning happens by HTTP | ||
// status code and/or HTTP method if the respective instance label names are | ||
// present in the ObserverVec. For unpartitioned observations, use an | ||
// ObserverVec with zero labels. Note that partitioning of Histograms is | ||
// expensive and should be used judiciously. | ||
// | ||
// If the wrapped RoundTripper panics or returns a non-nil error, no values are | ||
// reported. | ||
func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper) RoundTripperFunc { | ||
code, method := checkLabels(obs) | ||
|
||
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { | ||
start := time.Now() | ||
resp, err := next.RoundTrip(r) | ||
if err != nil { | ||
return nil, err | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same concern as above. |
||
} | ||
obs.With(labels(code, method, r.Method, resp.StatusCode)).Observe(time.Since(start).Seconds()) | ||
return resp, err | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// Copyright 2017 The Prometheus Authors | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
// +build go1.8 | ||
|
||
package promhttp | ||
|
||
import ( | ||
"context" | ||
"crypto/tls" | ||
"net/http" | ||
"net/http/httptrace" | ||
"time" | ||
) | ||
|
||
// InstrumentTrace is used to offer flexibility in instrumenting the available | ||
// httptrace.ClientTrace hook functions. Each function is passed a float64 | ||
// representing the time in seconds since the start of the http request. A user | ||
// may choose to use separately buckets Histograms, or implement custom | ||
// instance labels on a per function basis. | ||
type InstrumentTrace struct { | ||
GotConn func(float64) | ||
PutIdleConn func(float64) | ||
GotFirstResponseByte func(float64) | ||
Got100Continue func(float64) | ||
DNSStart func(float64) | ||
DNSDone func(float64) | ||
ConnectStart func(float64) | ||
ConnectDone func(float64) | ||
TLSHandshakeStart func(float64) | ||
TLSHandshakeDone func(float64) | ||
WroteHeaders func(float64) | ||
Wait100Continue func(float64) | ||
WroteRequest func(float64) | ||
} | ||
|
||
// InstrumentRoundTripperTrace is a middleware that wraps the provided | ||
// RoundTripper and reports times to hook functions provided in the | ||
// InstrumentTrace struct. Hook functions that are not present in the provided | ||
// InstrumentTrace struct are ignored. Times reported to the hook functions are | ||
// time since the start of the request. Note that partitioning of Histograms | ||
// is expensive and should be used judiciously. | ||
// | ||
// For hook functions that receive an error as an argument, no observations are | ||
// made in the event of a non-nil error value. | ||
// | ||
// See the example for ExampleInstrumentRoundTripperDuration for example usage. | ||
func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc { | ||
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { | ||
start := time.Now() | ||
|
||
trace := &httptrace.ClientTrace{ | ||
GotConn: func(_ httptrace.GotConnInfo) { | ||
if it.GotConn != nil { | ||
it.GotConn(time.Since(start).Seconds()) | ||
} | ||
}, | ||
PutIdleConn: func(err error) { | ||
if err != nil { | ||
return | ||
} | ||
if it.PutIdleConn != nil { | ||
it.PutIdleConn(time.Since(start).Seconds()) | ||
} | ||
}, | ||
DNSStart: func(_ httptrace.DNSStartInfo) { | ||
if it.DNSStart != nil { | ||
it.DNSStart(time.Since(start).Seconds()) | ||
} | ||
}, | ||
DNSDone: func(_ httptrace.DNSDoneInfo) { | ||
if it.DNSStart != nil { | ||
it.DNSStart(time.Since(start).Seconds()) | ||
} | ||
}, | ||
ConnectStart: func(_, _ string) { | ||
if it.ConnectStart != nil { | ||
it.ConnectStart(time.Since(start).Seconds()) | ||
} | ||
}, | ||
ConnectDone: func(_, _ string, err error) { | ||
if err != nil { | ||
return | ||
} | ||
if it.ConnectDone != nil { | ||
it.ConnectDone(time.Since(start).Seconds()) | ||
} | ||
}, | ||
GotFirstResponseByte: func() { | ||
if it.GotFirstResponseByte != nil { | ||
it.GotFirstResponseByte(time.Since(start).Seconds()) | ||
} | ||
}, | ||
Got100Continue: func() { | ||
if it.Got100Continue != nil { | ||
it.Got100Continue(time.Since(start).Seconds()) | ||
} | ||
}, | ||
TLSHandshakeStart: func() { | ||
if it.TLSHandshakeStart != nil { | ||
it.TLSHandshakeStart(time.Since(start).Seconds()) | ||
} | ||
}, | ||
TLSHandshakeDone: func(_ tls.ConnectionState, err error) { | ||
if err != nil { | ||
return | ||
} | ||
if it.TLSHandshakeDone != nil { | ||
it.TLSHandshakeDone(time.Since(start).Seconds()) | ||
} | ||
}, | ||
WroteHeaders: func() { | ||
if it.WroteHeaders != nil { | ||
it.WroteHeaders(time.Since(start).Seconds()) | ||
} | ||
}, | ||
Wait100Continue: func() { | ||
if it.Wait100Continue != nil { | ||
it.Wait100Continue(time.Since(start).Seconds()) | ||
} | ||
}, | ||
WroteRequest: func(_ httptrace.WroteRequestInfo) { | ||
if it.WroteRequest != nil { | ||
it.WroteRequest(time.Since(start).Seconds()) | ||
} | ||
}, | ||
} | ||
r = r.WithContext(httptrace.WithClientTrace(context.Background(), trace)) | ||
|
||
return next.RoundTrip(r) | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We just had a discussion about supported versions in #291 and the decision was to test against 1.6 but to just build tags for 1.7+ features.