diff --git a/expfmt/openmetrics_create.go b/expfmt/openmetrics_create.go index 21cdddcf..e9f18481 100644 --- a/expfmt/openmetrics_create.go +++ b/expfmt/openmetrics_create.go @@ -23,6 +23,7 @@ import ( "strings" "github.com/prometheus/common/model" + "google.golang.org/protobuf/types/known/timestamppb" dto "github.com/prometheus/client_model/go" ) @@ -152,6 +153,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int return } + var createdBytesWritten int // Finally the samples, one line for each. for _, metric := range in.Metric { switch metricType { @@ -169,6 +171,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int metric.Counter.GetValue(), 0, false, metric.Counter.Exemplar, ) + if metric.Counter.CreatedTimestamp != nil { + createdBytesWritten, err = writeOpenMetricsCreated(w, name, "_total", metric, "", 0, metric.Counter.GetCreatedTimestamp()) + n += createdBytesWritten + } case dto.MetricType_GAUGE: if metric.Gauge == nil { return written, fmt.Errorf( @@ -223,6 +229,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int 0, metric.Summary.GetSampleCount(), true, nil, ) + if metric.Summary.CreatedTimestamp != nil { + createdBytesWritten, err = writeOpenMetricsCreated(w, name, "", metric, "", 0, metric.Summary.GetCreatedTimestamp()) + n += createdBytesWritten + } case dto.MetricType_HISTOGRAM: if metric.Histogram == nil { return written, fmt.Errorf( @@ -271,6 +281,10 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int 0, metric.Histogram.GetSampleCount(), true, nil, ) + if metric.Histogram.CreatedTimestamp != nil { + createdBytesWritten, err = writeOpenMetricsCreated(w, name, "", metric, "", 0, metric.Histogram.GetCreatedTimestamp()) + n += createdBytesWritten + } default: return written, fmt.Errorf( "unexpected type in metric %s %s", name, metric, @@ -442,6 +456,62 @@ func writeOpenMetricsLabelPairs( return written, nil } +// writeOpenMetricsCreated writes the created timestamp for a single time series +// following OpenMetrics text format to w, given the metric name, the metric proto +// message itself, optionally a suffix to be removed, e.g. '_total' for counters, +// an additional label name with a float64 value (use empty string as label name if +// not required) and the timestamp that represents the created timestamp. +// The function returns the number of bytes written and any error encountered. +func writeOpenMetricsCreated(w enhancedWriter, + name, suffixToTrim string, metric *dto.Metric, + additionalLabelName string, additionalLabelValue float64, + createdTimestamp *timestamppb.Timestamp, +) (int, error) { + written := 0 + n, err := w.WriteString(strings.TrimSuffix(name, suffixToTrim)) + written += n + if err != nil { + return written, err + } + + n, err = w.WriteString("_created") + written += n + if err != nil { + return written, err + } + + n, err = writeOpenMetricsLabelPairs( + w, metric.Label, additionalLabelName, additionalLabelValue, + ) + written += n + if err != nil { + return written, err + } + + err = w.WriteByte(' ') + written++ + if err != nil { + return written, err + } + + ts := createdTimestamp.AsTime() + // TODO(beorn7): Format this directly from components of ts to + // avoid overflow/underflow and precision issues of the float + // conversion. + n, err = writeOpenMetricsFloat(w, float64(ts.UnixNano())/1e9) + written += n + if err != nil { + return written, err + } + + err = w.WriteByte('\n') + written++ + if err != nil { + return written, err + } + return written, nil +} + // writeExemplar writes the provided exemplar in OpenMetrics format to w. The // function returns the number of bytes written and any error encountered. func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) {