Skip to content

Commit 38dbb2e

Browse files
committed
Merge pull request #78 from prometheus/beorn7/histogram
Histograms for the exposition library.
2 parents a7c5688 + 9eaf370 commit 38dbb2e

File tree

13 files changed

+921
-80
lines changed

13 files changed

+921
-80
lines changed

extraction/metricfamilyprocessor.go

Lines changed: 81 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package extraction
1616
import (
1717
"fmt"
1818
"io"
19+
"math"
1920

2021
dto "github.com/prometheus/client_model/go"
2122

@@ -85,24 +86,23 @@ func extractCounter(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error
8586
continue
8687
}
8788

88-
sample := new(model.Sample)
89+
sample := &model.Sample{
90+
Metric: model.Metric{},
91+
Value: model.SampleValue(m.Counter.GetValue()),
92+
}
8993
samples = append(samples, sample)
9094

9195
if m.TimestampMs != nil {
9296
sample.Timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000)
9397
} else {
9498
sample.Timestamp = o.Timestamp
9599
}
96-
sample.Metric = model.Metric{}
97-
metric := sample.Metric
98100

101+
metric := sample.Metric
99102
for _, p := range m.Label {
100103
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
101104
}
102-
103105
metric[model.MetricNameLabel] = model.LabelValue(f.GetName())
104-
105-
sample.Value = model.SampleValue(m.Counter.GetValue())
106106
}
107107

108108
return out.Ingest(samples)
@@ -116,24 +116,23 @@ func extractGauge(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error {
116116
continue
117117
}
118118

119-
sample := new(model.Sample)
119+
sample := &model.Sample{
120+
Metric: model.Metric{},
121+
Value: model.SampleValue(m.Gauge.GetValue()),
122+
}
120123
samples = append(samples, sample)
121124

122125
if m.TimestampMs != nil {
123126
sample.Timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000)
124127
} else {
125128
sample.Timestamp = o.Timestamp
126129
}
127-
sample.Metric = model.Metric{}
128-
metric := sample.Metric
129130

131+
metric := sample.Metric
130132
for _, p := range m.Label {
131133
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
132134
}
133-
134135
metric[model.MetricNameLabel] = model.LabelValue(f.GetName())
135-
136-
sample.Value = model.SampleValue(m.Gauge.GetValue())
137136
}
138137

139138
return out.Ingest(samples)
@@ -153,48 +152,50 @@ func extractSummary(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error
153152
}
154153

155154
for _, q := range m.Summary.Quantile {
156-
sample := new(model.Sample)
155+
sample := &model.Sample{
156+
Metric: model.Metric{},
157+
Value: model.SampleValue(q.GetValue()),
158+
Timestamp: timestamp,
159+
}
157160
samples = append(samples, sample)
158161

159-
sample.Timestamp = timestamp
160-
sample.Metric = model.Metric{}
161162
metric := sample.Metric
162-
163163
for _, p := range m.Label {
164164
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
165165
}
166166
// BUG(matt): Update other names to "quantile".
167-
metric[model.LabelName("quantile")] = model.LabelValue(fmt.Sprint(q.GetQuantile()))
168-
167+
metric[model.LabelName(model.QuantileLabel)] = model.LabelValue(fmt.Sprint(q.GetQuantile()))
169168
metric[model.MetricNameLabel] = model.LabelValue(f.GetName())
170-
171-
sample.Value = model.SampleValue(q.GetValue())
172169
}
173170

174171
if m.Summary.SampleSum != nil {
175-
sum := new(model.Sample)
176-
sum.Timestamp = timestamp
177-
metric := model.Metric{}
172+
sum := &model.Sample{
173+
Metric: model.Metric{},
174+
Value: model.SampleValue(m.Summary.GetSampleSum()),
175+
Timestamp: timestamp,
176+
}
177+
samples = append(samples, sum)
178+
179+
metric := sum.Metric
178180
for _, p := range m.Label {
179181
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
180182
}
181183
metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum")
182-
sum.Metric = metric
183-
sum.Value = model.SampleValue(m.Summary.GetSampleSum())
184-
samples = append(samples, sum)
185184
}
186185

187186
if m.Summary.SampleCount != nil {
188-
count := new(model.Sample)
189-
count.Timestamp = timestamp
190-
metric := model.Metric{}
187+
count := &model.Sample{
188+
Metric: model.Metric{},
189+
Value: model.SampleValue(m.Summary.GetSampleCount()),
190+
Timestamp: timestamp,
191+
}
192+
samples = append(samples, count)
193+
194+
metric := count.Metric
191195
for _, p := range m.Label {
192196
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
193197
}
194198
metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count")
195-
count.Metric = metric
196-
count.Value = model.SampleValue(m.Summary.GetSampleCount())
197-
samples = append(samples, count)
198199
}
199200
}
200201

@@ -209,24 +210,23 @@ func extractUntyped(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error
209210
continue
210211
}
211212

212-
sample := new(model.Sample)
213+
sample := &model.Sample{
214+
Metric: model.Metric{},
215+
Value: model.SampleValue(m.Untyped.GetValue()),
216+
}
213217
samples = append(samples, sample)
214218

215219
if m.TimestampMs != nil {
216220
sample.Timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000)
217221
} else {
218222
sample.Timestamp = o.Timestamp
219223
}
220-
sample.Metric = model.Metric{}
221-
metric := sample.Metric
222224

225+
metric := sample.Metric
223226
for _, p := range m.Label {
224227
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
225228
}
226-
227229
metric[model.MetricNameLabel] = model.LabelValue(f.GetName())
228-
229-
sample.Value = model.SampleValue(m.Untyped.GetValue())
230230
}
231231

232232
return out.Ingest(samples)
@@ -245,49 +245,72 @@ func extractHistogram(out Ingester, o *ProcessOptions, f *dto.MetricFamily) erro
245245
timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000)
246246
}
247247

248+
infSeen := false
249+
248250
for _, q := range m.Histogram.Bucket {
249-
sample := new(model.Sample)
251+
sample := &model.Sample{
252+
Metric: model.Metric{},
253+
Value: model.SampleValue(q.GetCumulativeCount()),
254+
Timestamp: timestamp,
255+
}
250256
samples = append(samples, sample)
251257

252-
sample.Timestamp = timestamp
253-
sample.Metric = model.Metric{}
254258
metric := sample.Metric
255-
256259
for _, p := range m.Label {
257260
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
258261
}
259-
metric[model.LabelName("le")] = model.LabelValue(fmt.Sprint(q.GetUpperBound()))
260-
262+
metric[model.LabelName(model.BucketLabel)] = model.LabelValue(fmt.Sprint(q.GetUpperBound()))
261263
metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket")
262264

263-
sample.Value = model.SampleValue(q.GetCumulativeCount())
265+
if math.IsInf(q.GetUpperBound(), +1) {
266+
infSeen = true
267+
}
264268
}
265-
// TODO: If +Inf bucket is missing, add it.
266269

267270
if m.Histogram.SampleSum != nil {
268-
sum := new(model.Sample)
269-
sum.Timestamp = timestamp
270-
metric := model.Metric{}
271+
sum := &model.Sample{
272+
Metric: model.Metric{},
273+
Value: model.SampleValue(m.Histogram.GetSampleSum()),
274+
Timestamp: timestamp,
275+
}
276+
samples = append(samples, sum)
277+
278+
metric := sum.Metric
271279
for _, p := range m.Label {
272280
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
273281
}
274282
metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum")
275-
sum.Metric = metric
276-
sum.Value = model.SampleValue(m.Histogram.GetSampleSum())
277-
samples = append(samples, sum)
278283
}
279284

280285
if m.Histogram.SampleCount != nil {
281-
count := new(model.Sample)
282-
count.Timestamp = timestamp
283-
metric := model.Metric{}
286+
count := &model.Sample{
287+
Metric: model.Metric{},
288+
Value: model.SampleValue(m.Histogram.GetSampleCount()),
289+
Timestamp: timestamp,
290+
}
291+
samples = append(samples, count)
292+
293+
metric := count.Metric
284294
for _, p := range m.Label {
285295
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
286296
}
287297
metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count")
288-
count.Metric = metric
289-
count.Value = model.SampleValue(m.Histogram.GetSampleCount())
290-
samples = append(samples, count)
298+
299+
if !infSeen {
300+
infBucket := &model.Sample{
301+
Metric: model.Metric{},
302+
Value: count.Value,
303+
Timestamp: timestamp,
304+
}
305+
samples = append(samples, infBucket)
306+
307+
metric := infBucket.Metric
308+
for _, p := range m.Label {
309+
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
310+
}
311+
metric[model.LabelName(model.BucketLabel)] = model.LabelValue("+Inf")
312+
metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket")
313+
}
291314
}
292315
}
293316

model/labelname.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ const (
3333
// JobLabel is the label name indicating the job from which a timeseries
3434
// was scraped.
3535
JobLabel LabelName = "job"
36+
37+
// BucketLabel is used for the label that defines the upper bound of a
38+
// bucket of a histogram ("le" -> "less or equal").
39+
BucketLabel = "le"
40+
41+
// QuantileLabel is used for the label that defines the quantile in a
42+
// summary.
43+
QuantileLabel = "quantile"
3644
)
3745

3846
// A LabelName is a key for a LabelSet or Metric. It has a value associated

prometheus/benchmark_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,31 @@ func BenchmarkSummaryNoLabels(b *testing.B) {
129129
m.Observe(3.1415)
130130
}
131131
}
132+
133+
func BenchmarkHistogramWithLabelValues(b *testing.B) {
134+
m := NewHistogramVec(
135+
HistogramOpts{
136+
Name: "benchmark_histogram",
137+
Help: "A histogram to benchmark it.",
138+
},
139+
[]string{"one", "two", "three"},
140+
)
141+
b.ReportAllocs()
142+
b.ResetTimer()
143+
for i := 0; i < b.N; i++ {
144+
m.WithLabelValues("eins", "zwei", "drei").Observe(3.1415)
145+
}
146+
}
147+
148+
func BenchmarkHistogramNoLabels(b *testing.B) {
149+
m := NewHistogram(HistogramOpts{
150+
Name: "benchmark_histogram",
151+
Help: "A histogram to benchmark it.",
152+
},
153+
)
154+
b.ReportAllocs()
155+
b.ResetTimer()
156+
for i := 0; i < b.N; i++ {
157+
m.Observe(3.1415)
158+
}
159+
}

prometheus/counter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func (c *counter) Add(v float64) {
7474
// CounterVec is a Collector that bundles a set of Counters that all share the
7575
// same Desc, but have different values for their variable labels. This is used
7676
// if you want to count the same thing partitioned by various dimensions
77-
// (e.g. number of http requests, partitioned by response code and
77+
// (e.g. number of HTTP requests, partitioned by response code and
7878
// method). Create instances with NewCounterVec.
7979
//
8080
// CounterVec embeds MetricVec. See there for a full list of methods with

prometheus/examples_test.go

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func ExampleCounterVec() {
129129
httpReqs := prometheus.NewCounterVec(
130130
prometheus.CounterOpts{
131131
Name: "http_requests_total",
132-
Help: "How many HTTP requests processed, partitioned by status code and http method.",
132+
Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
133133
ConstLabels: prometheus.Labels{"env": *binaryVersion},
134134
},
135135
[]string{"code", "method"},
@@ -200,7 +200,7 @@ func ExampleRegister() {
200200
fmt.Println("taskCounter registered.")
201201
}
202202
// Don't forget to tell the HTTP server about the Prometheus handler.
203-
// (In a real program, you still need to start the http server...)
203+
// (In a real program, you still need to start the HTTP server...)
204204
http.Handle("/metrics", prometheus.Handler())
205205

206206
// Now you can start workers and give every one of them a pointer to
@@ -240,7 +240,7 @@ func ExampleRegister() {
240240

241241
// Prometheus will not allow you to ever export metrics with
242242
// inconsistent help strings or label names. After unregistering, the
243-
// unregistered metrics will cease to show up in the /metrics http
243+
// unregistered metrics will cease to show up in the /metrics HTTP
244244
// response, but the registry still remembers that those metrics had
245245
// been exported before. For this example, we will now choose a
246246
// different name. (In a real program, you would obviously not export
@@ -452,3 +452,49 @@ func ExampleSummaryVec() {
452452
// >
453453
// ]
454454
}
455+
456+
func ExampleHistogram() {
457+
temps := prometheus.NewHistogram(prometheus.HistogramOpts{
458+
Name: "pond_temperature_celsius",
459+
Help: "The temperature of the frog pond.", // Sorry, we can't measure how badly it smells.
460+
Buckets: prometheus.LinearBuckets(20, 5, 5), // 5 buckets, each 5 centigrade wide.
461+
})
462+
463+
// Simulate some observations.
464+
for i := 0; i < 1000; i++ {
465+
temps.Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10)
466+
}
467+
468+
// Just for demonstration, let's check the state of the histogram by
469+
// (ab)using its Write method (which is usually only used by Prometheus
470+
// internally).
471+
metric := &dto.Metric{}
472+
temps.Write(metric)
473+
fmt.Println(proto.MarshalTextString(metric))
474+
475+
// Output:
476+
// histogram: <
477+
// sample_count: 1000
478+
// sample_sum: 29969.50000000001
479+
// bucket: <
480+
// cumulative_count: 192
481+
// upper_bound: 20
482+
// >
483+
// bucket: <
484+
// cumulative_count: 366
485+
// upper_bound: 25
486+
// >
487+
// bucket: <
488+
// cumulative_count: 501
489+
// upper_bound: 30
490+
// >
491+
// bucket: <
492+
// cumulative_count: 638
493+
// upper_bound: 35
494+
// >
495+
// bucket: <
496+
// cumulative_count: 816
497+
// upper_bound: 40
498+
// >
499+
// >
500+
}

0 commit comments

Comments
 (0)