1// Copyright 2015 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	"math"
19	"runtime"
20	"sort"
21	"sync"
22	"sync/atomic"
23
24	"github.com/golang/protobuf/proto"
25
26	dto "github.com/prometheus/client_model/go"
27)
28
29// A Histogram counts individual observations from an event or sample stream in
30// configurable buckets. Similar to a summary, it also provides a sum of
31// observations and an observation count.
32//
33// On the Prometheus server, quantiles can be calculated from a Histogram using
34// the histogram_quantile function in the query language.
35//
36// Note that Histograms, in contrast to Summaries, can be aggregated with the
37// Prometheus query language (see the documentation for detailed
38// procedures). However, Histograms require the user to pre-define suitable
39// buckets, and they are in general less accurate. The Observe method of a
40// Histogram has a very low performance overhead in comparison with the Observe
41// method of a Summary.
42//
43// To create Histogram instances, use NewHistogram.
44type Histogram interface {
45	Metric
46	Collector
47
48	// Observe adds a single observation to the histogram.
49	Observe(float64)
50}
51
52// bucketLabel is used for the label that defines the upper bound of a
53// bucket of a histogram ("le" -> "less or equal").
54const bucketLabel = "le"
55
56// DefBuckets are the default Histogram buckets. The default buckets are
57// tailored to broadly measure the response time (in seconds) of a network
58// service. Most likely, however, you will be required to define buckets
59// customized to your use case.
60var (
61	DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
62
63	errBucketLabelNotAllowed = fmt.Errorf(
64		"%q is not allowed as label name in histograms", bucketLabel,
65	)
66)
67
68// LinearBuckets creates 'count' buckets, each 'width' wide, where the lowest
69// bucket has an upper bound of 'start'. The final +Inf bucket is not counted
70// and not included in the returned slice. The returned slice is meant to be
71// used for the Buckets field of HistogramOpts.
72//
73// The function panics if 'count' is zero or negative.
74func LinearBuckets(start, width float64, count int) []float64 {
75	if count < 1 {
76		panic("LinearBuckets needs a positive count")
77	}
78	buckets := make([]float64, count)
79	for i := range buckets {
80		buckets[i] = start
81		start += width
82	}
83	return buckets
84}
85
86// ExponentialBuckets creates 'count' buckets, where the lowest bucket has an
87// upper bound of 'start' and each following bucket's upper bound is 'factor'
88// times the previous bucket's upper bound. The final +Inf bucket is not counted
89// and not included in the returned slice. The returned slice is meant to be
90// used for the Buckets field of HistogramOpts.
91//
92// The function panics if 'count' is 0 or negative, if 'start' is 0 or negative,
93// or if 'factor' is less than or equal 1.
94func ExponentialBuckets(start, factor float64, count int) []float64 {
95	if count < 1 {
96		panic("ExponentialBuckets needs a positive count")
97	}
98	if start <= 0 {
99		panic("ExponentialBuckets needs a positive start value")
100	}
101	if factor <= 1 {
102		panic("ExponentialBuckets needs a factor greater than 1")
103	}
104	buckets := make([]float64, count)
105	for i := range buckets {
106		buckets[i] = start
107		start *= factor
108	}
109	return buckets
110}
111
112// HistogramOpts bundles the options for creating a Histogram metric. It is
113// mandatory to set Name to a non-empty string. All other fields are optional
114// and can safely be left at their zero value, although it is strongly
115// encouraged to set a Help string.
116type HistogramOpts struct {
117	// Namespace, Subsystem, and Name are components of the fully-qualified
118	// name of the Histogram (created by joining these components with
119	// "_"). Only Name is mandatory, the others merely help structuring the
120	// name. Note that the fully-qualified name of the Histogram must be a
121	// valid Prometheus metric name.
122	Namespace string
123	Subsystem string
124	Name      string
125
126	// Help provides information about this Histogram.
127	//
128	// Metrics with the same fully-qualified name must have the same Help
129	// string.
130	Help string
131
132	// ConstLabels are used to attach fixed labels to this metric. Metrics
133	// with the same fully-qualified name must have the same label names in
134	// their ConstLabels.
135	//
136	// ConstLabels are only used rarely. In particular, do not use them to
137	// attach the same labels to all your metrics. Those use cases are
138	// better covered by target labels set by the scraping Prometheus
139	// server, or by one specific metric (e.g. a build_info or a
140	// machine_role metric). See also
141	// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
142	ConstLabels Labels
143
144	// Buckets defines the buckets into which observations are counted. Each
145	// element in the slice is the upper inclusive bound of a bucket. The
146	// values must be sorted in strictly increasing order. There is no need
147	// to add a highest bucket with +Inf bound, it will be added
148	// implicitly. The default value is DefBuckets.
149	Buckets []float64
150}
151
152// NewHistogram creates a new Histogram based on the provided HistogramOpts. It
153// panics if the buckets in HistogramOpts are not in strictly increasing order.
154func NewHistogram(opts HistogramOpts) Histogram {
155	return newHistogram(
156		NewDesc(
157			BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
158			opts.Help,
159			nil,
160			opts.ConstLabels,
161		),
162		opts,
163	)
164}
165
166func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
167	if len(desc.variableLabels) != len(labelValues) {
168		panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
169	}
170
171	for _, n := range desc.variableLabels {
172		if n == bucketLabel {
173			panic(errBucketLabelNotAllowed)
174		}
175	}
176	for _, lp := range desc.constLabelPairs {
177		if lp.GetName() == bucketLabel {
178			panic(errBucketLabelNotAllowed)
179		}
180	}
181
182	if len(opts.Buckets) == 0 {
183		opts.Buckets = DefBuckets
184	}
185
186	h := &histogram{
187		desc:        desc,
188		upperBounds: opts.Buckets,
189		labelPairs:  makeLabelPairs(desc, labelValues),
190		counts:      [2]*histogramCounts{&histogramCounts{}, &histogramCounts{}},
191	}
192	for i, upperBound := range h.upperBounds {
193		if i < len(h.upperBounds)-1 {
194			if upperBound >= h.upperBounds[i+1] {
195				panic(fmt.Errorf(
196					"histogram buckets must be in increasing order: %f >= %f",
197					upperBound, h.upperBounds[i+1],
198				))
199			}
200		} else {
201			if math.IsInf(upperBound, +1) {
202				// The +Inf bucket is implicit. Remove it here.
203				h.upperBounds = h.upperBounds[:i]
204			}
205		}
206	}
207	// Finally we know the final length of h.upperBounds and can make buckets
208	// for both counts:
209	h.counts[0].buckets = make([]uint64, len(h.upperBounds))
210	h.counts[1].buckets = make([]uint64, len(h.upperBounds))
211
212	h.init(h) // Init self-collection.
213	return h
214}
215
216type histogramCounts struct {
217	// sumBits contains the bits of the float64 representing the sum of all
218	// observations. sumBits and count have to go first in the struct to
219	// guarantee alignment for atomic operations.
220	// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
221	sumBits uint64
222	count   uint64
223	buckets []uint64
224}
225
226type histogram struct {
227	// countAndHotIdx enables lock-free writes with use of atomic updates.
228	// The most significant bit is the hot index [0 or 1] of the count field
229	// below. Observe calls update the hot one. All remaining bits count the
230	// number of Observe calls. Observe starts by incrementing this counter,
231	// and finish by incrementing the count field in the respective
232	// histogramCounts, as a marker for completion.
233	//
234	// Calls of the Write method (which are non-mutating reads from the
235	// perspective of the histogram) swap the hot–cold under the writeMtx
236	// lock. A cooldown is awaited (while locked) by comparing the number of
237	// observations with the initiation count. Once they match, then the
238	// last observation on the now cool one has completed. All cool fields must
239	// be merged into the new hot before releasing writeMtx.
240	//
241	// Fields with atomic access first! See alignment constraint:
242	// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
243	countAndHotIdx uint64
244
245	selfCollector
246	desc     *Desc
247	writeMtx sync.Mutex // Only used in the Write method.
248
249	// Two counts, one is "hot" for lock-free observations, the other is
250	// "cold" for writing out a dto.Metric. It has to be an array of
251	// pointers to guarantee 64bit alignment of the histogramCounts, see
252	// http://golang.org/pkg/sync/atomic/#pkg-note-BUG.
253	counts [2]*histogramCounts
254
255	upperBounds []float64
256	labelPairs  []*dto.LabelPair
257}
258
259func (h *histogram) Desc() *Desc {
260	return h.desc
261}
262
263func (h *histogram) Observe(v float64) {
264	// TODO(beorn7): For small numbers of buckets (<30), a linear search is
265	// slightly faster than the binary search. If we really care, we could
266	// switch from one search strategy to the other depending on the number
267	// of buckets.
268	//
269	// Microbenchmarks (BenchmarkHistogramNoLabels):
270	// 11 buckets: 38.3 ns/op linear - binary 48.7 ns/op
271	// 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op
272	// 300 buckets: 154 ns/op linear - binary 61.6 ns/op
273	i := sort.SearchFloat64s(h.upperBounds, v)
274
275	// We increment h.countAndHotIdx so that the counter in the lower
276	// 63 bits gets incremented. At the same time, we get the new value
277	// back, which we can use to find the currently-hot counts.
278	n := atomic.AddUint64(&h.countAndHotIdx, 1)
279	hotCounts := h.counts[n>>63]
280
281	if i < len(h.upperBounds) {
282		atomic.AddUint64(&hotCounts.buckets[i], 1)
283	}
284	for {
285		oldBits := atomic.LoadUint64(&hotCounts.sumBits)
286		newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
287		if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
288			break
289		}
290	}
291	// Increment count last as we take it as a signal that the observation
292	// is complete.
293	atomic.AddUint64(&hotCounts.count, 1)
294}
295
296func (h *histogram) Write(out *dto.Metric) error {
297	// For simplicity, we protect this whole method by a mutex. It is not in
298	// the hot path, i.e. Observe is called much more often than Write. The
299	// complication of making Write lock-free isn't worth it, if possible at
300	// all.
301	h.writeMtx.Lock()
302	defer h.writeMtx.Unlock()
303
304	// Adding 1<<63 switches the hot index (from 0 to 1 or from 1 to 0)
305	// without touching the count bits. See the struct comments for a full
306	// description of the algorithm.
307	n := atomic.AddUint64(&h.countAndHotIdx, 1<<63)
308	// count is contained unchanged in the lower 63 bits.
309	count := n & ((1 << 63) - 1)
310	// The most significant bit tells us which counts is hot. The complement
311	// is thus the cold one.
312	hotCounts := h.counts[n>>63]
313	coldCounts := h.counts[(^n)>>63]
314
315	// Await cooldown.
316	for count != atomic.LoadUint64(&coldCounts.count) {
317		runtime.Gosched() // Let observations get work done.
318	}
319
320	his := &dto.Histogram{
321		Bucket:      make([]*dto.Bucket, len(h.upperBounds)),
322		SampleCount: proto.Uint64(count),
323		SampleSum:   proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
324	}
325	var cumCount uint64
326	for i, upperBound := range h.upperBounds {
327		cumCount += atomic.LoadUint64(&coldCounts.buckets[i])
328		his.Bucket[i] = &dto.Bucket{
329			CumulativeCount: proto.Uint64(cumCount),
330			UpperBound:      proto.Float64(upperBound),
331		}
332	}
333
334	out.Histogram = his
335	out.Label = h.labelPairs
336
337	// Finally add all the cold counts to the new hot counts and reset the cold counts.
338	atomic.AddUint64(&hotCounts.count, count)
339	atomic.StoreUint64(&coldCounts.count, 0)
340	for {
341		oldBits := atomic.LoadUint64(&hotCounts.sumBits)
342		newBits := math.Float64bits(math.Float64frombits(oldBits) + his.GetSampleSum())
343		if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
344			atomic.StoreUint64(&coldCounts.sumBits, 0)
345			break
346		}
347	}
348	for i := range h.upperBounds {
349		atomic.AddUint64(&hotCounts.buckets[i], atomic.LoadUint64(&coldCounts.buckets[i]))
350		atomic.StoreUint64(&coldCounts.buckets[i], 0)
351	}
352	return nil
353}
354
355// HistogramVec is a Collector that bundles a set of Histograms that all share the
356// same Desc, but have different values for their variable labels. This is used
357// if you want to count the same thing partitioned by various dimensions
358// (e.g. HTTP request latencies, partitioned by status code and method). Create
359// instances with NewHistogramVec.
360type HistogramVec struct {
361	*metricVec
362}
363
364// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
365// partitioned by the given label names.
366func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
367	desc := NewDesc(
368		BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
369		opts.Help,
370		labelNames,
371		opts.ConstLabels,
372	)
373	return &HistogramVec{
374		metricVec: newMetricVec(desc, func(lvs ...string) Metric {
375			return newHistogram(desc, opts, lvs...)
376		}),
377	}
378}
379
380// GetMetricWithLabelValues returns the Histogram for the given slice of label
381// values (same order as the VariableLabels in Desc). If that combination of
382// label values is accessed for the first time, a new Histogram is created.
383//
384// It is possible to call this method without using the returned Histogram to only
385// create the new Histogram but leave it at its starting value, a Histogram without
386// any observations.
387//
388// Keeping the Histogram for later use is possible (and should be considered if
389// performance is critical), but keep in mind that Reset, DeleteLabelValues and
390// Delete can be used to delete the Histogram from the HistogramVec. In that case, the
391// Histogram will still exist, but it will not be exported anymore, even if a
392// Histogram with the same label values is created later. See also the CounterVec
393// example.
394//
395// An error is returned if the number of label values is not the same as the
396// number of VariableLabels in Desc (minus any curried labels).
397//
398// Note that for more than one label value, this method is prone to mistakes
399// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
400// an alternative to avoid that type of mistake. For higher label numbers, the
401// latter has a much more readable (albeit more verbose) syntax, but it comes
402// with a performance overhead (for creating and processing the Labels map).
403// See also the GaugeVec example.
404func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
405	metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
406	if metric != nil {
407		return metric.(Observer), err
408	}
409	return nil, err
410}
411
412// GetMetricWith returns the Histogram for the given Labels map (the label names
413// must match those of the VariableLabels in Desc). If that label map is
414// accessed for the first time, a new Histogram is created. Implications of
415// creating a Histogram without using it and keeping the Histogram for later use
416// are the same as for GetMetricWithLabelValues.
417//
418// An error is returned if the number and names of the Labels are inconsistent
419// with those of the VariableLabels in Desc (minus any curried labels).
420//
421// This method is used for the same purpose as
422// GetMetricWithLabelValues(...string). See there for pros and cons of the two
423// methods.
424func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) {
425	metric, err := v.metricVec.getMetricWith(labels)
426	if metric != nil {
427		return metric.(Observer), err
428	}
429	return nil, err
430}
431
432// WithLabelValues works as GetMetricWithLabelValues, but panics where
433// GetMetricWithLabelValues would have returned an error. Not returning an
434// error allows shortcuts like
435//     myVec.WithLabelValues("404", "GET").Observe(42.21)
436func (v *HistogramVec) WithLabelValues(lvs ...string) Observer {
437	h, err := v.GetMetricWithLabelValues(lvs...)
438	if err != nil {
439		panic(err)
440	}
441	return h
442}
443
444// With works as GetMetricWith but panics where GetMetricWithLabels would have
445// returned an error. Not returning an error allows shortcuts like
446//     myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
447func (v *HistogramVec) With(labels Labels) Observer {
448	h, err := v.GetMetricWith(labels)
449	if err != nil {
450		panic(err)
451	}
452	return h
453}
454
455// CurryWith returns a vector curried with the provided labels, i.e. the
456// returned vector has those labels pre-set for all labeled operations performed
457// on it. The cardinality of the curried vector is reduced accordingly. The
458// order of the remaining labels stays the same (just with the curried labels
459// taken out of the sequence – which is relevant for the
460// (GetMetric)WithLabelValues methods). It is possible to curry a curried
461// vector, but only with labels not yet used for currying before.
462//
463// The metrics contained in the HistogramVec are shared between the curried and
464// uncurried vectors. They are just accessed differently. Curried and uncurried
465// vectors behave identically in terms of collection. Only one must be
466// registered with a given registry (usually the uncurried version). The Reset
467// method deletes all metrics, even if called on a curried vector.
468func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error) {
469	vec, err := v.curryWith(labels)
470	if vec != nil {
471		return &HistogramVec{vec}, err
472	}
473	return nil, err
474}
475
476// MustCurryWith works as CurryWith but panics where CurryWith would have
477// returned an error.
478func (v *HistogramVec) MustCurryWith(labels Labels) ObserverVec {
479	vec, err := v.CurryWith(labels)
480	if err != nil {
481		panic(err)
482	}
483	return vec
484}
485
486type constHistogram struct {
487	desc       *Desc
488	count      uint64
489	sum        float64
490	buckets    map[float64]uint64
491	labelPairs []*dto.LabelPair
492}
493
494func (h *constHistogram) Desc() *Desc {
495	return h.desc
496}
497
498func (h *constHistogram) Write(out *dto.Metric) error {
499	his := &dto.Histogram{}
500	buckets := make([]*dto.Bucket, 0, len(h.buckets))
501
502	his.SampleCount = proto.Uint64(h.count)
503	his.SampleSum = proto.Float64(h.sum)
504
505	for upperBound, count := range h.buckets {
506		buckets = append(buckets, &dto.Bucket{
507			CumulativeCount: proto.Uint64(count),
508			UpperBound:      proto.Float64(upperBound),
509		})
510	}
511
512	if len(buckets) > 0 {
513		sort.Sort(buckSort(buckets))
514	}
515	his.Bucket = buckets
516
517	out.Histogram = his
518	out.Label = h.labelPairs
519
520	return nil
521}
522
523// NewConstHistogram returns a metric representing a Prometheus histogram with
524// fixed values for the count, sum, and bucket counts. As those parameters
525// cannot be changed, the returned value does not implement the Histogram
526// interface (but only the Metric interface). Users of this package will not
527// have much use for it in regular operations. However, when implementing custom
528// Collectors, it is useful as a throw-away metric that is generated on the fly
529// to send it to Prometheus in the Collect method.
530//
531// buckets is a map of upper bounds to cumulative counts, excluding the +Inf
532// bucket.
533//
534// NewConstHistogram returns an error if the length of labelValues is not
535// consistent with the variable labels in Desc or if Desc is invalid.
536func NewConstHistogram(
537	desc *Desc,
538	count uint64,
539	sum float64,
540	buckets map[float64]uint64,
541	labelValues ...string,
542) (Metric, error) {
543	if desc.err != nil {
544		return nil, desc.err
545	}
546	if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
547		return nil, err
548	}
549	return &constHistogram{
550		desc:       desc,
551		count:      count,
552		sum:        sum,
553		buckets:    buckets,
554		labelPairs: makeLabelPairs(desc, labelValues),
555	}, nil
556}
557
558// MustNewConstHistogram is a version of NewConstHistogram that panics where
559// NewConstMetric would have returned an error.
560func MustNewConstHistogram(
561	desc *Desc,
562	count uint64,
563	sum float64,
564	buckets map[float64]uint64,
565	labelValues ...string,
566) Metric {
567	m, err := NewConstHistogram(desc, count, sum, buckets, labelValues...)
568	if err != nil {
569		panic(err)
570	}
571	return m
572}
573
574type buckSort []*dto.Bucket
575
576func (s buckSort) Len() int {
577	return len(s)
578}
579
580func (s buckSort) Swap(i, j int) {
581	s[i], s[j] = s[j], s[i]
582}
583
584func (s buckSort) Less(i, j int) bool {
585	return s[i].GetUpperBound() < s[j].GetUpperBound()
586}
587