blob: 509827b6ef1081c8d2e2bcc273df8d9575dc7f57 [file] [log] [blame]
Serge Bazanskic50f6942023-04-24 18:27:22 +02001// Package metrics implements a Prometheus metrics submission interface for BMDB
2// client components. A Metrics object can be attached to a BMDB object, which
3// will make all BMDB sessions/transactions/work statistics be submitted to that
4// Metrics object.
5package metrics
6
7import (
8 "github.com/prometheus/client_golang/prometheus"
9
10 "source.monogon.dev/cloud/bmaas/bmdb/model"
11)
12
13// Processor describes some cloud component and possibly sub-component which acts
14// upon the BMDB. When starting a BMDB session, this Processor can be provided to
15// contextualize the metrics emitted by this session. Because the selected
16// Processor ends up directly as a Prometheus metric label, it must be
17// low-cardinality - thus all possible values are defined as an enum here. If a
18// Session is not configured with a Processor, the default (ProcessorUnknown)
19// will be used.
20type Processor string
21
22const (
23 ProcessorUnknown Processor = ""
24 ProcessorShepherdInitializer Processor = "shepherd-initializer"
25 ProcessorShepherdProvisioner Processor = "shepherd-provisioner"
26 ProcessorShepherdRecoverer Processor = "shepherd-recoverer"
27 ProcessorShepherdUpdater Processor = "shepherd-updater"
28 ProcessorBMSRV Processor = "bmsrv"
Serge Bazanski6f599512023-04-26 19:08:19 +020029 ProcessorScruffyStats Processor = "scruffy-stats"
Serge Bazanskic50f6942023-04-24 18:27:22 +020030)
31
32// String returns the Prometheus label value for use with the 'processor' label
33// key.
34func (p Processor) String() string {
35 switch p {
36 case ProcessorUnknown:
37 return "unknown"
38 default:
39 return string(p)
40 }
41}
42
43// MetricsSet contains all the Prometheus metrics objects related to a BMDB
44// client.
45//
46// The MetricsSet object is goroutine-safe.
47//
48// An empty MetricsSet object is not valid, and should be instead constructed
49// using New.
50//
51// A nil MetricsSet object is valid and represents a no-op metrics recorder
52// that's never collected.
53type MetricsSet struct {
54 sessionStarted *prometheus.CounterVec
55 transactionExecuted *prometheus.CounterVec
56 transactionRetried *prometheus.CounterVec
57 transactionFailed *prometheus.CounterVec
58 workStarted *prometheus.CounterVec
59 workFinished *prometheus.CounterVec
60}
61
62func processorCounter(name, help string, labels ...string) *prometheus.CounterVec {
63 labels = append([]string{"processor"}, labels...)
64 return prometheus.NewCounterVec(
65 prometheus.CounterOpts{
66 Name: name,
67 Help: help,
68 },
69 labels,
70 )
71}
72
73// New creates a new BMDB MetricsSet object which can be then attached to a BMDB
74// object by calling BMDB.EnableMetrics on the MetricsSet object.
75//
76// The given registry must be a valid Prometheus registry, and all metrics
77// contained in this MetricsSet object will be registered into it.
78//
79// The MetricsSet object can be shared between multiple BMDB object.
80//
81// The MetricsSet object is goroutine-safe.
82func New(registry *prometheus.Registry) *MetricsSet {
83 m := &MetricsSet{
84 sessionStarted: processorCounter("bmdb_session_started", "How many sessions this worker started"),
85 transactionExecuted: processorCounter("bmdb_transaction_executed", "How many transactions were performed by this worker"),
86 transactionRetried: processorCounter("bmdb_transaction_retried", "How many transaction retries were performed by this worker"),
87 transactionFailed: processorCounter("bmdb_transaction_failed", "How many transactions failed permanently on this worker"),
88 workStarted: processorCounter("bmdb_work_started", "How many work items were performed by this worker, partitioned by process", "process"),
89 workFinished: processorCounter("bmdb_work_finished", "How many work items were finished by this worker, partitioned by process and result", "process", "result"),
90 }
91 registry.MustRegister(
92 m.sessionStarted,
93 m.transactionExecuted,
94 m.transactionRetried,
95 m.transactionFailed,
96 m.workStarted,
97 m.workFinished,
98 )
99 return m
100}
101
102// ProcessorRecorder wraps a MetricsSet object with the context of some
103// Processor. It exposes methods that record specific events into the managed
104// Metrics.
105//
106// The ProcessorRecorder object is goroutine safe.
107//
108// An empty ProcessorRecorder object is not valid, and should be instead
109// constructed using Metrics.Recorder.
110//
111// A nil ProcessorRecorder object is valid and represents a no-op metrics
112// recorder.
113type ProcessorRecorder struct {
114 m *MetricsSet
115 labels prometheus.Labels
116}
117
118// Recorder builds a ProcessorRecorder for the given Metrics and a given
119// Processor.
120func (m *MetricsSet) Recorder(p Processor) *ProcessorRecorder {
121 if m == nil {
122 return nil
123 }
124 return &ProcessorRecorder{
125 m: m,
126 labels: prometheus.Labels{
127 "processor": p.String(),
128 },
129 }
130}
131
132// OnTransactionStarted should be called any time a BMDB client starts or
133// re-starts a BMDB Transaction. The attempt should either be '1' (for the first
134// attempt) or a number larger than 1 for any subsequent attempt (i.e. retry) of
135// a transaction.
136func (r *ProcessorRecorder) OnTransactionStarted(attempt int64) {
137 if r == nil {
138 return
139 }
140 if attempt == 1 {
141 r.m.transactionExecuted.With(r.labels).Inc()
142 } else {
143 r.m.transactionRetried.With(r.labels).Inc()
144 }
145}
146
147// OnTransactionFailed should be called any time a BMDB client fails a
148// BMDB Transaction permanently.
149func (r *ProcessorRecorder) OnTransactionFailed() {
150 if r == nil {
151 return
152 }
153 r.m.transactionFailed.With(r.labels).Inc()
154}
155
156// OnSessionStarted should be called any time a BMDB client opens a new BMDB
157// Session.
158func (r *ProcessorRecorder) OnSessionStarted() {
159 if r == nil {
160 return
161 }
162 r.m.sessionStarted.With(r.labels).Inc()
163}
164
165// ProcessRecorder wraps a ProcessorRecorder with an additional model.Process.
166// The resulting object can then record work-specific events.
167//
168// The PusherWithProcess object is goroutine-safe.
169type ProcessRecorder struct {
170 *ProcessorRecorder
171 labels prometheus.Labels
172}
173
174// WithProcess wraps a given Pusher with a Process.
175//
176// The resulting PusherWithProcess object is goroutine-safe.
177func (r *ProcessorRecorder) WithProcess(process model.Process) *ProcessRecorder {
178 if r == nil {
179 return nil
180 }
181 return &ProcessRecorder{
182 ProcessorRecorder: r,
183 labels: prometheus.Labels{
184 "processor": r.labels["processor"],
185 "process": string(process),
186 },
187 }
188}
189
190// OnWorkStarted should be called any time a BMDB client starts a new Work item.
191func (r *ProcessRecorder) OnWorkStarted() {
192 if r == nil {
193 return
194 }
195 r.m.workStarted.With(r.labels).Inc()
196}
197
198type WorkResult string
199
200const (
201 WorkResultFinished WorkResult = "finished"
202 WorkResultCanceled WorkResult = "canceled"
203 WorkResultFailed WorkResult = "failed"
204)
205
206// OnWorkFinished should be called any time a BMDB client finishes, cancels or
207// fails a Work item.
208func (r *ProcessRecorder) OnWorkFinished(result WorkResult) {
209 if r == nil {
210 return
211 }
212 r.m.workFinished.MustCurryWith(r.labels).With(prometheus.Labels{"result": string(result)}).Inc()
213}