-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathprometheus.go
More file actions
340 lines (283 loc) · 10.1 KB
/
prometheus.go
File metadata and controls
340 lines (283 loc) · 10.1 KB
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
package metrics
import (
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
"strings"
"time"
"code.cloudfoundry.org/tlsconfig"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// The Registry keeps track of registered counters and gauges. Optionally, it can
// provide a server on a Prometheus-formatted endpoint.
type Registry struct {
port string
loggr *log.Logger
registerer prometheus.Registerer
mux *http.ServeMux
}
// A cumulative metric that represents a single monotonically increasing counter
// whose value can only increase or be reset to zero on restart
type Counter interface {
Add(float64)
}
// counterVec allows us to hide the prometheus logic from the user. [prometheus.CounterVec] is not
// an actual metric but more of a metric factory which returns a metric for each set of labels.
// To not leak the returned prometheus types, this type is used.
type counterVec struct {
vec *prometheus.CounterVec
}
// gaugeVec allows us to hide the prometheus logic from the user. [prometheus.GaugeVec] is not
// an actual metric but more of a metric factory which returns a metric for each set of labels.
// To not leak the returned prometheus types, this type is used.
type gaugeVec struct {
vec *prometheus.GaugeVec
}
func (c counterVec) Add(f float64, labels []string) {
c.vec.WithLabelValues(labels...).Add(f)
}
func (g gaugeVec) Add(f float64, labels []string) {
g.vec.WithLabelValues(labels...).Add(f)
}
func (g gaugeVec) Set(f float64, labels []string) {
g.vec.WithLabelValues(labels...).Set(f)
}
type CounterVec interface {
// Add to metric, the number of labels must match the number of label names that were
// given when the [CounterVec] was created.
Add(float64, []string)
}
type GaugeVec interface {
// Add to metric, the number of labels must match the number of label names that were
// given when the [GaugeVec] was created.
Add(float64, []string)
Set(float64, []string)
}
// A single numerical value that can arbitrarily go up and down.
type Gauge interface {
Add(float64)
Set(float64)
}
// A histogram counts observations into buckets.
type Histogram interface {
Observe(float64)
}
// histogramVec allows us to hide the prometheus logic from the user
type histogramVec struct {
vec *prometheus.HistogramVec
}
func (c histogramVec) Observe(f float64, labels []string) {
c.vec.WithLabelValues(labels...).Observe(f)
}
type HistogramVec interface {
// Observe the metric, the number of labels must match the number of label names that were
// given when the [HistogramVec] was created.
Observe(f float64, labels []string)
}
// Registry will register the metrics route with the default http mux but will not
// start an http server. This is intentional so that we can combine metrics with
// other things like pprof into one server. To start a server
// just for metrics, use the WithServer RegistryOption
func NewRegistry(logger *log.Logger, opts ...RegistryOption) *Registry {
pr := &Registry{
loggr: logger,
mux: http.NewServeMux(),
}
for _, o := range opts {
o(pr)
}
registry := prometheus.NewRegistry()
pr.registerer = registry
pr.mux.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{
Registry: pr.registerer,
}))
return pr
}
func (p *Registry) RegisterDebugMetrics() {
p.registerer.MustRegister(collectors.NewGoCollector())
p.registerer.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
}
// Creates new counter. When a duplicate is registered, the Registry will return
// the previously created metric.
func (p *Registry) NewCounter(name, helpText string, opts ...MetricOption) Counter {
opt := toPromOpt(name, helpText, opts...)
c := prometheus.NewCounter(prometheus.CounterOpts(opt))
return p.registerCollector(name, c).(Counter)
}
// Creates new counter vector. When a duplicate is registered, the Registry will return
// the previously created metric.
func (p *Registry) NewCounterVec(name, helpText string, labelNames []string, opts ...MetricOption) CounterVec {
opt := toPromOpt(name, helpText, opts...)
c := prometheus.NewCounterVec(prometheus.CounterOpts(opt), labelNames)
// See [counterVec] for details.
return counterVec{vec: p.registerCollector(name, c).(*prometheus.CounterVec)}
}
// Creates new gauge vector. When a duplicate is registered, the Registry will return
// the previously created metric.
func (p *Registry) NewGaugeVec(name, helpText string, labelNames []string, opts ...MetricOption) GaugeVec {
opt := toPromOpt(name, helpText, opts...)
g := prometheus.NewGaugeVec(prometheus.GaugeOpts(opt), labelNames)
// See [gaugeVec] for details.
return gaugeVec{vec: p.registerCollector(name, g).(*prometheus.GaugeVec)}
}
// Creates new gauge. When a duplicate is registered, the Registry will return
// the previously created metric.
func (p *Registry) NewGauge(name, helpText string, opts ...MetricOption) Gauge {
opt := toPromOpt(name, helpText, opts...)
g := prometheus.NewGauge(prometheus.GaugeOpts(opt))
return p.registerCollector(name, g).(Gauge)
}
// Creates new histogram. When a duplicate is registered, the Registry will return
// the previously created metric.
func (p *Registry) NewHistogram(name, helpText string, buckets []float64, opts ...MetricOption) Histogram {
h := prometheus.NewHistogram(toHistogramOpts(name, helpText, buckets, opts...))
return p.registerCollector(name, h).(Histogram)
}
// NewHistogramVec creates new histogram vector. When a duplicate is registered, the Registry will return
// the previously created metric.
func (p *Registry) NewHistogramVec(name, helpText string, labelNames []string, buckets []float64, opts ...MetricOption) HistogramVec {
c := prometheus.NewHistogramVec(toHistogramOpts(name, helpText, buckets, opts...), labelNames)
// See [histogramVec] for details.
return histogramVec{vec: p.registerCollector(name, c).(*prometheus.HistogramVec)}
}
func (p *Registry) RemoveGauge(g Gauge) {
p.registerer.Unregister(g.(prometheus.Collector))
}
func (p *Registry) RemoveHistogram(h Histogram) {
p.registerer.Unregister(h.(prometheus.Collector))
}
func (p *Registry) RemoveHistogramVec(hv HistogramVec) {
if hvIntern, ok := hv.(histogramVec); ok {
p.registerer.Unregister(hvIntern.vec)
}
}
func (p *Registry) RemoveCounter(c Counter) {
p.registerer.Unregister(c.(prometheus.Collector))
}
func (p *Registry) RemoveCounterVec(cv CounterVec) {
if cvIntern, ok := cv.(counterVec); ok {
p.registerer.Unregister(cvIntern.vec)
}
}
func (p *Registry) RemoveGaugeVec(gv GaugeVec) {
if gvIntern, ok := gv.(gaugeVec); ok {
p.registerer.Unregister(gvIntern.vec)
}
}
func (p *Registry) registerCollector(name string, c prometheus.Collector) prometheus.Collector {
err := p.registerer.Register(c)
if err != nil {
typ, ok := err.(prometheus.AlreadyRegisteredError)
if !ok {
p.loggr.Panicf("unable to create %s: %s", name, err)
}
return typ.ExistingCollector
}
return c
}
// Get the port of the running metrics server
func (p *Registry) Port() string {
return fmt.Sprint(p.port)
}
func toPromOpt(name, helpText string, mOpts ...MetricOption) prometheus.Opts {
opt := prometheus.Opts{
Name: name,
Help: helpText,
ConstLabels: make(map[string]string),
}
for _, o := range mOpts {
o(&opt)
}
return opt
}
func toHistogramOpts(name, helpText string, buckets []float64, mOpts ...MetricOption) prometheus.HistogramOpts {
promOpt := toPromOpt(name, helpText, mOpts...)
return prometheus.HistogramOpts{
Namespace: promOpt.Namespace,
Subsystem: promOpt.Subsystem,
Name: promOpt.Name,
Help: promOpt.Help,
ConstLabels: promOpt.ConstLabels,
Buckets: buckets,
}
}
// Options for registry initialization
type RegistryOption func(r *Registry)
// Starts an http server on localhost at the given port to host metrics.
func WithServer(port int) RegistryOption {
return func(r *Registry) {
r.start("127.0.0.1", port)
}
}
// Starts an https server on localhost at the given port to host metrics.
func WithTLSServer(port int, certFile, keyFile, caFile string) RegistryOption {
return func(r *Registry) {
r.startTLS(port, certFile, keyFile, caFile)
}
}
// Starts an http server on the given port to host metrics.
func WithPublicServer(port int) RegistryOption {
return func(r *Registry) {
r.start("0.0.0.0", port)
}
}
func (p *Registry) start(ipAddr string, port int) {
addr := fmt.Sprintf("%s:%d", ipAddr, port)
s := http.Server{
Addr: addr,
Handler: p.mux,
ReadTimeout: 5 * time.Minute,
ReadHeaderTimeout: 5 * time.Minute,
WriteTimeout: 5 * time.Minute,
}
lis, err := net.Listen("tcp", addr)
if err != nil {
p.loggr.Fatalf("Unable to setup metrics endpoint (%s): %s", addr, err)
}
p.loggr.Printf("Metrics endpoint is listening on %s/metrics", lis.Addr().String())
parts := strings.Split(lis.Addr().String(), ":")
p.port = parts[len(parts)-1]
go s.Serve(lis) //nolint:errcheck
}
func (p *Registry) startTLS(port int, certFile, keyFile, caFile string) {
tlsConfig, err := tlsconfig.Build(
tlsconfig.WithInternalServiceDefaults(),
tlsconfig.WithIdentityFromFile(certFile, keyFile),
).Server(
tlsconfig.WithClientAuthenticationFromFile(caFile),
)
if err != nil {
p.loggr.Fatalf("unable to generate server TLS Config: %s", err)
}
addr := fmt.Sprintf("127.0.0.1:%d", port)
s := http.Server{
Addr: addr,
Handler: p.mux,
TLSConfig: tlsConfig,
ReadTimeout: 5 * time.Minute,
ReadHeaderTimeout: 5 * time.Minute,
WriteTimeout: 5 * time.Minute,
}
lis, err := tls.Listen("tcp", addr, tlsConfig)
if err != nil {
p.loggr.Fatalf("Unable to setup metrics endpoint (%s): %s", addr, err)
}
p.loggr.Printf("Metrics endpoint is listening on %s", lis.Addr().String())
parts := strings.Split(lis.Addr().String(), ":")
p.port = parts[len(parts)-1]
go s.Serve(lis) //nolint:errcheck
}
// Options applied to metrics on creation
type MetricOption func(o *prometheus.Opts)
// Add these tags to the metrics
func WithMetricLabels(labels map[string]string) MetricOption {
return func(o *prometheus.Opts) {
for k, v := range labels {
o.ConstLabels[k] = v
}
}
}