blob: f88da707bc840cc3c0612cf8a58e709a55ec8a4e [file] [log] [blame]
khenaidooffe076b2019-01-15 16:08:08 -05001// 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 counts
208 // for both states:
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 is a complicated one. For lock-free yet atomic
228 // observations, we need to save the total count of observations again,
229 // combined with the index of the currently-hot counts struct, so that
230 // we can perform the operation on both values atomically. The least
231 // significant bit defines the hot counts struct. The remaining 63 bits
232 // represent the total count of observations. This happens under the
233 // assumption that the 63bit count will never overflow. Rationale: An
234 // observations takes about 30ns. Let's assume it could happen in
235 // 10ns. Overflowing the counter will then take at least (2^63)*10ns,
236 // which is about 3000 years.
237 //
238 // This has to be first in the struct for 64bit alignment. See
239 // http://golang.org/pkg/sync/atomic/#pkg-note-BUG
240 countAndHotIdx uint64
241
242 selfCollector
243 desc *Desc
244 writeMtx sync.Mutex // Only used in the Write method.
245
246 upperBounds []float64
247
248 // Two counts, one is "hot" for lock-free observations, the other is
249 // "cold" for writing out a dto.Metric. It has to be an array of
250 // pointers to guarantee 64bit alignment of the histogramCounts, see
251 // http://golang.org/pkg/sync/atomic/#pkg-note-BUG.
252 counts [2]*histogramCounts
253 hotIdx int // Index of currently-hot counts. Only used within Write.
254
255 labelPairs []*dto.LabelPair
256}
257
258func (h *histogram) Desc() *Desc {
259 return h.desc
260}
261
262func (h *histogram) Observe(v float64) {
263 // TODO(beorn7): For small numbers of buckets (<30), a linear search is
264 // slightly faster than the binary search. If we really care, we could
265 // switch from one search strategy to the other depending on the number
266 // of buckets.
267 //
268 // Microbenchmarks (BenchmarkHistogramNoLabels):
269 // 11 buckets: 38.3 ns/op linear - binary 48.7 ns/op
270 // 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op
271 // 300 buckets: 154 ns/op linear - binary 61.6 ns/op
272 i := sort.SearchFloat64s(h.upperBounds, v)
273
274 // We increment h.countAndHotIdx by 2 so that the counter in the upper
275 // 63 bits gets incremented by 1. At the same time, we get the new value
276 // back, which we can use to find the currently-hot counts.
277 n := atomic.AddUint64(&h.countAndHotIdx, 2)
278 hotCounts := h.counts[n%2]
279
280 if i < len(h.upperBounds) {
281 atomic.AddUint64(&hotCounts.buckets[i], 1)
282 }
283 for {
284 oldBits := atomic.LoadUint64(&hotCounts.sumBits)
285 newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
286 if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
287 break
288 }
289 }
290 // Increment count last as we take it as a signal that the observation
291 // is complete.
292 atomic.AddUint64(&hotCounts.count, 1)
293}
294
295func (h *histogram) Write(out *dto.Metric) error {
296 var (
297 his = &dto.Histogram{}
298 buckets = make([]*dto.Bucket, len(h.upperBounds))
299 hotCounts, coldCounts *histogramCounts
300 count uint64
301 )
302
303 // For simplicity, we mutex the rest of this method. It is not in the
304 // hot path, i.e. Observe is called much more often than Write. The
305 // complication of making Write lock-free isn't worth it.
306 h.writeMtx.Lock()
307 defer h.writeMtx.Unlock()
308
309 // This is a bit arcane, which is why the following spells out this if
310 // clause in English:
311 //
312 // If the currently-hot counts struct is #0, we atomically increment
313 // h.countAndHotIdx by 1 so that from now on Observe will use the counts
314 // struct #1. Furthermore, the atomic increment gives us the new value,
315 // which, in its most significant 63 bits, tells us the count of
316 // observations done so far up to and including currently ongoing
317 // observations still using the counts struct just changed from hot to
318 // cold. To have a normal uint64 for the count, we bitshift by 1 and
319 // save the result in count. We also set h.hotIdx to 1 for the next
320 // Write call, and we will refer to counts #1 as hotCounts and to counts
321 // #0 as coldCounts.
322 //
323 // If the currently-hot counts struct is #1, we do the corresponding
324 // things the other way round. We have to _decrement_ h.countAndHotIdx
325 // (which is a bit arcane in itself, as we have to express -1 with an
326 // unsigned int...).
327 if h.hotIdx == 0 {
328 count = atomic.AddUint64(&h.countAndHotIdx, 1) >> 1
329 h.hotIdx = 1
330 hotCounts = h.counts[1]
331 coldCounts = h.counts[0]
332 } else {
333 count = atomic.AddUint64(&h.countAndHotIdx, ^uint64(0)) >> 1 // Decrement.
334 h.hotIdx = 0
335 hotCounts = h.counts[0]
336 coldCounts = h.counts[1]
337 }
338
339 // Now we have to wait for the now-declared-cold counts to actually cool
340 // down, i.e. wait for all observations still using it to finish. That's
341 // the case once the count in the cold counts struct is the same as the
342 // one atomically retrieved from the upper 63bits of h.countAndHotIdx.
343 for {
344 if count == atomic.LoadUint64(&coldCounts.count) {
345 break
346 }
347 runtime.Gosched() // Let observations get work done.
348 }
349
350 his.SampleCount = proto.Uint64(count)
351 his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits)))
352 var cumCount uint64
353 for i, upperBound := range h.upperBounds {
354 cumCount += atomic.LoadUint64(&coldCounts.buckets[i])
355 buckets[i] = &dto.Bucket{
356 CumulativeCount: proto.Uint64(cumCount),
357 UpperBound: proto.Float64(upperBound),
358 }
359 }
360
361 his.Bucket = buckets
362 out.Histogram = his
363 out.Label = h.labelPairs
364
365 // Finally add all the cold counts to the new hot counts and reset the cold counts.
366 atomic.AddUint64(&hotCounts.count, count)
367 atomic.StoreUint64(&coldCounts.count, 0)
368 for {
369 oldBits := atomic.LoadUint64(&hotCounts.sumBits)
370 newBits := math.Float64bits(math.Float64frombits(oldBits) + his.GetSampleSum())
371 if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
372 atomic.StoreUint64(&coldCounts.sumBits, 0)
373 break
374 }
375 }
376 for i := range h.upperBounds {
377 atomic.AddUint64(&hotCounts.buckets[i], atomic.LoadUint64(&coldCounts.buckets[i]))
378 atomic.StoreUint64(&coldCounts.buckets[i], 0)
379 }
380 return nil
381}
382
383// HistogramVec is a Collector that bundles a set of Histograms that all share the
384// same Desc, but have different values for their variable labels. This is used
385// if you want to count the same thing partitioned by various dimensions
386// (e.g. HTTP request latencies, partitioned by status code and method). Create
387// instances with NewHistogramVec.
388type HistogramVec struct {
389 *metricVec
390}
391
392// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
393// partitioned by the given label names.
394func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
395 desc := NewDesc(
396 BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
397 opts.Help,
398 labelNames,
399 opts.ConstLabels,
400 )
401 return &HistogramVec{
402 metricVec: newMetricVec(desc, func(lvs ...string) Metric {
403 return newHistogram(desc, opts, lvs...)
404 }),
405 }
406}
407
408// GetMetricWithLabelValues returns the Histogram for the given slice of label
409// values (same order as the VariableLabels in Desc). If that combination of
410// label values is accessed for the first time, a new Histogram is created.
411//
412// It is possible to call this method without using the returned Histogram to only
413// create the new Histogram but leave it at its starting value, a Histogram without
414// any observations.
415//
416// Keeping the Histogram for later use is possible (and should be considered if
417// performance is critical), but keep in mind that Reset, DeleteLabelValues and
418// Delete can be used to delete the Histogram from the HistogramVec. In that case, the
419// Histogram will still exist, but it will not be exported anymore, even if a
420// Histogram with the same label values is created later. See also the CounterVec
421// example.
422//
423// An error is returned if the number of label values is not the same as the
424// number of VariableLabels in Desc (minus any curried labels).
425//
426// Note that for more than one label value, this method is prone to mistakes
427// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
428// an alternative to avoid that type of mistake. For higher label numbers, the
429// latter has a much more readable (albeit more verbose) syntax, but it comes
430// with a performance overhead (for creating and processing the Labels map).
431// See also the GaugeVec example.
432func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
433 metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
434 if metric != nil {
435 return metric.(Observer), err
436 }
437 return nil, err
438}
439
440// GetMetricWith returns the Histogram for the given Labels map (the label names
441// must match those of the VariableLabels in Desc). If that label map is
442// accessed for the first time, a new Histogram is created. Implications of
443// creating a Histogram without using it and keeping the Histogram for later use
444// are the same as for GetMetricWithLabelValues.
445//
446// An error is returned if the number and names of the Labels are inconsistent
447// with those of the VariableLabels in Desc (minus any curried labels).
448//
449// This method is used for the same purpose as
450// GetMetricWithLabelValues(...string). See there for pros and cons of the two
451// methods.
452func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) {
453 metric, err := v.metricVec.getMetricWith(labels)
454 if metric != nil {
455 return metric.(Observer), err
456 }
457 return nil, err
458}
459
460// WithLabelValues works as GetMetricWithLabelValues, but panics where
461// GetMetricWithLabelValues would have returned an error. Not returning an
462// error allows shortcuts like
463// myVec.WithLabelValues("404", "GET").Observe(42.21)
464func (v *HistogramVec) WithLabelValues(lvs ...string) Observer {
465 h, err := v.GetMetricWithLabelValues(lvs...)
466 if err != nil {
467 panic(err)
468 }
469 return h
470}
471
472// With works as GetMetricWith but panics where GetMetricWithLabels would have
473// returned an error. Not returning an error allows shortcuts like
474// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
475func (v *HistogramVec) With(labels Labels) Observer {
476 h, err := v.GetMetricWith(labels)
477 if err != nil {
478 panic(err)
479 }
480 return h
481}
482
483// CurryWith returns a vector curried with the provided labels, i.e. the
484// returned vector has those labels pre-set for all labeled operations performed
485// on it. The cardinality of the curried vector is reduced accordingly. The
486// order of the remaining labels stays the same (just with the curried labels
487// taken out of the sequence – which is relevant for the
488// (GetMetric)WithLabelValues methods). It is possible to curry a curried
489// vector, but only with labels not yet used for currying before.
490//
491// The metrics contained in the HistogramVec are shared between the curried and
492// uncurried vectors. They are just accessed differently. Curried and uncurried
493// vectors behave identically in terms of collection. Only one must be
494// registered with a given registry (usually the uncurried version). The Reset
495// method deletes all metrics, even if called on a curried vector.
496func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error) {
497 vec, err := v.curryWith(labels)
498 if vec != nil {
499 return &HistogramVec{vec}, err
500 }
501 return nil, err
502}
503
504// MustCurryWith works as CurryWith but panics where CurryWith would have
505// returned an error.
506func (v *HistogramVec) MustCurryWith(labels Labels) ObserverVec {
507 vec, err := v.CurryWith(labels)
508 if err != nil {
509 panic(err)
510 }
511 return vec
512}
513
514type constHistogram struct {
515 desc *Desc
516 count uint64
517 sum float64
518 buckets map[float64]uint64
519 labelPairs []*dto.LabelPair
520}
521
522func (h *constHistogram) Desc() *Desc {
523 return h.desc
524}
525
526func (h *constHistogram) Write(out *dto.Metric) error {
527 his := &dto.Histogram{}
528 buckets := make([]*dto.Bucket, 0, len(h.buckets))
529
530 his.SampleCount = proto.Uint64(h.count)
531 his.SampleSum = proto.Float64(h.sum)
532
533 for upperBound, count := range h.buckets {
534 buckets = append(buckets, &dto.Bucket{
535 CumulativeCount: proto.Uint64(count),
536 UpperBound: proto.Float64(upperBound),
537 })
538 }
539
540 if len(buckets) > 0 {
541 sort.Sort(buckSort(buckets))
542 }
543 his.Bucket = buckets
544
545 out.Histogram = his
546 out.Label = h.labelPairs
547
548 return nil
549}
550
551// NewConstHistogram returns a metric representing a Prometheus histogram with
552// fixed values for the count, sum, and bucket counts. As those parameters
553// cannot be changed, the returned value does not implement the Histogram
554// interface (but only the Metric interface). Users of this package will not
555// have much use for it in regular operations. However, when implementing custom
556// Collectors, it is useful as a throw-away metric that is generated on the fly
557// to send it to Prometheus in the Collect method.
558//
559// buckets is a map of upper bounds to cumulative counts, excluding the +Inf
560// bucket.
561//
562// NewConstHistogram returns an error if the length of labelValues is not
563// consistent with the variable labels in Desc or if Desc is invalid.
564func NewConstHistogram(
565 desc *Desc,
566 count uint64,
567 sum float64,
568 buckets map[float64]uint64,
569 labelValues ...string,
570) (Metric, error) {
571 if desc.err != nil {
572 return nil, desc.err
573 }
574 if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
575 return nil, err
576 }
577 return &constHistogram{
578 desc: desc,
579 count: count,
580 sum: sum,
581 buckets: buckets,
582 labelPairs: makeLabelPairs(desc, labelValues),
583 }, nil
584}
585
586// MustNewConstHistogram is a version of NewConstHistogram that panics where
587// NewConstMetric would have returned an error.
588func MustNewConstHistogram(
589 desc *Desc,
590 count uint64,
591 sum float64,
592 buckets map[float64]uint64,
593 labelValues ...string,
594) Metric {
595 m, err := NewConstHistogram(desc, count, sum, buckets, labelValues...)
596 if err != nil {
597 panic(err)
598 }
599 return m
600}
601
602type buckSort []*dto.Bucket
603
604func (s buckSort) Len() int {
605 return len(s)
606}
607
608func (s buckSort) Swap(i, j int) {
609 s[i], s[j] = s[j], s[i]
610}
611
612func (s buckSort) Less(i, j int) bool {
613 return s[i].GetUpperBound() < s[j].GetUpperBound()
614}