Skip to content

Commit 6efaf95

Browse files
committed
Merge pull request #115 from prometheus/beorn7/const-summaries-histograms
Make it possible to create constant summaries and histograms from static values.
2 parents 066ab78 + c8a7ccf commit 6efaf95

File tree

3 files changed

+293
-0
lines changed

3 files changed

+293
-0
lines changed

prometheus/examples_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,56 @@ func ExampleSummaryVec() {
455455
// ]
456456
}
457457

458+
func ExampleConstSummary() {
459+
desc := prometheus.NewDesc(
460+
"http_request_duration_seconds",
461+
"A summary of the HTTP request durations.",
462+
[]string{"code", "method"},
463+
prometheus.Labels{"owner": "example"},
464+
)
465+
466+
// Create a constant summary from values we got from a 3rd party telemetry system.
467+
s := prometheus.MustNewConstSummary(
468+
desc,
469+
4711, 403.34,
470+
map[float64]float64{0.5: 42.3, 0.9: 323.3},
471+
"200", "get",
472+
)
473+
474+
// Just for demonstration, let's check the state of the summary by
475+
// (ab)using its Write method (which is usually only used by Prometheus
476+
// internally).
477+
metric := &dto.Metric{}
478+
s.Write(metric)
479+
fmt.Println(proto.MarshalTextString(metric))
480+
481+
// Output:
482+
// label: <
483+
// name: "code"
484+
// value: "200"
485+
// >
486+
// label: <
487+
// name: "method"
488+
// value: "get"
489+
// >
490+
// label: <
491+
// name: "owner"
492+
// value: "example"
493+
// >
494+
// summary: <
495+
// sample_count: 4711
496+
// sample_sum: 403.34
497+
// quantile: <
498+
// quantile: 0.5
499+
// value: 42.3
500+
// >
501+
// quantile: <
502+
// quantile: 0.9
503+
// value: 323.3
504+
// >
505+
// >
506+
}
507+
458508
func ExampleHistogram() {
459509
temps := prometheus.NewHistogram(prometheus.HistogramOpts{
460510
Name: "pond_temperature_celsius",
@@ -501,6 +551,64 @@ func ExampleHistogram() {
501551
// >
502552
}
503553

554+
func ExampleConstHistogram() {
555+
desc := prometheus.NewDesc(
556+
"http_request_duration_seconds",
557+
"A histogram of the HTTP request durations.",
558+
[]string{"code", "method"},
559+
prometheus.Labels{"owner": "example"},
560+
)
561+
562+
// Create a constant histogram from values we got from a 3rd party telemetry system.
563+
h := prometheus.MustNewConstHistogram(
564+
desc,
565+
4711, 403.34,
566+
map[float64]uint64{25: 121, 50: 2403, 100: 3221, 200: 4233},
567+
"200", "get",
568+
)
569+
570+
// Just for demonstration, let's check the state of the histogram by
571+
// (ab)using its Write method (which is usually only used by Prometheus
572+
// internally).
573+
metric := &dto.Metric{}
574+
h.Write(metric)
575+
fmt.Println(proto.MarshalTextString(metric))
576+
577+
// Output:
578+
// label: <
579+
// name: "code"
580+
// value: "200"
581+
// >
582+
// label: <
583+
// name: "method"
584+
// value: "get"
585+
// >
586+
// label: <
587+
// name: "owner"
588+
// value: "example"
589+
// >
590+
// histogram: <
591+
// sample_count: 4711
592+
// sample_sum: 403.34
593+
// bucket: <
594+
// cumulative_count: 121
595+
// upper_bound: 25
596+
// >
597+
// bucket: <
598+
// cumulative_count: 2403
599+
// upper_bound: 50
600+
// >
601+
// bucket: <
602+
// cumulative_count: 3221
603+
// upper_bound: 100
604+
// >
605+
// bucket: <
606+
// cumulative_count: 4233
607+
// upper_bound: 200
608+
// >
609+
// >
610+
}
611+
504612
func ExamplePushCollectors() {
505613
hostname, _ := os.Hostname()
506614
completionTime := prometheus.NewGauge(prometheus.GaugeOpts{

prometheus/histogram.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,102 @@ func (m *HistogramVec) WithLabelValues(lvs ...string) Histogram {
342342
func (m *HistogramVec) With(labels Labels) Histogram {
343343
return m.MetricVec.With(labels).(Histogram)
344344
}
345+
346+
type constHistogram struct {
347+
desc *Desc
348+
count uint64
349+
sum float64
350+
buckets map[float64]uint64
351+
labelPairs []*dto.LabelPair
352+
}
353+
354+
func (h *constHistogram) Desc() *Desc {
355+
return h.desc
356+
}
357+
358+
func (h *constHistogram) Write(out *dto.Metric) error {
359+
his := &dto.Histogram{}
360+
buckets := make([]*dto.Bucket, 0, len(h.buckets))
361+
362+
his.SampleCount = proto.Uint64(h.count)
363+
his.SampleSum = proto.Float64(h.sum)
364+
365+
for upperBound, count := range h.buckets {
366+
buckets = append(buckets, &dto.Bucket{
367+
CumulativeCount: proto.Uint64(count),
368+
UpperBound: proto.Float64(upperBound),
369+
})
370+
}
371+
372+
if len(buckets) > 0 {
373+
sort.Sort(buckSort(buckets))
374+
}
375+
his.Bucket = buckets
376+
377+
out.Histogram = his
378+
out.Label = h.labelPairs
379+
380+
return nil
381+
}
382+
383+
// NewConstHistogram returns a metric representing a Prometheus histogram with
384+
// fixed values for the count, sum, and bucket counts. As those parameters
385+
// cannot be changed, the returned value does not implement the Histogram
386+
// interface (but only the Metric interface). Users of this package will not
387+
// have much use for it in regular operations. However, when implementing custom
388+
// Collectors, it is useful as a throw-away metric that is generated on the fly
389+
// to send it to Prometheus in the Collect method.
390+
//
391+
// buckets is a map of upper bounds to cumulative counts, excluding the +Inf
392+
// bucket.
393+
//
394+
// NewConstHistogram returns an error if the length of labelValues is not
395+
// consistent with the variable labels in Desc.
396+
func NewConstHistogram(
397+
desc *Desc,
398+
count uint64,
399+
sum float64,
400+
buckets map[float64]uint64,
401+
labelValues ...string,
402+
) (Metric, error) {
403+
if len(desc.variableLabels) != len(labelValues) {
404+
return nil, errInconsistentCardinality
405+
}
406+
return &constHistogram{
407+
desc: desc,
408+
count: count,
409+
sum: sum,
410+
buckets: buckets,
411+
labelPairs: makeLabelPairs(desc, labelValues),
412+
}, nil
413+
}
414+
415+
// MustNewConstHistogram is a version of NewConstHistogram that panics where
416+
// NewConstMetric would have returned an error.
417+
func MustNewConstHistogram(
418+
desc *Desc,
419+
count uint64,
420+
sum float64,
421+
buckets map[float64]uint64,
422+
labelValues ...string,
423+
) Metric {
424+
m, err := NewConstHistogram(desc, count, sum, buckets, labelValues...)
425+
if err != nil {
426+
panic(err)
427+
}
428+
return m
429+
}
430+
431+
type buckSort []*dto.Bucket
432+
433+
func (s buckSort) Len() int {
434+
return len(s)
435+
}
436+
437+
func (s buckSort) Swap(i, j int) {
438+
s[i], s[j] = s[j], s[i]
439+
}
440+
441+
func (s buckSort) Less(i, j int) bool {
442+
return s[i].GetUpperBound() < s[j].GetUpperBound()
443+
}

prometheus/summary.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,3 +448,89 @@ func (m *SummaryVec) WithLabelValues(lvs ...string) Summary {
448448
func (m *SummaryVec) With(labels Labels) Summary {
449449
return m.MetricVec.With(labels).(Summary)
450450
}
451+
452+
type constSummary struct {
453+
desc *Desc
454+
count uint64
455+
sum float64
456+
quantiles map[float64]float64
457+
labelPairs []*dto.LabelPair
458+
}
459+
460+
func (s *constSummary) Desc() *Desc {
461+
return s.desc
462+
}
463+
464+
func (s *constSummary) Write(out *dto.Metric) error {
465+
sum := &dto.Summary{}
466+
qs := make([]*dto.Quantile, 0, len(s.quantiles))
467+
468+
sum.SampleCount = proto.Uint64(s.count)
469+
sum.SampleSum = proto.Float64(s.sum)
470+
471+
for rank, q := range s.quantiles {
472+
qs = append(qs, &dto.Quantile{
473+
Quantile: proto.Float64(rank),
474+
Value: proto.Float64(q),
475+
})
476+
}
477+
478+
if len(qs) > 0 {
479+
sort.Sort(quantSort(qs))
480+
}
481+
sum.Quantile = qs
482+
483+
out.Summary = sum
484+
out.Label = s.labelPairs
485+
486+
return nil
487+
}
488+
489+
// NewConstSummary returns a metric representing a Prometheus summary with fixed
490+
// values for the count, sum, and quantiles. As those parameters cannot be
491+
// changed, the returned value does not implement the Summary interface (but
492+
// only the Metric interface). Users of this package will not have much use for
493+
// it in regular operations. However, when implementing custom Collectors, it is
494+
// useful as a throw-away metric that is generated on the fly to send it to
495+
// Prometheus in the Collect method.
496+
//
497+
// quantiles maps ranks to quantile values. For example, a median latency of
498+
// 0.23s and a 99th percentile latency of 0.56s would be expressed as:
499+
// map[float64]float64{0.5: 0.23, 0.99: 0.56}
500+
//
501+
// NewConstSummary returns an error if the length of labelValues is not
502+
// consistent with the variable labels in Desc.
503+
func NewConstSummary(
504+
desc *Desc,
505+
count uint64,
506+
sum float64,
507+
quantiles map[float64]float64,
508+
labelValues ...string,
509+
) (Metric, error) {
510+
if len(desc.variableLabels) != len(labelValues) {
511+
return nil, errInconsistentCardinality
512+
}
513+
return &constSummary{
514+
desc: desc,
515+
count: count,
516+
sum: sum,
517+
quantiles: quantiles,
518+
labelPairs: makeLabelPairs(desc, labelValues),
519+
}, nil
520+
}
521+
522+
// MustNewConstSummary is a version of NewConstSummary that panics where
523+
// NewConstMetric would have returned an error.
524+
func MustNewConstSummary(
525+
desc *Desc,
526+
count uint64,
527+
sum float64,
528+
quantiles map[float64]float64,
529+
labelValues ...string,
530+
) Metric {
531+
m, err := NewConstSummary(desc, count, sum, quantiles, labelValues...)
532+
if err != nil {
533+
panic(err)
534+
}
535+
return m
536+
}

0 commit comments

Comments
 (0)