blob: 4ababe6c98128c5d7421b926e84c5c633f8502d9 [file] [log] [blame]
khenaidooab1f7bd2019-11-14 14:00:27 -05001// Copyright 2014 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package prometheus
15
16import (
17 "fmt"
18 "sync"
19
20 "github.com/prometheus/common/model"
21)
22
khenaidood948f772021-08-11 17:49:24 -040023// MetricVec is a Collector to bundle metrics of the same name that differ in
24// their label values. MetricVec is not used directly but as a building block
25// for implementations of vectors of a given metric type, like GaugeVec,
26// CounterVec, SummaryVec, and HistogramVec. It is exported so that it can be
27// used for custom Metric implementations.
28//
29// To create a FooVec for custom Metric Foo, embed a pointer to MetricVec in
30// FooVec and initialize it with NewMetricVec. Implement wrappers for
31// GetMetricWithLabelValues and GetMetricWith that return (Foo, error) rather
32// than (Metric, error). Similarly, create a wrapper for CurryWith that returns
33// (*FooVec, error) rather than (*MetricVec, error). It is recommended to also
34// add the convenience methods WithLabelValues, With, and MustCurryWith, which
35// panic instead of returning errors. See also the MetricVec example.
36type MetricVec struct {
khenaidooab1f7bd2019-11-14 14:00:27 -050037 *metricMap
38
39 curry []curriedLabelValue
40
41 // hashAdd and hashAddByte can be replaced for testing collision handling.
42 hashAdd func(h uint64, s string) uint64
43 hashAddByte func(h uint64, b byte) uint64
44}
45
khenaidood948f772021-08-11 17:49:24 -040046// NewMetricVec returns an initialized metricVec.
47func NewMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *MetricVec {
48 return &MetricVec{
khenaidooab1f7bd2019-11-14 14:00:27 -050049 metricMap: &metricMap{
50 metrics: map[uint64][]metricWithLabelValues{},
51 desc: desc,
52 newMetric: newMetric,
53 },
54 hashAdd: hashAdd,
55 hashAddByte: hashAddByte,
56 }
57}
58
59// DeleteLabelValues removes the metric where the variable labels are the same
60// as those passed in as labels (same order as the VariableLabels in Desc). It
61// returns true if a metric was deleted.
62//
63// It is not an error if the number of label values is not the same as the
64// number of VariableLabels in Desc. However, such inconsistent label count can
65// never match an actual metric, so the method will always return false in that
66// case.
67//
68// Note that for more than one label value, this method is prone to mistakes
69// caused by an incorrect order of arguments. Consider Delete(Labels) as an
70// alternative to avoid that type of mistake. For higher label numbers, the
71// latter has a much more readable (albeit more verbose) syntax, but it comes
72// with a performance overhead (for creating and processing the Labels map).
73// See also the CounterVec example.
khenaidood948f772021-08-11 17:49:24 -040074func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
khenaidooab1f7bd2019-11-14 14:00:27 -050075 h, err := m.hashLabelValues(lvs)
76 if err != nil {
77 return false
78 }
79
80 return m.metricMap.deleteByHashWithLabelValues(h, lvs, m.curry)
81}
82
83// Delete deletes the metric where the variable labels are the same as those
84// passed in as labels. It returns true if a metric was deleted.
85//
86// It is not an error if the number and names of the Labels are inconsistent
87// with those of the VariableLabels in Desc. However, such inconsistent Labels
88// can never match an actual metric, so the method will always return false in
89// that case.
90//
91// This method is used for the same purpose as DeleteLabelValues(...string). See
92// there for pros and cons of the two methods.
khenaidood948f772021-08-11 17:49:24 -040093func (m *MetricVec) Delete(labels Labels) bool {
khenaidooab1f7bd2019-11-14 14:00:27 -050094 h, err := m.hashLabels(labels)
95 if err != nil {
96 return false
97 }
98
99 return m.metricMap.deleteByHashWithLabels(h, labels, m.curry)
100}
101
khenaidood948f772021-08-11 17:49:24 -0400102// Without explicit forwarding of Describe, Collect, Reset, those methods won't
103// show up in GoDoc.
104
105// Describe implements Collector.
106func (m *MetricVec) Describe(ch chan<- *Desc) { m.metricMap.Describe(ch) }
107
108// Collect implements Collector.
109func (m *MetricVec) Collect(ch chan<- Metric) { m.metricMap.Collect(ch) }
110
111// Reset deletes all metrics in this vector.
112func (m *MetricVec) Reset() { m.metricMap.Reset() }
113
114// CurryWith returns a vector curried with the provided labels, i.e. the
115// returned vector has those labels pre-set for all labeled operations performed
116// on it. The cardinality of the curried vector is reduced accordingly. The
117// order of the remaining labels stays the same (just with the curried labels
118// taken out of the sequence – which is relevant for the
119// (GetMetric)WithLabelValues methods). It is possible to curry a curried
120// vector, but only with labels not yet used for currying before.
121//
122// The metrics contained in the MetricVec are shared between the curried and
123// uncurried vectors. They are just accessed differently. Curried and uncurried
124// vectors behave identically in terms of collection. Only one must be
125// registered with a given registry (usually the uncurried version). The Reset
126// method deletes all metrics, even if called on a curried vector.
127//
128// Note that CurryWith is usually not called directly but through a wrapper
129// around MetricVec, implementing a vector for a specific Metric
130// implementation, for example GaugeVec.
131func (m *MetricVec) CurryWith(labels Labels) (*MetricVec, error) {
khenaidooab1f7bd2019-11-14 14:00:27 -0500132 var (
133 newCurry []curriedLabelValue
134 oldCurry = m.curry
135 iCurry int
136 )
137 for i, label := range m.desc.variableLabels {
138 val, ok := labels[label]
139 if iCurry < len(oldCurry) && oldCurry[iCurry].index == i {
140 if ok {
141 return nil, fmt.Errorf("label name %q is already curried", label)
142 }
143 newCurry = append(newCurry, oldCurry[iCurry])
144 iCurry++
145 } else {
146 if !ok {
147 continue // Label stays uncurried.
148 }
149 newCurry = append(newCurry, curriedLabelValue{i, val})
150 }
151 }
152 if l := len(oldCurry) + len(labels) - len(newCurry); l > 0 {
153 return nil, fmt.Errorf("%d unknown label(s) found during currying", l)
154 }
155
khenaidood948f772021-08-11 17:49:24 -0400156 return &MetricVec{
khenaidooab1f7bd2019-11-14 14:00:27 -0500157 metricMap: m.metricMap,
158 curry: newCurry,
159 hashAdd: m.hashAdd,
160 hashAddByte: m.hashAddByte,
161 }, nil
162}
163
khenaidood948f772021-08-11 17:49:24 -0400164// GetMetricWithLabelValues returns the Metric for the given slice of label
165// values (same order as the variable labels in Desc). If that combination of
166// label values is accessed for the first time, a new Metric is created (by
167// calling the newMetric function provided during construction of the
168// MetricVec).
169//
170// It is possible to call this method without using the returned Metric to only
171// create the new Metric but leave it in its initial state.
172//
173// Keeping the Metric for later use is possible (and should be considered if
174// performance is critical), but keep in mind that Reset, DeleteLabelValues and
175// Delete can be used to delete the Metric from the MetricVec. In that case, the
176// Metric will still exist, but it will not be exported anymore, even if a
177// Metric with the same label values is created later.
178//
179// An error is returned if the number of label values is not the same as the
180// number of variable labels in Desc (minus any curried labels).
181//
182// Note that for more than one label value, this method is prone to mistakes
183// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
184// an alternative to avoid that type of mistake. For higher label numbers, the
185// latter has a much more readable (albeit more verbose) syntax, but it comes
186// with a performance overhead (for creating and processing the Labels map).
187//
188// Note that GetMetricWithLabelValues is usually not called directly but through
189// a wrapper around MetricVec, implementing a vector for a specific Metric
190// implementation, for example GaugeVec.
191func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
khenaidooab1f7bd2019-11-14 14:00:27 -0500192 h, err := m.hashLabelValues(lvs)
193 if err != nil {
194 return nil, err
195 }
196
197 return m.metricMap.getOrCreateMetricWithLabelValues(h, lvs, m.curry), nil
198}
199
khenaidood948f772021-08-11 17:49:24 -0400200// GetMetricWith returns the Metric for the given Labels map (the label names
201// must match those of the variable labels in Desc). If that label map is
202// accessed for the first time, a new Metric is created. Implications of
203// creating a Metric without using it and keeping the Metric for later use
204// are the same as for GetMetricWithLabelValues.
205//
206// An error is returned if the number and names of the Labels are inconsistent
207// with those of the variable labels in Desc (minus any curried labels).
208//
209// This method is used for the same purpose as
210// GetMetricWithLabelValues(...string). See there for pros and cons of the two
211// methods.
212//
213// Note that GetMetricWith is usually not called directly but through a wrapper
214// around MetricVec, implementing a vector for a specific Metric implementation,
215// for example GaugeVec.
216func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) {
khenaidooab1f7bd2019-11-14 14:00:27 -0500217 h, err := m.hashLabels(labels)
218 if err != nil {
219 return nil, err
220 }
221
222 return m.metricMap.getOrCreateMetricWithLabels(h, labels, m.curry), nil
223}
224
khenaidood948f772021-08-11 17:49:24 -0400225func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) {
khenaidooab1f7bd2019-11-14 14:00:27 -0500226 if err := validateLabelValues(vals, len(m.desc.variableLabels)-len(m.curry)); err != nil {
227 return 0, err
228 }
229
230 var (
231 h = hashNew()
232 curry = m.curry
233 iVals, iCurry int
234 )
235 for i := 0; i < len(m.desc.variableLabels); i++ {
236 if iCurry < len(curry) && curry[iCurry].index == i {
237 h = m.hashAdd(h, curry[iCurry].value)
238 iCurry++
239 } else {
240 h = m.hashAdd(h, vals[iVals])
241 iVals++
242 }
243 h = m.hashAddByte(h, model.SeparatorByte)
244 }
245 return h, nil
246}
247
khenaidood948f772021-08-11 17:49:24 -0400248func (m *MetricVec) hashLabels(labels Labels) (uint64, error) {
khenaidooab1f7bd2019-11-14 14:00:27 -0500249 if err := validateValuesInLabels(labels, len(m.desc.variableLabels)-len(m.curry)); err != nil {
250 return 0, err
251 }
252
253 var (
254 h = hashNew()
255 curry = m.curry
256 iCurry int
257 )
258 for i, label := range m.desc.variableLabels {
259 val, ok := labels[label]
260 if iCurry < len(curry) && curry[iCurry].index == i {
261 if ok {
262 return 0, fmt.Errorf("label name %q is already curried", label)
263 }
264 h = m.hashAdd(h, curry[iCurry].value)
265 iCurry++
266 } else {
267 if !ok {
268 return 0, fmt.Errorf("label name %q missing in label map", label)
269 }
270 h = m.hashAdd(h, val)
271 }
272 h = m.hashAddByte(h, model.SeparatorByte)
273 }
274 return h, nil
275}
276
277// metricWithLabelValues provides the metric and its label values for
278// disambiguation on hash collision.
279type metricWithLabelValues struct {
280 values []string
281 metric Metric
282}
283
284// curriedLabelValue sets the curried value for a label at the given index.
285type curriedLabelValue struct {
286 index int
287 value string
288}
289
290// metricMap is a helper for metricVec and shared between differently curried
291// metricVecs.
292type metricMap struct {
293 mtx sync.RWMutex // Protects metrics.
294 metrics map[uint64][]metricWithLabelValues
295 desc *Desc
296 newMetric func(labelValues ...string) Metric
297}
298
299// Describe implements Collector. It will send exactly one Desc to the provided
300// channel.
301func (m *metricMap) Describe(ch chan<- *Desc) {
302 ch <- m.desc
303}
304
305// Collect implements Collector.
306func (m *metricMap) Collect(ch chan<- Metric) {
307 m.mtx.RLock()
308 defer m.mtx.RUnlock()
309
310 for _, metrics := range m.metrics {
311 for _, metric := range metrics {
312 ch <- metric.metric
313 }
314 }
315}
316
317// Reset deletes all metrics in this vector.
318func (m *metricMap) Reset() {
319 m.mtx.Lock()
320 defer m.mtx.Unlock()
321
322 for h := range m.metrics {
323 delete(m.metrics, h)
324 }
325}
326
327// deleteByHashWithLabelValues removes the metric from the hash bucket h. If
328// there are multiple matches in the bucket, use lvs to select a metric and
329// remove only that metric.
330func (m *metricMap) deleteByHashWithLabelValues(
331 h uint64, lvs []string, curry []curriedLabelValue,
332) bool {
333 m.mtx.Lock()
334 defer m.mtx.Unlock()
335
336 metrics, ok := m.metrics[h]
337 if !ok {
338 return false
339 }
340
341 i := findMetricWithLabelValues(metrics, lvs, curry)
342 if i >= len(metrics) {
343 return false
344 }
345
346 if len(metrics) > 1 {
khenaidood948f772021-08-11 17:49:24 -0400347 old := metrics
khenaidooab1f7bd2019-11-14 14:00:27 -0500348 m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
khenaidood948f772021-08-11 17:49:24 -0400349 old[len(old)-1] = metricWithLabelValues{}
khenaidooab1f7bd2019-11-14 14:00:27 -0500350 } else {
351 delete(m.metrics, h)
352 }
353 return true
354}
355
356// deleteByHashWithLabels removes the metric from the hash bucket h. If there
357// are multiple matches in the bucket, use lvs to select a metric and remove
358// only that metric.
359func (m *metricMap) deleteByHashWithLabels(
360 h uint64, labels Labels, curry []curriedLabelValue,
361) bool {
362 m.mtx.Lock()
363 defer m.mtx.Unlock()
364
365 metrics, ok := m.metrics[h]
366 if !ok {
367 return false
368 }
369 i := findMetricWithLabels(m.desc, metrics, labels, curry)
370 if i >= len(metrics) {
371 return false
372 }
373
374 if len(metrics) > 1 {
khenaidood948f772021-08-11 17:49:24 -0400375 old := metrics
khenaidooab1f7bd2019-11-14 14:00:27 -0500376 m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
khenaidood948f772021-08-11 17:49:24 -0400377 old[len(old)-1] = metricWithLabelValues{}
khenaidooab1f7bd2019-11-14 14:00:27 -0500378 } else {
379 delete(m.metrics, h)
380 }
381 return true
382}
383
384// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
385// or creates it and returns the new one.
386//
387// This function holds the mutex.
388func (m *metricMap) getOrCreateMetricWithLabelValues(
389 hash uint64, lvs []string, curry []curriedLabelValue,
390) Metric {
391 m.mtx.RLock()
392 metric, ok := m.getMetricWithHashAndLabelValues(hash, lvs, curry)
393 m.mtx.RUnlock()
394 if ok {
395 return metric
396 }
397
398 m.mtx.Lock()
399 defer m.mtx.Unlock()
400 metric, ok = m.getMetricWithHashAndLabelValues(hash, lvs, curry)
401 if !ok {
402 inlinedLVs := inlineLabelValues(lvs, curry)
403 metric = m.newMetric(inlinedLVs...)
404 m.metrics[hash] = append(m.metrics[hash], metricWithLabelValues{values: inlinedLVs, metric: metric})
405 }
406 return metric
407}
408
409// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
410// or creates it and returns the new one.
411//
412// This function holds the mutex.
413func (m *metricMap) getOrCreateMetricWithLabels(
414 hash uint64, labels Labels, curry []curriedLabelValue,
415) Metric {
416 m.mtx.RLock()
417 metric, ok := m.getMetricWithHashAndLabels(hash, labels, curry)
418 m.mtx.RUnlock()
419 if ok {
420 return metric
421 }
422
423 m.mtx.Lock()
424 defer m.mtx.Unlock()
425 metric, ok = m.getMetricWithHashAndLabels(hash, labels, curry)
426 if !ok {
427 lvs := extractLabelValues(m.desc, labels, curry)
428 metric = m.newMetric(lvs...)
429 m.metrics[hash] = append(m.metrics[hash], metricWithLabelValues{values: lvs, metric: metric})
430 }
431 return metric
432}
433
434// getMetricWithHashAndLabelValues gets a metric while handling possible
435// collisions in the hash space. Must be called while holding the read mutex.
436func (m *metricMap) getMetricWithHashAndLabelValues(
437 h uint64, lvs []string, curry []curriedLabelValue,
438) (Metric, bool) {
439 metrics, ok := m.metrics[h]
440 if ok {
441 if i := findMetricWithLabelValues(metrics, lvs, curry); i < len(metrics) {
442 return metrics[i].metric, true
443 }
444 }
445 return nil, false
446}
447
448// getMetricWithHashAndLabels gets a metric while handling possible collisions in
449// the hash space. Must be called while holding read mutex.
450func (m *metricMap) getMetricWithHashAndLabels(
451 h uint64, labels Labels, curry []curriedLabelValue,
452) (Metric, bool) {
453 metrics, ok := m.metrics[h]
454 if ok {
455 if i := findMetricWithLabels(m.desc, metrics, labels, curry); i < len(metrics) {
456 return metrics[i].metric, true
457 }
458 }
459 return nil, false
460}
461
462// findMetricWithLabelValues returns the index of the matching metric or
463// len(metrics) if not found.
464func findMetricWithLabelValues(
465 metrics []metricWithLabelValues, lvs []string, curry []curriedLabelValue,
466) int {
467 for i, metric := range metrics {
468 if matchLabelValues(metric.values, lvs, curry) {
469 return i
470 }
471 }
472 return len(metrics)
473}
474
475// findMetricWithLabels returns the index of the matching metric or len(metrics)
476// if not found.
477func findMetricWithLabels(
478 desc *Desc, metrics []metricWithLabelValues, labels Labels, curry []curriedLabelValue,
479) int {
480 for i, metric := range metrics {
481 if matchLabels(desc, metric.values, labels, curry) {
482 return i
483 }
484 }
485 return len(metrics)
486}
487
488func matchLabelValues(values []string, lvs []string, curry []curriedLabelValue) bool {
489 if len(values) != len(lvs)+len(curry) {
490 return false
491 }
492 var iLVs, iCurry int
493 for i, v := range values {
494 if iCurry < len(curry) && curry[iCurry].index == i {
495 if v != curry[iCurry].value {
496 return false
497 }
498 iCurry++
499 continue
500 }
501 if v != lvs[iLVs] {
502 return false
503 }
504 iLVs++
505 }
506 return true
507}
508
509func matchLabels(desc *Desc, values []string, labels Labels, curry []curriedLabelValue) bool {
510 if len(values) != len(labels)+len(curry) {
511 return false
512 }
513 iCurry := 0
514 for i, k := range desc.variableLabels {
515 if iCurry < len(curry) && curry[iCurry].index == i {
516 if values[i] != curry[iCurry].value {
517 return false
518 }
519 iCurry++
520 continue
521 }
522 if values[i] != labels[k] {
523 return false
524 }
525 }
526 return true
527}
528
529func extractLabelValues(desc *Desc, labels Labels, curry []curriedLabelValue) []string {
530 labelValues := make([]string, len(labels)+len(curry))
531 iCurry := 0
532 for i, k := range desc.variableLabels {
533 if iCurry < len(curry) && curry[iCurry].index == i {
534 labelValues[i] = curry[iCurry].value
535 iCurry++
536 continue
537 }
538 labelValues[i] = labels[k]
539 }
540 return labelValues
541}
542
543func inlineLabelValues(lvs []string, curry []curriedLabelValue) []string {
544 labelValues := make([]string, len(lvs)+len(curry))
545 var iCurry, iLVs int
546 for i := range labelValues {
547 if iCurry < len(curry) && curry[iCurry].index == i {
548 labelValues[i] = curry[iCurry].value
549 iCurry++
550 continue
551 }
552 labelValues[i] = lvs[iLVs]
553 iLVs++
554 }
555 return labelValues
556}