1
2
3
4
5
6
7
8
9
10
11
12
13
14 package expfmt
15
16 import (
17 "bufio"
18 "fmt"
19 "io"
20 "math"
21 "strconv"
22 "strings"
23 "sync"
24
25 "github.com/prometheus/common/model"
26
27 dto "github.com/prometheus/client_model/go"
28 )
29
30
31
32 type enhancedWriter interface {
33 io.Writer
34 WriteRune(r rune) (n int, err error)
35 WriteString(s string) (n int, err error)
36 WriteByte(c byte) error
37 }
38
39 const (
40 initialNumBufSize = 24
41 )
42
43 var (
44 bufPool = sync.Pool{
45 New: func() interface{} {
46 return bufio.NewWriter(io.Discard)
47 },
48 }
49 numBufPool = sync.Pool{
50 New: func() interface{} {
51 b := make([]byte, 0, initialNumBufSize)
52 return &b
53 },
54 }
55 )
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err error) {
79
80 if len(in.Metric) == 0 {
81 return 0, fmt.Errorf("MetricFamily has no metrics: %s", in)
82 }
83 name := in.GetName()
84 if name == "" {
85 return 0, fmt.Errorf("MetricFamily has no name: %s", in)
86 }
87
88
89
90 w, ok := out.(enhancedWriter)
91 if !ok {
92 b := bufPool.Get().(*bufio.Writer)
93 b.Reset(out)
94 w = b
95 defer func() {
96 bErr := b.Flush()
97 if err == nil {
98 err = bErr
99 }
100 bufPool.Put(b)
101 }()
102 }
103
104 var n int
105
106
107 if in.Help != nil {
108 n, err = w.WriteString("# HELP ")
109 written += n
110 if err != nil {
111 return
112 }
113 n, err = writeName(w, name)
114 written += n
115 if err != nil {
116 return
117 }
118 err = w.WriteByte(' ')
119 written++
120 if err != nil {
121 return
122 }
123 n, err = writeEscapedString(w, *in.Help, false)
124 written += n
125 if err != nil {
126 return
127 }
128 err = w.WriteByte('\n')
129 written++
130 if err != nil {
131 return
132 }
133 }
134 n, err = w.WriteString("# TYPE ")
135 written += n
136 if err != nil {
137 return
138 }
139 n, err = writeName(w, name)
140 written += n
141 if err != nil {
142 return
143 }
144 metricType := in.GetType()
145 switch metricType {
146 case dto.MetricType_COUNTER:
147 n, err = w.WriteString(" counter\n")
148 case dto.MetricType_GAUGE:
149 n, err = w.WriteString(" gauge\n")
150 case dto.MetricType_SUMMARY:
151 n, err = w.WriteString(" summary\n")
152 case dto.MetricType_UNTYPED:
153 n, err = w.WriteString(" untyped\n")
154 case dto.MetricType_HISTOGRAM:
155 n, err = w.WriteString(" histogram\n")
156 default:
157 return written, fmt.Errorf("unknown metric type %s", metricType.String())
158 }
159 written += n
160 if err != nil {
161 return
162 }
163
164
165 for _, metric := range in.Metric {
166 switch metricType {
167 case dto.MetricType_COUNTER:
168 if metric.Counter == nil {
169 return written, fmt.Errorf(
170 "expected counter in metric %s %s", name, metric,
171 )
172 }
173 n, err = writeSample(
174 w, name, "", metric, "", 0,
175 metric.Counter.GetValue(),
176 )
177 case dto.MetricType_GAUGE:
178 if metric.Gauge == nil {
179 return written, fmt.Errorf(
180 "expected gauge in metric %s %s", name, metric,
181 )
182 }
183 n, err = writeSample(
184 w, name, "", metric, "", 0,
185 metric.Gauge.GetValue(),
186 )
187 case dto.MetricType_UNTYPED:
188 if metric.Untyped == nil {
189 return written, fmt.Errorf(
190 "expected untyped in metric %s %s", name, metric,
191 )
192 }
193 n, err = writeSample(
194 w, name, "", metric, "", 0,
195 metric.Untyped.GetValue(),
196 )
197 case dto.MetricType_SUMMARY:
198 if metric.Summary == nil {
199 return written, fmt.Errorf(
200 "expected summary in metric %s %s", name, metric,
201 )
202 }
203 for _, q := range metric.Summary.Quantile {
204 n, err = writeSample(
205 w, name, "", metric,
206 model.QuantileLabel, q.GetQuantile(),
207 q.GetValue(),
208 )
209 written += n
210 if err != nil {
211 return
212 }
213 }
214 n, err = writeSample(
215 w, name, "_sum", metric, "", 0,
216 metric.Summary.GetSampleSum(),
217 )
218 written += n
219 if err != nil {
220 return
221 }
222 n, err = writeSample(
223 w, name, "_count", metric, "", 0,
224 float64(metric.Summary.GetSampleCount()),
225 )
226 case dto.MetricType_HISTOGRAM:
227 if metric.Histogram == nil {
228 return written, fmt.Errorf(
229 "expected histogram in metric %s %s", name, metric,
230 )
231 }
232 infSeen := false
233 for _, b := range metric.Histogram.Bucket {
234 n, err = writeSample(
235 w, name, "_bucket", metric,
236 model.BucketLabel, b.GetUpperBound(),
237 float64(b.GetCumulativeCount()),
238 )
239 written += n
240 if err != nil {
241 return
242 }
243 if math.IsInf(b.GetUpperBound(), +1) {
244 infSeen = true
245 }
246 }
247 if !infSeen {
248 n, err = writeSample(
249 w, name, "_bucket", metric,
250 model.BucketLabel, math.Inf(+1),
251 float64(metric.Histogram.GetSampleCount()),
252 )
253 written += n
254 if err != nil {
255 return
256 }
257 }
258 n, err = writeSample(
259 w, name, "_sum", metric, "", 0,
260 metric.Histogram.GetSampleSum(),
261 )
262 written += n
263 if err != nil {
264 return
265 }
266 n, err = writeSample(
267 w, name, "_count", metric, "", 0,
268 float64(metric.Histogram.GetSampleCount()),
269 )
270 default:
271 return written, fmt.Errorf(
272 "unexpected type in metric %s %s", name, metric,
273 )
274 }
275 written += n
276 if err != nil {
277 return
278 }
279 }
280 return
281 }
282
283
284
285
286
287
288 func writeSample(
289 w enhancedWriter,
290 name, suffix string,
291 metric *dto.Metric,
292 additionalLabelName string, additionalLabelValue float64,
293 value float64,
294 ) (int, error) {
295 written := 0
296 n, err := writeNameAndLabelPairs(
297 w, name+suffix, metric.Label, additionalLabelName, additionalLabelValue,
298 )
299 written += n
300 if err != nil {
301 return written, err
302 }
303 err = w.WriteByte(' ')
304 written++
305 if err != nil {
306 return written, err
307 }
308 n, err = writeFloat(w, value)
309 written += n
310 if err != nil {
311 return written, err
312 }
313 if metric.TimestampMs != nil {
314 err = w.WriteByte(' ')
315 written++
316 if err != nil {
317 return written, err
318 }
319 n, err = writeInt(w, *metric.TimestampMs)
320 written += n
321 if err != nil {
322 return written, err
323 }
324 }
325 err = w.WriteByte('\n')
326 written++
327 if err != nil {
328 return written, err
329 }
330 return written, nil
331 }
332
333
334
335
336
337
338
339
340
341
342 func writeNameAndLabelPairs(
343 w enhancedWriter,
344 name string,
345 in []*dto.LabelPair,
346 additionalLabelName string, additionalLabelValue float64,
347 ) (int, error) {
348 var (
349 written int
350 separator byte = '{'
351 metricInsideBraces = false
352 )
353
354 if name != "" {
355
356
357 if !model.IsValidLegacyMetricName(model.LabelValue(name)) {
358 metricInsideBraces = true
359 err := w.WriteByte(separator)
360 written++
361 if err != nil {
362 return written, err
363 }
364 separator = ','
365 }
366 n, err := writeName(w, name)
367 written += n
368 if err != nil {
369 return written, err
370 }
371 }
372
373 if len(in) == 0 && additionalLabelName == "" {
374 if metricInsideBraces {
375 err := w.WriteByte('}')
376 written++
377 if err != nil {
378 return written, err
379 }
380 }
381 return written, nil
382 }
383
384 for _, lp := range in {
385 err := w.WriteByte(separator)
386 written++
387 if err != nil {
388 return written, err
389 }
390 n, err := writeName(w, lp.GetName())
391 written += n
392 if err != nil {
393 return written, err
394 }
395 n, err = w.WriteString(`="`)
396 written += n
397 if err != nil {
398 return written, err
399 }
400 n, err = writeEscapedString(w, lp.GetValue(), true)
401 written += n
402 if err != nil {
403 return written, err
404 }
405 err = w.WriteByte('"')
406 written++
407 if err != nil {
408 return written, err
409 }
410 separator = ','
411 }
412 if additionalLabelName != "" {
413 err := w.WriteByte(separator)
414 written++
415 if err != nil {
416 return written, err
417 }
418 n, err := w.WriteString(additionalLabelName)
419 written += n
420 if err != nil {
421 return written, err
422 }
423 n, err = w.WriteString(`="`)
424 written += n
425 if err != nil {
426 return written, err
427 }
428 n, err = writeFloat(w, additionalLabelValue)
429 written += n
430 if err != nil {
431 return written, err
432 }
433 err = w.WriteByte('"')
434 written++
435 if err != nil {
436 return written, err
437 }
438 }
439 err := w.WriteByte('}')
440 written++
441 if err != nil {
442 return written, err
443 }
444 return written, nil
445 }
446
447
448
449 var (
450 escaper = strings.NewReplacer("\\", `\\`, "\n", `\n`)
451 quotedEscaper = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
452 )
453
454 func writeEscapedString(w enhancedWriter, v string, includeDoubleQuote bool) (int, error) {
455 if includeDoubleQuote {
456 return quotedEscaper.WriteString(w, v)
457 }
458 return escaper.WriteString(w, v)
459 }
460
461
462
463
464 func writeFloat(w enhancedWriter, f float64) (int, error) {
465 switch {
466 case f == 1:
467 return 1, w.WriteByte('1')
468 case f == 0:
469 return 1, w.WriteByte('0')
470 case f == -1:
471 return w.WriteString("-1")
472 case math.IsNaN(f):
473 return w.WriteString("NaN")
474 case math.IsInf(f, +1):
475 return w.WriteString("+Inf")
476 case math.IsInf(f, -1):
477 return w.WriteString("-Inf")
478 default:
479 bp := numBufPool.Get().(*[]byte)
480 *bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
481 written, err := w.Write(*bp)
482 numBufPool.Put(bp)
483 return written, err
484 }
485 }
486
487
488
489
490 func writeInt(w enhancedWriter, i int64) (int, error) {
491 bp := numBufPool.Get().(*[]byte)
492 *bp = strconv.AppendInt((*bp)[:0], i, 10)
493 written, err := w.Write(*bp)
494 numBufPool.Put(bp)
495 return written, err
496 }
497
498
499
500 func writeName(w enhancedWriter, name string) (int, error) {
501 if model.IsValidLegacyMetricName(model.LabelValue(name)) {
502 return w.WriteString(name)
503 }
504 var written int
505 var err error
506 err = w.WriteByte('"')
507 written++
508 if err != nil {
509 return written, err
510 }
511 var n int
512 n, err = writeEscapedString(w, name, true)
513 written += n
514 if err != nil {
515 return written, err
516 }
517 err = w.WriteByte('"')
518 written++
519 return written, err
520 }
521
View as plain text