blob: 8dbf6d04ed65176e304e7a21903576f01b990c02 [file] [log] [blame]
khenaidood948f772021-08-11 17:49:24 -04001// Copyright 2020 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package expfmt
15
16import (
17 "bufio"
18 "bytes"
19 "fmt"
20 "io"
21 "math"
22 "strconv"
23 "strings"
24
khenaidood948f772021-08-11 17:49:24 -040025 dto "github.com/prometheus/client_model/go"
Abhay Kumara2ae5992025-11-10 14:02:24 +000026 "google.golang.org/protobuf/types/known/timestamppb"
27
28 "github.com/prometheus/common/model"
khenaidood948f772021-08-11 17:49:24 -040029)
30
Abhay Kumara2ae5992025-11-10 14:02:24 +000031type encoderOption struct {
32 withCreatedLines bool
33 withUnit bool
34}
35
36type EncoderOption func(*encoderOption)
37
38// WithCreatedLines is an EncoderOption that configures the OpenMetrics encoder
39// to include _created lines (See
40// https://github.com/prometheus/OpenMetrics/blob/v1.0.0/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.
48func WithCreatedLines() EncoderOption {
49 return func(t *encoderOption) {
50 t.withCreatedLines = true
51 }
52}
53
54// WithUnit is an EncoderOption enabling a set unit to be written to the output
55// and to be added to the metric name, if it's not there already, as a suffix.
56// Without opting in this way, the unit will not be added to the metric name and,
57// on top of that, the unit will not be passed onto the output, even if it
58// were declared in the *dto.MetricFamily struct, i.e. even if in.Unit !=nil.
59func WithUnit() EncoderOption {
60 return func(t *encoderOption) {
61 t.withUnit = true
62 }
63}
64
khenaidood948f772021-08-11 17:49:24 -040065// MetricFamilyToOpenMetrics converts a MetricFamily proto message into the
66// OpenMetrics text format and writes the resulting lines to 'out'. It returns
67// the number of bytes written and any error encountered. The output will have
68// the same order as the input, no further sorting is performed. Furthermore,
69// this function assumes the input is already sanitized and does not perform any
70// sanity checks. If the input contains duplicate metrics or invalid metric or
71// label names, the conversion will result in invalid text format output.
72//
Abhay Kumara2ae5992025-11-10 14:02:24 +000073// If metric names conform to the legacy validation pattern, they will be placed
74// outside the brackets in the traditional way, like `foo{}`. If the metric name
75// fails the legacy validation check, it will be placed quoted inside the
76// brackets: `{"foo"}`. As stated above, the input is assumed to be santized and
77// no error will be thrown in this case.
78//
79// Similar to metric names, if label names conform to the legacy validation
80// pattern, they will be unquoted as normal, like `foo{bar="baz"}`. If the label
81// name fails the legacy validation check, it will be quoted:
82// `foo{"bar"="baz"}`. As stated above, the input is assumed to be santized and
83// no error will be thrown in this case.
84//
khenaidood948f772021-08-11 17:49:24 -040085// This function fulfills the type 'expfmt.encoder'.
86//
87// Note that OpenMetrics requires a final `# EOF` line. Since this function acts
88// on individual metric families, it is the responsibility of the caller to
89// append this line to 'out' once all metric families have been written.
90// Conveniently, this can be done by calling FinalizeOpenMetrics.
91//
92// The output should be fully OpenMetrics compliant. However, there are a few
93// missing features and peculiarities to avoid complications when switching from
94// Prometheus to OpenMetrics or vice versa:
95//
Abhay Kumara2ae5992025-11-10 14:02:24 +000096// - Counters are expected to have the `_total` suffix in their metric name. In
97// the output, the suffix will be truncated from the `# TYPE`, `# HELP` and `# UNIT`
98// lines. A counter with a missing `_total` suffix is not an error. However,
99// its type will be set to `unknown` in that case to avoid invalid OpenMetrics
100// output.
khenaidood948f772021-08-11 17:49:24 -0400101//
Abhay Kumara2ae5992025-11-10 14:02:24 +0000102// - According to the OM specs, the `# UNIT` line is optional, but if populated,
103// the unit has to be present in the metric name as its suffix:
104// (see https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#unit).
105// However, in order to accommodate any potential scenario where such a change in the
106// metric name is not desirable, the users are here given the choice of either explicitly
107// opt in, in case they wish for the unit to be included in the output AND in the metric name
108// as a suffix (see the description of the WithUnit function above),
109// or not to opt in, in case they don't want for any of that to happen.
khenaidood948f772021-08-11 17:49:24 -0400110//
Abhay Kumara2ae5992025-11-10 14:02:24 +0000111// - No support for the following (optional) features: info type,
112// stateset type, gaugehistogram type.
khenaidood948f772021-08-11 17:49:24 -0400113//
Abhay Kumara2ae5992025-11-10 14:02:24 +0000114// - The size of exemplar labels is not checked (i.e. it's possible to create
115// exemplars that are larger than allowed by the OpenMetrics specification).
116//
117// - The value of Counters is not checked. (OpenMetrics doesn't allow counters
118// with a `NaN` value.)
119func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...EncoderOption) (written int, err error) {
120 toOM := encoderOption{}
121 for _, option := range options {
122 option(&toOM)
123 }
124
khenaidood948f772021-08-11 17:49:24 -0400125 name := in.GetName()
126 if name == "" {
127 return 0, fmt.Errorf("MetricFamily has no name: %s", in)
128 }
129
130 // Try the interface upgrade. If it doesn't work, we'll use a
131 // bufio.Writer from the sync.Pool.
132 w, ok := out.(enhancedWriter)
133 if !ok {
134 b := bufPool.Get().(*bufio.Writer)
135 b.Reset(out)
136 w = b
137 defer func() {
138 bErr := b.Flush()
139 if err == nil {
140 err = bErr
141 }
142 bufPool.Put(b)
143 }()
144 }
145
146 var (
Abhay Kumara2ae5992025-11-10 14:02:24 +0000147 n int
148 metricType = in.GetType()
149 compliantName = name
khenaidood948f772021-08-11 17:49:24 -0400150 )
Abhay Kumara2ae5992025-11-10 14:02:24 +0000151 if metricType == dto.MetricType_COUNTER && strings.HasSuffix(compliantName, "_total") {
152 compliantName = name[:len(name)-6]
153 }
154 if toOM.withUnit && in.Unit != nil && !strings.HasSuffix(compliantName, "_"+*in.Unit) {
155 compliantName = compliantName + "_" + *in.Unit
khenaidood948f772021-08-11 17:49:24 -0400156 }
157
158 // Comments, first HELP, then TYPE.
159 if in.Help != nil {
160 n, err = w.WriteString("# HELP ")
161 written += n
162 if err != nil {
163 return
164 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000165 n, err = writeName(w, compliantName)
khenaidood948f772021-08-11 17:49:24 -0400166 written += n
167 if err != nil {
168 return
169 }
170 err = w.WriteByte(' ')
171 written++
172 if err != nil {
173 return
174 }
175 n, err = writeEscapedString(w, *in.Help, true)
176 written += n
177 if err != nil {
178 return
179 }
180 err = w.WriteByte('\n')
181 written++
182 if err != nil {
183 return
184 }
185 }
186 n, err = w.WriteString("# TYPE ")
187 written += n
188 if err != nil {
189 return
190 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000191 n, err = writeName(w, compliantName)
khenaidood948f772021-08-11 17:49:24 -0400192 written += n
193 if err != nil {
194 return
195 }
196 switch metricType {
197 case dto.MetricType_COUNTER:
198 if strings.HasSuffix(name, "_total") {
199 n, err = w.WriteString(" counter\n")
200 } else {
201 n, err = w.WriteString(" unknown\n")
202 }
203 case dto.MetricType_GAUGE:
204 n, err = w.WriteString(" gauge\n")
205 case dto.MetricType_SUMMARY:
206 n, err = w.WriteString(" summary\n")
207 case dto.MetricType_UNTYPED:
208 n, err = w.WriteString(" unknown\n")
209 case dto.MetricType_HISTOGRAM:
210 n, err = w.WriteString(" histogram\n")
211 default:
212 return written, fmt.Errorf("unknown metric type %s", metricType.String())
213 }
214 written += n
215 if err != nil {
216 return
217 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000218 if toOM.withUnit && in.Unit != nil {
219 n, err = w.WriteString("# UNIT ")
220 written += n
221 if err != nil {
222 return
223 }
224 n, err = writeName(w, compliantName)
225 written += n
226 if err != nil {
227 return
228 }
229
230 err = w.WriteByte(' ')
231 written++
232 if err != nil {
233 return
234 }
235 n, err = writeEscapedString(w, *in.Unit, true)
236 written += n
237 if err != nil {
238 return
239 }
240 err = w.WriteByte('\n')
241 written++
242 if err != nil {
243 return
244 }
245 }
246
247 var createdTsBytesWritten int
khenaidood948f772021-08-11 17:49:24 -0400248
249 // Finally the samples, one line for each.
Abhay Kumara2ae5992025-11-10 14:02:24 +0000250 if metricType == dto.MetricType_COUNTER && strings.HasSuffix(name, "_total") {
251 compliantName += "_total"
252 }
khenaidood948f772021-08-11 17:49:24 -0400253 for _, metric := range in.Metric {
254 switch metricType {
255 case dto.MetricType_COUNTER:
256 if metric.Counter == nil {
257 return written, fmt.Errorf(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000258 "expected counter in metric %s %s", compliantName, metric,
khenaidood948f772021-08-11 17:49:24 -0400259 )
260 }
khenaidood948f772021-08-11 17:49:24 -0400261 n, err = writeOpenMetricsSample(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000262 w, compliantName, "", metric, "", 0,
khenaidood948f772021-08-11 17:49:24 -0400263 metric.Counter.GetValue(), 0, false,
264 metric.Counter.Exemplar,
265 )
Abhay Kumara2ae5992025-11-10 14:02:24 +0000266 if toOM.withCreatedLines && metric.Counter.CreatedTimestamp != nil {
267 createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "_total", metric, "", 0, metric.Counter.GetCreatedTimestamp())
268 n += createdTsBytesWritten
269 }
khenaidood948f772021-08-11 17:49:24 -0400270 case dto.MetricType_GAUGE:
271 if metric.Gauge == nil {
272 return written, fmt.Errorf(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000273 "expected gauge in metric %s %s", compliantName, metric,
khenaidood948f772021-08-11 17:49:24 -0400274 )
275 }
276 n, err = writeOpenMetricsSample(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000277 w, compliantName, "", metric, "", 0,
khenaidood948f772021-08-11 17:49:24 -0400278 metric.Gauge.GetValue(), 0, false,
279 nil,
280 )
281 case dto.MetricType_UNTYPED:
282 if metric.Untyped == nil {
283 return written, fmt.Errorf(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000284 "expected untyped in metric %s %s", compliantName, metric,
khenaidood948f772021-08-11 17:49:24 -0400285 )
286 }
287 n, err = writeOpenMetricsSample(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000288 w, compliantName, "", metric, "", 0,
khenaidood948f772021-08-11 17:49:24 -0400289 metric.Untyped.GetValue(), 0, false,
290 nil,
291 )
292 case dto.MetricType_SUMMARY:
293 if metric.Summary == nil {
294 return written, fmt.Errorf(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000295 "expected summary in metric %s %s", compliantName, metric,
khenaidood948f772021-08-11 17:49:24 -0400296 )
297 }
298 for _, q := range metric.Summary.Quantile {
299 n, err = writeOpenMetricsSample(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000300 w, compliantName, "", metric,
khenaidood948f772021-08-11 17:49:24 -0400301 model.QuantileLabel, q.GetQuantile(),
302 q.GetValue(), 0, false,
303 nil,
304 )
305 written += n
306 if err != nil {
307 return
308 }
309 }
310 n, err = writeOpenMetricsSample(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000311 w, compliantName, "_sum", metric, "", 0,
khenaidood948f772021-08-11 17:49:24 -0400312 metric.Summary.GetSampleSum(), 0, false,
313 nil,
314 )
315 written += n
316 if err != nil {
317 return
318 }
319 n, err = writeOpenMetricsSample(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000320 w, compliantName, "_count", metric, "", 0,
khenaidood948f772021-08-11 17:49:24 -0400321 0, metric.Summary.GetSampleCount(), true,
322 nil,
323 )
Abhay Kumara2ae5992025-11-10 14:02:24 +0000324 if toOM.withCreatedLines && metric.Summary.CreatedTimestamp != nil {
325 createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "", metric, "", 0, metric.Summary.GetCreatedTimestamp())
326 n += createdTsBytesWritten
327 }
khenaidood948f772021-08-11 17:49:24 -0400328 case dto.MetricType_HISTOGRAM:
329 if metric.Histogram == nil {
330 return written, fmt.Errorf(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000331 "expected histogram in metric %s %s", compliantName, metric,
khenaidood948f772021-08-11 17:49:24 -0400332 )
333 }
334 infSeen := false
335 for _, b := range metric.Histogram.Bucket {
336 n, err = writeOpenMetricsSample(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000337 w, compliantName, "_bucket", metric,
khenaidood948f772021-08-11 17:49:24 -0400338 model.BucketLabel, b.GetUpperBound(),
339 0, b.GetCumulativeCount(), true,
340 b.Exemplar,
341 )
342 written += n
343 if err != nil {
344 return
345 }
346 if math.IsInf(b.GetUpperBound(), +1) {
347 infSeen = true
348 }
349 }
350 if !infSeen {
351 n, err = writeOpenMetricsSample(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000352 w, compliantName, "_bucket", metric,
khenaidood948f772021-08-11 17:49:24 -0400353 model.BucketLabel, math.Inf(+1),
354 0, metric.Histogram.GetSampleCount(), true,
355 nil,
356 )
357 written += n
358 if err != nil {
359 return
360 }
361 }
362 n, err = writeOpenMetricsSample(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000363 w, compliantName, "_sum", metric, "", 0,
khenaidood948f772021-08-11 17:49:24 -0400364 metric.Histogram.GetSampleSum(), 0, false,
365 nil,
366 )
367 written += n
368 if err != nil {
369 return
370 }
371 n, err = writeOpenMetricsSample(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000372 w, compliantName, "_count", metric, "", 0,
khenaidood948f772021-08-11 17:49:24 -0400373 0, metric.Histogram.GetSampleCount(), true,
374 nil,
375 )
Abhay Kumara2ae5992025-11-10 14:02:24 +0000376 if toOM.withCreatedLines && metric.Histogram.CreatedTimestamp != nil {
377 createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "", metric, "", 0, metric.Histogram.GetCreatedTimestamp())
378 n += createdTsBytesWritten
379 }
khenaidood948f772021-08-11 17:49:24 -0400380 default:
381 return written, fmt.Errorf(
Abhay Kumara2ae5992025-11-10 14:02:24 +0000382 "unexpected type in metric %s %s", compliantName, metric,
khenaidood948f772021-08-11 17:49:24 -0400383 )
384 }
385 written += n
386 if err != nil {
387 return
388 }
389 }
390 return
391}
392
393// FinalizeOpenMetrics writes the final `# EOF\n` line required by OpenMetrics.
394func FinalizeOpenMetrics(w io.Writer) (written int, err error) {
395 return w.Write([]byte("# EOF\n"))
396}
397
398// writeOpenMetricsSample writes a single sample in OpenMetrics text format to
399// w, given the metric name, the metric proto message itself, optionally an
400// additional label name with a float64 value (use empty string as label name if
401// not required), the value (optionally as float64 or uint64, determined by
402// useIntValue), and optionally an exemplar (use nil if not required). The
403// function returns the number of bytes written and any error encountered.
404func writeOpenMetricsSample(
405 w enhancedWriter,
406 name, suffix string,
407 metric *dto.Metric,
408 additionalLabelName string, additionalLabelValue float64,
409 floatValue float64, intValue uint64, useIntValue bool,
410 exemplar *dto.Exemplar,
411) (int, error) {
Abhay Kumara2ae5992025-11-10 14:02:24 +0000412 written := 0
413 n, err := writeOpenMetricsNameAndLabelPairs(
414 w, name+suffix, metric.Label, additionalLabelName, additionalLabelValue,
khenaidood948f772021-08-11 17:49:24 -0400415 )
416 written += n
417 if err != nil {
418 return written, err
419 }
420 err = w.WriteByte(' ')
421 written++
422 if err != nil {
423 return written, err
424 }
425 if useIntValue {
426 n, err = writeUint(w, intValue)
427 } else {
428 n, err = writeOpenMetricsFloat(w, floatValue)
429 }
430 written += n
431 if err != nil {
432 return written, err
433 }
434 if metric.TimestampMs != nil {
435 err = w.WriteByte(' ')
436 written++
437 if err != nil {
438 return written, err
439 }
440 // TODO(beorn7): Format this directly without converting to a float first.
441 n, err = writeOpenMetricsFloat(w, float64(*metric.TimestampMs)/1000)
442 written += n
443 if err != nil {
444 return written, err
445 }
446 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000447 if exemplar != nil && len(exemplar.Label) > 0 {
khenaidood948f772021-08-11 17:49:24 -0400448 n, err = writeExemplar(w, exemplar)
449 written += n
450 if err != nil {
451 return written, err
452 }
453 }
454 err = w.WriteByte('\n')
455 written++
456 if err != nil {
457 return written, err
458 }
459 return written, nil
460}
461
Abhay Kumara2ae5992025-11-10 14:02:24 +0000462// writeOpenMetricsNameAndLabelPairs works like writeOpenMetricsSample but
463// formats the float in OpenMetrics style.
464func writeOpenMetricsNameAndLabelPairs(
khenaidood948f772021-08-11 17:49:24 -0400465 w enhancedWriter,
Abhay Kumara2ae5992025-11-10 14:02:24 +0000466 name string,
khenaidood948f772021-08-11 17:49:24 -0400467 in []*dto.LabelPair,
468 additionalLabelName string, additionalLabelValue float64,
469) (int, error) {
khenaidood948f772021-08-11 17:49:24 -0400470 var (
Abhay Kumara2ae5992025-11-10 14:02:24 +0000471 written int
472 separator byte = '{'
473 metricInsideBraces = false
khenaidood948f772021-08-11 17:49:24 -0400474 )
Abhay Kumara2ae5992025-11-10 14:02:24 +0000475
476 if name != "" {
477 // If the name does not pass the legacy validity check, we must put the
478 // metric name inside the braces, quoted.
479 if !model.LegacyValidation.IsValidMetricName(name) {
480 metricInsideBraces = true
481 err := w.WriteByte(separator)
482 written++
483 if err != nil {
484 return written, err
485 }
486 separator = ','
487 }
488
489 n, err := writeName(w, name)
490 written += n
491 if err != nil {
492 return written, err
493 }
494 }
495
496 if len(in) == 0 && additionalLabelName == "" {
497 if metricInsideBraces {
498 err := w.WriteByte('}')
499 written++
500 if err != nil {
501 return written, err
502 }
503 }
504 return written, nil
505 }
506
khenaidood948f772021-08-11 17:49:24 -0400507 for _, lp := range in {
508 err := w.WriteByte(separator)
509 written++
510 if err != nil {
511 return written, err
512 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000513 n, err := writeName(w, lp.GetName())
khenaidood948f772021-08-11 17:49:24 -0400514 written += n
515 if err != nil {
516 return written, err
517 }
518 n, err = w.WriteString(`="`)
519 written += n
520 if err != nil {
521 return written, err
522 }
523 n, err = writeEscapedString(w, lp.GetValue(), true)
524 written += n
525 if err != nil {
526 return written, err
527 }
528 err = w.WriteByte('"')
529 written++
530 if err != nil {
531 return written, err
532 }
533 separator = ','
534 }
535 if additionalLabelName != "" {
536 err := w.WriteByte(separator)
537 written++
538 if err != nil {
539 return written, err
540 }
541 n, err := w.WriteString(additionalLabelName)
542 written += n
543 if err != nil {
544 return written, err
545 }
546 n, err = w.WriteString(`="`)
547 written += n
548 if err != nil {
549 return written, err
550 }
551 n, err = writeOpenMetricsFloat(w, additionalLabelValue)
552 written += n
553 if err != nil {
554 return written, err
555 }
556 err = w.WriteByte('"')
557 written++
558 if err != nil {
559 return written, err
560 }
561 }
562 err := w.WriteByte('}')
563 written++
564 if err != nil {
565 return written, err
566 }
567 return written, nil
568}
569
Abhay Kumara2ae5992025-11-10 14:02:24 +0000570// writeOpenMetricsCreated writes the created timestamp for a single time series
571// following OpenMetrics text format to w, given the metric name, the metric proto
572// message itself, optionally a suffix to be removed, e.g. '_total' for counters,
573// an additional label name with a float64 value (use empty string as label name if
574// not required) and the timestamp that represents the created timestamp.
575// The function returns the number of bytes written and any error encountered.
576func writeOpenMetricsCreated(w enhancedWriter,
577 name, suffixToTrim string, metric *dto.Metric,
578 additionalLabelName string, additionalLabelValue float64,
579 createdTimestamp *timestamppb.Timestamp,
580) (int, error) {
581 written := 0
582 n, err := writeOpenMetricsNameAndLabelPairs(
583 w, strings.TrimSuffix(name, suffixToTrim)+"_created", metric.Label, additionalLabelName, additionalLabelValue,
584 )
585 written += n
586 if err != nil {
587 return written, err
588 }
589
590 err = w.WriteByte(' ')
591 written++
592 if err != nil {
593 return written, err
594 }
595
596 // TODO(beorn7): Format this directly from components of ts to
597 // avoid overflow/underflow and precision issues of the float
598 // conversion.
599 n, err = writeOpenMetricsFloat(w, float64(createdTimestamp.AsTime().UnixNano())/1e9)
600 written += n
601 if err != nil {
602 return written, err
603 }
604
605 err = w.WriteByte('\n')
606 written++
607 if err != nil {
608 return written, err
609 }
610 return written, nil
611}
612
khenaidood948f772021-08-11 17:49:24 -0400613// writeExemplar writes the provided exemplar in OpenMetrics format to w. The
614// function returns the number of bytes written and any error encountered.
615func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) {
616 written := 0
617 n, err := w.WriteString(" # ")
618 written += n
619 if err != nil {
620 return written, err
621 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000622 n, err = writeOpenMetricsNameAndLabelPairs(w, "", e.Label, "", 0)
khenaidood948f772021-08-11 17:49:24 -0400623 written += n
624 if err != nil {
625 return written, err
626 }
627 err = w.WriteByte(' ')
628 written++
629 if err != nil {
630 return written, err
631 }
632 n, err = writeOpenMetricsFloat(w, e.GetValue())
633 written += n
634 if err != nil {
635 return written, err
636 }
637 if e.Timestamp != nil {
638 err = w.WriteByte(' ')
639 written++
640 if err != nil {
641 return written, err
642 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000643 err = e.Timestamp.CheckValid()
khenaidood948f772021-08-11 17:49:24 -0400644 if err != nil {
645 return written, err
646 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000647 ts := e.Timestamp.AsTime()
khenaidood948f772021-08-11 17:49:24 -0400648 // TODO(beorn7): Format this directly from components of ts to
649 // avoid overflow/underflow and precision issues of the float
650 // conversion.
651 n, err = writeOpenMetricsFloat(w, float64(ts.UnixNano())/1e9)
652 written += n
653 if err != nil {
654 return written, err
655 }
656 }
657 return written, nil
658}
659
660// writeOpenMetricsFloat works like writeFloat but appends ".0" if the resulting
661// number would otherwise contain neither a "." nor an "e".
662func writeOpenMetricsFloat(w enhancedWriter, f float64) (int, error) {
663 switch {
664 case f == 1:
665 return w.WriteString("1.0")
666 case f == 0:
667 return w.WriteString("0.0")
668 case f == -1:
669 return w.WriteString("-1.0")
670 case math.IsNaN(f):
671 return w.WriteString("NaN")
672 case math.IsInf(f, +1):
673 return w.WriteString("+Inf")
674 case math.IsInf(f, -1):
675 return w.WriteString("-Inf")
676 default:
677 bp := numBufPool.Get().(*[]byte)
678 *bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
679 if !bytes.ContainsAny(*bp, "e.") {
680 *bp = append(*bp, '.', '0')
681 }
682 written, err := w.Write(*bp)
683 numBufPool.Put(bp)
684 return written, err
685 }
686}
687
688// writeUint is like writeInt just for uint64.
689func writeUint(w enhancedWriter, u uint64) (int, error) {
690 bp := numBufPool.Get().(*[]byte)
691 *bp = strconv.AppendUint((*bp)[:0], u, 10)
692 written, err := w.Write(*bp)
693 numBufPool.Put(bp)
694 return written, err
695}