Merge pull request #302 from prometheus/beorn7/http

Improve promhttp middleware
This commit is contained in:
Björn Rabenstein 2017-05-11 16:12:51 +02:00 committed by GitHub
commit 42552c195d
3 changed files with 56 additions and 43 deletions

View File

@ -28,6 +28,8 @@ func TestClientMiddlewareAPI(t *testing.T) {
client := http.DefaultClient client := http.DefaultClient
client.Timeout = 1 * time.Second client.Timeout = 1 * time.Second
reg := prometheus.NewRegistry()
inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{ inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "client_in_flight_requests", Name: "client_in_flight_requests",
Help: "A gauge of in-flight requests for the wrapped client.", Help: "A gauge of in-flight requests for the wrapped client.",
@ -68,7 +70,7 @@ func TestClientMiddlewareAPI(t *testing.T) {
[]string{"method"}, []string{"method"},
) )
prometheus.MustRegister(counter, tlsLatencyVec, dnsLatencyVec, histVec, inFlightGauge) reg.MustRegister(counter, tlsLatencyVec, dnsLatencyVec, histVec, inFlightGauge)
trace := &InstrumentTrace{ trace := &InstrumentTrace{
DNSStart: func(t float64) { DNSStart: func(t float64) {

View File

@ -27,6 +27,9 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
) )
// magicString is used for the hacky label test in checkLabels. Remove once fixed.
const magicString = "zZgWfBxLqvG8kc8IMv3POi2Bb0tZI3vAnBx+gBaFi9FyPzB/CzKUer1yufDa"
// InstrumentHandlerInFlight is a middleware that wraps the provided // InstrumentHandlerInFlight is a middleware that wraps the provided
// http.Handler. It sets the provided prometheus.Gauge to the number of // http.Handler. It sets the provided prometheus.Gauge to the number of
// requests currently handled by the wrapped http.Handler. // requests currently handled by the wrapped http.Handler.
@ -191,37 +194,46 @@ func checkLabels(c prometheus.Collector) (code bool, method bool) {
if _, err := prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0); err == nil { if _, err := prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0); err == nil {
return return
} else if m, err := prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, ""); err == nil { }
if m, err := prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, magicString); err == nil {
if err := m.Write(&pm); err != nil { if err := m.Write(&pm); err != nil {
panic("error checking metric for labels") panic("error checking metric for labels")
} }
name := *pm.Label[0].Name
if name == "code" {
code = true
} else if name == "method" {
method = true
} else {
panic("metric partitioned with non-supported labels")
}
return
} else if m, err := prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, "", ""); err == nil {
if err := m.Write(&pm); err != nil {
panic("error checking metric for labels")
}
for _, label := range pm.Label { for _, label := range pm.Label {
if *label.Name == "code" || *label.Name == "method" { name, value := label.GetName(), label.GetValue()
if value != magicString {
continue
}
switch name {
case "code":
code = true
case "method":
method = true
default:
panic("metric partitioned with non-supported labels")
}
return
}
panic("previously set label not found this must never happen")
}
if m, err := prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, magicString, magicString); err == nil {
if err := m.Write(&pm); err != nil {
panic("error checking metric for labels")
}
for _, label := range pm.Label {
name, value := label.GetName(), label.GetValue()
if value != magicString {
continue
}
if name == "code" || name == "method" {
continue continue
} }
panic("metric partitioned with non-supported labels") panic("metric partitioned with non-supported labels")
} }
code = true code = true
method = true method = true
return return
} }
panic("metric partitioned with non-supported labels") panic("metric partitioned with non-supported labels")
} }

View File

@ -23,6 +23,8 @@ import (
) )
func TestMiddlewareAPI(t *testing.T) { func TestMiddlewareAPI(t *testing.T) {
reg := prometheus.NewRegistry()
inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{ inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "in_flight_requests", Name: "in_flight_requests",
Help: "A gauge of requests currently being served by the wrapped handler.", Help: "A gauge of requests currently being served by the wrapped handler.",
@ -38,9 +40,10 @@ func TestMiddlewareAPI(t *testing.T) {
histVec := prometheus.NewHistogramVec( histVec := prometheus.NewHistogramVec(
prometheus.HistogramOpts{ prometheus.HistogramOpts{
Name: "response_duration_seconds", Name: "response_duration_seconds",
Help: "A histogram of request latencies.", Help: "A histogram of request latencies.",
Buckets: prometheus.DefBuckets, Buckets: prometheus.DefBuckets,
ConstLabels: prometheus.Labels{"handler": "api"},
}, },
[]string{"method"}, []string{"method"},
) )
@ -58,7 +61,7 @@ func TestMiddlewareAPI(t *testing.T) {
w.Write([]byte("OK")) w.Write([]byte("OK"))
}) })
prometheus.MustRegister(inFlightGauge, counter, histVec, responseSize) reg.MustRegister(inFlightGauge, counter, histVec, responseSize)
chain := InstrumentHandlerInFlight(inFlightGauge, chain := InstrumentHandlerInFlight(inFlightGauge,
InstrumentHandlerCounter(counter, InstrumentHandlerCounter(counter,
@ -87,29 +90,25 @@ func ExampleInstrumentHandlerDuration() {
[]string{"code", "method"}, []string{"code", "method"},
) )
// pushVec is partitioned by the HTTP method and uses custom buckets based on // pushVec and pullVec are partitioned by the HTTP method and use custom
// the expected request duration. It uses ConstLabels to set a handler label // buckets based on the expected request duration. ConstLabels are used
// marking pushVec as tracking the durations for pushes. // to set a handler label to mark pushVec as tracking the durations for
// pushes and pullVec as tracking the durations for pulls. Note that
// Name, Help, and Buckets need to be the same for consistency, so we
// use the same HistogramOpts after just modifying the ConstLabels.
histogramOpts := prometheus.HistogramOpts{
Name: "request_duration_seconds",
Help: "A histogram of latencies for requests.",
Buckets: []float64{.25, .5, 1, 2.5, 5, 10},
ConstLabels: prometheus.Labels{"handler": "push"},
}
pushVec := prometheus.NewHistogramVec( pushVec := prometheus.NewHistogramVec(
prometheus.HistogramOpts{ histogramOpts,
Name: "request_duration_seconds",
Help: "A histogram of latencies for requests to the push handler.",
Buckets: []float64{.25, .5, 1, 2.5, 5, 10},
ConstLabels: prometheus.Labels{"handler": "push"},
},
[]string{"method"}, []string{"method"},
) )
histogramOpts.ConstLabels = prometheus.Labels{"handler": "pull"}
// pullVec is also partitioned by the HTTP method but uses custom buckets
// different from those for pushVec. It also has a different value for the
// constant "handler" label.
pullVec := prometheus.NewHistogramVec( pullVec := prometheus.NewHistogramVec(
prometheus.HistogramOpts{ histogramOpts,
Name: "request_duration_seconds",
Help: "A histogram of latencies for requests to the pull handler.",
Buckets: []float64{.005, .01, .025, .05},
ConstLabels: prometheus.Labels{"handler": "pull"},
},
[]string{"method"}, []string{"method"},
) )