131 lines
4.8 KiB
Go
131 lines
4.8 KiB
Go
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||
|
//
|
||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
// of this software and associated documentation files (the "Software"), to deal
|
||
|
// in the Software without restriction, including without limitation the rights
|
||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
// copies of the Software, and to permit persons to whom the Software is
|
||
|
// furnished to do so, subject to the following conditions:
|
||
|
//
|
||
|
// The above copyright notice and this permission notice shall be included in
|
||
|
// all copies or substantial portions of the Software.
|
||
|
//
|
||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
|
// THE SOFTWARE.
|
||
|
|
||
|
package rpcmetrics
|
||
|
|
||
|
import (
|
||
|
"sync"
|
||
|
|
||
|
"github.com/uber/jaeger-lib/metrics"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
otherEndpointsPlaceholder = "other"
|
||
|
endpointNameMetricTag = "endpoint"
|
||
|
)
|
||
|
|
||
|
// Metrics is a collection of metrics for an endpoint describing
|
||
|
// throughput, success, errors, and performance.
|
||
|
type Metrics struct {
|
||
|
// RequestCountSuccess is a counter of the total number of successes.
|
||
|
RequestCountSuccess metrics.Counter `metric:"requests" tags:"error=false"`
|
||
|
|
||
|
// RequestCountFailures is a counter of the number of times any failure has been observed.
|
||
|
RequestCountFailures metrics.Counter `metric:"requests" tags:"error=true"`
|
||
|
|
||
|
// RequestLatencySuccess is a latency histogram of succesful requests.
|
||
|
RequestLatencySuccess metrics.Timer `metric:"request_latency" tags:"error=false"`
|
||
|
|
||
|
// RequestLatencyFailures is a latency histogram of failed requests.
|
||
|
RequestLatencyFailures metrics.Timer `metric:"request_latency" tags:"error=true"`
|
||
|
|
||
|
// HTTPStatusCode2xx is a counter of the total number of requests with HTTP status code 200-299
|
||
|
HTTPStatusCode2xx metrics.Counter `metric:"http_requests" tags:"status_code=2xx"`
|
||
|
|
||
|
// HTTPStatusCode3xx is a counter of the total number of requests with HTTP status code 300-399
|
||
|
HTTPStatusCode3xx metrics.Counter `metric:"http_requests" tags:"status_code=3xx"`
|
||
|
|
||
|
// HTTPStatusCode4xx is a counter of the total number of requests with HTTP status code 400-499
|
||
|
HTTPStatusCode4xx metrics.Counter `metric:"http_requests" tags:"status_code=4xx"`
|
||
|
|
||
|
// HTTPStatusCode5xx is a counter of the total number of requests with HTTP status code 500-599
|
||
|
HTTPStatusCode5xx metrics.Counter `metric:"http_requests" tags:"status_code=5xx"`
|
||
|
}
|
||
|
|
||
|
func (m *Metrics) recordHTTPStatusCode(statusCode uint16) {
|
||
|
if statusCode >= 200 && statusCode < 300 {
|
||
|
m.HTTPStatusCode2xx.Inc(1)
|
||
|
} else if statusCode >= 300 && statusCode < 400 {
|
||
|
m.HTTPStatusCode3xx.Inc(1)
|
||
|
} else if statusCode >= 400 && statusCode < 500 {
|
||
|
m.HTTPStatusCode4xx.Inc(1)
|
||
|
} else if statusCode >= 500 && statusCode < 600 {
|
||
|
m.HTTPStatusCode5xx.Inc(1)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// MetricsByEndpoint is a registry/cache of metrics for each unique endpoint name.
|
||
|
// Only maxNumberOfEndpoints Metrics are stored, all other endpoint names are mapped
|
||
|
// to a generic endpoint name "other".
|
||
|
type MetricsByEndpoint struct {
|
||
|
metricsFactory metrics.Factory
|
||
|
endpoints *normalizedEndpoints
|
||
|
metricsByEndpoint map[string]*Metrics
|
||
|
mux sync.RWMutex
|
||
|
}
|
||
|
|
||
|
func newMetricsByEndpoint(
|
||
|
metricsFactory metrics.Factory,
|
||
|
normalizer NameNormalizer,
|
||
|
maxNumberOfEndpoints int,
|
||
|
) *MetricsByEndpoint {
|
||
|
return &MetricsByEndpoint{
|
||
|
metricsFactory: metricsFactory,
|
||
|
endpoints: newNormalizedEndpoints(maxNumberOfEndpoints, normalizer),
|
||
|
metricsByEndpoint: make(map[string]*Metrics, maxNumberOfEndpoints+1), // +1 for "other"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (m *MetricsByEndpoint) get(endpoint string) *Metrics {
|
||
|
safeName := m.endpoints.normalize(endpoint)
|
||
|
if safeName == "" {
|
||
|
safeName = otherEndpointsPlaceholder
|
||
|
}
|
||
|
m.mux.RLock()
|
||
|
met := m.metricsByEndpoint[safeName]
|
||
|
m.mux.RUnlock()
|
||
|
if met != nil {
|
||
|
return met
|
||
|
}
|
||
|
|
||
|
return m.getWithWriteLock(safeName)
|
||
|
}
|
||
|
|
||
|
// split to make easier to test
|
||
|
func (m *MetricsByEndpoint) getWithWriteLock(safeName string) *Metrics {
|
||
|
m.mux.Lock()
|
||
|
defer m.mux.Unlock()
|
||
|
|
||
|
// it is possible that the name has been already registered after we released
|
||
|
// the read lock and before we grabbed the write lock, so check for that.
|
||
|
if met, ok := m.metricsByEndpoint[safeName]; ok {
|
||
|
return met
|
||
|
}
|
||
|
|
||
|
// it would be nice to create the struct before locking, since Init() is somewhat
|
||
|
// expensive, however some metrics backends (e.g. expvar) may not like duplicate metrics.
|
||
|
met := &Metrics{}
|
||
|
tags := map[string]string{endpointNameMetricTag: safeName}
|
||
|
metrics.Init(met, m.metricsFactory, tags)
|
||
|
|
||
|
m.metricsByEndpoint[safeName] = met
|
||
|
return met
|
||
|
}
|