diff options
Diffstat (limited to 'vendor/go.opencensus.io/plugin/ochttp/client_stats.go')
-rw-r--r-- | vendor/go.opencensus.io/plugin/ochttp/client_stats.go | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/vendor/go.opencensus.io/plugin/ochttp/client_stats.go b/vendor/go.opencensus.io/plugin/ochttp/client_stats.go new file mode 100644 index 0000000..066ebb8 --- /dev/null +++ b/vendor/go.opencensus.io/plugin/ochttp/client_stats.go | |||
@@ -0,0 +1,135 @@ | |||
1 | // Copyright 2018, OpenCensus Authors | ||
2 | // | ||
3 | // Licensed under the Apache License, Version 2.0 (the "License"); | ||
4 | // you may not use this file except in compliance with the License. | ||
5 | // You may obtain a copy of the License at | ||
6 | // | ||
7 | // http://www.apache.org/licenses/LICENSE-2.0 | ||
8 | // | ||
9 | // Unless required by applicable law or agreed to in writing, software | ||
10 | // distributed under the License is distributed on an "AS IS" BASIS, | ||
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
12 | // See the License for the specific language governing permissions and | ||
13 | // limitations under the License. | ||
14 | |||
15 | package ochttp | ||
16 | |||
17 | import ( | ||
18 | "context" | ||
19 | "io" | ||
20 | "net/http" | ||
21 | "strconv" | ||
22 | "sync" | ||
23 | "time" | ||
24 | |||
25 | "go.opencensus.io/stats" | ||
26 | "go.opencensus.io/tag" | ||
27 | ) | ||
28 | |||
29 | // statsTransport is an http.RoundTripper that collects stats for the outgoing requests. | ||
30 | type statsTransport struct { | ||
31 | base http.RoundTripper | ||
32 | } | ||
33 | |||
34 | // RoundTrip implements http.RoundTripper, delegating to Base and recording stats for the request. | ||
35 | func (t statsTransport) RoundTrip(req *http.Request) (*http.Response, error) { | ||
36 | ctx, _ := tag.New(req.Context(), | ||
37 | tag.Upsert(KeyClientHost, req.URL.Host), | ||
38 | tag.Upsert(Host, req.URL.Host), | ||
39 | tag.Upsert(KeyClientPath, req.URL.Path), | ||
40 | tag.Upsert(Path, req.URL.Path), | ||
41 | tag.Upsert(KeyClientMethod, req.Method), | ||
42 | tag.Upsert(Method, req.Method)) | ||
43 | req = req.WithContext(ctx) | ||
44 | track := &tracker{ | ||
45 | start: time.Now(), | ||
46 | ctx: ctx, | ||
47 | } | ||
48 | if req.Body == nil { | ||
49 | // TODO: Handle cases where ContentLength is not set. | ||
50 | track.reqSize = -1 | ||
51 | } else if req.ContentLength > 0 { | ||
52 | track.reqSize = req.ContentLength | ||
53 | } | ||
54 | stats.Record(ctx, ClientRequestCount.M(1)) | ||
55 | |||
56 | // Perform request. | ||
57 | resp, err := t.base.RoundTrip(req) | ||
58 | |||
59 | if err != nil { | ||
60 | track.statusCode = http.StatusInternalServerError | ||
61 | track.end() | ||
62 | } else { | ||
63 | track.statusCode = resp.StatusCode | ||
64 | if resp.Body == nil { | ||
65 | track.end() | ||
66 | } else { | ||
67 | track.body = resp.Body | ||
68 | resp.Body = track | ||
69 | } | ||
70 | } | ||
71 | return resp, err | ||
72 | } | ||
73 | |||
74 | // CancelRequest cancels an in-flight request by closing its connection. | ||
75 | func (t statsTransport) CancelRequest(req *http.Request) { | ||
76 | type canceler interface { | ||
77 | CancelRequest(*http.Request) | ||
78 | } | ||
79 | if cr, ok := t.base.(canceler); ok { | ||
80 | cr.CancelRequest(req) | ||
81 | } | ||
82 | } | ||
83 | |||
84 | type tracker struct { | ||
85 | ctx context.Context | ||
86 | respSize int64 | ||
87 | reqSize int64 | ||
88 | start time.Time | ||
89 | body io.ReadCloser | ||
90 | statusCode int | ||
91 | endOnce sync.Once | ||
92 | } | ||
93 | |||
94 | var _ io.ReadCloser = (*tracker)(nil) | ||
95 | |||
96 | func (t *tracker) end() { | ||
97 | t.endOnce.Do(func() { | ||
98 | latencyMs := float64(time.Since(t.start)) / float64(time.Millisecond) | ||
99 | m := []stats.Measurement{ | ||
100 | ClientSentBytes.M(t.reqSize), | ||
101 | ClientReceivedBytes.M(t.respSize), | ||
102 | ClientRoundtripLatency.M(latencyMs), | ||
103 | ClientLatency.M(latencyMs), | ||
104 | ClientResponseBytes.M(t.respSize), | ||
105 | } | ||
106 | if t.reqSize >= 0 { | ||
107 | m = append(m, ClientRequestBytes.M(t.reqSize)) | ||
108 | } | ||
109 | |||
110 | stats.RecordWithTags(t.ctx, []tag.Mutator{ | ||
111 | tag.Upsert(StatusCode, strconv.Itoa(t.statusCode)), | ||
112 | tag.Upsert(KeyClientStatus, strconv.Itoa(t.statusCode)), | ||
113 | }, m...) | ||
114 | }) | ||
115 | } | ||
116 | |||
117 | func (t *tracker) Read(b []byte) (int, error) { | ||
118 | n, err := t.body.Read(b) | ||
119 | switch err { | ||
120 | case nil: | ||
121 | t.respSize += int64(n) | ||
122 | return n, nil | ||
123 | case io.EOF: | ||
124 | t.end() | ||
125 | } | ||
126 | return n, err | ||
127 | } | ||
128 | |||
129 | func (t *tracker) Close() error { | ||
130 | // Invoking endSpan on Close will help catch the cases | ||
131 | // in which a read returned a non-nil error, we set the | ||
132 | // span status but didn't end the span. | ||
133 | t.end() | ||
134 | return t.body.Close() | ||
135 | } | ||