1// 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
23// 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 {
37	*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
46// NewMetricVec returns an initialized metricVec.
47func NewMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *MetricVec {
48	return &MetricVec{
49		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.
74func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
75	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.
93func (m *MetricVec) Delete(labels Labels) bool {
94	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
102// 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) {
132	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
156	return &MetricVec{
157		metricMap:   m.metricMap,
158		curry:       newCurry,
159		hashAdd:     m.hashAdd,
160		hashAddByte: m.hashAddByte,
161	}, nil
162}
163
164// 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 Metry to only
171// create the new Metric but leave it in its intitial 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) {
192	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
200// 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) {
217	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
225func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) {
226	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
248func (m *MetricVec) hashLabels(labels Labels) (uint64, error) {
249	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 {
347		old := metrics
348		m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
349		old[len(old)-1] = metricWithLabelValues{}
350	} 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 {
375		old := metrics
376		m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
377		old[len(old)-1] = metricWithLabelValues{}
378	} 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}
557