Skip to content

Commit 3d6b23a

Browse files
committed
Merge pull request #76 from prometheus/histogram
Add support for histograms to parsers, extraction and creation.
2 parents 3d127c2 + 6f2f8f2 commit 3d6b23a

File tree

6 files changed

+344
-16
lines changed

6 files changed

+344
-16
lines changed

extraction/metricfamilyprocessor.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ func extractMetricFamily(out Ingester, o *ProcessOptions, family *dto.MetricFami
6969
if err := extractUntyped(out, o, family); err != nil {
7070
return err
7171
}
72+
case dto.MetricType_HISTOGRAM:
73+
if err := extractHistogram(out, o, family); err != nil {
74+
return err
75+
}
7276
}
7377
return nil
7478
}
@@ -227,3 +231,65 @@ func extractUntyped(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error
227231

228232
return out.Ingest(samples)
229233
}
234+
235+
func extractHistogram(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error {
236+
samples := make(model.Samples, 0, len(f.Metric))
237+
238+
for _, m := range f.Metric {
239+
if m.Histogram == nil {
240+
continue
241+
}
242+
243+
timestamp := o.Timestamp
244+
if m.TimestampMs != nil {
245+
timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000)
246+
}
247+
248+
for _, q := range m.Histogram.Bucket {
249+
sample := new(model.Sample)
250+
samples = append(samples, sample)
251+
252+
sample.Timestamp = timestamp
253+
sample.Metric = model.Metric{}
254+
metric := sample.Metric
255+
256+
for _, p := range m.Label {
257+
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
258+
}
259+
metric[model.LabelName("le")] = model.LabelValue(fmt.Sprint(q.GetUpperBound()))
260+
261+
metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket")
262+
263+
sample.Value = model.SampleValue(q.GetCumulativeCount())
264+
}
265+
// TODO: If +Inf bucket is missing, add it.
266+
267+
if m.Histogram.SampleSum != nil {
268+
sum := new(model.Sample)
269+
sum.Timestamp = timestamp
270+
metric := model.Metric{}
271+
for _, p := range m.Label {
272+
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
273+
}
274+
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)
278+
}
279+
280+
if m.Histogram.SampleCount != nil {
281+
count := new(model.Sample)
282+
count.Timestamp = timestamp
283+
metric := model.Metric{}
284+
for _, p := range m.Label {
285+
metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
286+
}
287+
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)
291+
}
292+
}
293+
294+
return out.Ingest(samples)
295+
}

extraction/metricfamilyprocessor_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,48 @@ func TestMetricFamilyProcessor(t *testing.T) {
103103
},
104104
},
105105
},
106+
{
107+
in: "\x8d\x01\n\x1drequest_duration_microseconds\x12\x15The response latency.\x18\x04\"S:Q\b\x85\x15\x11\xcd\xcc\xccL\x8f\xcb:A\x1a\v\b{\x11\x00\x00\x00\x00\x00\x00Y@\x1a\f\b\x9c\x03\x11\x00\x00\x00\x00\x00\x00^@\x1a\f\b\xd0\x04\x11\x00\x00\x00\x00\x00\x00b@\x1a\f\b\xf4\v\x11\x9a\x99\x99\x99\x99\x99e@\x1a\f\b\x85\x15\x11\x00\x00\x00\x00\x00\x00\xf0\u007f",
108+
expected: []model.Samples{
109+
model.Samples{
110+
&model.Sample{
111+
Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_bucket", "le": "100"},
112+
Value: 123,
113+
Timestamp: testTime,
114+
},
115+
&model.Sample{
116+
Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_bucket", "le": "120"},
117+
Value: 412,
118+
Timestamp: testTime,
119+
},
120+
&model.Sample{
121+
Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_bucket", "le": "144"},
122+
Value: 592,
123+
Timestamp: testTime,
124+
},
125+
&model.Sample{
126+
Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_bucket", "le": "172.8"},
127+
Value: 1524,
128+
Timestamp: testTime,
129+
},
130+
&model.Sample{
131+
Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_bucket", "le": "+Inf"},
132+
Value: 2693,
133+
Timestamp: testTime,
134+
},
135+
&model.Sample{
136+
Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_sum"},
137+
Value: 1756047.3,
138+
Timestamp: testTime,
139+
},
140+
&model.Sample{
141+
Metric: model.Metric{model.MetricNameLabel: "request_duration_microseconds_count"},
142+
Value: 2693,
143+
Timestamp: testTime,
144+
},
145+
},
146+
},
147+
},
106148
}
107149

108150
for i, scenario := range scenarios {

text/create.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,39 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
139139
float64(metric.Summary.GetSampleCount()),
140140
out,
141141
)
142+
case dto.MetricType_HISTOGRAM:
143+
if metric.Histogram == nil {
144+
return written, fmt.Errorf(
145+
"expected summary in metric %s", metric,
146+
)
147+
}
148+
for _, q := range metric.Histogram.Bucket {
149+
n, err = writeSample(
150+
name+"_bucket", metric,
151+
"le", fmt.Sprint(q.GetUpperBound()),
152+
float64(q.GetCumulativeCount()),
153+
out,
154+
)
155+
written += n
156+
if err != nil {
157+
return written, err
158+
}
159+
// TODO: Add +inf bucket if it's missing.
160+
}
161+
n, err = writeSample(
162+
name+"_sum", metric, "", "",
163+
metric.Histogram.GetSampleSum(),
164+
out,
165+
)
166+
if err != nil {
167+
return written, err
168+
}
169+
written += n
170+
n, err = writeSample(
171+
name+"_count", metric, "", "",
172+
float64(metric.Histogram.GetSampleCount()),
173+
out,
174+
)
142175
default:
143176
return written, fmt.Errorf(
144177
"unexpected type in metric %s", metric,

text/create_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,54 @@ summary_name{name_1="value 1",name_2="value 2",quantile="0.9"} 2
219219
summary_name{name_1="value 1",name_2="value 2",quantile="0.99"} 3
220220
summary_name_sum{name_1="value 1",name_2="value 2"} 2010.1971
221221
summary_name_count{name_1="value 1",name_2="value 2"} 4711
222+
`,
223+
},
224+
// 4: Histogram
225+
{
226+
in: &dto.MetricFamily{
227+
Name: proto.String("request_duration_microseconds"),
228+
Help: proto.String("The response latency."),
229+
Type: dto.MetricType_HISTOGRAM.Enum(),
230+
Metric: []*dto.Metric{
231+
&dto.Metric{
232+
Histogram: &dto.Histogram{
233+
SampleCount: proto.Uint64(2693),
234+
SampleSum: proto.Float64(1756047.3),
235+
Bucket: []*dto.Bucket{
236+
&dto.Bucket{
237+
UpperBound: proto.Float64(100),
238+
CumulativeCount: proto.Uint64(123),
239+
},
240+
&dto.Bucket{
241+
UpperBound: proto.Float64(120),
242+
CumulativeCount: proto.Uint64(412),
243+
},
244+
&dto.Bucket{
245+
UpperBound: proto.Float64(144),
246+
CumulativeCount: proto.Uint64(592),
247+
},
248+
&dto.Bucket{
249+
UpperBound: proto.Float64(172.8),
250+
CumulativeCount: proto.Uint64(1524),
251+
},
252+
&dto.Bucket{
253+
UpperBound: proto.Float64(math.Inf(+1)),
254+
CumulativeCount: proto.Uint64(2693),
255+
},
256+
},
257+
},
258+
},
259+
},
260+
},
261+
out: `# HELP request_duration_microseconds The response latency.
262+
# TYPE request_duration_microseconds histogram
263+
request_duration_microseconds_bucket{le="100"} 123
264+
request_duration_microseconds_bucket{le="120"} 412
265+
request_duration_microseconds_bucket{le="144"} 592
266+
request_duration_microseconds_bucket{le="172.8"} 1524
267+
request_duration_microseconds_bucket{le="+Inf"} 2693
268+
request_duration_microseconds_sum 1.7560473e+06
269+
request_duration_microseconds_count 2693
222270
`,
223271
},
224272
}

0 commit comments

Comments
 (0)