blob: c970fdee0e4ffdde7ce64514c8ca8bb1a3ddb048 [file] [log] [blame]
khenaidooab1f7bd2019-11-14 14:00:27 -05001// Copyright 2014 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 "time"
24
25 "github.com/beorn7/perks/quantile"
26 "github.com/golang/protobuf/proto"
27
28 dto "github.com/prometheus/client_model/go"
29)
30
31// quantileLabel is used for the label that defines the quantile in a
32// summary.
33const quantileLabel = "quantile"
34
35// A Summary captures individual observations from an event or sample stream and
36// summarizes them in a manner similar to traditional summary statistics: 1. sum
37// of observations, 2. observation count, 3. rank estimations.
38//
39// A typical use-case is the observation of request latencies. By default, a
40// Summary provides the median, the 90th and the 99th percentile of the latency
41// as rank estimations. However, the default behavior will change in the
42// upcoming v1.0.0 of the library. There will be no rank estimations at all by
43// default. For a sane transition, it is recommended to set the desired rank
44// estimations explicitly.
45//
46// Note that the rank estimations cannot be aggregated in a meaningful way with
47// the Prometheus query language (i.e. you cannot average or add them). If you
48// need aggregatable quantiles (e.g. you want the 99th percentile latency of all
49// queries served across all instances of a service), consider the Histogram
50// metric type. See the Prometheus documentation for more details.
51//
52// To create Summary instances, use NewSummary.
53type Summary interface {
54 Metric
55 Collector
56
57 // Observe adds a single observation to the summary.
58 Observe(float64)
59}
60
61var errQuantileLabelNotAllowed = fmt.Errorf(
62 "%q is not allowed as label name in summaries", quantileLabel,
63)
64
65// Default values for SummaryOpts.
66const (
67 // DefMaxAge is the default duration for which observations stay
68 // relevant.
69 DefMaxAge time.Duration = 10 * time.Minute
70 // DefAgeBuckets is the default number of buckets used to calculate the
71 // age of observations.
72 DefAgeBuckets = 5
73 // DefBufCap is the standard buffer size for collecting Summary observations.
74 DefBufCap = 500
75)
76
77// SummaryOpts bundles the options for creating a Summary metric. It is
78// mandatory to set Name to a non-empty string. While all other fields are
79// optional and can safely be left at their zero value, it is recommended to set
80// a help string and to explicitly set the Objectives field to the desired value
81// as the default value will change in the upcoming v1.0.0 of the library.
82type SummaryOpts struct {
83 // Namespace, Subsystem, and Name are components of the fully-qualified
84 // name of the Summary (created by joining these components with
85 // "_"). Only Name is mandatory, the others merely help structuring the
86 // name. Note that the fully-qualified name of the Summary must be a
87 // valid Prometheus metric name.
88 Namespace string
89 Subsystem string
90 Name string
91
92 // Help provides information about this Summary.
93 //
94 // Metrics with the same fully-qualified name must have the same Help
95 // string.
96 Help string
97
98 // ConstLabels are used to attach fixed labels to this metric. Metrics
99 // with the same fully-qualified name must have the same label names in
100 // their ConstLabels.
101 //
102 // Due to the way a Summary is represented in the Prometheus text format
103 // and how it is handled by the Prometheus server internally, “quantile”
104 // is an illegal label name. Construction of a Summary or SummaryVec
105 // will panic if this label name is used in ConstLabels.
106 //
107 // ConstLabels are only used rarely. In particular, do not use them to
108 // attach the same labels to all your metrics. Those use cases are
109 // better covered by target labels set by the scraping Prometheus
110 // server, or by one specific metric (e.g. a build_info or a
111 // machine_role metric). See also
112 // https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
113 ConstLabels Labels
114
115 // Objectives defines the quantile rank estimates with their respective
116 // absolute error. If Objectives[q] = e, then the value reported for q
117 // will be the φ-quantile value for some φ between q-e and q+e. The
118 // default value is an empty map, resulting in a summary without
119 // quantiles.
120 Objectives map[float64]float64
121
122 // MaxAge defines the duration for which an observation stays relevant
123 // for the summary. Must be positive. The default value is DefMaxAge.
124 MaxAge time.Duration
125
126 // AgeBuckets is the number of buckets used to exclude observations that
127 // are older than MaxAge from the summary. A higher number has a
128 // resource penalty, so only increase it if the higher resolution is
129 // really required. For very high observation rates, you might want to
130 // reduce the number of age buckets. With only one age bucket, you will
131 // effectively see a complete reset of the summary each time MaxAge has
132 // passed. The default value is DefAgeBuckets.
133 AgeBuckets uint32
134
135 // BufCap defines the default sample stream buffer size. The default
136 // value of DefBufCap should suffice for most uses. If there is a need
137 // to increase the value, a multiple of 500 is recommended (because that
138 // is the internal buffer size of the underlying package
139 // "github.com/bmizerany/perks/quantile").
140 BufCap uint32
141}
142
143// Problem with the sliding-window decay algorithm... The Merge method of
144// perk/quantile is actually not working as advertised - and it might be
145// unfixable, as the underlying algorithm is apparently not capable of merging
146// summaries in the first place. To avoid using Merge, we are currently adding
147// observations to _each_ age bucket, i.e. the effort to add a sample is
148// essentially multiplied by the number of age buckets. When rotating age
149// buckets, we empty the previous head stream. On scrape time, we simply take
150// the quantiles from the head stream (no merging required). Result: More effort
151// on observation time, less effort on scrape time, which is exactly the
152// opposite of what we try to accomplish, but at least the results are correct.
153//
154// The quite elegant previous contraption to merge the age buckets efficiently
155// on scrape time (see code up commit 6b9530d72ea715f0ba612c0120e6e09fbf1d49d0)
156// can't be used anymore.
157
158// NewSummary creates a new Summary based on the provided SummaryOpts.
159func NewSummary(opts SummaryOpts) Summary {
160 return newSummary(
161 NewDesc(
162 BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
163 opts.Help,
164 nil,
165 opts.ConstLabels,
166 ),
167 opts,
168 )
169}
170
171func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
172 if len(desc.variableLabels) != len(labelValues) {
173 panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
174 }
175
176 for _, n := range desc.variableLabels {
177 if n == quantileLabel {
178 panic(errQuantileLabelNotAllowed)
179 }
180 }
181 for _, lp := range desc.constLabelPairs {
182 if lp.GetName() == quantileLabel {
183 panic(errQuantileLabelNotAllowed)
184 }
185 }
186
187 if opts.Objectives == nil {
188 opts.Objectives = map[float64]float64{}
189 }
190
191 if opts.MaxAge < 0 {
192 panic(fmt.Errorf("illegal max age MaxAge=%v", opts.MaxAge))
193 }
194 if opts.MaxAge == 0 {
195 opts.MaxAge = DefMaxAge
196 }
197
198 if opts.AgeBuckets == 0 {
199 opts.AgeBuckets = DefAgeBuckets
200 }
201
202 if opts.BufCap == 0 {
203 opts.BufCap = DefBufCap
204 }
205
206 if len(opts.Objectives) == 0 {
207 // Use the lock-free implementation of a Summary without objectives.
208 s := &noObjectivesSummary{
209 desc: desc,
210 labelPairs: makeLabelPairs(desc, labelValues),
211 counts: [2]*summaryCounts{&summaryCounts{}, &summaryCounts{}},
212 }
213 s.init(s) // Init self-collection.
214 return s
215 }
216
217 s := &summary{
218 desc: desc,
219
220 objectives: opts.Objectives,
221 sortedObjectives: make([]float64, 0, len(opts.Objectives)),
222
223 labelPairs: makeLabelPairs(desc, labelValues),
224
225 hotBuf: make([]float64, 0, opts.BufCap),
226 coldBuf: make([]float64, 0, opts.BufCap),
227 streamDuration: opts.MaxAge / time.Duration(opts.AgeBuckets),
228 }
229 s.headStreamExpTime = time.Now().Add(s.streamDuration)
230 s.hotBufExpTime = s.headStreamExpTime
231
232 for i := uint32(0); i < opts.AgeBuckets; i++ {
233 s.streams = append(s.streams, s.newStream())
234 }
235 s.headStream = s.streams[0]
236
237 for qu := range s.objectives {
238 s.sortedObjectives = append(s.sortedObjectives, qu)
239 }
240 sort.Float64s(s.sortedObjectives)
241
242 s.init(s) // Init self-collection.
243 return s
244}
245
246type summary struct {
247 selfCollector
248
249 bufMtx sync.Mutex // Protects hotBuf and hotBufExpTime.
250 mtx sync.Mutex // Protects every other moving part.
251 // Lock bufMtx before mtx if both are needed.
252
253 desc *Desc
254
255 objectives map[float64]float64
256 sortedObjectives []float64
257
258 labelPairs []*dto.LabelPair
259
260 sum float64
261 cnt uint64
262
263 hotBuf, coldBuf []float64
264
265 streams []*quantile.Stream
266 streamDuration time.Duration
267 headStream *quantile.Stream
268 headStreamIdx int
269 headStreamExpTime, hotBufExpTime time.Time
270}
271
272func (s *summary) Desc() *Desc {
273 return s.desc
274}
275
276func (s *summary) Observe(v float64) {
277 s.bufMtx.Lock()
278 defer s.bufMtx.Unlock()
279
280 now := time.Now()
281 if now.After(s.hotBufExpTime) {
282 s.asyncFlush(now)
283 }
284 s.hotBuf = append(s.hotBuf, v)
285 if len(s.hotBuf) == cap(s.hotBuf) {
286 s.asyncFlush(now)
287 }
288}
289
290func (s *summary) Write(out *dto.Metric) error {
291 sum := &dto.Summary{}
292 qs := make([]*dto.Quantile, 0, len(s.objectives))
293
294 s.bufMtx.Lock()
295 s.mtx.Lock()
296 // Swap bufs even if hotBuf is empty to set new hotBufExpTime.
297 s.swapBufs(time.Now())
298 s.bufMtx.Unlock()
299
300 s.flushColdBuf()
301 sum.SampleCount = proto.Uint64(s.cnt)
302 sum.SampleSum = proto.Float64(s.sum)
303
304 for _, rank := range s.sortedObjectives {
305 var q float64
306 if s.headStream.Count() == 0 {
307 q = math.NaN()
308 } else {
309 q = s.headStream.Query(rank)
310 }
311 qs = append(qs, &dto.Quantile{
312 Quantile: proto.Float64(rank),
313 Value: proto.Float64(q),
314 })
315 }
316
317 s.mtx.Unlock()
318
319 if len(qs) > 0 {
320 sort.Sort(quantSort(qs))
321 }
322 sum.Quantile = qs
323
324 out.Summary = sum
325 out.Label = s.labelPairs
326 return nil
327}
328
329func (s *summary) newStream() *quantile.Stream {
330 return quantile.NewTargeted(s.objectives)
331}
332
333// asyncFlush needs bufMtx locked.
334func (s *summary) asyncFlush(now time.Time) {
335 s.mtx.Lock()
336 s.swapBufs(now)
337
338 // Unblock the original goroutine that was responsible for the mutation
339 // that triggered the compaction. But hold onto the global non-buffer
340 // state mutex until the operation finishes.
341 go func() {
342 s.flushColdBuf()
343 s.mtx.Unlock()
344 }()
345}
346
347// rotateStreams needs mtx AND bufMtx locked.
348func (s *summary) maybeRotateStreams() {
349 for !s.hotBufExpTime.Equal(s.headStreamExpTime) {
350 s.headStream.Reset()
351 s.headStreamIdx++
352 if s.headStreamIdx >= len(s.streams) {
353 s.headStreamIdx = 0
354 }
355 s.headStream = s.streams[s.headStreamIdx]
356 s.headStreamExpTime = s.headStreamExpTime.Add(s.streamDuration)
357 }
358}
359
360// flushColdBuf needs mtx locked.
361func (s *summary) flushColdBuf() {
362 for _, v := range s.coldBuf {
363 for _, stream := range s.streams {
364 stream.Insert(v)
365 }
366 s.cnt++
367 s.sum += v
368 }
369 s.coldBuf = s.coldBuf[0:0]
370 s.maybeRotateStreams()
371}
372
373// swapBufs needs mtx AND bufMtx locked, coldBuf must be empty.
374func (s *summary) swapBufs(now time.Time) {
375 if len(s.coldBuf) != 0 {
376 panic("coldBuf is not empty")
377 }
378 s.hotBuf, s.coldBuf = s.coldBuf, s.hotBuf
379 // hotBuf is now empty and gets new expiration set.
380 for now.After(s.hotBufExpTime) {
381 s.hotBufExpTime = s.hotBufExpTime.Add(s.streamDuration)
382 }
383}
384
385type summaryCounts struct {
386 // sumBits contains the bits of the float64 representing the sum of all
387 // observations. sumBits and count have to go first in the struct to
388 // guarantee alignment for atomic operations.
389 // http://golang.org/pkg/sync/atomic/#pkg-note-BUG
390 sumBits uint64
391 count uint64
392}
393
394type noObjectivesSummary struct {
395 // countAndHotIdx enables lock-free writes with use of atomic updates.
396 // The most significant bit is the hot index [0 or 1] of the count field
397 // below. Observe calls update the hot one. All remaining bits count the
398 // number of Observe calls. Observe starts by incrementing this counter,
399 // and finish by incrementing the count field in the respective
400 // summaryCounts, as a marker for completion.
401 //
402 // Calls of the Write method (which are non-mutating reads from the
403 // perspective of the summary) swap the hot–cold under the writeMtx
404 // lock. A cooldown is awaited (while locked) by comparing the number of
405 // observations with the initiation count. Once they match, then the
406 // last observation on the now cool one has completed. All cool fields must
407 // be merged into the new hot before releasing writeMtx.
408
409 // Fields with atomic access first! See alignment constraint:
410 // http://golang.org/pkg/sync/atomic/#pkg-note-BUG
411 countAndHotIdx uint64
412
413 selfCollector
414 desc *Desc
415 writeMtx sync.Mutex // Only used in the Write method.
416
417 // Two counts, one is "hot" for lock-free observations, the other is
418 // "cold" for writing out a dto.Metric. It has to be an array of
419 // pointers to guarantee 64bit alignment of the histogramCounts, see
420 // http://golang.org/pkg/sync/atomic/#pkg-note-BUG.
421 counts [2]*summaryCounts
422
423 labelPairs []*dto.LabelPair
424}
425
426func (s *noObjectivesSummary) Desc() *Desc {
427 return s.desc
428}
429
430func (s *noObjectivesSummary) Observe(v float64) {
431 // We increment h.countAndHotIdx so that the counter in the lower
432 // 63 bits gets incremented. At the same time, we get the new value
433 // back, which we can use to find the currently-hot counts.
434 n := atomic.AddUint64(&s.countAndHotIdx, 1)
435 hotCounts := s.counts[n>>63]
436
437 for {
438 oldBits := atomic.LoadUint64(&hotCounts.sumBits)
439 newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
440 if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
441 break
442 }
443 }
444 // Increment count last as we take it as a signal that the observation
445 // is complete.
446 atomic.AddUint64(&hotCounts.count, 1)
447}
448
449func (s *noObjectivesSummary) Write(out *dto.Metric) error {
450 // For simplicity, we protect this whole method by a mutex. It is not in
451 // the hot path, i.e. Observe is called much more often than Write. The
452 // complication of making Write lock-free isn't worth it, if possible at
453 // all.
454 s.writeMtx.Lock()
455 defer s.writeMtx.Unlock()
456
457 // Adding 1<<63 switches the hot index (from 0 to 1 or from 1 to 0)
458 // without touching the count bits. See the struct comments for a full
459 // description of the algorithm.
460 n := atomic.AddUint64(&s.countAndHotIdx, 1<<63)
461 // count is contained unchanged in the lower 63 bits.
462 count := n & ((1 << 63) - 1)
463 // The most significant bit tells us which counts is hot. The complement
464 // is thus the cold one.
465 hotCounts := s.counts[n>>63]
466 coldCounts := s.counts[(^n)>>63]
467
468 // Await cooldown.
469 for count != atomic.LoadUint64(&coldCounts.count) {
470 runtime.Gosched() // Let observations get work done.
471 }
472
473 sum := &dto.Summary{
474 SampleCount: proto.Uint64(count),
475 SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
476 }
477
478 out.Summary = sum
479 out.Label = s.labelPairs
480
481 // Finally add all the cold counts to the new hot counts and reset the cold counts.
482 atomic.AddUint64(&hotCounts.count, count)
483 atomic.StoreUint64(&coldCounts.count, 0)
484 for {
485 oldBits := atomic.LoadUint64(&hotCounts.sumBits)
486 newBits := math.Float64bits(math.Float64frombits(oldBits) + sum.GetSampleSum())
487 if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
488 atomic.StoreUint64(&coldCounts.sumBits, 0)
489 break
490 }
491 }
492 return nil
493}
494
495type quantSort []*dto.Quantile
496
497func (s quantSort) Len() int {
498 return len(s)
499}
500
501func (s quantSort) Swap(i, j int) {
502 s[i], s[j] = s[j], s[i]
503}
504
505func (s quantSort) Less(i, j int) bool {
506 return s[i].GetQuantile() < s[j].GetQuantile()
507}
508
509// SummaryVec is a Collector that bundles a set of Summaries that all share the
510// same Desc, but have different values for their variable labels. This is used
511// if you want to count the same thing partitioned by various dimensions
512// (e.g. HTTP request latencies, partitioned by status code and method). Create
513// instances with NewSummaryVec.
514type SummaryVec struct {
515 *metricVec
516}
517
518// NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and
519// partitioned by the given label names.
520//
521// Due to the way a Summary is represented in the Prometheus text format and how
522// it is handled by the Prometheus server internally, “quantile” is an illegal
523// label name. NewSummaryVec will panic if this label name is used.
524func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
525 for _, ln := range labelNames {
526 if ln == quantileLabel {
527 panic(errQuantileLabelNotAllowed)
528 }
529 }
530 desc := NewDesc(
531 BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
532 opts.Help,
533 labelNames,
534 opts.ConstLabels,
535 )
536 return &SummaryVec{
537 metricVec: newMetricVec(desc, func(lvs ...string) Metric {
538 return newSummary(desc, opts, lvs...)
539 }),
540 }
541}
542
543// GetMetricWithLabelValues returns the Summary for the given slice of label
544// values (same order as the VariableLabels in Desc). If that combination of
545// label values is accessed for the first time, a new Summary is created.
546//
547// It is possible to call this method without using the returned Summary to only
548// create the new Summary but leave it at its starting value, a Summary without
549// any observations.
550//
551// Keeping the Summary for later use is possible (and should be considered if
552// performance is critical), but keep in mind that Reset, DeleteLabelValues and
553// Delete can be used to delete the Summary from the SummaryVec. In that case,
554// the Summary will still exist, but it will not be exported anymore, even if a
555// Summary with the same label values is created later. See also the CounterVec
556// example.
557//
558// An error is returned if the number of label values is not the same as the
559// number of VariableLabels in Desc (minus any curried labels).
560//
561// Note that for more than one label value, this method is prone to mistakes
562// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
563// an alternative to avoid that type of mistake. For higher label numbers, the
564// latter has a much more readable (albeit more verbose) syntax, but it comes
565// with a performance overhead (for creating and processing the Labels map).
566// See also the GaugeVec example.
567func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
568 metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
569 if metric != nil {
570 return metric.(Observer), err
571 }
572 return nil, err
573}
574
575// GetMetricWith returns the Summary for the given Labels map (the label names
576// must match those of the VariableLabels in Desc). If that label map is
577// accessed for the first time, a new Summary is created. Implications of
578// creating a Summary without using it and keeping the Summary for later use are
579// the same as for GetMetricWithLabelValues.
580//
581// An error is returned if the number and names of the Labels are inconsistent
582// with those of the VariableLabels in Desc (minus any curried labels).
583//
584// This method is used for the same purpose as
585// GetMetricWithLabelValues(...string). See there for pros and cons of the two
586// methods.
587func (v *SummaryVec) GetMetricWith(labels Labels) (Observer, error) {
588 metric, err := v.metricVec.getMetricWith(labels)
589 if metric != nil {
590 return metric.(Observer), err
591 }
592 return nil, err
593}
594
595// WithLabelValues works as GetMetricWithLabelValues, but panics where
596// GetMetricWithLabelValues would have returned an error. Not returning an
597// error allows shortcuts like
598// myVec.WithLabelValues("404", "GET").Observe(42.21)
599func (v *SummaryVec) WithLabelValues(lvs ...string) Observer {
600 s, err := v.GetMetricWithLabelValues(lvs...)
601 if err != nil {
602 panic(err)
603 }
604 return s
605}
606
607// With works as GetMetricWith, but panics where GetMetricWithLabels would have
608// returned an error. Not returning an error allows shortcuts like
609// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
610func (v *SummaryVec) With(labels Labels) Observer {
611 s, err := v.GetMetricWith(labels)
612 if err != nil {
613 panic(err)
614 }
615 return s
616}
617
618// CurryWith returns a vector curried with the provided labels, i.e. the
619// returned vector has those labels pre-set for all labeled operations performed
620// on it. The cardinality of the curried vector is reduced accordingly. The
621// order of the remaining labels stays the same (just with the curried labels
622// taken out of the sequence – which is relevant for the
623// (GetMetric)WithLabelValues methods). It is possible to curry a curried
624// vector, but only with labels not yet used for currying before.
625//
626// The metrics contained in the SummaryVec are shared between the curried and
627// uncurried vectors. They are just accessed differently. Curried and uncurried
628// vectors behave identically in terms of collection. Only one must be
629// registered with a given registry (usually the uncurried version). The Reset
630// method deletes all metrics, even if called on a curried vector.
631func (v *SummaryVec) CurryWith(labels Labels) (ObserverVec, error) {
632 vec, err := v.curryWith(labels)
633 if vec != nil {
634 return &SummaryVec{vec}, err
635 }
636 return nil, err
637}
638
639// MustCurryWith works as CurryWith but panics where CurryWith would have
640// returned an error.
641func (v *SummaryVec) MustCurryWith(labels Labels) ObserverVec {
642 vec, err := v.CurryWith(labels)
643 if err != nil {
644 panic(err)
645 }
646 return vec
647}
648
649type constSummary struct {
650 desc *Desc
651 count uint64
652 sum float64
653 quantiles map[float64]float64
654 labelPairs []*dto.LabelPair
655}
656
657func (s *constSummary) Desc() *Desc {
658 return s.desc
659}
660
661func (s *constSummary) Write(out *dto.Metric) error {
662 sum := &dto.Summary{}
663 qs := make([]*dto.Quantile, 0, len(s.quantiles))
664
665 sum.SampleCount = proto.Uint64(s.count)
666 sum.SampleSum = proto.Float64(s.sum)
667
668 for rank, q := range s.quantiles {
669 qs = append(qs, &dto.Quantile{
670 Quantile: proto.Float64(rank),
671 Value: proto.Float64(q),
672 })
673 }
674
675 if len(qs) > 0 {
676 sort.Sort(quantSort(qs))
677 }
678 sum.Quantile = qs
679
680 out.Summary = sum
681 out.Label = s.labelPairs
682
683 return nil
684}
685
686// NewConstSummary returns a metric representing a Prometheus summary with fixed
687// values for the count, sum, and quantiles. As those parameters cannot be
688// changed, the returned value does not implement the Summary interface (but
689// only the Metric interface). Users of this package will not have much use for
690// it in regular operations. However, when implementing custom Collectors, it is
691// useful as a throw-away metric that is generated on the fly to send it to
692// Prometheus in the Collect method.
693//
694// quantiles maps ranks to quantile values. For example, a median latency of
695// 0.23s and a 99th percentile latency of 0.56s would be expressed as:
696// map[float64]float64{0.5: 0.23, 0.99: 0.56}
697//
698// NewConstSummary returns an error if the length of labelValues is not
699// consistent with the variable labels in Desc or if Desc is invalid.
700func NewConstSummary(
701 desc *Desc,
702 count uint64,
703 sum float64,
704 quantiles map[float64]float64,
705 labelValues ...string,
706) (Metric, error) {
707 if desc.err != nil {
708 return nil, desc.err
709 }
710 if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
711 return nil, err
712 }
713 return &constSummary{
714 desc: desc,
715 count: count,
716 sum: sum,
717 quantiles: quantiles,
718 labelPairs: makeLabelPairs(desc, labelValues),
719 }, nil
720}
721
722// MustNewConstSummary is a version of NewConstSummary that panics where
723// NewConstMetric would have returned an error.
724func MustNewConstSummary(
725 desc *Desc,
726 count uint64,
727 sum float64,
728 quantiles map[float64]float64,
729 labelValues ...string,
730) Metric {
731 m, err := NewConstSummary(desc, count, sum, quantiles, labelValues...)
732 if err != nil {
733 panic(err)
734 }
735 return m
736}