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