@@ -23,10 +23,23 @@ import (
23
23
"strings"
24
24
25
25
"github.com/prometheus/common/model"
26
+ "google.golang.org/protobuf/types/known/timestamppb"
26
27
27
28
dto "github.com/prometheus/client_model/go"
28
29
)
29
30
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
+
30
43
// MetricFamilyToOpenMetrics converts a MetricFamily proto message into the
31
44
// OpenMetrics text format and writes the resulting lines to 'out'. It returns
32
45
// the number of bytes written and any error encountered. The output will have
@@ -64,15 +77,20 @@ import (
64
77
// its type will be set to `unknown` in that case to avoid invalid OpenMetrics
65
78
// output.
66
79
//
67
- // - No support for the following (optional) features: `# UNIT` line, `_created`
68
- // line, info type, stateset type, gaugehistogram type.
80
+ // - No support for the following (optional) features: `# UNIT` line, info type,
81
+ // stateset type, gaugehistogram type.
69
82
//
70
83
// - The size of exemplar labels is not checked (i.e. it's possible to create
71
84
// exemplars that are larger than allowed by the OpenMetrics specification).
72
85
//
73
86
// - The value of Counters is not checked. (OpenMetrics doesn't allow counters
74
87
// with a `NaN` value.)
75
- func MetricFamilyToOpenMetrics (out io.Writer , in * dto.MetricFamily ) (written int , err error ) {
88
+ func MetricFamilyToOpenMetrics (out io.Writer , in * dto.MetricFamily , options ... ToOpenMetricsOption ) (written int , err error ) {
89
+ toOM := toOpenMetrics {}
90
+ for _ , option := range options {
91
+ option (& toOM )
92
+ }
93
+
76
94
name := in .GetName ()
77
95
if name == "" {
78
96
return 0 , fmt .Errorf ("MetricFamily has no name: %s" , in )
@@ -164,6 +182,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
164
182
return
165
183
}
166
184
185
+ var createdTsBytesWritten int
167
186
// Finally the samples, one line for each.
168
187
for _ , metric := range in .Metric {
169
188
switch metricType {
@@ -181,6 +200,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
181
200
metric .Counter .GetValue (), 0 , false ,
182
201
metric .Counter .Exemplar ,
183
202
)
203
+ if toOM .withCreatedLines && metric .Counter .CreatedTimestamp != nil {
204
+ createdTsBytesWritten , err = writeOpenMetricsCreated (w , name , "_total" , metric , "" , 0 , metric .Counter .GetCreatedTimestamp ())
205
+ n += createdTsBytesWritten
206
+ }
184
207
case dto .MetricType_GAUGE :
185
208
if metric .Gauge == nil {
186
209
return written , fmt .Errorf (
@@ -235,6 +258,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
235
258
0 , metric .Summary .GetSampleCount (), true ,
236
259
nil ,
237
260
)
261
+ if metric .Summary .CreatedTimestamp != nil {
262
+ createdTsBytesWritten , err = writeOpenMetricsCreated (w , name , "" , metric , "" , 0 , metric .Summary .GetCreatedTimestamp ())
263
+ n += createdTsBytesWritten
264
+ }
238
265
case dto .MetricType_HISTOGRAM :
239
266
if metric .Histogram == nil {
240
267
return written , fmt .Errorf (
@@ -283,6 +310,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
283
310
0 , metric .Histogram .GetSampleCount (), true ,
284
311
nil ,
285
312
)
313
+ if metric .Histogram .CreatedTimestamp != nil {
314
+ createdTsBytesWritten , err = writeOpenMetricsCreated (w , name , "" , metric , "" , 0 , metric .Histogram .GetCreatedTimestamp ())
315
+ n += createdTsBytesWritten
316
+ }
286
317
default :
287
318
return written , fmt .Errorf (
288
319
"unexpected type in metric %s %s" , name , metric ,
@@ -473,6 +504,62 @@ func writeOpenMetricsNameAndLabelPairs(
473
504
return written , nil
474
505
}
475
506
507
+ // writeOpenMetricsCreated writes the created timestamp for a single time series
508
+ // following OpenMetrics text format to w, given the metric name, the metric proto
509
+ // message itself, optionally a suffix to be removed, e.g. '_total' for counters,
510
+ // an additional label name with a float64 value (use empty string as label name if
511
+ // not required) and the timestamp that represents the created timestamp.
512
+ // The function returns the number of bytes written and any error encountered.
513
+ func writeOpenMetricsCreated (w enhancedWriter ,
514
+ name , suffixToTrim string , metric * dto.Metric ,
515
+ additionalLabelName string , additionalLabelValue float64 ,
516
+ createdTimestamp * timestamppb.Timestamp ,
517
+ ) (int , error ) {
518
+ written := 0
519
+ n , err := w .WriteString (strings .TrimSuffix (name , suffixToTrim ))
520
+ written += n
521
+ if err != nil {
522
+ return written , err
523
+ }
524
+
525
+ n , err = w .WriteString ("_created" )
526
+ written += n
527
+ if err != nil {
528
+ return written , err
529
+ }
530
+
531
+ n , err = writeOpenMetricsLabelPairs (
532
+ w , metric .Label , additionalLabelName , additionalLabelValue ,
533
+ )
534
+ written += n
535
+ if err != nil {
536
+ return written , err
537
+ }
538
+
539
+ err = w .WriteByte (' ' )
540
+ written ++
541
+ if err != nil {
542
+ return written , err
543
+ }
544
+
545
+ ts := createdTimestamp .AsTime ()
546
+ // TODO(beorn7): Format this directly from components of ts to
547
+ // avoid overflow/underflow and precision issues of the float
548
+ // conversion.
549
+ n , err = writeOpenMetricsFloat (w , float64 (ts .UnixNano ())/ 1e9 )
550
+ written += n
551
+ if err != nil {
552
+ return written , err
553
+ }
554
+
555
+ err = w .WriteByte ('\n' )
556
+ written ++
557
+ if err != nil {
558
+ return written , err
559
+ }
560
+ return written , nil
561
+ }
562
+
476
563
// writeExemplar writes the provided exemplar in OpenMetrics format to w. The
477
564
// function returns the number of bytes written and any error encountered.
478
565
func writeExemplar (w enhancedWriter , e * dto.Exemplar ) (int , error ) {
0 commit comments