Add a configurable version of InstrumentHandler and InstrumentHandlerFunc.

Also, remove quotes from the Content-type header. It's not illegal to
have quotes there, but they are not needed, and at other places, we
are not using them. So fewer characters and more consistency.

Change-Id: If7a78bde85154163e4426daec493d973213e83e9
This commit is contained in:
Bjoern Rabenstein 2014-07-15 15:34:52 +02:00
parent 23e5e5fefd
commit 96297bcbae
4 changed files with 114 additions and 59 deletions

View File

@ -20,45 +20,7 @@ import (
"time" "time"
) )
var ( var instLabels = []string{"method", "code"}
instLabels = []string{"handler", "method", "code"}
reqCnt = NewCounterVec(
CounterOpts{
Subsystem: "http",
Name: "requests_total",
Help: "Total number of HTTP requests made.",
},
instLabels,
)
reqDur = NewSummaryVec(
SummaryOpts{
Subsystem: "http",
Name: "request_duration_microseconds",
Help: "The HTTP request latencies in microseconds.",
},
instLabels,
)
reqSz = NewSummaryVec(
SummaryOpts{
Subsystem: "http",
Name: "request_size_bytes",
Help: "The HTTP request sizes in bytes.",
},
instLabels,
)
resSz = NewSummaryVec(
SummaryOpts{
Subsystem: "http",
Name: "response_size_bytes",
Help: "The HTTP response sizes in bytes.",
},
instLabels,
)
)
type nower interface { type nower interface {
Now() time.Time Now() time.Time
@ -103,6 +65,71 @@ func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFun
// (SummaryVec). Each has three labels: handler, method, code. The value of the // (SummaryVec). Each has three labels: handler, method, code. The value of the
// handler label is set by the handlerName parameter of this function. // handler label is set by the handlerName parameter of this function.
func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc { func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
return InstrumentHandlerFuncWithOpts(
SummaryOpts{
Subsystem: "http",
ConstLabels: Labels{"handler": handlerName},
},
handlerFunc,
)
}
// InstrumentHandlerWithOpts works like InstrumentHandler but provides more
// flexibility (at the cost of a more complex call syntax). As
// InstrumentHandler, this function registers four metric vector collectors, but
// it uses the provided SummaryOpts to create them. However, the fields "Name"
// and "Help" in the SummaryOpts are ignored. "Name" is replaced by
// "requests_total", "request_duration_microseconds", "request_size_bytes", and
// "response_size_bytes", respectively. "Help" is replaced by an appropriate
// help string. The names of the variable labels of the vector collectors are
// "method" (get, post, etc.), and "code" (HTTP status code).
//
// If InstrumentHandlerWithOpts is called as follows, it mimics exactly the
// behavior of InstrumentHandler:
//
// prometheus.InstrumentHandlerWithOpts(
// prometheus.SummaryOpts{
// Subsystem: "http",
// ConstLabels: prometheus.Labels{"handler": handlerName},
// },
// handler,
// )
//
// Technical detail: "requests_total" is a CounterVec, not a SummaryVec, so it
// cannot use SummaryOpts. Instead, a CounterOpts struct is created internally,
// and all its fields are set to the equally named fields in the provided
// SummaryOpts.
func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.HandlerFunc {
return InstrumentHandlerFuncWithOpts(opts, handler.ServeHTTP)
}
// InstrumentHandlerFuncWithOpts works like InstrumentHandlerFunc but provides
// more flexibility (at the cost of a more complex call syntax). See
// InstrumentHandlerWithOpts for details how the provided SummaryOpts are used.
func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
reqCnt := NewCounterVec(
CounterOpts{
Namespace: opts.Namespace,
Subsystem: opts.Subsystem,
Name: "requests_total",
Help: "Total number of HTTP requests made.",
ConstLabels: opts.ConstLabels,
},
instLabels,
)
opts.Name = "request_duration_microseconds"
opts.Help = "The HTTP request latencies in microseconds."
reqDur := NewSummaryVec(opts, instLabels)
opts.Name = "request_size_bytes"
opts.Help = "The HTTP request sizes in bytes."
reqSz := NewSummaryVec(opts, instLabels)
opts.Name = "response_size_bytes"
opts.Help = "The HTTP response sizes in bytes."
resSz := NewSummaryVec(opts, instLabels)
regReqCnt := MustRegisterOrGet(reqCnt).(*CounterVec) regReqCnt := MustRegisterOrGet(reqCnt).(*CounterVec)
regReqDur := MustRegisterOrGet(reqDur).(*SummaryVec) regReqDur := MustRegisterOrGet(reqDur).(*SummaryVec)
regReqSz := MustRegisterOrGet(reqSz).(*SummaryVec) regReqSz := MustRegisterOrGet(reqSz).(*SummaryVec)
@ -120,10 +147,10 @@ func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWri
method := sanitizeMethod(r.Method) method := sanitizeMethod(r.Method)
code := sanitizeCode(delegate.status) code := sanitizeCode(delegate.status)
regReqCnt.WithLabelValues(handlerName, method, code).Inc() regReqCnt.WithLabelValues(method, code).Inc()
regReqDur.WithLabelValues(handlerName, method, code).Observe(elapsed) regReqDur.WithLabelValues(method, code).Observe(elapsed)
regResSz.WithLabelValues(handlerName, method, code).Observe(float64(delegate.written)) regResSz.WithLabelValues(method, code).Observe(float64(delegate.written))
regReqSz.WithLabelValues(handlerName, method, code).Observe(float64(<-out)) regReqSz.WithLabelValues(method, code).Observe(float64(<-out))
}) })
} }

View File

@ -37,15 +37,43 @@ func TestInstrumentHandler(t *testing.T) {
instant := time.Now() instant := time.Now()
end := instant.Add(30 * time.Second) end := instant.Add(30 * time.Second)
now = nowSeries(instant, end) now = nowSeries(instant, end)
respBody := respBody("Howdy there!")
hndlr := InstrumentHandler("test-handler", respBody)
opts := SummaryOpts{
Subsystem: "http",
ConstLabels: Labels{"handler": "test-handler"},
}
reqCnt := MustRegisterOrGet(NewCounterVec(
CounterOpts{
Namespace: opts.Namespace,
Subsystem: opts.Subsystem,
Name: "requests_total",
Help: "Total number of HTTP requests made.",
ConstLabels: opts.ConstLabels,
},
instLabels,
)).(*CounterVec)
opts.Name = "request_duration_microseconds"
opts.Help = "The HTTP request latencies in microseconds."
reqDur := MustRegisterOrGet(NewSummaryVec(opts, instLabels)).(*SummaryVec)
opts.Name = "request_size_bytes"
opts.Help = "The HTTP request sizes in bytes."
reqSz := MustRegisterOrGet(NewSummaryVec(opts, instLabels)).(*SummaryVec)
opts.Name = "response_size_bytes"
opts.Help = "The HTTP response sizes in bytes."
resSz := MustRegisterOrGet(NewSummaryVec(opts, instLabels)).(*SummaryVec)
reqCnt.Reset() reqCnt.Reset()
reqDur.Reset() reqDur.Reset()
reqSz.Reset() reqSz.Reset()
resSz.Reset() resSz.Reset()
respBody := respBody("Howdy there!")
hndlr := InstrumentHandler("test-handler", respBody)
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
req := &http.Request{ req := &http.Request{
Method: "GET", Method: "GET",
@ -63,7 +91,7 @@ func TestInstrumentHandler(t *testing.T) {
if want, got := 1, len(reqDur.children); want != got { if want, got := 1, len(reqDur.children); want != got {
t.Errorf("want %d children in reqDur, got %d", want, got) t.Errorf("want %d children in reqDur, got %d", want, got)
} }
sum, err := reqDur.GetMetricWithLabelValues("test-handler", "get", "418") sum, err := reqDur.GetMetricWithLabelValues("get", "418")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -86,7 +114,7 @@ func TestInstrumentHandler(t *testing.T) {
if want, got := 1, len(reqCnt.children); want != got { if want, got := 1, len(reqCnt.children); want != got {
t.Errorf("want %d children in reqCnt, got %d", want, got) t.Errorf("want %d children in reqCnt, got %d", want, got)
} }
cnt, err := reqCnt.GetMetricWithLabelValues("test-handler", "get", "418") cnt, err := reqCnt.GetMetricWithLabelValues("get", "418")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -55,17 +55,17 @@ const (
// DelimitedTelemetryContentType is the content type set on telemetry // DelimitedTelemetryContentType is the content type set on telemetry
// data responses in delimited protobuf format. // data responses in delimited protobuf format.
DelimitedTelemetryContentType = `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"` DelimitedTelemetryContentType = `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`
// TextTelemetryContentType is the content type set on telemetry data // TextTelemetryContentType is the content type set on telemetry data
// responses in text format. // responses in text format.
TextTelemetryContentType = `text/plain; version=` + APIVersion TextTelemetryContentType = `text/plain; version=` + APIVersion
// ProtoTextTelemetryContentType is the content type set on telemetry // ProtoTextTelemetryContentType is the content type set on telemetry
// data responses in protobuf text format. (Only used for debugging.) // data responses in protobuf text format. (Only used for debugging.)
ProtoTextTelemetryContentType = `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="text"` ProtoTextTelemetryContentType = `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text`
// ProtoCompactTextTelemetryContentType is the content type set on // ProtoCompactTextTelemetryContentType is the content type set on
// telemetry data responses in protobuf compact text format. (Only used // telemetry data responses in protobuf compact text format. (Only used
// for debugging.) // for debugging.)
ProtoCompactTextTelemetryContentType = `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="compact-text"` ProtoCompactTextTelemetryContentType = `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text`
// Constants for object pools. // Constants for object pools.
numBufs = 4 numBufs = 4

View File

@ -266,7 +266,7 @@ metric: <
}, },
out: output{ out: output{
headers: map[string]string{ headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`, "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`,
}, },
body: []byte{}, body: []byte{},
}, },
@ -289,7 +289,7 @@ metric: <
}, },
out: output{ out: output{
headers: map[string]string{ headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`, "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`,
}, },
body: expectedMetricFamilyAsBytes, body: expectedMetricFamilyAsBytes,
}, },
@ -313,7 +313,7 @@ metric: <
}, },
out: output{ out: output{
headers: map[string]string{ headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`, "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`,
}, },
body: externalMetricFamilyAsBytes, body: externalMetricFamilyAsBytes,
}, },
@ -325,7 +325,7 @@ metric: <
}, },
out: output{ out: output{
headers: map[string]string{ headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`, "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`,
}, },
body: bytes.Join( body: bytes.Join(
[][]byte{ [][]byte{
@ -386,7 +386,7 @@ metric: <
}, },
out: output{ out: output{
headers: map[string]string{ headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`, "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`,
}, },
body: bytes.Join( body: bytes.Join(
[][]byte{ [][]byte{
@ -405,7 +405,7 @@ metric: <
}, },
out: output{ out: output{
headers: map[string]string{ headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="text"`, "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text`,
}, },
body: bytes.Join( body: bytes.Join(
[][]byte{ [][]byte{
@ -424,7 +424,7 @@ metric: <
}, },
out: output{ out: output{
headers: map[string]string{ headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="compact-text"`, "Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text`,
}, },
body: bytes.Join( body: bytes.Join(
[][]byte{ [][]byte{