From 78c294c1640cb77dcbc3fc8e14f2377a7f417ec5 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Mon, 24 Apr 2017 21:31:03 +0200 Subject: [PATCH 01/12] Add client --- prometheus/promhttp/client.go | 226 +++++++++++++++++++++++++++++ prometheus/promhttp/client_test.go | 105 ++++++++++++++ 2 files changed, 331 insertions(+) create mode 100644 prometheus/promhttp/client.go create mode 100644 prometheus/promhttp/client_test.go diff --git a/prometheus/promhttp/client.go b/prometheus/promhttp/client.go new file mode 100644 index 000000000..bfb3b5344 --- /dev/null +++ b/prometheus/promhttp/client.go @@ -0,0 +1,226 @@ +// 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 ( + "context" + "crypto/tls" + "net/http" + "net/http/httptrace" + "time" + + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" +) + +// RoundTripperFunc is an adapter to allow wrapping an http.Client or other +// Middleware funcs, allowing the user to construct layers of middleware around +// an http client request. +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) +} + +// InstrumentTrace is used to offer flexibility in instrumenting the available +// httptrace.ClientTrace hooks. 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 +// per function. +type InstrumentTrace struct { + GotConn, PutIdleConn, GotFirstResponseByte, Got100Continue, DNSStart, DNSDone, ConnectStart, ConnectDone, TLSHandshakeStart, TLSHandshakeDone, WroteHeaders, Wait100Continue, WroteRequest func(float64) +} + +// InstrumentRoundTripperTrace accepts an InstrumentTrace structand a +// http.RoundTripper, returning a RoundTripperFunc that wraps the supplied +// http.RoundTripper. +// Note: Partitioning histograms is expensive. +func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc { + return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { + var ( + 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) + }) +} + +// InstrumentRoundTripperInFlight accepts a Gauge and an http.RoundTripper, +// returning a new RoundTripperFunc that wraps the supplied http.RoundTripper. +// The provided Gauge must be registered in a registry in order to be used. +func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripper) RoundTripperFunc { + return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { + gauge.Inc() + resp, err := next.RoundTrip(r) + if err != nil { + return nil, err + } + gauge.Dec() + return resp, err + }) +} + +// InstrumentRoundTripperCounter accepts an CounterVec interface and an +// http.RoundTripper, returning a new RoundTripperFunc that wraps the supplied +// http.RoundTripper. The provided CounterVec must be registered in a registry +// in order to be used. +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 + } + counter.With(labels(code, method, r.Method, resp.StatusCode)).Inc() + return resp, err + }) +} + +// InstrumentRoundTripperDuration accepts an ObserverVec interface and an +// http.RoundTripper, returning a new http.RoundTripper that wraps the supplied +// http.RoundTripper. The provided ObserverVec must be registered in a registry +// in order to be used. The instance labels "code" and "method" are supported +// on the provided ObserverVec. Note: Partitioning histograms is expensive. +func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper) RoundTripperFunc { + code, method := checkLabels(obs) + + return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { + var ( + start = time.Now() + resp, err = next.RoundTrip(r) + ) + if err != nil { + return nil, err + } + obs.With(labels(code, method, r.Method, resp.StatusCode)).Observe(time.Since(start).Seconds()) + return resp, err + }) +} + +func checkEventLabel(c prometheus.Collector) { + var ( + desc *prometheus.Desc + pm dto.Metric + ) + + descc := make(chan *prometheus.Desc, 1) + c.Describe(descc) + + select { + case desc = <-descc: + default: + panic("no description provided by collector") + } + select { + case <-descc: + panic("more than one description provided by collector") + default: + } + + close(descc) + + m, err := prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, "") + if err != nil { + panic("error checking metric for labels") + } + + if err := m.Write(&pm); err != nil { + panic("error checking metric for labels") + } + + name := *pm.Label[0].Name + if name != "event" { + panic("metric partitioned with non-supported label") + } +} diff --git a/prometheus/promhttp/client_test.go b/prometheus/promhttp/client_test.go new file mode 100644 index 000000000..46b73ae2b --- /dev/null +++ b/prometheus/promhttp/client_test.go @@ -0,0 +1,105 @@ +// 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 ( + "fmt" + "net/http" + "net/http/httputil" + "testing" + "time" + + "github.com/prometheus/client_golang/prometheus" +) + +func TestClientMiddlewareAPI(t *testing.T) { + client := http.DefaultClient + client.Timeout = 1 * time.Second + + inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "in_flight", + Help: "In-flight count.", + }) + + counter := prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "test_counter", + Help: "Counter.", + }, + []string{"code", "method"}, + ) + + dnsLatencyVec := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "dns_latency", + Help: "Trace dns latency histogram.", + Buckets: []float64{.005, .01, .025, .05}, + }, + []string{"event"}, + ) + tlsLatencyVec := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "tls_latency", + Help: "Trace tls latency histogram.", + Buckets: []float64{.05, .1, .25, .5}, + }, + []string{"event"}, + ) + + latencyVec := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "latency", + Help: "Overall latency histogram.", + Buckets: prometheus.DefBuckets, + }, + []string{"code", "method"}, + ) + + prometheus.MustRegister(counter, tlsLatencyVec, dnsLatencyVec, latencyVec, inFlightGauge) + + trace := &InstrumentTrace{ + DNSStart: func(t float64) { + dnsLatencyVec.WithLabelValues("DNSStart") + }, + DNSDone: func(t float64) { + dnsLatencyVec.WithLabelValues("DNSDone") + }, + TLSHandshakeStart: func(t float64) { + tlsLatencyVec.WithLabelValues("TLSHandshakeStart") + }, + TLSHandshakeDone: func(t float64) { + tlsLatencyVec.WithLabelValues("TLSHandshakeDone") + }, + } + + client.Transport = InstrumentRoundTripperInFlight(inFlightGauge, + InstrumentRoundTripperCounter(counter, + InstrumentRoundTripperTrace(trace, + InstrumentRoundTripperDuration(latencyVec, http.DefaultTransport), + ), + ), + ) + + resp, err := client.Get("http://google.com") + if err != nil { + t.Fatalf("%v", err) + } + defer resp.Body.Close() + + out, err := httputil.DumpResponse(resp, true) + if err != nil { + t.Fatalf("%v", err) + } + fmt.Println(string(out)) +} From 29ba5d1f928281f3475fae87cce1148e2dbacd25 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Tue, 25 Apr 2017 15:29:38 +0200 Subject: [PATCH 02/12] Fix client_test.go --- prometheus/promhttp/client_test.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/prometheus/promhttp/client_test.go b/prometheus/promhttp/client_test.go index 46b73ae2b..f5aae0b0b 100644 --- a/prometheus/promhttp/client_test.go +++ b/prometheus/promhttp/client_test.go @@ -28,45 +28,46 @@ func TestClientMiddlewareAPI(t *testing.T) { client.Timeout = 1 * time.Second inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{ - Name: "in_flight", - Help: "In-flight count.", + Name: "client_in_flight_requests", + Help: "A gauge of in-flight requests for the wrapped client.", }) counter := prometheus.NewCounterVec( prometheus.CounterOpts{ - Name: "test_counter", - Help: "Counter.", + Name: "client_api_requests_total", + Help: "A counter for requests from the wrapped client.", }, []string{"code", "method"}, ) dnsLatencyVec := prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Name: "dns_latency", + Name: "dns_duration_seconds", Help: "Trace dns latency histogram.", Buckets: []float64{.005, .01, .025, .05}, }, []string{"event"}, ) + tlsLatencyVec := prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Name: "tls_latency", + Name: "tls_duration_seconds", Help: "Trace tls latency histogram.", Buckets: []float64{.05, .1, .25, .5}, }, []string{"event"}, ) - latencyVec := prometheus.NewHistogramVec( + histVec := prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Name: "latency", - Help: "Overall latency histogram.", + Name: "request_duration_seconds", + Help: "A histogram of request latencies.", Buckets: prometheus.DefBuckets, }, - []string{"code", "method"}, + []string{"method"}, ) - prometheus.MustRegister(counter, tlsLatencyVec, dnsLatencyVec, latencyVec, inFlightGauge) + prometheus.MustRegister(counter, tlsLatencyVec, dnsLatencyVec, histVec, inFlightGauge) trace := &InstrumentTrace{ DNSStart: func(t float64) { @@ -86,7 +87,7 @@ func TestClientMiddlewareAPI(t *testing.T) { client.Transport = InstrumentRoundTripperInFlight(inFlightGauge, InstrumentRoundTripperCounter(counter, InstrumentRoundTripperTrace(trace, - InstrumentRoundTripperDuration(latencyVec, http.DefaultTransport), + InstrumentRoundTripperDuration(histVec, http.DefaultTransport), ), ), ) From ec7947227ca75bfda3dc5e39b8ac057b2851388f Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Tue, 25 Apr 2017 22:16:29 +0200 Subject: [PATCH 03/12] Update comments and examples --- prometheus/promhttp/client.go | 66 +++++++++++++-------- prometheus/promhttp/client_test.go | 95 ++++++++++++++++++++++++++++-- 2 files changed, 133 insertions(+), 28 deletions(-) diff --git a/prometheus/promhttp/client.go b/prometheus/promhttp/client.go index bfb3b5344..83be19bd8 100644 --- a/prometheus/promhttp/client.go +++ b/prometheus/promhttp/client.go @@ -24,9 +24,8 @@ import ( dto "github.com/prometheus/client_model/go" ) -// RoundTripperFunc is an adapter to allow wrapping an http.Client or other -// Middleware funcs, allowing the user to construct layers of middleware around -// an http client request. +// RoundTripperFunc is an adapter to allow wrapping an interface implementing +// http.RoundTripper, allowing the user to construct layers of middleware. type RoundTripperFunc func(req *http.Request) (*http.Response, error) // RoundTrip implements the RoundTripper interface. @@ -35,18 +34,22 @@ func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { } // InstrumentTrace is used to offer flexibility in instrumenting the available -// httptrace.ClientTrace hooks. 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 -// per function. +// 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, PutIdleConn, GotFirstResponseByte, Got100Continue, DNSStart, DNSDone, ConnectStart, ConnectDone, TLSHandshakeStart, TLSHandshakeDone, WroteHeaders, Wait100Continue, WroteRequest func(float64) } -// InstrumentRoundTripperTrace accepts an InstrumentTrace structand a -// http.RoundTripper, returning a RoundTripperFunc that wraps the supplied -// http.RoundTripper. -// Note: Partitioning histograms is expensive. +// 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. +// +// 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) { var ( @@ -135,9 +138,11 @@ func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) Ro }) } -// InstrumentRoundTripperInFlight accepts a Gauge and an http.RoundTripper, -// returning a new RoundTripperFunc that wraps the supplied http.RoundTripper. -// The provided Gauge must be registered in a registry in order to be used. +// 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() @@ -150,10 +155,18 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp }) } -// InstrumentRoundTripperCounter accepts an CounterVec interface and an -// http.RoundTripper, returning a new RoundTripperFunc that wraps the supplied -// http.RoundTripper. The provided CounterVec must be registered in a registry -// in order to be used. +// 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 observations, use a CounterVec with +// zero labels. +// +// If the wrapped RoundTripper panics, 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) @@ -167,11 +180,18 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou }) } -// InstrumentRoundTripperDuration accepts an ObserverVec interface and an -// http.RoundTripper, returning a new http.RoundTripper that wraps the supplied -// http.RoundTripper. The provided ObserverVec must be registered in a registry -// in order to be used. The instance labels "code" and "method" are supported -// on the provided ObserverVec. Note: Partitioning histograms is expensive. +// 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, no values are reported. func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper) RoundTripperFunc { code, method := checkLabels(obs) diff --git a/prometheus/promhttp/client_test.go b/prometheus/promhttp/client_test.go index f5aae0b0b..74f8e800f 100644 --- a/prometheus/promhttp/client_test.go +++ b/prometheus/promhttp/client_test.go @@ -14,9 +14,8 @@ package promhttp import ( - "fmt" + "log" "net/http" - "net/http/httputil" "testing" "time" @@ -97,10 +96,96 @@ func TestClientMiddlewareAPI(t *testing.T) { t.Fatalf("%v", err) } defer resp.Body.Close() +} + +func ExampleInstrumentRoundTripperDuration() { + client := http.DefaultClient + client.Timeout = 1 * time.Second + + inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "client_in_flight_requests", + Help: "A gauge of in-flight requests for the wrapped client.", + }) + + counter := prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "client_api_requests_total", + Help: "A counter for requests from the wrapped client.", + }, + []string{"code", "method"}, + ) + + // dnsLatencyVec uses custom buckets based on expected dns durations. + // It has an instance label "event", which is set in the + // DNSStart and DNSDonehook functions defined in the + // InstrumentTrace struct below. + dnsLatencyVec := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "dns_duration_seconds", + Help: "Trace dns latency histogram.", + Buckets: []float64{.005, .01, .025, .05}, + }, + []string{"event"}, + ) + + // tlsLatencyVec uses custom buckets based on expected tls durations. + // It has an instance label "event", which is set in the + // TLSHandshakeStart and TLSHandshakeDone hook functions defined in the + // InstrumentTrace struct below. + tlsLatencyVec := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "tls_duration_seconds", + Help: "Trace tls latency histogram.", + Buckets: []float64{.05, .1, .25, .5}, + }, + []string{"event"}, + ) - out, err := httputil.DumpResponse(resp, true) + // histVec has no labels, making it a zero-dimensional ObserverVec. + histVec := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "request_duration_seconds", + Help: "A histogram of request latencies.", + Buckets: prometheus.DefBuckets, + }, + []string{}, + ) + + // Register all of the metrics in the standard registry. + prometheus.MustRegister(counter, tlsLatencyVec, dnsLatencyVec, histVec, inFlightGauge) + + // Define functions for the available httptrace.ClientTrace hook + // functions that we want to instrument. + trace := &InstrumentTrace{ + DNSStart: func(t float64) { + dnsLatencyVec.WithLabelValues("DNSStart") + }, + DNSDone: func(t float64) { + dnsLatencyVec.WithLabelValues("DNSDone") + }, + TLSHandshakeStart: func(t float64) { + tlsLatencyVec.WithLabelValues("TLSHandshakeStart") + }, + TLSHandshakeDone: func(t float64) { + tlsLatencyVec.WithLabelValues("TLSHandshakeDone") + }, + } + + // Wrap the default RoundTripper with middleware. + roundTripper := InstrumentRoundTripperInFlight(inFlightGauge, + InstrumentRoundTripperCounter(counter, + InstrumentRoundTripperTrace(trace, + InstrumentRoundTripperDuration(histVec, http.DefaultTransport), + ), + ), + ) + + // Set the RoundTripper on our client. + client.Transport = roundTripper + + resp, err := client.Get("http://google.com") if err != nil { - t.Fatalf("%v", err) + log.Printf("error: %v", err) } - fmt.Println(string(out)) + defer resp.Body.Close() } From 2c1b043e4e52a84b1b0cfbfa03ee06d3f930a785 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Tue, 25 Apr 2017 22:16:40 +0200 Subject: [PATCH 04/12] Build for go 1.8.1, drop 1.6 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d9969ce42..4ada9370c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,8 @@ sudo: false language: go go: - - 1.6.3 - 1.7 + - 1.8.1 script: - - go test -short ./... + - go test -short ./... From 0fba3c19c64ca1da7df68d79b45e4fdb60288542 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Wed, 26 Apr 2017 15:41:35 +0200 Subject: [PATCH 05/12] Re-format InstrumentTrace struct --- prometheus/promhttp/client.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/prometheus/promhttp/client.go b/prometheus/promhttp/client.go index 83be19bd8..280782b63 100644 --- a/prometheus/promhttp/client.go +++ b/prometheus/promhttp/client.go @@ -39,7 +39,19 @@ func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { // may choose to use separately buckets Histograms, or implement custom // instance labels on a per function basis. type InstrumentTrace struct { - GotConn, PutIdleConn, GotFirstResponseByte, Got100Continue, DNSStart, DNSDone, ConnectStart, ConnectDone, TLSHandshakeStart, TLSHandshakeDone, WroteHeaders, Wait100Continue, WroteRequest func(float64) + 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 From 890cefe94aa1efed1d553ebc997370fdb327fcca Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Wed, 26 Apr 2017 15:42:05 +0200 Subject: [PATCH 06/12] Revert "Build for go 1.8.1, drop 1.6" This reverts commit 2c1b043e4e52a84b1b0cfbfa03ee06d3f930a785. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4ada9370c..d9969ce42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,8 @@ sudo: false language: go go: + - 1.6.3 - 1.7 - - 1.8.1 script: - - go test -short ./... + - go test -short ./... From e7cbef58af0e1eec7aec225eed0249b1745c68fe Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Wed, 26 Apr 2017 15:47:46 +0200 Subject: [PATCH 07/12] Separate code by build directive httptrace is only available in go-1.7+ --- prometheus/promhttp/client.go | 120 --------------- prometheus/promhttp/client_1_7.go | 141 ++++++++++++++++++ .../{client_test.go => client_1_7_test.go} | 2 + 3 files changed, 143 insertions(+), 120 deletions(-) create mode 100644 prometheus/promhttp/client_1_7.go rename prometheus/promhttp/{client_test.go => client_1_7_test.go} (99%) diff --git a/prometheus/promhttp/client.go b/prometheus/promhttp/client.go index 280782b63..0b3004ed2 100644 --- a/prometheus/promhttp/client.go +++ b/prometheus/promhttp/client.go @@ -14,10 +14,7 @@ package promhttp import ( - "context" - "crypto/tls" "net/http" - "net/http/httptrace" "time" "github.com/prometheus/client_golang/prometheus" @@ -33,123 +30,6 @@ func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { return rt(r) } -// 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. -// -// 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) { - var ( - 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) - }) -} - // 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. diff --git a/prometheus/promhttp/client_1_7.go b/prometheus/promhttp/client_1_7.go new file mode 100644 index 000000000..347051a60 --- /dev/null +++ b/prometheus/promhttp/client_1_7.go @@ -0,0 +1,141 @@ +// 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.7 + +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. +// +// 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) { + var ( + 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) + }) +} diff --git a/prometheus/promhttp/client_test.go b/prometheus/promhttp/client_1_7_test.go similarity index 99% rename from prometheus/promhttp/client_test.go rename to prometheus/promhttp/client_1_7_test.go index 74f8e800f..ee47e04c5 100644 --- a/prometheus/promhttp/client_test.go +++ b/prometheus/promhttp/client_1_7_test.go @@ -11,6 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// +build go1.7 + package promhttp import ( From 3b0e2d12b92e08a0f88df5fce36290d4dc6c4cce Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Wed, 26 Apr 2017 16:18:59 +0200 Subject: [PATCH 08/12] Build against go-1.8 httptrace hook functions I'm referencing were only added in 1.8 it seems. --- .travis.yml | 3 ++- prometheus/promhttp/{client_1_7.go => client_1_8.go} | 2 +- prometheus/promhttp/{client_1_7_test.go => client_1_8_test.go} | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) rename prometheus/promhttp/{client_1_7.go => client_1_8.go} (99%) rename prometheus/promhttp/{client_1_7_test.go => client_1_8_test.go} (99%) diff --git a/.travis.yml b/.travis.yml index d9969ce42..85b51152e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ language: go go: - 1.6.3 - 1.7 + - 1.8.1 script: - - go test -short ./... + - go test -short ./... diff --git a/prometheus/promhttp/client_1_7.go b/prometheus/promhttp/client_1_8.go similarity index 99% rename from prometheus/promhttp/client_1_7.go rename to prometheus/promhttp/client_1_8.go index 347051a60..c0a1e8f97 100644 --- a/prometheus/promhttp/client_1_7.go +++ b/prometheus/promhttp/client_1_8.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build go1.7 +// +build go1.8 package promhttp diff --git a/prometheus/promhttp/client_1_7_test.go b/prometheus/promhttp/client_1_8_test.go similarity index 99% rename from prometheus/promhttp/client_1_7_test.go rename to prometheus/promhttp/client_1_8_test.go index ee47e04c5..e225a8011 100644 --- a/prometheus/promhttp/client_1_7_test.go +++ b/prometheus/promhttp/client_1_8_test.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build go1.7 +// +build go1.8 package promhttp From 60ff76b70ee881c0e4b99dc794732e85b10ac9b5 Mon Sep 17 00:00:00 2001 From: Stuart Nelson Date: Tue, 2 May 2017 10:30:22 -0400 Subject: [PATCH 09/12] Rename all client files to instrument_client --- prometheus/promhttp/{client.go => instrument_client.go} | 0 prometheus/promhttp/{client_1_8.go => instrument_client_1_8.go} | 0 .../{client_1_8_test.go => instrument_client_1_8_test.go} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename prometheus/promhttp/{client.go => instrument_client.go} (100%) rename prometheus/promhttp/{client_1_8.go => instrument_client_1_8.go} (100%) rename prometheus/promhttp/{client_1_8_test.go => instrument_client_1_8_test.go} (100%) diff --git a/prometheus/promhttp/client.go b/prometheus/promhttp/instrument_client.go similarity index 100% rename from prometheus/promhttp/client.go rename to prometheus/promhttp/instrument_client.go diff --git a/prometheus/promhttp/client_1_8.go b/prometheus/promhttp/instrument_client_1_8.go similarity index 100% rename from prometheus/promhttp/client_1_8.go rename to prometheus/promhttp/instrument_client_1_8.go diff --git a/prometheus/promhttp/client_1_8_test.go b/prometheus/promhttp/instrument_client_1_8_test.go similarity index 100% rename from prometheus/promhttp/client_1_8_test.go rename to prometheus/promhttp/instrument_client_1_8_test.go From 15441079bca0bccf97e295437c2a63e7ce33b7e5 Mon Sep 17 00:00:00 2001 From: Stuart Nelson Date: Tue, 2 May 2017 10:41:41 -0400 Subject: [PATCH 10/12] Update package doc comment --- prometheus/promhttp/http.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/prometheus/promhttp/http.go b/prometheus/promhttp/http.go index 82d565625..4c70a7af6 100644 --- a/prometheus/promhttp/http.go +++ b/prometheus/promhttp/http.go @@ -24,6 +24,11 @@ // via middleware. Middleware wrappers follow the naming scheme // InstrumentHandlerX, where X describes the intended use of the middleware. // See each function's doc comment for specific details. +// +// Finally, the package allows for an http.RoundTripper to be instrumented via +// middleware. Middleware wrappers follow the naming scheme +// InstrumentRoundTripperX, where X describes the intended use of the +// middleware. See each function's doc comment for specific details. package promhttp import ( From 51be061435cccbc325e2a734d71cc26b6926b6bd Mon Sep 17 00:00:00 2001 From: Stuart Nelson Date: Tue, 2 May 2017 10:55:51 -0400 Subject: [PATCH 11/12] Address comments from beorn --- prometheus/promhttp/instrument_client.go | 59 ++++--------------- prometheus/promhttp/instrument_client_1_8.go | 7 ++- .../promhttp/instrument_client_1_8_test.go | 16 ++--- prometheus/promhttp/instrument_server.go | 2 +- 4 files changed, 24 insertions(+), 60 deletions(-) diff --git a/prometheus/promhttp/instrument_client.go b/prometheus/promhttp/instrument_client.go index 0b3004ed2..aa5b6fae6 100644 --- a/prometheus/promhttp/instrument_client.go +++ b/prometheus/promhttp/instrument_client.go @@ -18,11 +18,11 @@ import ( "time" "github.com/prometheus/client_golang/prometheus" - dto "github.com/prometheus/client_model/go" ) -// RoundTripperFunc is an adapter to allow wrapping an interface implementing -// http.RoundTripper, allowing the user to construct layers of middleware. +// 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. @@ -38,11 +38,11 @@ func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { 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) if err != nil { return nil, err } - gauge.Dec() return resp, err }) } @@ -53,10 +53,11 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp // 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 observations, use a CounterVec with +// in the CounterVec. For unpartitioned counting, use a CounterVec with // zero labels. // -// If the wrapped RoundTripper panics, the Counter is not incremented. +// 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 { @@ -83,15 +84,14 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou // ObserverVec with zero labels. Note that partitioning of Histograms is // expensive and should be used judiciously. // -// If the wrapped RoundTripper panics, no values are reported. +// 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) { - var ( - start = time.Now() - resp, err = next.RoundTrip(r) - ) + start := time.Now() + resp, err := next.RoundTrip(r) if err != nil { return nil, err } @@ -99,40 +99,3 @@ func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundT return resp, err }) } - -func checkEventLabel(c prometheus.Collector) { - var ( - desc *prometheus.Desc - pm dto.Metric - ) - - descc := make(chan *prometheus.Desc, 1) - c.Describe(descc) - - select { - case desc = <-descc: - default: - panic("no description provided by collector") - } - select { - case <-descc: - panic("more than one description provided by collector") - default: - } - - close(descc) - - m, err := prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, "") - if err != nil { - panic("error checking metric for labels") - } - - if err := m.Write(&pm); err != nil { - panic("error checking metric for labels") - } - - name := *pm.Label[0].Name - if name != "event" { - panic("metric partitioned with non-supported label") - } -} diff --git a/prometheus/promhttp/instrument_client_1_8.go b/prometheus/promhttp/instrument_client_1_8.go index c0a1e8f97..b51d91052 100644 --- a/prometheus/promhttp/instrument_client_1_8.go +++ b/prometheus/promhttp/instrument_client_1_8.go @@ -51,12 +51,13 @@ type InstrumentTrace struct { // 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) { - var ( - start = time.Now() - ) + start := time.Now() trace := &httptrace.ClientTrace{ GotConn: func(_ httptrace.GotConnInfo) { diff --git a/prometheus/promhttp/instrument_client_1_8_test.go b/prometheus/promhttp/instrument_client_1_8_test.go index e225a8011..235a3d21c 100644 --- a/prometheus/promhttp/instrument_client_1_8_test.go +++ b/prometheus/promhttp/instrument_client_1_8_test.go @@ -72,16 +72,16 @@ func TestClientMiddlewareAPI(t *testing.T) { trace := &InstrumentTrace{ DNSStart: func(t float64) { - dnsLatencyVec.WithLabelValues("DNSStart") + dnsLatencyVec.WithLabelValues("dns_start") }, DNSDone: func(t float64) { - dnsLatencyVec.WithLabelValues("DNSDone") + dnsLatencyVec.WithLabelValues("dns_done") }, TLSHandshakeStart: func(t float64) { - tlsLatencyVec.WithLabelValues("TLSHandshakeStart") + tlsLatencyVec.WithLabelValues("tls_handshake_start") }, TLSHandshakeDone: func(t float64) { - tlsLatencyVec.WithLabelValues("TLSHandshakeDone") + tlsLatencyVec.WithLabelValues("tls_handshake_done") }, } @@ -160,16 +160,16 @@ func ExampleInstrumentRoundTripperDuration() { // functions that we want to instrument. trace := &InstrumentTrace{ DNSStart: func(t float64) { - dnsLatencyVec.WithLabelValues("DNSStart") + dnsLatencyVec.WithLabelValues("dns_start") }, DNSDone: func(t float64) { - dnsLatencyVec.WithLabelValues("DNSDone") + dnsLatencyVec.WithLabelValues("dns_done") }, TLSHandshakeStart: func(t float64) { - tlsLatencyVec.WithLabelValues("TLSHandshakeStart") + tlsLatencyVec.WithLabelValues("tls_handshake_start") }, TLSHandshakeDone: func(t float64) { - tlsLatencyVec.WithLabelValues("TLSHandshakeDone") + tlsLatencyVec.WithLabelValues("tls_handshake_done") }, } diff --git a/prometheus/promhttp/instrument_server.go b/prometheus/promhttp/instrument_server.go index 31e15cbae..193598dba 100644 --- a/prometheus/promhttp/instrument_server.go +++ b/prometheus/promhttp/instrument_server.go @@ -80,7 +80,7 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) ht // 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 observations, use a CounterVec with +// in the CounterVec. For unpartitioned counting, use a CounterVec with // zero labels. // // If the wrapped Handler does not set a status code, a status code of 200 is assumed. From 63bb61ab5d0cad314b8a6f5de5cab41eaf8a4cf6 Mon Sep 17 00:00:00 2001 From: Stuart Nelson Date: Mon, 8 May 2017 12:56:16 -0400 Subject: [PATCH 12/12] Always return response and err --- prometheus/promhttp/instrument_client.go | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/prometheus/promhttp/instrument_client.go b/prometheus/promhttp/instrument_client.go index aa5b6fae6..1cf21f217 100644 --- a/prometheus/promhttp/instrument_client.go +++ b/prometheus/promhttp/instrument_client.go @@ -39,11 +39,7 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { gauge.Inc() defer gauge.Dec() - resp, err := next.RoundTrip(r) - if err != nil { - return nil, err - } - return resp, err + return next.RoundTrip(r) }) } @@ -65,10 +61,9 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { resp, err := next.RoundTrip(r) - if err != nil { - return nil, err + if err == nil { + counter.With(labels(code, method, r.Method, resp.StatusCode)).Inc() } - counter.With(labels(code, method, r.Method, resp.StatusCode)).Inc() return resp, err }) } @@ -92,10 +87,9 @@ func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundT return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { start := time.Now() resp, err := next.RoundTrip(r) - if err != nil { - return nil, err + if err == nil { + obs.With(labels(code, method, r.Method, resp.StatusCode)).Observe(time.Since(start).Seconds()) } - obs.With(labels(code, method, r.Method, resp.StatusCode)).Observe(time.Since(start).Seconds()) return resp, err }) }