/
prometheus.go
151 lines (127 loc) · 5.16 KB
/
prometheus.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
148
149
150
151
package main
// This file is mainly a copy of the prometheus recorder implementation
// provided by the slok/go-http-metrics library adding the server_requests_seconds
// metric used to determine golden signals.
import (
"context"
"errors"
"fmt"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/slok/go-http-metrics/metrics"
)
// Config has the dependencies and values of the recorder.
type Config struct {
// Prefix is the prefix that will be set on the metrics, by default it will be empty.
Prefix string
// DurationBuckets are the buckets used by Prometheus for the HTTP request duration metrics,
// by default uses Prometheus default buckets (from 5ms to 10s).
DurationBuckets []float64
// SizeBuckets are the buckets used by Prometheus for the HTTP response size metrics,
// by default uses a exponential buckets from 100B to 1GB.
SizeBuckets []float64
// Registry is the registry that will be used by the recorder to store the metrics,
// if the default registry is not used then it will use the default one.
Registry prometheus.Registerer
// HandlerIDLabel is the name that will be set to the handler ID label, by default is `handler`.
HandlerIDLabel string
// StatusCodeLabel is the name that will be set to the status code label, by default is `code`.
StatusCodeLabel string
// MethodLabel is the name that will be set to the method label, by default is `method`.
MethodLabel string
// ServiceLabel is the name that will be set to the service label, by default is `service`.
ServiceLabel string
}
func (c *Config) defaults() {
if len(c.DurationBuckets) == 0 {
c.DurationBuckets = prometheus.DefBuckets
}
if len(c.SizeBuckets) == 0 {
c.SizeBuckets = prometheus.ExponentialBuckets(100, 10, 8)
}
if c.Registry == nil {
c.Registry = prometheus.DefaultRegisterer
}
if c.HandlerIDLabel == "" {
c.HandlerIDLabel = "handler"
}
if c.StatusCodeLabel == "" {
c.StatusCodeLabel = "code"
}
if c.MethodLabel == "" {
c.MethodLabel = "method"
}
if c.ServiceLabel == "" {
c.ServiceLabel = "service"
}
}
type recorder struct {
httpRequestDurHistogram *prometheus.HistogramVec
httpResponseSizeHistogram *prometheus.HistogramVec
httpRequestsInflight *prometheus.GaugeVec
httpResponseDurSummary *prometheus.SummaryVec
}
// NewRecorder returns a new metrics recorder that implements the recorder
// using Prometheus as the backend.
func NewRecorder(cfg Config) metrics.Recorder {
cfg.defaults()
r := &recorder{
httpRequestDurHistogram: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: cfg.Prefix,
Subsystem: "http",
Name: "request_duration_seconds",
Help: "The latency of the HTTP requests.",
ConstLabels: map[string]string{"intuit_alerts": "true"},
Buckets: cfg.DurationBuckets,
}, []string{cfg.ServiceLabel, cfg.HandlerIDLabel, cfg.MethodLabel, cfg.StatusCodeLabel}),
httpResponseDurSummary: prometheus.NewSummaryVec(prometheus.SummaryOpts{
Namespace: cfg.Prefix,
Subsystem: "http",
Name: "server_requests_seconds",
Help: "The time spent serving requests.",
ConstLabels: map[string]string{"intuit_alerts": "true"},
}, []string{cfg.ServiceLabel, cfg.HandlerIDLabel, cfg.MethodLabel, cfg.StatusCodeLabel}),
httpResponseSizeHistogram: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: cfg.Prefix,
Subsystem: "http",
Name: "response_size_bytes",
Help: "The size of the HTTP responses.",
Buckets: cfg.SizeBuckets,
ConstLabels: map[string]string{"intuit_alerts": "true"},
}, []string{cfg.ServiceLabel, cfg.HandlerIDLabel, cfg.MethodLabel, cfg.StatusCodeLabel}),
httpRequestsInflight: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: cfg.Prefix,
Subsystem: "http",
Name: "requests_inflight",
Help: "The number of inflight requests being handled at the same time.",
ConstLabels: map[string]string{"intuit_alerts": "true"},
}, []string{cfg.ServiceLabel, cfg.HandlerIDLabel}),
}
var metricCollectors = []prometheus.Collector{
r.httpRequestDurHistogram,
r.httpResponseDurSummary,
r.httpResponseSizeHistogram,
r.httpRequestsInflight,
}
for _, c := range metricCollectors {
err := cfg.Registry.Register(c)
if err != nil {
// ignore already registered errors
var are prometheus.AlreadyRegisteredError
if ok := errors.As(err, &are); !ok {
panic(fmt.Errorf("recorder could not register prometheus metrics: %w", err))
}
}
}
return r
}
func (r recorder) ObserveHTTPRequestDuration(_ context.Context, p metrics.HTTPReqProperties, duration time.Duration) {
r.httpRequestDurHistogram.WithLabelValues(p.Service, p.ID, p.Method, p.Code).Observe(duration.Seconds())
r.httpResponseDurSummary.WithLabelValues(p.Service, p.ID, p.Method, p.Code).Observe(duration.Seconds())
}
func (r recorder) ObserveHTTPResponseSize(_ context.Context, p metrics.HTTPReqProperties, sizeBytes int64) {
r.httpResponseSizeHistogram.WithLabelValues(p.Service, p.ID, p.Method, p.Code).Observe(float64(sizeBytes))
}
func (r recorder) AddInflightRequests(_ context.Context, p metrics.HTTPProperties, quantity int) {
r.httpRequestsInflight.WithLabelValues(p.Service, p.ID).Add(float64(quantity))
}