blob: a034d1ec0f189a54e6269a24d04c39cfc7ce63d2 [file] [log] [blame]
khenaidooffe076b2019-01-15 16:08:08 -05001// Copyright 2017 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14// +build go1.8
15
16package promhttp
17
18import (
19 "context"
20 "crypto/tls"
21 "net/http"
22 "net/http/httptrace"
23 "time"
24)
25
26// InstrumentTrace is used to offer flexibility in instrumenting the available
27// httptrace.ClientTrace hook functions. Each function is passed a float64
28// representing the time in seconds since the start of the http request. A user
29// may choose to use separately buckets Histograms, or implement custom
30// instance labels on a per function basis.
31type InstrumentTrace struct {
32 GotConn func(float64)
33 PutIdleConn func(float64)
34 GotFirstResponseByte func(float64)
35 Got100Continue func(float64)
36 DNSStart func(float64)
37 DNSDone func(float64)
38 ConnectStart func(float64)
39 ConnectDone func(float64)
40 TLSHandshakeStart func(float64)
41 TLSHandshakeDone func(float64)
42 WroteHeaders func(float64)
43 Wait100Continue func(float64)
44 WroteRequest func(float64)
45}
46
47// InstrumentRoundTripperTrace is a middleware that wraps the provided
48// RoundTripper and reports times to hook functions provided in the
49// InstrumentTrace struct. Hook functions that are not present in the provided
50// InstrumentTrace struct are ignored. Times reported to the hook functions are
51// time since the start of the request. Only with Go1.9+, those times are
52// guaranteed to never be negative. (Earlier Go versions are not using a
53// monotonic clock.) Note that partitioning of Histograms is expensive and
54// should be used judiciously.
55//
56// For hook functions that receive an error as an argument, no observations are
57// made in the event of a non-nil error value.
58//
59// See the example for ExampleInstrumentRoundTripperDuration for example usage.
60func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc {
61 return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
62 start := time.Now()
63
64 trace := &httptrace.ClientTrace{
65 GotConn: func(_ httptrace.GotConnInfo) {
66 if it.GotConn != nil {
67 it.GotConn(time.Since(start).Seconds())
68 }
69 },
70 PutIdleConn: func(err error) {
71 if err != nil {
72 return
73 }
74 if it.PutIdleConn != nil {
75 it.PutIdleConn(time.Since(start).Seconds())
76 }
77 },
78 DNSStart: func(_ httptrace.DNSStartInfo) {
79 if it.DNSStart != nil {
80 it.DNSStart(time.Since(start).Seconds())
81 }
82 },
83 DNSDone: func(_ httptrace.DNSDoneInfo) {
84 if it.DNSDone != nil {
85 it.DNSDone(time.Since(start).Seconds())
86 }
87 },
88 ConnectStart: func(_, _ string) {
89 if it.ConnectStart != nil {
90 it.ConnectStart(time.Since(start).Seconds())
91 }
92 },
93 ConnectDone: func(_, _ string, err error) {
94 if err != nil {
95 return
96 }
97 if it.ConnectDone != nil {
98 it.ConnectDone(time.Since(start).Seconds())
99 }
100 },
101 GotFirstResponseByte: func() {
102 if it.GotFirstResponseByte != nil {
103 it.GotFirstResponseByte(time.Since(start).Seconds())
104 }
105 },
106 Got100Continue: func() {
107 if it.Got100Continue != nil {
108 it.Got100Continue(time.Since(start).Seconds())
109 }
110 },
111 TLSHandshakeStart: func() {
112 if it.TLSHandshakeStart != nil {
113 it.TLSHandshakeStart(time.Since(start).Seconds())
114 }
115 },
116 TLSHandshakeDone: func(_ tls.ConnectionState, err error) {
117 if err != nil {
118 return
119 }
120 if it.TLSHandshakeDone != nil {
121 it.TLSHandshakeDone(time.Since(start).Seconds())
122 }
123 },
124 WroteHeaders: func() {
125 if it.WroteHeaders != nil {
126 it.WroteHeaders(time.Since(start).Seconds())
127 }
128 },
129 Wait100Continue: func() {
130 if it.Wait100Continue != nil {
131 it.Wait100Continue(time.Since(start).Seconds())
132 }
133 },
134 WroteRequest: func(_ httptrace.WroteRequestInfo) {
135 if it.WroteRequest != nil {
136 it.WroteRequest(time.Since(start).Seconds())
137 }
138 },
139 }
140 r = r.WithContext(httptrace.WithClientTrace(context.Background(), trace))
141
142 return next.RoundTrip(r)
143 })
144}