blob: a8cec2fa685b92753b147b00ef6c79ee47e48ca5 [file] [log] [blame]
mpagenkoaf801632020-07-03 10:00:42 +00001// Copyright (c) 2017 Uber Technologies, Inc.
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
15package rpcmetrics
16
17import (
18 "sync"
19
20 "github.com/uber/jaeger-lib/metrics"
21)
22
23const (
24 otherEndpointsPlaceholder = "other"
25 endpointNameMetricTag = "endpoint"
26)
27
28// Metrics is a collection of metrics for an endpoint describing
29// throughput, success, errors, and performance.
30type Metrics struct {
31 // RequestCountSuccess is a counter of the total number of successes.
32 RequestCountSuccess metrics.Counter `metric:"requests" tags:"error=false"`
33
34 // RequestCountFailures is a counter of the number of times any failure has been observed.
35 RequestCountFailures metrics.Counter `metric:"requests" tags:"error=true"`
36
37 // RequestLatencySuccess is a latency histogram of successful requests.
38 RequestLatencySuccess metrics.Timer `metric:"request_latency" tags:"error=false"`
39
40 // RequestLatencyFailures is a latency histogram of failed requests.
41 RequestLatencyFailures metrics.Timer `metric:"request_latency" tags:"error=true"`
42
43 // HTTPStatusCode2xx is a counter of the total number of requests with HTTP status code 200-299
44 HTTPStatusCode2xx metrics.Counter `metric:"http_requests" tags:"status_code=2xx"`
45
46 // HTTPStatusCode3xx is a counter of the total number of requests with HTTP status code 300-399
47 HTTPStatusCode3xx metrics.Counter `metric:"http_requests" tags:"status_code=3xx"`
48
49 // HTTPStatusCode4xx is a counter of the total number of requests with HTTP status code 400-499
50 HTTPStatusCode4xx metrics.Counter `metric:"http_requests" tags:"status_code=4xx"`
51
52 // HTTPStatusCode5xx is a counter of the total number of requests with HTTP status code 500-599
53 HTTPStatusCode5xx metrics.Counter `metric:"http_requests" tags:"status_code=5xx"`
54}
55
56func (m *Metrics) recordHTTPStatusCode(statusCode uint16) {
57 if statusCode >= 200 && statusCode < 300 {
58 m.HTTPStatusCode2xx.Inc(1)
59 } else if statusCode >= 300 && statusCode < 400 {
60 m.HTTPStatusCode3xx.Inc(1)
61 } else if statusCode >= 400 && statusCode < 500 {
62 m.HTTPStatusCode4xx.Inc(1)
63 } else if statusCode >= 500 && statusCode < 600 {
64 m.HTTPStatusCode5xx.Inc(1)
65 }
66}
67
68// MetricsByEndpoint is a registry/cache of metrics for each unique endpoint name.
69// Only maxNumberOfEndpoints Metrics are stored, all other endpoint names are mapped
70// to a generic endpoint name "other".
71type MetricsByEndpoint struct {
72 metricsFactory metrics.Factory
73 endpoints *normalizedEndpoints
74 metricsByEndpoint map[string]*Metrics
75 mux sync.RWMutex
76}
77
78func newMetricsByEndpoint(
79 metricsFactory metrics.Factory,
80 normalizer NameNormalizer,
81 maxNumberOfEndpoints int,
82) *MetricsByEndpoint {
83 return &MetricsByEndpoint{
84 metricsFactory: metricsFactory,
85 endpoints: newNormalizedEndpoints(maxNumberOfEndpoints, normalizer),
86 metricsByEndpoint: make(map[string]*Metrics, maxNumberOfEndpoints+1), // +1 for "other"
87 }
88}
89
90func (m *MetricsByEndpoint) get(endpoint string) *Metrics {
91 safeName := m.endpoints.normalize(endpoint)
92 if safeName == "" {
93 safeName = otherEndpointsPlaceholder
94 }
95 m.mux.RLock()
96 met := m.metricsByEndpoint[safeName]
97 m.mux.RUnlock()
98 if met != nil {
99 return met
100 }
101
102 return m.getWithWriteLock(safeName)
103}
104
105// split to make easier to test
106func (m *MetricsByEndpoint) getWithWriteLock(safeName string) *Metrics {
107 m.mux.Lock()
108 defer m.mux.Unlock()
109
110 // it is possible that the name has been already registered after we released
111 // the read lock and before we grabbed the write lock, so check for that.
112 if met, ok := m.metricsByEndpoint[safeName]; ok {
113 return met
114 }
115
116 // it would be nice to create the struct before locking, since Init() is somewhat
117 // expensive, however some metrics backends (e.g. expvar) may not like duplicate metrics.
118 met := &Metrics{}
119 tags := map[string]string{endpointNameMetricTag: safeName}
120 metrics.Init(met, m.metricsFactory, tags)
121
122 m.metricsByEndpoint[safeName] = met
123 return met
124}