-
Notifications
You must be signed in to change notification settings - Fork 38
/
server_metrics.go
147 lines (132 loc) · 5.67 KB
/
server_metrics.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package http
import (
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/trace"
)
// ServerMetrics represents a collection of metrics to be registered on a
// Prometheus metrics registry for an HTTP server.
type ServerMetrics struct {
serverReqTotalCounter *prometheus.CounterVec
serverReqInflightGauge *prometheus.GaugeVec
serverReqSizeSummary *prometheus.SummaryVec
serverRespSizeSummary *prometheus.SummaryVec
serverReqDuration *prometheus.HistogramVec
}
// NewServerMetrics returns an instance of ServerMetrics
func NewServerMetrics(opts ...ServerMetricsOption) *ServerMetrics {
var config serverMetricsConfig
config.apply(opts)
return &ServerMetrics{
// host and path is not monitored because of virtual path
serverReqTotalCounter: prometheus.NewCounterVec(
config.counterOpts.apply(prometheus.CounterOpts{
Name: "http_server_received_total_requests",
Help: "Tracks the total number of HTTP requests.",
}), []string{"handler_name", "method", "http_status_code", "sp_error_code"}),
serverReqInflightGauge: prometheus.NewGaugeVec(
config.gaugeOpts.apply(prometheus.GaugeOpts{
Name: "http_server_inflight_requests",
Help: "Current number of HTTP requests the handler is responding to.",
}), []string{"handler_name", "method"}),
serverReqSizeSummary: prometheus.NewSummaryVec(
config.summaryOpts.apply(prometheus.SummaryOpts{
Name: "http_request_size_bytes",
Help: "Tracks the size of HTTP requests.",
}), []string{"handler_name", "method", "http_status_code", "sp_error_code"}),
serverRespSizeSummary: prometheus.NewSummaryVec(
config.summaryOpts.apply(prometheus.SummaryOpts{
Name: "http_response_size_bytes",
Help: "Tracks the size of HTTP responses.",
}), []string{"handler_name", "method", "http_status_code", "sp_error_code"}),
serverReqDuration: prometheus.NewHistogramVec(
config.histogramOpts.apply(prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Tracks the latencies for HTTP requests.",
Buckets: prometheus.DefBuckets,
}), []string{"handler_name", "method", "http_status_code", "sp_error_code"}),
}
}
// NewRegisteredServerMetrics returns a custom ServerMetrics object registered with the user's registry
// and registers some common metrics associated with every instance.
func NewRegisteredServerMetrics(registry prometheus.Registerer, opts ...ServerMetricsOption) *ServerMetrics {
customServerMetrics := NewServerMetrics(opts...)
customServerMetrics.MustRegister(registry)
return customServerMetrics
}
// Register registers the metrics with the registry.
func (m *ServerMetrics) Register(registry prometheus.Registerer) error {
for _, collector := range m.toRegister() {
if err := registry.Register(collector); err != nil {
return err
}
}
return nil
}
// MustRegister registers the metrics with the registry
// Panic if any error occurs much like DefaultRegisterer of Prometheus.
func (m *ServerMetrics) MustRegister(registry prometheus.Registerer) {
registry.MustRegister(m.toRegister()...)
}
func (m *ServerMetrics) toRegister() []prometheus.Collector {
res := []prometheus.Collector{
m.serverReqTotalCounter,
m.serverReqInflightGauge,
m.serverReqSizeSummary,
m.serverRespSizeSummary,
m.serverReqDuration,
}
return res
}
// Describe sends the super-set of all possible descriptors of metrics collected by this Collector to the provided
// channel and returns once the last descriptor has been sent.
func (m *ServerMetrics) Describe(ch chan<- *prometheus.Desc) {
m.serverReqTotalCounter.Describe(ch)
m.serverReqInflightGauge.Describe(ch)
m.serverReqSizeSummary.Describe(ch)
m.serverRespSizeSummary.Describe(ch)
m.serverReqDuration.Describe(ch)
}
// Collect is called by the Prometheus registry when collecting metrics. The implementation sends each
// collected metric via the provided channel and returns once the last metric has been sent.
func (m *ServerMetrics) Collect(ch chan<- prometheus.Metric) {
m.serverReqTotalCounter.Collect(ch)
m.serverReqInflightGauge.Collect(ch)
m.serverReqSizeSummary.Collect(ch)
m.serverRespSizeSummary.Collect(ch)
m.serverReqDuration.Collect(ch)
}
// InstrumentationHandler initializes all metrics, with their appropriate null value, for all HTTP methods registered
// on an HTTP server. This is useful, to ensure that all metrics exist when collecting and querying.
func (m *ServerMetrics) InstrumentationHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
now := time.Now()
wd := &responseWriterDelegator{w: w}
next.ServeHTTP(wd, r)
method := r.Method
httpStatusCode := wd.Status()
handlerName := mux.CurrentRoute(r).GetName()
spErrorCode := wd.GetSPErrorCode()
m.serverReqTotalCounter.WithLabelValues(handlerName, method, httpStatusCode, spErrorCode).Inc()
gauge := m.serverReqInflightGauge.WithLabelValues(handlerName, method)
gauge.Inc()
defer gauge.Dec()
m.serverReqSizeSummary.WithLabelValues(handlerName, method, httpStatusCode, spErrorCode).Observe(float64(computeApproximateRequestSize(r)))
m.serverRespSizeSummary.WithLabelValues(handlerName, method, httpStatusCode, spErrorCode).Observe(float64(wd.size))
observer := m.serverReqDuration.WithLabelValues(handlerName, method, httpStatusCode, spErrorCode)
observer.Observe(time.Since(now).Seconds())
var traceID string
span := trace.SpanFromContext(r.Context())
if span != nil && span.SpanContext().IsSampled() {
traceID = span.SpanContext().TraceID().String()
}
if traceID != "" {
observer.(prometheus.ExemplarObserver).ObserveWithExemplar(
time.Since(now).Seconds(),
prometheus.Labels{"traceID": traceID},
)
}
})
}