-
Notifications
You must be signed in to change notification settings - Fork 2.7k
/
metric.go
343 lines (302 loc) · 12.3 KB
/
metric.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
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
341
342
343
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium
package metric
import (
"fmt"
"github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus"
"golang.org/x/exp/maps"
"github.com/cilium/cilium/pkg/logging/logfields"
"github.com/cilium/cilium/pkg/metrics/metric/collections"
)
var logger = logrus.WithField(logfields.LogSubsys, "metric")
// WithMetadata is the interface implemented by any metric defined in this package. These typically embed existing
// prometheus metric types and add additional metadata. In addition, these metrics have the concept of being enabled
// or disabled which is used in place of conditional registration so all metric types can always be registered.
type WithMetadata interface {
IsEnabled() bool
SetEnabled(bool)
Opts() Opts
}
// metric is a "base" structure which can be embedded to provide common functionality.
type metric struct {
enabled bool
opts Opts
labels *labelSet
}
// forEachLabelVector performs a product of all possible label value combinations
// and calls the provided function for each combination.
func (b *metric) forEachLabelVector(fn func(lvls []string)) {
if b.labels == nil {
return
}
var labelValues [][]string
for _, label := range b.labels.lbls {
labelValues = append(labelValues, maps.Keys(label.Values))
}
for _, labelVector := range collections.CartesianProduct(labelValues...) {
fn(labelVector)
}
}
// checkLabelValues checks that the provided label values are within the range
// of provided label values, if labels where defined using the Labels type.
// Violations are logged as errors for detection, but metrics should still
// be collected as is.
func (b *metric) checkLabelValues(lvs ...string) {
if b.labels == nil {
return
}
if err := b.labels.checkLabelValues(lvs); err != nil {
logger.WithError(err).
WithFields(logrus.Fields{
"metric": b.opts.Name,
}).
Warning("metric label constraints violated, metric will still be collected")
}
}
func (b *metric) checkLabels(labels prometheus.Labels) {
if b.labels == nil {
return
}
if err := b.labels.checkLabels(labels); err != nil {
logger.WithError(err).
WithFields(logrus.Fields{
"metric": b.opts.Name,
}).
Warning("metric label constraints violated, metric will still be collected")
}
}
func (b *metric) IsEnabled() bool {
return b.enabled
}
func (b *metric) SetEnabled(e bool) {
b.enabled = e
}
func (b *metric) Opts() Opts {
return b.opts
}
// Vec is a generic type to describe the vectorized version of another metric type, for example Vec[Counter] would be
// our version of a prometheus.CounterVec.
type Vec[T any] interface {
prometheus.Collector
WithMetadata
// CurryWith returns a vector curried with the provided labels, i.e. the
// returned vector has those labels pre-set for all labeled operations performed
// on it. The cardinality of the curried vector is reduced accordingly. The
// order of the remaining labels stays the same (just with the curried labels
// taken out of the sequence – which is relevant for the
// (GetMetric)WithLabelValues methods). It is possible to curry a curried
// vector, but only with labels not yet used for currying before.
//
// The metrics contained in the `Vec[T]` are shared between the curried and
// uncurried vectors. They are just accessed differently. Curried and uncurried
// vectors behave identically in terms of collection. Only one must be
// registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector.
CurryWith(labels prometheus.Labels) (Vec[T], error)
// GetMetricWith returns the `T` for the given Labels map (the label names
// must match those of the variable labels in Desc). If that label map is
// accessed for the first time, a new `T` is created. Implications of
// creating a `T` without using it and keeping the `T` for later use are
// the same as for GetMetricWithLabelValues.
//
// An error is returned if the number and names of the Labels are inconsistent
// with those of the variable labels in Desc (minus any curried labels).
//
// This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods.
GetMetricWith(labels prometheus.Labels) (T, error)
// GetMetricWithLabelValues returns the `T` for the given slice of label
// values (same order as the variable labels in Desc). If that combination of
// label values is accessed for the first time, a new `T` is created.
//
// It is possible to call this method without using the returned `T` to only
// create the new `T` but leave it at its starting value 0.
//
// Keeping the `T` for later use is possible (and should be considered if
// performance is critical), but keep in mind that Reset, DeleteLabelValues and
// Delete can be used to delete the `T` from the `Vec[T]`, assuming it also
// implements `DeletableVec[T]`. In that case,
// the `T` will still exist, but it will not be exported anymore, even if a
// `T` with the same label values is created later.
//
// An error is returned if the number of label values is not the same as the
// number of variable labels in Desc (minus any curried labels).
//
// Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
// an alternative to avoid that type of mistake. For higher label numbers, the
// latter has a much more readable (albeit more verbose) syntax, but it comes
// with a performance overhead (for creating and processing the Labels map).
GetMetricWithLabelValues(lvs ...string) (T, error)
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
// returned an error. Not returning an error allows shortcuts like
//
// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
With(labels prometheus.Labels) T
// WithLabelValues works as GetMetricWithLabelValues, but panics where
// GetMetricWithLabelValues would have returned an error. Not returning an
// error allows shortcuts like
//
// myVec.WithLabelValues("404", "GET").Add(42)
WithLabelValues(lvs ...string) T
}
// DeletableVec is a generic type to describe a vectorized version of another metric type, like Vec[T], but with the
// additional ability to remove labels without re-creating the metric.
type DeletableVec[T any] interface {
Vec[T]
// Delete deletes the metric where the variable labels are the same as those
// passed in as labels. It returns true if a metric was deleted.
//
// It is not an error if the number and names of the Labels are inconsistent
// with those of the VariableLabels in Desc. However, such inconsistent Labels
// can never match an actual metric, so the method will always return false in
// that case.
//
// This method is used for the same purpose as DeleteLabelValues(...string). See
// there for pros and cons of the two methods.
Delete(labels prometheus.Labels) bool
// DeleteLabelValues removes the metric where the variable labels are the same
// as those passed in as labels (same order as the VariableLabels in Desc). It
// returns true if a metric was deleted.
//
// It is not an error if the number of label values is not the same as the
// number of VariableLabels in Desc. However, such inconsistent label count can
// never match an actual metric, so the method will always return false in that
// case.
//
// Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider Delete(Labels) as an
// alternative to avoid that type of mistake. For higher label numbers, the
// latter has a much more readable (albeit more verbose) syntax, but it comes
// with a performance overhead (for creating and processing the Labels map).
// See also the CounterVec example.
DeleteLabelValues(lvs ...string) bool
// DeletePartialMatch deletes all metrics where the variable labels contain all of those
// passed in as labels. The order of the labels does not matter.
// It returns the number of metrics deleted.
//
// Note that curried labels will never be matched if deleting from the curried vector.
// To match curried labels with DeletePartialMatch, it must be called on the base vector.
DeletePartialMatch(labels prometheus.Labels) int
// Reset deletes all metrics in this vector.
Reset()
}
// Opts are a modified and extended version of the prometheus.Opts
// https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Opts
type Opts struct {
// Namespace, Subsystem, and Name are components of the fully-qualified
// name of the Metric (created by joining these components with
// "_"). Only Name is mandatory, the others merely help structuring the
// name. Note that the fully-qualified name of the metric must be a
// valid Prometheus metric name.
Namespace string
Subsystem string
Name string
// Help provides information about this metric.
//
// Metrics with the same fully-qualified name must have the same Help
// string.
Help string
// ConstLabels are used to attach fixed labels to this metric. Metrics
// with the same fully-qualified name must have the same label names in
// their ConstLabels.
//
// ConstLabels are only used rarely. In particular, do not use them to
// attach the same labels to all your metrics. Those use cases are
// better covered by target labels set by the scraping Prometheus
// server, or by one specific metric (e.g. a build_info or a
// machine_role metric). See also
// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels-not-static-scraped-labels
ConstLabels prometheus.Labels
// The name used to enable/disable this metric via the config/flags
ConfigName string
// If true, the metric has to be explicitly enabled via config or flags
Disabled bool
}
func (b Opts) GetConfigName() string {
if b.ConfigName == "" {
return prometheus.BuildFQName(b.Namespace, b.Subsystem, b.Name)
}
return b.ConfigName
}
// Label represents a metric label with a pre-defined range of values.
// This is used with the NewxxxVecWithLabels metrics constructors to initialize
// vector metrics with known label value ranges, avoiding empty metrics.
type Label struct {
Name string
// If defined, only these values are allowed.
Values Values
}
// Values is a distinct set of possible label values for a particular Label.
type Values map[string]struct{}
// NewValues constructs a Values type from a set of strings.
func NewValues(vs ...string) Values {
vals := Values{}
for _, v := range vs {
vals[v] = struct{}{}
}
return vals
}
// Labels is a slice of labels that represents a label set for a vector type
// metric.
type Labels []Label
func (lbls Labels) labelNames() []string {
lns := make([]string, len(lbls))
for i, label := range lbls {
lns[i] = label.Name
}
return lns
}
type labelSet struct {
lbls Labels
m map[string]map[string]struct{}
}
func (l *labelSet) namesToValues() map[string]map[string]struct{} {
if l.m != nil {
return l.m
}
l.m = make(map[string]map[string]struct{})
for _, label := range l.lbls {
l.m[label.Name] = label.Values
}
return l.m
}
func (l *labelSet) checkLabels(labels prometheus.Labels) error {
for name, value := range labels {
if lvs, ok := l.namesToValues()[name]; ok {
if _, ok := lvs[value]; !ok {
return fmt.Errorf("unexpected label vector value for label %q: value %q not defined in label range %v",
name, value, maps.Keys(lvs))
}
} else {
return fmt.Errorf("invalid label name: %s", name)
}
}
return nil
}
func (l *labelSet) checkLabelValues(lvs []string) error {
if len(l.lbls) != len(lvs) {
return fmt.Errorf("unexpected label vector length: expected %d, got %d", len(l.lbls), len(lvs))
}
for i, label := range l.lbls {
if _, ok := label.Values[lvs[i]]; !ok {
return fmt.Errorf("unexpected label vector value for label %q: value %q not defined in label range %v",
label.Name, lvs[i], maps.Keys(label.Values))
}
}
return nil
}
// initLabels is a helper function to initialize the labels of a metric.
// It is used by xxxVecWithLabels metrics constructors to initialize the
// labels of the metric and the vector (i.e. registering all possible label value combinations).
func initLabels[T any](m *metric, labels Labels, vec Vec[T], disabled bool) {
if disabled {
return
}
m.labels = &labelSet{lbls: labels}
m.forEachLabelVector(func(vs []string) {
vec.WithLabelValues(vs...)
})
}