@@ -22,11 +22,35 @@ import (
22
22
"strconv"
23
23
"strings"
24
24
25
+ "google.golang.org/protobuf/types/known/timestamppb"
26
+
25
27
"github.com/prometheus/common/model"
26
28
27
29
dto "github.com/prometheus/client_model/go"
28
30
)
29
31
32
+ type encoderOption struct {
33
+ withCreatedLines bool
34
+ }
35
+
36
+ type EncoderOption func (* encoderOption )
37
+
38
+ // WithCreatedLines is an EncoderOption that configures the OpenMetrics encoder
39
+ // to include _created lines (See
40
+ // https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#counter-1).
41
+ // Created timestamps can improve the accuracy of series reset detection, but
42
+ // come with a bandwidth cost.
43
+ //
44
+ // At the time of writing, created timestamp ingestion is still experimental in
45
+ // Prometheus and need to be enabled with the feature-flag
46
+ // `--feature-flag=created-timestamp-zero-ingestion`, and breaking changes are
47
+ // still possible. Therefore, it is recommended to use this feature with caution.
48
+ func WithCreatedLines () EncoderOption {
49
+ return func (t * encoderOption ) {
50
+ t .withCreatedLines = true
51
+ }
52
+ }
53
+
30
54
// MetricFamilyToOpenMetrics converts a MetricFamily proto message into the
31
55
// OpenMetrics text format and writes the resulting lines to 'out'. It returns
32
56
// the number of bytes written and any error encountered. The output will have
@@ -64,15 +88,20 @@ import (
64
88
// its type will be set to `unknown` in that case to avoid invalid OpenMetrics
65
89
// output.
66
90
//
67
- // - No support for the following (optional) features: `# UNIT` line, `_created`
68
- // line, info type, stateset type, gaugehistogram type.
91
+ // - No support for the following (optional) features: `# UNIT` line, info type,
92
+ // stateset type, gaugehistogram type.
69
93
//
70
94
// - The size of exemplar labels is not checked (i.e. it's possible to create
71
95
// exemplars that are larger than allowed by the OpenMetrics specification).
72
96
//
73
97
// - The value of Counters is not checked. (OpenMetrics doesn't allow counters
74
98
// with a `NaN` value.)
75
- func MetricFamilyToOpenMetrics (out io.Writer , in * dto.MetricFamily ) (written int , err error ) {
99
+ func MetricFamilyToOpenMetrics (out io.Writer , in * dto.MetricFamily , options ... EncoderOption ) (written int , err error ) {
100
+ toOM := encoderOption {}
101
+ for _ , option := range options {
102
+ option (& toOM )
103
+ }
104
+
76
105
name := in .GetName ()
77
106
if name == "" {
78
107
return 0 , fmt .Errorf ("MetricFamily has no name: %s" , in )
@@ -164,6 +193,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
164
193
return
165
194
}
166
195
196
+ var createdTsBytesWritten int
167
197
// Finally the samples, one line for each.
168
198
for _ , metric := range in .Metric {
169
199
switch metricType {
@@ -181,6 +211,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
181
211
metric .Counter .GetValue (), 0 , false ,
182
212
metric .Counter .Exemplar ,
183
213
)
214
+ if toOM .withCreatedLines && metric .Counter .CreatedTimestamp != nil {
215
+ createdTsBytesWritten , err = writeOpenMetricsCreated (w , name , "_total" , metric , "" , 0 , metric .Counter .GetCreatedTimestamp ())
216
+ n += createdTsBytesWritten
217
+ }
184
218
case dto .MetricType_GAUGE :
185
219
if metric .Gauge == nil {
186
220
return written , fmt .Errorf (
@@ -235,6 +269,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
235
269
0 , metric .Summary .GetSampleCount (), true ,
236
270
nil ,
237
271
)
272
+ if toOM .withCreatedLines && metric .Summary .CreatedTimestamp != nil {
273
+ createdTsBytesWritten , err = writeOpenMetricsCreated (w , name , "" , metric , "" , 0 , metric .Summary .GetCreatedTimestamp ())
274
+ n += createdTsBytesWritten
275
+ }
238
276
case dto .MetricType_HISTOGRAM :
239
277
if metric .Histogram == nil {
240
278
return written , fmt .Errorf (
@@ -283,6 +321,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
283
321
0 , metric .Histogram .GetSampleCount (), true ,
284
322
nil ,
285
323
)
324
+ if toOM .withCreatedLines && metric .Histogram .CreatedTimestamp != nil {
325
+ createdTsBytesWritten , err = writeOpenMetricsCreated (w , name , "" , metric , "" , 0 , metric .Histogram .GetCreatedTimestamp ())
326
+ n += createdTsBytesWritten
327
+ }
286
328
default :
287
329
return written , fmt .Errorf (
288
330
"unexpected type in metric %s %s" , name , metric ,
@@ -473,6 +515,49 @@ func writeOpenMetricsNameAndLabelPairs(
473
515
return written , nil
474
516
}
475
517
518
+ // writeOpenMetricsCreated writes the created timestamp for a single time series
519
+ // following OpenMetrics text format to w, given the metric name, the metric proto
520
+ // message itself, optionally a suffix to be removed, e.g. '_total' for counters,
521
+ // an additional label name with a float64 value (use empty string as label name if
522
+ // not required) and the timestamp that represents the created timestamp.
523
+ // The function returns the number of bytes written and any error encountered.
524
+ func writeOpenMetricsCreated (w enhancedWriter ,
525
+ name , suffixToTrim string , metric * dto.Metric ,
526
+ additionalLabelName string , additionalLabelValue float64 ,
527
+ createdTimestamp * timestamppb.Timestamp ,
528
+ ) (int , error ) {
529
+ written := 0
530
+ n , err := writeOpenMetricsNameAndLabelPairs (
531
+ w , strings .TrimSuffix (name , suffixToTrim )+ "_created" , metric .Label , additionalLabelName , additionalLabelValue ,
532
+ )
533
+ written += n
534
+ if err != nil {
535
+ return written , err
536
+ }
537
+
538
+ err = w .WriteByte (' ' )
539
+ written ++
540
+ if err != nil {
541
+ return written , err
542
+ }
543
+
544
+ // TODO(beorn7): Format this directly from components of ts to
545
+ // avoid overflow/underflow and precision issues of the float
546
+ // conversion.
547
+ n , err = writeOpenMetricsFloat (w , float64 (createdTimestamp .AsTime ().UnixNano ())/ 1e9 )
548
+ written += n
549
+ if err != nil {
550
+ return written , err
551
+ }
552
+
553
+ err = w .WriteByte ('\n' )
554
+ written ++
555
+ if err != nil {
556
+ return written , err
557
+ }
558
+ return written , nil
559
+ }
560
+
476
561
// writeExemplar writes the provided exemplar in OpenMetrics format to w. The
477
562
// function returns the number of bytes written and any error encountered.
478
563
func writeExemplar (w enhancedWriter , e * dto.Exemplar ) (int , error ) {
0 commit comments