@@ -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
@@ -52,15 +65,20 @@ import (
52
65
// its type will be set to `unknown` in that case to avoid invalid OpenMetrics
53
66
// output.
54
67
//
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.
57
70
//
58
71
// - The size of exemplar labels is not checked (i.e. it's possible to create
59
72
// exemplars that are larger than allowed by the OpenMetrics specification).
60
73
//
61
74
// - The value of Counters is not checked. (OpenMetrics doesn't allow counters
62
75
// 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
+
64
82
name := in .GetName ()
65
83
if name == "" {
66
84
return 0 , fmt .Errorf ("MetricFamily has no name: %s" , in )
@@ -152,6 +170,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
152
170
return
153
171
}
154
172
173
+ var createdTsBytesWritten int
155
174
// Finally the samples, one line for each.
156
175
for _ , metric := range in .Metric {
157
176
switch metricType {
@@ -169,6 +188,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
169
188
metric .Counter .GetValue (), 0 , false ,
170
189
metric .Counter .Exemplar ,
171
190
)
191
+ if toOM .withCreatedLines && metric .Counter .CreatedTimestamp != nil {
192
+ createdTsBytesWritten , err = writeOpenMetricsCreated (w , name , "_total" , metric , "" , 0 , metric .Counter .GetCreatedTimestamp ())
193
+ n += createdTsBytesWritten
194
+ }
172
195
case dto .MetricType_GAUGE :
173
196
if metric .Gauge == nil {
174
197
return written , fmt .Errorf (
@@ -223,6 +246,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
223
246
0 , metric .Summary .GetSampleCount (), true ,
224
247
nil ,
225
248
)
249
+ if metric .Summary .CreatedTimestamp != nil {
250
+ createdTsBytesWritten , err = writeOpenMetricsCreated (w , name , "" , metric , "" , 0 , metric .Summary .GetCreatedTimestamp ())
251
+ n += createdTsBytesWritten
252
+ }
226
253
case dto .MetricType_HISTOGRAM :
227
254
if metric .Histogram == nil {
228
255
return written , fmt .Errorf (
@@ -271,6 +298,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int
271
298
0 , metric .Histogram .GetSampleCount (), true ,
272
299
nil ,
273
300
)
301
+ if metric .Histogram .CreatedTimestamp != nil {
302
+ createdTsBytesWritten , err = writeOpenMetricsCreated (w , name , "" , metric , "" , 0 , metric .Histogram .GetCreatedTimestamp ())
303
+ n += createdTsBytesWritten
304
+ }
274
305
default :
275
306
return written , fmt .Errorf (
276
307
"unexpected type in metric %s %s" , name , metric ,
@@ -442,6 +473,62 @@ func writeOpenMetricsLabelPairs(
442
473
return written , nil
443
474
}
444
475
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
+
445
532
// writeExemplar writes the provided exemplar in OpenMetrics format to w. The
446
533
// function returns the number of bytes written and any error encountered.
447
534
func writeExemplar (w enhancedWriter , e * dto.Exemplar ) (int , error ) {
0 commit comments