blob: 8e473d0fe92d2e0cde20999cc3c2080226ac1b63 [file] [log] [blame]
khenaidoo59ce9dd2019-11-11 13:05:32 -05001// Copyright 2014 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 "bytes"
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// enhancedWriter has all the enhanced write functions needed here. bytes.Buffer
31// implements it.
32type 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
39const (
40 initialBufSize = 512
41 initialNumBufSize = 24
42)
43
44var (
45 bufPool = sync.Pool{
46 New: func() interface{} {
47 return bytes.NewBuffer(make([]byte, 0, initialBufSize))
48 },
49 }
50 numBufPool = sync.Pool{
51 New: func() interface{} {
52 b := make([]byte, 0, initialNumBufSize)
53 return &b
54 },
55 }
56)
57
58// MetricFamilyToText converts a MetricFamily proto message into text format and
59// writes the resulting lines to 'out'. It returns the number of bytes written
60// and any error encountered. The output will have the same order as the input,
61// no further sorting is performed. Furthermore, this function assumes the input
62// is already sanitized and does not perform any sanity checks. If the input
63// contains duplicate metrics or invalid metric or label names, the conversion
64// will result in invalid text format output.
65//
66// This method fulfills the type 'prometheus.encoder'.
67func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err error) {
68 // Fail-fast checks.
69 if len(in.Metric) == 0 {
70 return 0, fmt.Errorf("MetricFamily has no metrics: %s", in)
71 }
72 name := in.GetName()
73 if name == "" {
74 return 0, fmt.Errorf("MetricFamily has no name: %s", in)
75 }
76
77 // Try the interface upgrade. If it doesn't work, we'll use a
78 // bytes.Buffer from the sync.Pool and write out its content to out in a
79 // single go in the end.
80 w, ok := out.(enhancedWriter)
81 if !ok {
82 b := bufPool.Get().(*bytes.Buffer)
83 b.Reset()
84 w = b
85 defer func() {
86 bWritten, bErr := out.Write(b.Bytes())
87 written = bWritten
88 if err == nil {
89 err = bErr
90 }
91 bufPool.Put(b)
92 }()
93 }
94
95 var n int
96
97 // Comments, first HELP, then TYPE.
98 if in.Help != nil {
99 n, err = w.WriteString("# HELP ")
100 written += n
101 if err != nil {
102 return
103 }
104 n, err = w.WriteString(name)
105 written += n
106 if err != nil {
107 return
108 }
109 err = w.WriteByte(' ')
110 written++
111 if err != nil {
112 return
113 }
114 n, err = writeEscapedString(w, *in.Help, false)
115 written += n
116 if err != nil {
117 return
118 }
119 err = w.WriteByte('\n')
120 written++
121 if err != nil {
122 return
123 }
124 }
125 n, err = w.WriteString("# TYPE ")
126 written += n
127 if err != nil {
128 return
129 }
130 n, err = w.WriteString(name)
131 written += n
132 if err != nil {
133 return
134 }
135 metricType := in.GetType()
136 switch metricType {
137 case dto.MetricType_COUNTER:
138 n, err = w.WriteString(" counter\n")
139 case dto.MetricType_GAUGE:
140 n, err = w.WriteString(" gauge\n")
141 case dto.MetricType_SUMMARY:
142 n, err = w.WriteString(" summary\n")
143 case dto.MetricType_UNTYPED:
144 n, err = w.WriteString(" untyped\n")
145 case dto.MetricType_HISTOGRAM:
146 n, err = w.WriteString(" histogram\n")
147 default:
148 return written, fmt.Errorf("unknown metric type %s", metricType.String())
149 }
150 written += n
151 if err != nil {
152 return
153 }
154
155 // Finally the samples, one line for each.
156 for _, metric := range in.Metric {
157 switch metricType {
158 case dto.MetricType_COUNTER:
159 if metric.Counter == nil {
160 return written, fmt.Errorf(
161 "expected counter in metric %s %s", name, metric,
162 )
163 }
164 n, err = writeSample(
165 w, name, "", metric, "", 0,
166 metric.Counter.GetValue(),
167 )
168 case dto.MetricType_GAUGE:
169 if metric.Gauge == nil {
170 return written, fmt.Errorf(
171 "expected gauge in metric %s %s", name, metric,
172 )
173 }
174 n, err = writeSample(
175 w, name, "", metric, "", 0,
176 metric.Gauge.GetValue(),
177 )
178 case dto.MetricType_UNTYPED:
179 if metric.Untyped == nil {
180 return written, fmt.Errorf(
181 "expected untyped in metric %s %s", name, metric,
182 )
183 }
184 n, err = writeSample(
185 w, name, "", metric, "", 0,
186 metric.Untyped.GetValue(),
187 )
188 case dto.MetricType_SUMMARY:
189 if metric.Summary == nil {
190 return written, fmt.Errorf(
191 "expected summary in metric %s %s", name, metric,
192 )
193 }
194 for _, q := range metric.Summary.Quantile {
195 n, err = writeSample(
196 w, name, "", metric,
197 model.QuantileLabel, q.GetQuantile(),
198 q.GetValue(),
199 )
200 written += n
201 if err != nil {
202 return
203 }
204 }
205 n, err = writeSample(
206 w, name, "_sum", metric, "", 0,
207 metric.Summary.GetSampleSum(),
208 )
209 written += n
210 if err != nil {
211 return
212 }
213 n, err = writeSample(
214 w, name, "_count", metric, "", 0,
215 float64(metric.Summary.GetSampleCount()),
216 )
217 case dto.MetricType_HISTOGRAM:
218 if metric.Histogram == nil {
219 return written, fmt.Errorf(
220 "expected histogram in metric %s %s", name, metric,
221 )
222 }
223 infSeen := false
224 for _, b := range metric.Histogram.Bucket {
225 n, err = writeSample(
226 w, name, "_bucket", metric,
227 model.BucketLabel, b.GetUpperBound(),
228 float64(b.GetCumulativeCount()),
229 )
230 written += n
231 if err != nil {
232 return
233 }
234 if math.IsInf(b.GetUpperBound(), +1) {
235 infSeen = true
236 }
237 }
238 if !infSeen {
239 n, err = writeSample(
240 w, name, "_bucket", metric,
241 model.BucketLabel, math.Inf(+1),
242 float64(metric.Histogram.GetSampleCount()),
243 )
244 written += n
245 if err != nil {
246 return
247 }
248 }
249 n, err = writeSample(
250 w, name, "_sum", metric, "", 0,
251 metric.Histogram.GetSampleSum(),
252 )
253 written += n
254 if err != nil {
255 return
256 }
257 n, err = writeSample(
258 w, name, "_count", metric, "", 0,
259 float64(metric.Histogram.GetSampleCount()),
260 )
261 default:
262 return written, fmt.Errorf(
263 "unexpected type in metric %s %s", name, metric,
264 )
265 }
266 written += n
267 if err != nil {
268 return
269 }
270 }
271 return
272}
273
274// writeSample writes a single sample in text format to w, given the metric
275// name, the metric proto message itself, optionally an additional label name
276// with a float64 value (use empty string as label name if not required), and
277// the value. The function returns the number of bytes written and any error
278// encountered.
279func writeSample(
280 w enhancedWriter,
281 name, suffix string,
282 metric *dto.Metric,
283 additionalLabelName string, additionalLabelValue float64,
284 value float64,
285) (int, error) {
286 var written int
287 n, err := w.WriteString(name)
288 written += n
289 if err != nil {
290 return written, err
291 }
292 if suffix != "" {
293 n, err = w.WriteString(suffix)
294 written += n
295 if err != nil {
296 return written, err
297 }
298 }
299 n, err = writeLabelPairs(
300 w, metric.Label, additionalLabelName, additionalLabelValue,
301 )
302 written += n
303 if err != nil {
304 return written, err
305 }
306 err = w.WriteByte(' ')
307 written++
308 if err != nil {
309 return written, err
310 }
311 n, err = writeFloat(w, value)
312 written += n
313 if err != nil {
314 return written, err
315 }
316 if metric.TimestampMs != nil {
317 err = w.WriteByte(' ')
318 written++
319 if err != nil {
320 return written, err
321 }
322 n, err = writeInt(w, *metric.TimestampMs)
323 written += n
324 if err != nil {
325 return written, err
326 }
327 }
328 err = w.WriteByte('\n')
329 written++
330 if err != nil {
331 return written, err
332 }
333 return written, nil
334}
335
336// writeLabelPairs converts a slice of LabelPair proto messages plus the
337// explicitly given additional label pair into text formatted as required by the
338// text format and writes it to 'w'. An empty slice in combination with an empty
339// string 'additionalLabelName' results in nothing being written. Otherwise, the
340// label pairs are written, escaped as required by the text format, and enclosed
341// in '{...}'. The function returns the number of bytes written and any error
342// encountered.
343func writeLabelPairs(
344 w enhancedWriter,
345 in []*dto.LabelPair,
346 additionalLabelName string, additionalLabelValue float64,
347) (int, error) {
348 if len(in) == 0 && additionalLabelName == "" {
349 return 0, nil
350 }
351 var (
352 written int
353 separator byte = '{'
354 )
355 for _, lp := range in {
356 err := w.WriteByte(separator)
357 written++
358 if err != nil {
359 return written, err
360 }
361 n, err := w.WriteString(lp.GetName())
362 written += n
363 if err != nil {
364 return written, err
365 }
366 n, err = w.WriteString(`="`)
367 written += n
368 if err != nil {
369 return written, err
370 }
371 n, err = writeEscapedString(w, lp.GetValue(), true)
372 written += n
373 if err != nil {
374 return written, err
375 }
376 err = w.WriteByte('"')
377 written++
378 if err != nil {
379 return written, err
380 }
381 separator = ','
382 }
383 if additionalLabelName != "" {
384 err := w.WriteByte(separator)
385 written++
386 if err != nil {
387 return written, err
388 }
389 n, err := w.WriteString(additionalLabelName)
390 written += n
391 if err != nil {
392 return written, err
393 }
394 n, err = w.WriteString(`="`)
395 written += n
396 if err != nil {
397 return written, err
398 }
399 n, err = writeFloat(w, additionalLabelValue)
400 written += n
401 if err != nil {
402 return written, err
403 }
404 err = w.WriteByte('"')
405 written++
406 if err != nil {
407 return written, err
408 }
409 }
410 err := w.WriteByte('}')
411 written++
412 if err != nil {
413 return written, err
414 }
415 return written, nil
416}
417
418// writeEscapedString replaces '\' by '\\', new line character by '\n', and - if
419// includeDoubleQuote is true - '"' by '\"'.
420var (
421 escaper = strings.NewReplacer("\\", `\\`, "\n", `\n`)
422 quotedEscaper = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
423)
424
425func writeEscapedString(w enhancedWriter, v string, includeDoubleQuote bool) (int, error) {
426 if includeDoubleQuote {
427 return quotedEscaper.WriteString(w, v)
428 } else {
429 return escaper.WriteString(w, v)
430 }
431}
432
433// writeFloat is equivalent to fmt.Fprint with a float64 argument but hardcodes
434// a few common cases for increased efficiency. For non-hardcoded cases, it uses
435// strconv.AppendFloat to avoid allocations, similar to writeInt.
436func writeFloat(w enhancedWriter, f float64) (int, error) {
437 switch {
438 case f == 1:
439 return 1, w.WriteByte('1')
440 case f == 0:
441 return 1, w.WriteByte('0')
442 case f == -1:
443 return w.WriteString("-1")
444 case math.IsNaN(f):
445 return w.WriteString("NaN")
446 case math.IsInf(f, +1):
447 return w.WriteString("+Inf")
448 case math.IsInf(f, -1):
449 return w.WriteString("-Inf")
450 default:
451 bp := numBufPool.Get().(*[]byte)
452 *bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
453 written, err := w.Write(*bp)
454 numBufPool.Put(bp)
455 return written, err
456 }
457}
458
459// writeInt is equivalent to fmt.Fprint with an int64 argument but uses
460// strconv.AppendInt with a byte slice taken from a sync.Pool to avoid
461// allocations.
462func writeInt(w enhancedWriter, i int64) (int, error) {
463 bp := numBufPool.Get().(*[]byte)
464 *bp = strconv.AppendInt((*bp)[:0], i, 10)
465 written, err := w.Write(*bp)
466 numBufPool.Put(bp)
467 return written, err
468}