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
6 // http://www.apache.org/licenses/LICENSE-2.0
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.
24 "github.com/golang/protobuf/proto"
26 dto "github.com/prometheus/client_model/go"
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.
33 // On the Prometheus server, quantiles can be calculated from a Histogram using
34 // the histogram_quantile function in the query language.
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.
43 // To create Histogram instances, use NewHistogram.
44 type Histogram interface {
48 // Observe adds a single observation to the histogram.
52 // bucketLabel is used for the label that defines the upper bound of a
53 // bucket of a histogram ("le" -> "less or equal").
54 const bucketLabel = "le"
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.
61 DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
63 errBucketLabelNotAllowed = fmt.Errorf(
64 "%q is not allowed as label name in histograms", bucketLabel,
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.
73 // The function panics if 'count' is zero or negative.
74 func LinearBuckets(start, width float64, count int) []float64 {
76 panic("LinearBuckets needs a positive count")
78 buckets := make([]float64, count)
79 for i := range buckets {
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.
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.
94 func ExponentialBuckets(start, factor float64, count int) []float64 {
96 panic("ExponentialBuckets needs a positive count")
99 panic("ExponentialBuckets needs a positive start value")
102 panic("ExponentialBuckets needs a factor greater than 1")
104 buckets := make([]float64, count)
105 for i := range buckets {
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.
116 type 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.
126 // Help provides information about this Histogram.
128 // Metrics with the same fully-qualified name must have the same Help
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.
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
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.
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.
154 func NewHistogram(opts HistogramOpts) Histogram {
157 BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
166 func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
167 if len(desc.variableLabels) != len(labelValues) {
168 panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
171 for _, n := range desc.variableLabels {
172 if n == bucketLabel {
173 panic(errBucketLabelNotAllowed)
176 for _, lp := range desc.constLabelPairs {
177 if lp.GetName() == bucketLabel {
178 panic(errBucketLabelNotAllowed)
182 if len(opts.Buckets) == 0 {
183 opts.Buckets = DefBuckets
188 upperBounds: opts.Buckets,
189 labelPairs: makeLabelPairs(desc, labelValues),
190 counts: [2]*histogramCounts{&histogramCounts{}, &histogramCounts{}},
192 for i, upperBound := range h.upperBounds {
193 if i < len(h.upperBounds)-1 {
194 if upperBound >= h.upperBounds[i+1] {
196 "histogram buckets must be in increasing order: %f >= %f",
197 upperBound, h.upperBounds[i+1],
201 if math.IsInf(upperBound, +1) {
202 // The +Inf bucket is implicit. Remove it here.
203 h.upperBounds = h.upperBounds[:i]
207 // Finally we know the final length of h.upperBounds and can make buckets
209 h.counts[0].buckets = make([]uint64, len(h.upperBounds))
210 h.counts[1].buckets = make([]uint64, len(h.upperBounds))
212 h.init(h) // Init self-collection.
216 type 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
226 type 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.
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
244 writeMtx sync.Mutex // Only used in the Write method.
246 upperBounds []float64
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.
255 labelPairs []*dto.LabelPair
258 func (h *histogram) Desc() *Desc {
262 func (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
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)
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]
280 if i < len(h.upperBounds) {
281 atomic.AddUint64(&hotCounts.buckets[i], 1)
284 oldBits := atomic.LoadUint64(&hotCounts.sumBits)
285 newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
286 if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
290 // Increment count last as we take it as a signal that the observation
292 atomic.AddUint64(&hotCounts.count, 1)
295 func (h *histogram) Write(out *dto.Metric) error {
297 his = &dto.Histogram{}
298 buckets = make([]*dto.Bucket, len(h.upperBounds))
299 hotCounts, coldCounts *histogramCounts
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.
307 defer h.writeMtx.Unlock()
309 // This is a bit arcane, which is why the following spells out this if
310 // clause in English:
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
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
328 count = atomic.AddUint64(&h.countAndHotIdx, 1) >> 1
330 hotCounts = h.counts[1]
331 coldCounts = h.counts[0]
333 count = atomic.AddUint64(&h.countAndHotIdx, ^uint64(0)) >> 1 // Decrement.
335 hotCounts = h.counts[0]
336 coldCounts = h.counts[1]
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.
344 if count == atomic.LoadUint64(&coldCounts.count) {
347 runtime.Gosched() // Let observations get work done.
350 his.SampleCount = proto.Uint64(count)
351 his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits)))
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),
363 out.Label = h.labelPairs
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)
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)
376 for i := range h.upperBounds {
377 atomic.AddUint64(&hotCounts.buckets[i], atomic.LoadUint64(&coldCounts.buckets[i]))
378 atomic.StoreUint64(&coldCounts.buckets[i], 0)
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.
388 type HistogramVec struct {
392 // NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
393 // partitioned by the given label names.
394 func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
396 BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
401 return &HistogramVec{
402 metricVec: newMetricVec(desc, func(lvs ...string) Metric {
403 return newHistogram(desc, opts, lvs...)
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.
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
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
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).
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.
432 func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
433 metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
435 return metric.(Observer), err
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.
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).
449 // This method is used for the same purpose as
450 // GetMetricWithLabelValues(...string). See there for pros and cons of the two
452 func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) {
453 metric, err := v.metricVec.getMetricWith(labels)
455 return metric.(Observer), err
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)
464 func (v *HistogramVec) WithLabelValues(lvs ...string) Observer {
465 h, err := v.GetMetricWithLabelValues(lvs...)
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)
475 func (v *HistogramVec) With(labels Labels) Observer {
476 h, err := v.GetMetricWith(labels)
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.
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.
496 func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error) {
497 vec, err := v.curryWith(labels)
499 return &HistogramVec{vec}, err
504 // MustCurryWith works as CurryWith but panics where CurryWith would have
505 // returned an error.
506 func (v *HistogramVec) MustCurryWith(labels Labels) ObserverVec {
507 vec, err := v.CurryWith(labels)
514 type constHistogram struct {
518 buckets map[float64]uint64
519 labelPairs []*dto.LabelPair
522 func (h *constHistogram) Desc() *Desc {
526 func (h *constHistogram) Write(out *dto.Metric) error {
527 his := &dto.Histogram{}
528 buckets := make([]*dto.Bucket, 0, len(h.buckets))
530 his.SampleCount = proto.Uint64(h.count)
531 his.SampleSum = proto.Float64(h.sum)
533 for upperBound, count := range h.buckets {
534 buckets = append(buckets, &dto.Bucket{
535 CumulativeCount: proto.Uint64(count),
536 UpperBound: proto.Float64(upperBound),
540 if len(buckets) > 0 {
541 sort.Sort(buckSort(buckets))
546 out.Label = h.labelPairs
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.
559 // buckets is a map of upper bounds to cumulative counts, excluding the +Inf
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.
564 func NewConstHistogram(
568 buckets map[float64]uint64,
569 labelValues ...string,
574 if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
577 return &constHistogram{
582 labelPairs: makeLabelPairs(desc, labelValues),
586 // MustNewConstHistogram is a version of NewConstHistogram that panics where
587 // NewConstMetric would have returned an error.
588 func MustNewConstHistogram(
592 buckets map[float64]uint64,
593 labelValues ...string,
595 m, err := NewConstHistogram(desc, count, sum, buckets, labelValues...)
602 type buckSort []*dto.Bucket
604 func (s buckSort) Len() int {
608 func (s buckSort) Swap(i, j int) {
609 s[i], s[j] = s[j], s[i]
612 func (s buckSort) Less(i, j int) bool {
613 return s[i].GetUpperBound() < s[j].GetUpperBound()