Skip to content

Commit 913b8f0

Browse files
author
Arthur Silva Sens
committed
expfmt/openmetrics: Write created timestamps for counters, summaries and histograms
Signed-off-by: Arthur Silva Sens <[email protected]>
1 parent 658f673 commit 913b8f0

File tree

2 files changed

+127
-8
lines changed

2 files changed

+127
-8
lines changed

expfmt/openmetrics_create.go

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,23 @@ import (
2323
"strings"
2424

2525
"github.com/prometheus/common/model"
26+
"google.golang.org/protobuf/types/known/timestamppb"
2627

2728
dto "github.com/prometheus/client_model/go"
2829
)
2930

31+
type toOpenMetrics struct {
32+
withCreatedLines bool
33+
}
34+
35+
type ToOpenMetricsOption func(*toOpenMetrics)
36+
37+
func WithCreatedLines() ToOpenMetricsOption {
38+
return func(t *toOpenMetrics) {
39+
t.withCreatedLines = true
40+
}
41+
}
42+
3043
// MetricFamilyToOpenMetrics converts a MetricFamily proto message into the
3144
// OpenMetrics text format and writes the resulting lines to 'out'. It returns
3245
// the number of bytes written and any error encountered. The output will have
@@ -52,15 +65,20 @@ import (
5265
// its type will be set to `unknown` in that case to avoid invalid OpenMetrics
5366
// output.
5467
//
55-
// - No support for the following (optional) features: `# UNIT` line, `_created`
56-
// line, info type, stateset type, gaugehistogram type.
68+
// - No support for the following (optional) features: `# UNIT` line, info type,
69+
// stateset type, gaugehistogram type.
5770
//
5871
// - The size of exemplar labels is not checked (i.e. it's possible to create
5972
// exemplars that are larger than allowed by the OpenMetrics specification).
6073
//
6174
// - The value of Counters is not checked. (OpenMetrics doesn't allow counters
6275
// with a `NaN` value.)
63-
func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int, err error) {
76+
func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...ToOpenMetricsOption) (written int, err error) {
77+
toOM := toOpenMetrics{}
78+
for _, option := range options {
79+
option(&toOM)
80+
}
81+
6482
name := in.GetName()
6583
if name == "" {
6684
return 0, fmt.Errorf("MetricFamily has no name: %s", in)
@@ -152,6 +170,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
152170
return
153171
}
154172

173+
var createdTsBytesWritten int
155174
// Finally the samples, one line for each.
156175
for _, metric := range in.Metric {
157176
switch metricType {
@@ -169,6 +188,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
169188
metric.Counter.GetValue(), 0, false,
170189
metric.Counter.Exemplar,
171190
)
191+
if toOM.withCreatedLines && metric.Counter.CreatedTimestamp != nil {
192+
createdTsBytesWritten, err = writeOpenMetricsCreated(w, name, "_total", metric, "", 0, metric.Counter.GetCreatedTimestamp())
193+
n += createdTsBytesWritten
194+
}
172195
case dto.MetricType_GAUGE:
173196
if metric.Gauge == nil {
174197
return written, fmt.Errorf(
@@ -223,6 +246,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
223246
0, metric.Summary.GetSampleCount(), true,
224247
nil,
225248
)
249+
if metric.Summary.CreatedTimestamp != nil {
250+
createdTsBytesWritten, err = writeOpenMetricsCreated(w, name, "", metric, "", 0, metric.Summary.GetCreatedTimestamp())
251+
n += createdTsBytesWritten
252+
}
226253
case dto.MetricType_HISTOGRAM:
227254
if metric.Histogram == nil {
228255
return written, fmt.Errorf(
@@ -271,6 +298,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
271298
0, metric.Histogram.GetSampleCount(), true,
272299
nil,
273300
)
301+
if metric.Histogram.CreatedTimestamp != nil {
302+
createdTsBytesWritten, err = writeOpenMetricsCreated(w, name, "", metric, "", 0, metric.Histogram.GetCreatedTimestamp())
303+
n += createdTsBytesWritten
304+
}
274305
default:
275306
return written, fmt.Errorf(
276307
"unexpected type in metric %s %s", name, metric,
@@ -442,6 +473,62 @@ func writeOpenMetricsLabelPairs(
442473
return written, nil
443474
}
444475

476+
// writeOpenMetricsCreated writes the created timestamp for a single time series
477+
// following OpenMetrics text format to w, given the metric name, the metric proto
478+
// message itself, optionally a suffix to be removed, e.g. '_total' for counters,
479+
// an additional label name with a float64 value (use empty string as label name if
480+
// not required) and the timestamp that represents the created timestamp.
481+
// The function returns the number of bytes written and any error encountered.
482+
func writeOpenMetricsCreated(w enhancedWriter,
483+
name, suffixToTrim string, metric *dto.Metric,
484+
additionalLabelName string, additionalLabelValue float64,
485+
createdTimestamp *timestamppb.Timestamp,
486+
) (int, error) {
487+
written := 0
488+
n, err := w.WriteString(strings.TrimSuffix(name, suffixToTrim))
489+
written += n
490+
if err != nil {
491+
return written, err
492+
}
493+
494+
n, err = w.WriteString("_created")
495+
written += n
496+
if err != nil {
497+
return written, err
498+
}
499+
500+
n, err = writeOpenMetricsLabelPairs(
501+
w, metric.Label, additionalLabelName, additionalLabelValue,
502+
)
503+
written += n
504+
if err != nil {
505+
return written, err
506+
}
507+
508+
err = w.WriteByte(' ')
509+
written++
510+
if err != nil {
511+
return written, err
512+
}
513+
514+
ts := createdTimestamp.AsTime()
515+
// TODO(beorn7): Format this directly from components of ts to
516+
// avoid overflow/underflow and precision issues of the float
517+
// conversion.
518+
n, err = writeOpenMetricsFloat(w, float64(ts.UnixNano())/1e9)
519+
written += n
520+
if err != nil {
521+
return written, err
522+
}
523+
524+
err = w.WriteByte('\n')
525+
written++
526+
if err != nil {
527+
return written, err
528+
}
529+
return written, nil
530+
}
531+
445532
// writeExemplar writes the provided exemplar in OpenMetrics format to w. The
446533
// function returns the number of bytes written and any error encountered.
447534
func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) {

expfmt/openmetrics_create_test.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ func TestCreateOpenMetrics(t *testing.T) {
3333
}
3434

3535
var scenarios = []struct {
36-
in *dto.MetricFamily
37-
out string
36+
in *dto.MetricFamily
37+
options []ToOpenMetricsOption
38+
out string
3839
}{
3940
// 0: Counter, timestamp given, no _total suffix.
4041
{
@@ -181,6 +182,7 @@ unknown_name{name_1="value 1"} -1.23e-45
181182
Value: proto.Float64(0),
182183
},
183184
},
185+
CreatedTimestamp: openMetricsTimestamp,
184186
},
185187
},
186188
&dto.Metric{
@@ -211,22 +213,26 @@ unknown_name{name_1="value 1"} -1.23e-45
211213
Value: proto.Float64(3),
212214
},
213215
},
216+
CreatedTimestamp: openMetricsTimestamp,
214217
},
215218
},
216219
},
217220
},
221+
options: []ToOpenMetricsOption{WithCreatedLines()},
218222
out: `# HELP summary_name summary docstring
219223
# TYPE summary_name summary
220224
summary_name{quantile="0.5"} -1.23
221225
summary_name{quantile="0.9"} 0.2342354
222226
summary_name{quantile="0.99"} 0.0
223227
summary_name_sum -3.4567
224228
summary_name_count 42
229+
summary_name_created 12345.6
225230
summary_name{name_1="value 1",name_2="value 2",quantile="0.5"} 1.0
226231
summary_name{name_1="value 1",name_2="value 2",quantile="0.9"} 2.0
227232
summary_name{name_1="value 1",name_2="value 2",quantile="0.99"} 3.0
228233
summary_name_sum{name_1="value 1",name_2="value 2"} 2010.1971
229234
summary_name_count{name_1="value 1",name_2="value 2"} 4711
235+
summary_name_created{name_1="value 1",name_2="value 2"} 12345.6
230236
`,
231237
},
232238
// 4: Histogram
@@ -262,10 +268,12 @@ summary_name_count{name_1="value 1",name_2="value 2"} 4711
262268
CumulativeCount: proto.Uint64(2693),
263269
},
264270
},
271+
CreatedTimestamp: openMetricsTimestamp,
265272
},
266273
},
267274
},
268275
},
276+
options: []ToOpenMetricsOption{WithCreatedLines()},
269277
out: `# HELP request_duration_microseconds The response latency.
270278
# TYPE request_duration_microseconds histogram
271279
request_duration_microseconds_bucket{le="100.0"} 123
@@ -275,6 +283,7 @@ request_duration_microseconds_bucket{le="172.8"} 1524
275283
request_duration_microseconds_bucket{le="+Inf"} 2693
276284
request_duration_microseconds_sum 1.7560473e+06
277285
request_duration_microseconds_count 2693
286+
request_duration_microseconds_created 12345.6
278287
`,
279288
},
280289
// 5: Histogram with missing +Inf bucket.
@@ -397,7 +406,30 @@ request_duration_microseconds_count 2693
397406
Metric: []*dto.Metric{
398407
&dto.Metric{
399408
Counter: &dto.Counter{
400-
Value: proto.Float64(42),
409+
Value: proto.Float64(42),
410+
CreatedTimestamp: openMetricsTimestamp,
411+
},
412+
},
413+
},
414+
},
415+
options: []ToOpenMetricsOption{WithCreatedLines()},
416+
out: `# HELP foos Number of foos.
417+
# TYPE foos counter
418+
foos_total 42.0
419+
foos_created 12345.6
420+
`,
421+
},
422+
// 8: Simple Counter without created line.
423+
{
424+
in: &dto.MetricFamily{
425+
Name: proto.String("foos_total"),
426+
Help: proto.String("Number of foos."),
427+
Type: dto.MetricType_COUNTER.Enum(),
428+
Metric: []*dto.Metric{
429+
&dto.Metric{
430+
Counter: &dto.Counter{
431+
Value: proto.Float64(42),
432+
CreatedTimestamp: openMetricsTimestamp,
401433
},
402434
},
403435
},
@@ -407,7 +439,7 @@ request_duration_microseconds_count 2693
407439
foos_total 42.0
408440
`,
409441
},
410-
// 8: No metric.
442+
// 9: No metric.
411443
{
412444
in: &dto.MetricFamily{
413445
Name: proto.String("name_total"),
@@ -423,7 +455,7 @@ foos_total 42.0
423455

424456
for i, scenario := range scenarios {
425457
out := bytes.NewBuffer(make([]byte, 0, len(scenario.out)))
426-
n, err := MetricFamilyToOpenMetrics(out, scenario.in)
458+
n, err := MetricFamilyToOpenMetrics(out, scenario.in, scenario.options...)
427459
if err != nil {
428460
t.Errorf("%d. error: %s", i, err)
429461
continue

0 commit comments

Comments
 (0)