1
2
3
4
5
6
7
8
9
10
11
12
13
14 package expfmt
15
16 import (
17 "bufio"
18 "bytes"
19 "fmt"
20 "io"
21 "math"
22 "strconv"
23 "strings"
24
25 "google.golang.org/protobuf/types/known/timestamppb"
26
27 "github.com/prometheus/common/model"
28
29 dto "github.com/prometheus/client_model/go"
30 )
31
32 type encoderOption struct {
33 withCreatedLines bool
34 withUnit bool
35 }
36
37 type EncoderOption func(*encoderOption)
38
39
40
41
42
43
44
45
46
47
48
49 func WithCreatedLines() EncoderOption {
50 return func(t *encoderOption) {
51 t.withCreatedLines = true
52 }
53 }
54
55
56
57
58
59
60 func WithUnit() EncoderOption {
61 return func(t *encoderOption) {
62 t.withUnit = true
63 }
64 }
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...EncoderOption) (written int, err error) {
121 toOM := encoderOption{}
122 for _, option := range options {
123 option(&toOM)
124 }
125
126 name := in.GetName()
127 if name == "" {
128 return 0, fmt.Errorf("MetricFamily has no name: %s", in)
129 }
130
131
132
133 w, ok := out.(enhancedWriter)
134 if !ok {
135 b := bufPool.Get().(*bufio.Writer)
136 b.Reset(out)
137 w = b
138 defer func() {
139 bErr := b.Flush()
140 if err == nil {
141 err = bErr
142 }
143 bufPool.Put(b)
144 }()
145 }
146
147 var (
148 n int
149 metricType = in.GetType()
150 compliantName = name
151 )
152 if metricType == dto.MetricType_COUNTER && strings.HasSuffix(compliantName, "_total") {
153 compliantName = name[:len(name)-6]
154 }
155 if toOM.withUnit && in.Unit != nil && !strings.HasSuffix(compliantName, fmt.Sprintf("_%s", *in.Unit)) {
156 compliantName = compliantName + fmt.Sprintf("_%s", *in.Unit)
157 }
158
159
160 if in.Help != nil {
161 n, err = w.WriteString("# HELP ")
162 written += n
163 if err != nil {
164 return
165 }
166 n, err = writeName(w, compliantName)
167 written += n
168 if err != nil {
169 return
170 }
171 err = w.WriteByte(' ')
172 written++
173 if err != nil {
174 return
175 }
176 n, err = writeEscapedString(w, *in.Help, true)
177 written += n
178 if err != nil {
179 return
180 }
181 err = w.WriteByte('\n')
182 written++
183 if err != nil {
184 return
185 }
186 }
187 n, err = w.WriteString("# TYPE ")
188 written += n
189 if err != nil {
190 return
191 }
192 n, err = writeName(w, compliantName)
193 written += n
194 if err != nil {
195 return
196 }
197 switch metricType {
198 case dto.MetricType_COUNTER:
199 if strings.HasSuffix(name, "_total") {
200 n, err = w.WriteString(" counter\n")
201 } else {
202 n, err = w.WriteString(" unknown\n")
203 }
204 case dto.MetricType_GAUGE:
205 n, err = w.WriteString(" gauge\n")
206 case dto.MetricType_SUMMARY:
207 n, err = w.WriteString(" summary\n")
208 case dto.MetricType_UNTYPED:
209 n, err = w.WriteString(" unknown\n")
210 case dto.MetricType_HISTOGRAM:
211 n, err = w.WriteString(" histogram\n")
212 default:
213 return written, fmt.Errorf("unknown metric type %s", metricType.String())
214 }
215 written += n
216 if err != nil {
217 return
218 }
219 if toOM.withUnit && in.Unit != nil {
220 n, err = w.WriteString("# UNIT ")
221 written += n
222 if err != nil {
223 return
224 }
225 n, err = writeName(w, compliantName)
226 written += n
227 if err != nil {
228 return
229 }
230
231 err = w.WriteByte(' ')
232 written++
233 if err != nil {
234 return
235 }
236 n, err = writeEscapedString(w, *in.Unit, true)
237 written += n
238 if err != nil {
239 return
240 }
241 err = w.WriteByte('\n')
242 written++
243 if err != nil {
244 return
245 }
246 }
247
248 var createdTsBytesWritten int
249
250
251 if metricType == dto.MetricType_COUNTER && strings.HasSuffix(name, "_total") {
252 compliantName = compliantName + "_total"
253 }
254 for _, metric := range in.Metric {
255 switch metricType {
256 case dto.MetricType_COUNTER:
257 if metric.Counter == nil {
258 return written, fmt.Errorf(
259 "expected counter in metric %s %s", compliantName, metric,
260 )
261 }
262 n, err = writeOpenMetricsSample(
263 w, compliantName, "", metric, "", 0,
264 metric.Counter.GetValue(), 0, false,
265 metric.Counter.Exemplar,
266 )
267 if toOM.withCreatedLines && metric.Counter.CreatedTimestamp != nil {
268 createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "_total", metric, "", 0, metric.Counter.GetCreatedTimestamp())
269 n += createdTsBytesWritten
270 }
271 case dto.MetricType_GAUGE:
272 if metric.Gauge == nil {
273 return written, fmt.Errorf(
274 "expected gauge in metric %s %s", compliantName, metric,
275 )
276 }
277 n, err = writeOpenMetricsSample(
278 w, compliantName, "", metric, "", 0,
279 metric.Gauge.GetValue(), 0, false,
280 nil,
281 )
282 case dto.MetricType_UNTYPED:
283 if metric.Untyped == nil {
284 return written, fmt.Errorf(
285 "expected untyped in metric %s %s", compliantName, metric,
286 )
287 }
288 n, err = writeOpenMetricsSample(
289 w, compliantName, "", metric, "", 0,
290 metric.Untyped.GetValue(), 0, false,
291 nil,
292 )
293 case dto.MetricType_SUMMARY:
294 if metric.Summary == nil {
295 return written, fmt.Errorf(
296 "expected summary in metric %s %s", compliantName, metric,
297 )
298 }
299 for _, q := range metric.Summary.Quantile {
300 n, err = writeOpenMetricsSample(
301 w, compliantName, "", metric,
302 model.QuantileLabel, q.GetQuantile(),
303 q.GetValue(), 0, false,
304 nil,
305 )
306 written += n
307 if err != nil {
308 return
309 }
310 }
311 n, err = writeOpenMetricsSample(
312 w, compliantName, "_sum", metric, "", 0,
313 metric.Summary.GetSampleSum(), 0, false,
314 nil,
315 )
316 written += n
317 if err != nil {
318 return
319 }
320 n, err = writeOpenMetricsSample(
321 w, compliantName, "_count", metric, "", 0,
322 0, metric.Summary.GetSampleCount(), true,
323 nil,
324 )
325 if toOM.withCreatedLines && metric.Summary.CreatedTimestamp != nil {
326 createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "", metric, "", 0, metric.Summary.GetCreatedTimestamp())
327 n += createdTsBytesWritten
328 }
329 case dto.MetricType_HISTOGRAM:
330 if metric.Histogram == nil {
331 return written, fmt.Errorf(
332 "expected histogram in metric %s %s", compliantName, metric,
333 )
334 }
335 infSeen := false
336 for _, b := range metric.Histogram.Bucket {
337 n, err = writeOpenMetricsSample(
338 w, compliantName, "_bucket", metric,
339 model.BucketLabel, b.GetUpperBound(),
340 0, b.GetCumulativeCount(), true,
341 b.Exemplar,
342 )
343 written += n
344 if err != nil {
345 return
346 }
347 if math.IsInf(b.GetUpperBound(), +1) {
348 infSeen = true
349 }
350 }
351 if !infSeen {
352 n, err = writeOpenMetricsSample(
353 w, compliantName, "_bucket", metric,
354 model.BucketLabel, math.Inf(+1),
355 0, metric.Histogram.GetSampleCount(), true,
356 nil,
357 )
358 written += n
359 if err != nil {
360 return
361 }
362 }
363 n, err = writeOpenMetricsSample(
364 w, compliantName, "_sum", metric, "", 0,
365 metric.Histogram.GetSampleSum(), 0, false,
366 nil,
367 )
368 written += n
369 if err != nil {
370 return
371 }
372 n, err = writeOpenMetricsSample(
373 w, compliantName, "_count", metric, "", 0,
374 0, metric.Histogram.GetSampleCount(), true,
375 nil,
376 )
377 if toOM.withCreatedLines && metric.Histogram.CreatedTimestamp != nil {
378 createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "", metric, "", 0, metric.Histogram.GetCreatedTimestamp())
379 n += createdTsBytesWritten
380 }
381 default:
382 return written, fmt.Errorf(
383 "unexpected type in metric %s %s", compliantName, metric,
384 )
385 }
386 written += n
387 if err != nil {
388 return
389 }
390 }
391 return
392 }
393
394
395 func FinalizeOpenMetrics(w io.Writer) (written int, err error) {
396 return w.Write([]byte("# EOF\n"))
397 }
398
399
400
401
402
403
404
405 func writeOpenMetricsSample(
406 w enhancedWriter,
407 name, suffix string,
408 metric *dto.Metric,
409 additionalLabelName string, additionalLabelValue float64,
410 floatValue float64, intValue uint64, useIntValue bool,
411 exemplar *dto.Exemplar,
412 ) (int, error) {
413 written := 0
414 n, err := writeOpenMetricsNameAndLabelPairs(
415 w, name+suffix, metric.Label, additionalLabelName, additionalLabelValue,
416 )
417 written += n
418 if err != nil {
419 return written, err
420 }
421 err = w.WriteByte(' ')
422 written++
423 if err != nil {
424 return written, err
425 }
426 if useIntValue {
427 n, err = writeUint(w, intValue)
428 } else {
429 n, err = writeOpenMetricsFloat(w, floatValue)
430 }
431 written += n
432 if err != nil {
433 return written, err
434 }
435 if metric.TimestampMs != nil {
436 err = w.WriteByte(' ')
437 written++
438 if err != nil {
439 return written, err
440 }
441
442 n, err = writeOpenMetricsFloat(w, float64(*metric.TimestampMs)/1000)
443 written += n
444 if err != nil {
445 return written, err
446 }
447 }
448 if exemplar != nil && len(exemplar.Label) > 0 {
449 n, err = writeExemplar(w, exemplar)
450 written += n
451 if err != nil {
452 return written, err
453 }
454 }
455 err = w.WriteByte('\n')
456 written++
457 if err != nil {
458 return written, err
459 }
460 return written, nil
461 }
462
463
464
465 func writeOpenMetricsNameAndLabelPairs(
466 w enhancedWriter,
467 name string,
468 in []*dto.LabelPair,
469 additionalLabelName string, additionalLabelValue float64,
470 ) (int, error) {
471 var (
472 written int
473 separator byte = '{'
474 metricInsideBraces = false
475 )
476
477 if name != "" {
478
479
480 if !model.IsValidLegacyMetricName(model.LabelValue(name)) {
481 metricInsideBraces = true
482 err := w.WriteByte(separator)
483 written++
484 if err != nil {
485 return written, err
486 }
487 separator = ','
488 }
489
490 n, err := writeName(w, name)
491 written += n
492 if err != nil {
493 return written, err
494 }
495 }
496
497 if len(in) == 0 && additionalLabelName == "" {
498 if metricInsideBraces {
499 err := w.WriteByte('}')
500 written++
501 if err != nil {
502 return written, err
503 }
504 }
505 return written, nil
506 }
507
508 for _, lp := range in {
509 err := w.WriteByte(separator)
510 written++
511 if err != nil {
512 return written, err
513 }
514 n, err := writeName(w, lp.GetName())
515 written += n
516 if err != nil {
517 return written, err
518 }
519 n, err = w.WriteString(`="`)
520 written += n
521 if err != nil {
522 return written, err
523 }
524 n, err = writeEscapedString(w, lp.GetValue(), true)
525 written += n
526 if err != nil {
527 return written, err
528 }
529 err = w.WriteByte('"')
530 written++
531 if err != nil {
532 return written, err
533 }
534 separator = ','
535 }
536 if additionalLabelName != "" {
537 err := w.WriteByte(separator)
538 written++
539 if err != nil {
540 return written, err
541 }
542 n, err := w.WriteString(additionalLabelName)
543 written += n
544 if err != nil {
545 return written, err
546 }
547 n, err = w.WriteString(`="`)
548 written += n
549 if err != nil {
550 return written, err
551 }
552 n, err = writeOpenMetricsFloat(w, additionalLabelValue)
553 written += n
554 if err != nil {
555 return written, err
556 }
557 err = w.WriteByte('"')
558 written++
559 if err != nil {
560 return written, err
561 }
562 }
563 err := w.WriteByte('}')
564 written++
565 if err != nil {
566 return written, err
567 }
568 return written, nil
569 }
570
571
572
573
574
575
576
577 func writeOpenMetricsCreated(w enhancedWriter,
578 name, suffixToTrim string, metric *dto.Metric,
579 additionalLabelName string, additionalLabelValue float64,
580 createdTimestamp *timestamppb.Timestamp,
581 ) (int, error) {
582 written := 0
583 n, err := writeOpenMetricsNameAndLabelPairs(
584 w, strings.TrimSuffix(name, suffixToTrim)+"_created", metric.Label, additionalLabelName, additionalLabelValue,
585 )
586 written += n
587 if err != nil {
588 return written, err
589 }
590
591 err = w.WriteByte(' ')
592 written++
593 if err != nil {
594 return written, err
595 }
596
597
598
599
600 n, err = writeOpenMetricsFloat(w, float64(createdTimestamp.AsTime().UnixNano())/1e9)
601 written += n
602 if err != nil {
603 return written, err
604 }
605
606 err = w.WriteByte('\n')
607 written++
608 if err != nil {
609 return written, err
610 }
611 return written, nil
612 }
613
614
615
616 func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) {
617 written := 0
618 n, err := w.WriteString(" # ")
619 written += n
620 if err != nil {
621 return written, err
622 }
623 n, err = writeOpenMetricsNameAndLabelPairs(w, "", e.Label, "", 0)
624 written += n
625 if err != nil {
626 return written, err
627 }
628 err = w.WriteByte(' ')
629 written++
630 if err != nil {
631 return written, err
632 }
633 n, err = writeOpenMetricsFloat(w, e.GetValue())
634 written += n
635 if err != nil {
636 return written, err
637 }
638 if e.Timestamp != nil {
639 err = w.WriteByte(' ')
640 written++
641 if err != nil {
642 return written, err
643 }
644 err = (*e).Timestamp.CheckValid()
645 if err != nil {
646 return written, err
647 }
648 ts := (*e).Timestamp.AsTime()
649
650
651
652 n, err = writeOpenMetricsFloat(w, float64(ts.UnixNano())/1e9)
653 written += n
654 if err != nil {
655 return written, err
656 }
657 }
658 return written, nil
659 }
660
661
662
663 func writeOpenMetricsFloat(w enhancedWriter, f float64) (int, error) {
664 switch {
665 case f == 1:
666 return w.WriteString("1.0")
667 case f == 0:
668 return w.WriteString("0.0")
669 case f == -1:
670 return w.WriteString("-1.0")
671 case math.IsNaN(f):
672 return w.WriteString("NaN")
673 case math.IsInf(f, +1):
674 return w.WriteString("+Inf")
675 case math.IsInf(f, -1):
676 return w.WriteString("-Inf")
677 default:
678 bp := numBufPool.Get().(*[]byte)
679 *bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
680 if !bytes.ContainsAny(*bp, "e.") {
681 *bp = append(*bp, '.', '0')
682 }
683 written, err := w.Write(*bp)
684 numBufPool.Put(bp)
685 return written, err
686 }
687 }
688
689
690 func writeUint(w enhancedWriter, u uint64) (int, error) {
691 bp := numBufPool.Get().(*[]byte)
692 *bp = strconv.AppendUint((*bp)[:0], u, 10)
693 written, err := w.Write(*bp)
694 numBufPool.Put(bp)
695 return written, err
696 }
697
View as plain text