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.
22 "github.com/golang/protobuf/proto"
24 dto "github.com/prometheus/client_model/go"
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.
31 // On the Prometheus server, quantiles can be calculated from a Histogram using
32 // the histogram_quantile function in the query language.
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.
41 // To create Histogram instances, use NewHistogram.
42 type Histogram interface {
46 // Observe adds a single observation to the histogram.
50 // bucketLabel is used for the label that defines the upper bound of a
51 // bucket of a histogram ("le" -> "less or equal").
52 const bucketLabel = "le"
55 // DefBuckets are the default Histogram buckets. The default buckets are
56 // tailored to broadly measure the response time (in seconds) of a
57 // network service. Most likely, however, you will be required to define
58 // buckets customized to your use case.
59 DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
61 errBucketLabelNotAllowed = fmt.Errorf(
62 "%q is not allowed as label name in histograms", bucketLabel,
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.
71 // The function panics if 'count' is zero or negative.
72 func LinearBuckets(start, width float64, count int) []float64 {
74 panic("LinearBuckets needs a positive count")
76 buckets := make([]float64, count)
77 for i := range buckets {
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.
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.
92 func ExponentialBuckets(start, factor float64, count int) []float64 {
94 panic("ExponentialBuckets needs a positive count")
97 panic("ExponentialBuckets needs a positive start value")
100 panic("ExponentialBuckets needs a factor greater than 1")
102 buckets := make([]float64, count)
103 for i := range buckets {
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.
113 type 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.
123 // Help provides information about this Histogram. Mandatory!
125 // Metrics with the same fully-qualified name must have the same Help
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.
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.
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
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.
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.
158 func NewHistogram(opts HistogramOpts) Histogram {
161 BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
170 func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
171 if len(desc.variableLabels) != len(labelValues) {
172 panic(errInconsistentCardinality)
175 for _, n := range desc.variableLabels {
176 if n == bucketLabel {
177 panic(errBucketLabelNotAllowed)
180 for _, lp := range desc.constLabelPairs {
181 if lp.GetName() == bucketLabel {
182 panic(errBucketLabelNotAllowed)
186 if len(opts.Buckets) == 0 {
187 opts.Buckets = DefBuckets
192 upperBounds: opts.Buckets,
193 labelPairs: makeLabelPairs(desc, labelValues),
195 for i, upperBound := range h.upperBounds {
196 if i < len(h.upperBounds)-1 {
197 if upperBound >= h.upperBounds[i+1] {
199 "histogram buckets must be in increasing order: %f >= %f",
200 upperBound, h.upperBounds[i+1],
204 if math.IsInf(upperBound, +1) {
205 // The +Inf bucket is implicit. Remove it here.
206 h.upperBounds = h.upperBounds[:i]
210 // Finally we know the final length of h.upperBounds and can make counts.
211 h.counts = make([]uint64, len(h.upperBounds))
213 h.Init(h) // Init self-collection.
217 type 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
226 // Note that there is no mutex required.
230 upperBounds []float64
233 labelPairs []*dto.LabelPair
236 func (h *histogram) Desc() *Desc {
240 func (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
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)
254 atomic.AddUint64(&h.count, 1)
256 oldBits := atomic.LoadUint64(&h.sumBits)
257 newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
258 if atomic.CompareAndSwapUint64(&h.sumBits, oldBits, newBits) {
264 func (h *histogram) Write(out *dto.Metric) error {
265 his := &dto.Histogram{}
266 buckets := make([]*dto.Bucket, len(h.upperBounds))
268 his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&h.sumBits)))
269 his.SampleCount = proto.Uint64(atomic.LoadUint64(&h.count))
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),
280 out.Label = h.labelPairs
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.
289 type HistogramVec struct {
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
296 func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
298 BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
303 return &HistogramVec{
304 MetricVec: MetricVec{
305 children: map[uint64]Metric{},
307 newMetric: func(lvs ...string) Metric {
308 return newHistogram(desc, opts, lvs...)
314 // GetMetricWithLabelValues replaces the method of the same name in
315 // MetricVec. The difference is that this method returns a Histogram and not a
316 // Metric so that no type conversion is required.
317 func (m *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Histogram, error) {
318 metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
320 return metric.(Histogram), err
325 // GetMetricWith replaces the method of the same name in MetricVec. The
326 // difference is that this method returns a Histogram and not a Metric so that no
327 // type conversion is required.
328 func (m *HistogramVec) GetMetricWith(labels Labels) (Histogram, error) {
329 metric, err := m.MetricVec.GetMetricWith(labels)
331 return metric.(Histogram), err
336 // WithLabelValues works as GetMetricWithLabelValues, but panics where
337 // GetMetricWithLabelValues would have returned an error. By not returning an
338 // error, WithLabelValues allows shortcuts like
339 // myVec.WithLabelValues("404", "GET").Observe(42.21)
340 func (m *HistogramVec) WithLabelValues(lvs ...string) Histogram {
341 return m.MetricVec.WithLabelValues(lvs...).(Histogram)
344 // With works as GetMetricWith, but panics where GetMetricWithLabels would have
345 // returned an error. By not returning an error, With allows shortcuts like
346 // myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21)
347 func (m *HistogramVec) With(labels Labels) Histogram {
348 return m.MetricVec.With(labels).(Histogram)
351 type constHistogram struct {
355 buckets map[float64]uint64
356 labelPairs []*dto.LabelPair
359 func (h *constHistogram) Desc() *Desc {
363 func (h *constHistogram) Write(out *dto.Metric) error {
364 his := &dto.Histogram{}
365 buckets := make([]*dto.Bucket, 0, len(h.buckets))
367 his.SampleCount = proto.Uint64(h.count)
368 his.SampleSum = proto.Float64(h.sum)
370 for upperBound, count := range h.buckets {
371 buckets = append(buckets, &dto.Bucket{
372 CumulativeCount: proto.Uint64(count),
373 UpperBound: proto.Float64(upperBound),
377 if len(buckets) > 0 {
378 sort.Sort(buckSort(buckets))
383 out.Label = h.labelPairs
388 // NewConstHistogram returns a metric representing a Prometheus histogram with
389 // fixed values for the count, sum, and bucket counts. As those parameters
390 // cannot be changed, the returned value does not implement the Histogram
391 // interface (but only the Metric interface). Users of this package will not
392 // have much use for it in regular operations. However, when implementing custom
393 // Collectors, it is useful as a throw-away metric that is generated on the fly
394 // to send it to Prometheus in the Collect method.
396 // buckets is a map of upper bounds to cumulative counts, excluding the +Inf
399 // NewConstHistogram returns an error if the length of labelValues is not
400 // consistent with the variable labels in Desc.
401 func NewConstHistogram(
405 buckets map[float64]uint64,
406 labelValues ...string,
408 if len(desc.variableLabels) != len(labelValues) {
409 return nil, errInconsistentCardinality
411 return &constHistogram{
416 labelPairs: makeLabelPairs(desc, labelValues),
420 // MustNewConstHistogram is a version of NewConstHistogram that panics where
421 // NewConstMetric would have returned an error.
422 func MustNewConstHistogram(
426 buckets map[float64]uint64,
427 labelValues ...string,
429 m, err := NewConstHistogram(desc, count, sum, buckets, labelValues...)
436 type buckSort []*dto.Bucket
438 func (s buckSort) Len() int {
442 func (s buckSort) Swap(i, j int) {
443 s[i], s[j] = s[j], s[i]
446 func (s buckSort) Less(i, j int) bool {
447 return s[i].GetUpperBound() < s[j].GetUpperBound()