forked from mirror/client_golang
Add time to write header handler middleware (#304)
This commit is contained in:
parent
42552c195d
commit
2b3ab50dcd
|
@ -20,8 +20,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDelegator(w http.ResponseWriter) delegator {
|
func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) delegator {
|
||||||
d := &responseWriterDelegator{ResponseWriter: w}
|
d := &responseWriterDelegator{
|
||||||
|
ResponseWriter: w,
|
||||||
|
observeWriteHeader: observeWriteHeaderFunc,
|
||||||
|
}
|
||||||
|
|
||||||
_, cn := w.(http.CloseNotifier)
|
_, cn := w.(http.CloseNotifier)
|
||||||
_, fl := w.(http.Flusher)
|
_, fl := w.(http.Flusher)
|
||||||
|
|
|
@ -22,8 +22,11 @@ import (
|
||||||
|
|
||||||
// newDelegator handles the four different methods of upgrading a
|
// newDelegator handles the four different methods of upgrading a
|
||||||
// http.ResponseWriter to delegator.
|
// http.ResponseWriter to delegator.
|
||||||
func newDelegator(w http.ResponseWriter) delegator {
|
func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) delegator {
|
||||||
d := &responseWriterDelegator{ResponseWriter: w}
|
d := &responseWriterDelegator{
|
||||||
|
ResponseWriter: w,
|
||||||
|
observeWriteHeader: observeWriteHeaderFunc,
|
||||||
|
}
|
||||||
|
|
||||||
_, cn := w.(http.CloseNotifier)
|
_, cn := w.(http.CloseNotifier)
|
||||||
_, fl := w.(http.Flusher)
|
_, fl := w.(http.Flusher)
|
||||||
|
|
|
@ -63,7 +63,7 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) ht
|
||||||
if code {
|
if code {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
d := newDelegator(w)
|
d := newDelegator(w, nil)
|
||||||
next.ServeHTTP(d, r)
|
next.ServeHTTP(d, r)
|
||||||
|
|
||||||
obs.With(labels(code, method, r.Method, d.Status())).Observe(time.Since(now).Seconds())
|
obs.With(labels(code, method, r.Method, d.Status())).Observe(time.Since(now).Seconds())
|
||||||
|
@ -96,7 +96,7 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler)
|
||||||
|
|
||||||
if code {
|
if code {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
d := newDelegator(w)
|
d := newDelegator(w, nil)
|
||||||
next.ServeHTTP(d, r)
|
next.ServeHTTP(d, r)
|
||||||
counter.With(labels(code, method, r.Method, d.Status())).Inc()
|
counter.With(labels(code, method, r.Method, d.Status())).Inc()
|
||||||
})
|
})
|
||||||
|
@ -108,6 +108,34 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided
|
||||||
|
// http.Handler to observe with the provided ObserverVec the request duration
|
||||||
|
// until the response headers are written. 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 Handler panics before calling WriteHeader, no value is
|
||||||
|
// reported.
|
||||||
|
//
|
||||||
|
// See the example for InstrumentHandlerDuration for example usage.
|
||||||
|
func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
|
||||||
|
code, method := checkLabels(obs)
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
now := time.Now()
|
||||||
|
d := newDelegator(w, func(status int) {
|
||||||
|
obs.With(labels(code, method, r.Method, status)).Observe(time.Since(now).Seconds())
|
||||||
|
})
|
||||||
|
next.ServeHTTP(d, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// InstrumentHandlerRequestSize is a middleware that wraps the provided
|
// InstrumentHandlerRequestSize is a middleware that wraps the provided
|
||||||
// http.Handler to observe the request size with the provided ObserverVec.
|
// http.Handler to observe the request size with the provided ObserverVec.
|
||||||
// The ObserverVec must have zero, one, or two labels. The only allowed label
|
// The ObserverVec must have zero, one, or two labels. The only allowed label
|
||||||
|
@ -129,7 +157,7 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler)
|
||||||
|
|
||||||
if code {
|
if code {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
d := newDelegator(w)
|
d := newDelegator(w, nil)
|
||||||
next.ServeHTTP(d, r)
|
next.ServeHTTP(d, r)
|
||||||
size := computeApproximateRequestSize(r)
|
size := computeApproximateRequestSize(r)
|
||||||
obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(size))
|
obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(size))
|
||||||
|
@ -162,7 +190,7 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler)
|
||||||
func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler) http.Handler {
|
func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler) http.Handler {
|
||||||
code, method := checkLabels(obs)
|
code, method := checkLabels(obs)
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
d := newDelegator(w)
|
d := newDelegator(w, nil)
|
||||||
next.ServeHTTP(d, r)
|
next.ServeHTTP(d, r)
|
||||||
obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(d.Written()))
|
obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(d.Written()))
|
||||||
})
|
})
|
||||||
|
@ -418,10 +446,11 @@ type delegator interface {
|
||||||
type responseWriterDelegator struct {
|
type responseWriterDelegator struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
|
|
||||||
handler, method string
|
handler, method string
|
||||||
status int
|
status int
|
||||||
written int64
|
written int64
|
||||||
wroteHeader bool
|
wroteHeader bool
|
||||||
|
observeWriteHeader func(int)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *responseWriterDelegator) Status() int {
|
func (r *responseWriterDelegator) Status() int {
|
||||||
|
@ -436,6 +465,9 @@ func (r *responseWriterDelegator) WriteHeader(code int) {
|
||||||
r.status = code
|
r.status = code
|
||||||
r.wroteHeader = true
|
r.wroteHeader = true
|
||||||
r.ResponseWriter.WriteHeader(code)
|
r.ResponseWriter.WriteHeader(code)
|
||||||
|
if r.observeWriteHeader != nil {
|
||||||
|
r.observeWriteHeader(code)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *responseWriterDelegator) Write(b []byte) (int, error) {
|
func (r *responseWriterDelegator) Write(b []byte) (int, error) {
|
||||||
|
|
|
@ -48,6 +48,16 @@ func TestMiddlewareAPI(t *testing.T) {
|
||||||
[]string{"method"},
|
[]string{"method"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
writeHeaderVec := prometheus.NewHistogramVec(
|
||||||
|
prometheus.HistogramOpts{
|
||||||
|
Name: "write_header_duration_seconds",
|
||||||
|
Help: "A histogram of time to first write latencies.",
|
||||||
|
Buckets: prometheus.DefBuckets,
|
||||||
|
ConstLabels: prometheus.Labels{"handler": "api"},
|
||||||
|
},
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
|
||||||
responseSize := prometheus.NewHistogramVec(
|
responseSize := prometheus.NewHistogramVec(
|
||||||
prometheus.HistogramOpts{
|
prometheus.HistogramOpts{
|
||||||
Name: "push_request_size_bytes",
|
Name: "push_request_size_bytes",
|
||||||
|
@ -61,12 +71,14 @@ func TestMiddlewareAPI(t *testing.T) {
|
||||||
w.Write([]byte("OK"))
|
w.Write([]byte("OK"))
|
||||||
})
|
})
|
||||||
|
|
||||||
reg.MustRegister(inFlightGauge, counter, histVec, responseSize)
|
reg.MustRegister(inFlightGauge, counter, histVec, responseSize, writeHeaderVec)
|
||||||
|
|
||||||
chain := InstrumentHandlerInFlight(inFlightGauge,
|
chain := InstrumentHandlerInFlight(inFlightGauge,
|
||||||
InstrumentHandlerCounter(counter,
|
InstrumentHandlerCounter(counter,
|
||||||
InstrumentHandlerDuration(histVec,
|
InstrumentHandlerDuration(histVec,
|
||||||
InstrumentHandlerResponseSize(responseSize, handler),
|
InstrumentHandlerTimeToWriteHeader(writeHeaderVec,
|
||||||
|
InstrumentHandlerResponseSize(responseSize, handler),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -76,6 +88,23 @@ func TestMiddlewareAPI(t *testing.T) {
|
||||||
chain.ServeHTTP(w, r)
|
chain.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInstrumentTimeToFirstWrite(t *testing.T) {
|
||||||
|
var i int
|
||||||
|
dobs := &responseWriterDelegator{
|
||||||
|
ResponseWriter: httptest.NewRecorder(),
|
||||||
|
observeWriteHeader: func(status int) {
|
||||||
|
i = status
|
||||||
|
},
|
||||||
|
}
|
||||||
|
d := newDelegator(dobs, nil)
|
||||||
|
|
||||||
|
d.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
if i != http.StatusOK {
|
||||||
|
t.Fatalf("failed to execute observeWriteHeader")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleInstrumentHandlerDuration() {
|
func ExampleInstrumentHandlerDuration() {
|
||||||
inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
Name: "in_flight_requests",
|
Name: "in_flight_requests",
|
||||||
|
|
Loading…
Reference in New Issue