Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

671 рядки
23 KiB

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