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