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	"sort"
20	"sync/atomic"
21
22	"github.com/golang/protobuf/proto"
23
24	dto "github.com/prometheus/client_model/go"
25)
26
27// A Histogram counts individual observations from an event or sample stream in
28// configurable buckets. Similar to a summary, it also provides a sum of
29// observations and an observation count.
30//
31// On the Prometheus server, quantiles can be calculated from a Histogram using
32// the histogram_quantile function in the query language.
33//
34// Note that Histograms, in contrast to Summaries, can be aggregated with the
35// Prometheus query language (see the documentation for detailed
36// procedures). However, Histograms require the user to pre-define suitable
37// buckets, and they are in general less accurate. The Observe method of a
38// Histogram has a very low performance overhead in comparison with the Observe
39// method of a Summary.
40//
41// To create Histogram instances, use NewHistogram.
42type Histogram interface {
43	Metric
44	Collector
45
46	// Observe adds a single observation to the histogram.
47	Observe(float64)
48}
49
50// bucketLabel is used for the label that defines the upper bound of a
51// bucket of a histogram ("le" -> "less or equal").
52const bucketLabel = "le"
53
54// DefBuckets are the default Histogram buckets. The default buckets are
55// tailored to broadly measure the response time (in seconds) of a network
56// service. Most likely, however, you will be required to define buckets
57// customized to your use case.
58var (
59	DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
60
61	errBucketLabelNotAllowed = fmt.Errorf(
62		"%q is not allowed as label name in histograms", bucketLabel,
63	)
64)
65
66// LinearBuckets creates 'count' buckets, each 'width' wide, where the lowest
67// bucket has an upper bound of 'start'. The final +Inf bucket is not counted
68// and not included in the returned slice. The returned slice is meant to be
69// used for the Buckets field of HistogramOpts.
70//
71// The function panics if 'count' is zero or negative.
72func LinearBuckets(start, width float64, count int) []float64 {
73	if count < 1 {
74		panic("LinearBuckets needs a positive count")
75	}
76	buckets := make([]float64, count)
77	for i := range buckets {
78		buckets[i] = start
79		start += width
80	}
81	return buckets
82}
83
84// ExponentialBuckets creates 'count' buckets, where the lowest bucket has an
85// upper bound of 'start' and each following bucket's upper bound is 'factor'
86// times the previous bucket's upper bound. The final +Inf bucket is not counted
87// and not included in the returned slice. The returned slice is meant to be
88// used for the Buckets field of HistogramOpts.
89//
90// The function panics if 'count' is 0 or negative, if 'start' is 0 or negative,
91// or if 'factor' is less than or equal 1.
92func ExponentialBuckets(start, factor float64, count int) []float64 {
93	if count < 1 {
94		panic("ExponentialBuckets needs a positive count")
95	}
96	if start <= 0 {
97		panic("ExponentialBuckets needs a positive start value")
98	}
99	if factor <= 1 {
100		panic("ExponentialBuckets needs a factor greater than 1")
101	}
102	buckets := make([]float64, count)
103	for i := range buckets {
104		buckets[i] = start
105		start *= factor
106	}
107	return buckets
108}
109
110// HistogramOpts bundles the options for creating a Histogram metric. It is
111// mandatory to set Name and Help to a non-empty string. All other fields are
112// optional and can safely be left at their zero value.
113type HistogramOpts struct {
114	// Namespace, Subsystem, and Name are components of the fully-qualified
115	// name of the Histogram (created by joining these components with
116	// "_"). Only Name is mandatory, the others merely help structuring the
117	// name. Note that the fully-qualified name of the Histogram must be a
118	// valid Prometheus metric name.
119	Namespace string
120	Subsystem string
121	Name      string
122
123	// Help provides information about this Histogram. Mandatory!
124	//
125	// Metrics with the same fully-qualified name must have the same Help
126	// string.
127	Help string
128
129	// ConstLabels are used to attach fixed labels to this
130	// Histogram. Histograms with the same fully-qualified name must have the
131	// same label names in their ConstLabels.
132	//
133	// Note that in most cases, labels have a value that varies during the
134	// lifetime of a process. Those labels are usually managed with a
135	// HistogramVec. ConstLabels serve only special purposes. One is for the
136	// special case where the value of a label does not change during the
137	// lifetime of a process, e.g. if the revision of the running binary is
138	// put into a label. Another, more advanced purpose is if more than one
139	// Collector needs to collect Histograms with the same fully-qualified
140	// name. In that case, those Summaries must differ in the values of
141	// their ConstLabels. See the Collector examples.
142	//
143	// If the value of a label never changes (not even between binaries),
144	// that label most likely should not be a label at all (but part of the
145	// metric name).
146	ConstLabels Labels
147
148	// Buckets defines the buckets into which observations are counted. Each
149	// element in the slice is the upper inclusive bound of a bucket. The
150	// values must be sorted in strictly increasing order. There is no need
151	// to add a highest bucket with +Inf bound, it will be added
152	// implicitly. The default value is DefBuckets.
153	Buckets []float64
154}
155
156// NewHistogram creates a new Histogram based on the provided HistogramOpts. It
157// panics if the buckets in HistogramOpts are not in strictly increasing order.
158func NewHistogram(opts HistogramOpts) Histogram {
159	return newHistogram(
160		NewDesc(
161			BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
162			opts.Help,
163			nil,
164			opts.ConstLabels,
165		),
166		opts,
167	)
168}
169
170func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
171	if len(desc.variableLabels) != len(labelValues) {
172		panic(errInconsistentCardinality)
173	}
174
175	for _, n := range desc.variableLabels {
176		if n == bucketLabel {
177			panic(errBucketLabelNotAllowed)
178		}
179	}
180	for _, lp := range desc.constLabelPairs {
181		if lp.GetName() == bucketLabel {
182			panic(errBucketLabelNotAllowed)
183		}
184	}
185
186	if len(opts.Buckets) == 0 {
187		opts.Buckets = DefBuckets
188	}
189
190	h := &histogram{
191		desc:        desc,
192		upperBounds: opts.Buckets,
193		labelPairs:  makeLabelPairs(desc, labelValues),
194	}
195	for i, upperBound := range h.upperBounds {
196		if i < len(h.upperBounds)-1 {
197			if upperBound >= h.upperBounds[i+1] {
198				panic(fmt.Errorf(
199					"histogram buckets must be in increasing order: %f >= %f",
200					upperBound, h.upperBounds[i+1],
201				))
202			}
203		} else {
204			if math.IsInf(upperBound, +1) {
205				// The +Inf bucket is implicit. Remove it here.
206				h.upperBounds = h.upperBounds[:i]
207			}
208		}
209	}
210	// Finally we know the final length of h.upperBounds and can make counts.
211	h.counts = make([]uint64, len(h.upperBounds))
212
213	h.init(h) // Init self-collection.
214	return h
215}
216
217type histogram struct {
218	// sumBits contains the bits of the float64 representing the sum of all
219	// observations. sumBits and count have to go first in the struct to
220	// guarantee alignment for atomic operations.
221	// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
222	sumBits uint64
223	count   uint64
224
225	selfCollector
226	// Note that there is no mutex required.
227
228	desc *Desc
229
230	upperBounds []float64
231	counts      []uint64
232
233	labelPairs []*dto.LabelPair
234}
235
236func (h *histogram) Desc() *Desc {
237	return h.desc
238}
239
240func (h *histogram) Observe(v float64) {
241	// TODO(beorn7): For small numbers of buckets (<30), a linear search is
242	// slightly faster than the binary search. If we really care, we could
243	// switch from one search strategy to the other depending on the number
244	// of buckets.
245	//
246	// Microbenchmarks (BenchmarkHistogramNoLabels):
247	// 11 buckets: 38.3 ns/op linear - binary 48.7 ns/op
248	// 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op
249	// 300 buckets: 154 ns/op linear - binary 61.6 ns/op
250	i := sort.SearchFloat64s(h.upperBounds, v)
251	if i < len(h.counts) {
252		atomic.AddUint64(&h.counts[i], 1)
253	}
254	atomic.AddUint64(&h.count, 1)
255	for {
256		oldBits := atomic.LoadUint64(&h.sumBits)
257		newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
258		if atomic.CompareAndSwapUint64(&h.sumBits, oldBits, newBits) {
259			break
260		}
261	}
262}
263
264func (h *histogram) Write(out *dto.Metric) error {
265	his := &dto.Histogram{}
266	buckets := make([]*dto.Bucket, len(h.upperBounds))
267
268	his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&h.sumBits)))
269	his.SampleCount = proto.Uint64(atomic.LoadUint64(&h.count))
270	var count uint64
271	for i, upperBound := range h.upperBounds {
272		count += atomic.LoadUint64(&h.counts[i])
273		buckets[i] = &dto.Bucket{
274			CumulativeCount: proto.Uint64(count),
275			UpperBound:      proto.Float64(upperBound),
276		}
277	}
278	his.Bucket = buckets
279	out.Histogram = his
280	out.Label = h.labelPairs
281	return nil
282}
283
284// HistogramVec is a Collector that bundles a set of Histograms that all share the
285// same Desc, but have different values for their variable labels. This is used
286// if you want to count the same thing partitioned by various dimensions
287// (e.g. HTTP request latencies, partitioned by status code and method). Create
288// instances with NewHistogramVec.
289type HistogramVec struct {
290	*MetricVec
291}
292
293// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
294// partitioned by the given label names. At least one label name must be
295// provided.
296func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
297	desc := NewDesc(
298		BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
299		opts.Help,
300		labelNames,
301		opts.ConstLabels,
302	)
303	return &HistogramVec{
304		MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
305			return newHistogram(desc, opts, lvs...)
306		}),
307	}
308}
309
310// GetMetricWithLabelValues replaces the method of the same name in
311// MetricVec. The difference is that this method returns a Histogram and not a
312// Metric so that no type conversion is required.
313func (m *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Histogram, error) {
314	metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
315	if metric != nil {
316		return metric.(Histogram), err
317	}
318	return nil, err
319}
320
321// GetMetricWith replaces the method of the same name in MetricVec. The
322// difference is that this method returns a Histogram and not a Metric so that no
323// type conversion is required.
324func (m *HistogramVec) GetMetricWith(labels Labels) (Histogram, error) {
325	metric, err := m.MetricVec.GetMetricWith(labels)
326	if metric != nil {
327		return metric.(Histogram), err
328	}
329	return nil, err
330}
331
332// WithLabelValues works as GetMetricWithLabelValues, but panics where
333// GetMetricWithLabelValues would have returned an error. By not returning an
334// error, WithLabelValues allows shortcuts like
335//     myVec.WithLabelValues("404", "GET").Observe(42.21)
336func (m *HistogramVec) WithLabelValues(lvs ...string) Histogram {
337	return m.MetricVec.WithLabelValues(lvs...).(Histogram)
338}
339
340// With works as GetMetricWith, but panics where GetMetricWithLabels would have
341// returned an error. By not returning an error, With allows shortcuts like
342//     myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21)
343func (m *HistogramVec) With(labels Labels) Histogram {
344	return m.MetricVec.With(labels).(Histogram)
345}
346
347type constHistogram struct {
348	desc       *Desc
349	count      uint64
350	sum        float64
351	buckets    map[float64]uint64
352	labelPairs []*dto.LabelPair
353}
354
355func (h *constHistogram) Desc() *Desc {
356	return h.desc
357}
358
359func (h *constHistogram) Write(out *dto.Metric) error {
360	his := &dto.Histogram{}
361	buckets := make([]*dto.Bucket, 0, len(h.buckets))
362
363	his.SampleCount = proto.Uint64(h.count)
364	his.SampleSum = proto.Float64(h.sum)
365
366	for upperBound, count := range h.buckets {
367		buckets = append(buckets, &dto.Bucket{
368			CumulativeCount: proto.Uint64(count),
369			UpperBound:      proto.Float64(upperBound),
370		})
371	}
372
373	if len(buckets) > 0 {
374		sort.Sort(buckSort(buckets))
375	}
376	his.Bucket = buckets
377
378	out.Histogram = his
379	out.Label = h.labelPairs
380
381	return nil
382}
383
384// NewConstHistogram returns a metric representing a Prometheus histogram with
385// fixed values for the count, sum, and bucket counts. As those parameters
386// cannot be changed, the returned value does not implement the Histogram
387// interface (but only the Metric interface). Users of this package will not
388// have much use for it in regular operations. However, when implementing custom
389// Collectors, it is useful as a throw-away metric that is generated on the fly
390// to send it to Prometheus in the Collect method.
391//
392// buckets is a map of upper bounds to cumulative counts, excluding the +Inf
393// bucket.
394//
395// NewConstHistogram returns an error if the length of labelValues is not
396// consistent with the variable labels in Desc.
397func NewConstHistogram(
398	desc *Desc,
399	count uint64,
400	sum float64,
401	buckets map[float64]uint64,
402	labelValues ...string,
403) (Metric, error) {
404	if len(desc.variableLabels) != len(labelValues) {
405		return nil, errInconsistentCardinality
406	}
407	return &constHistogram{
408		desc:       desc,
409		count:      count,
410		sum:        sum,
411		buckets:    buckets,
412		labelPairs: makeLabelPairs(desc, labelValues),
413	}, nil
414}
415
416// MustNewConstHistogram is a version of NewConstHistogram that panics where
417// NewConstMetric would have returned an error.
418func MustNewConstHistogram(
419	desc *Desc,
420	count uint64,
421	sum float64,
422	buckets map[float64]uint64,
423	labelValues ...string,
424) Metric {
425	m, err := NewConstHistogram(desc, count, sum, buckets, labelValues...)
426	if err != nil {
427		panic(err)
428	}
429	return m
430}
431
432type buckSort []*dto.Bucket
433
434func (s buckSort) Len() int {
435	return len(s)
436}
437
438func (s buckSort) Swap(i, j int) {
439	s[i], s[j] = s[j], s[i]
440}
441
442func (s buckSort) Less(i, j int) bool {
443	return s[i].GetUpperBound() < s[j].GetUpperBound()
444}
445