blob: 5ba503b06547dcf81dd71e259e32893289832a12 [file] [log] [blame]
khenaidooab1f7bd2019-11-14 14:00:27 -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 (
khenaidood948f772021-08-11 17:49:24 -040017 "bufio"
khenaidooab1f7bd2019-11-14 14:00:27 -050018 "fmt"
19 "io"
khenaidood948f772021-08-11 17:49:24 -040020 "io/ioutil"
khenaidooab1f7bd2019-11-14 14:00:27 -050021 "math"
22 "strconv"
23 "strings"
24 "sync"
25
26 "github.com/prometheus/common/model"
27
28 dto "github.com/prometheus/client_model/go"
29)
30
khenaidood948f772021-08-11 17:49:24 -040031// enhancedWriter has all the enhanced write functions needed here. bufio.Writer
khenaidooab1f7bd2019-11-14 14:00:27 -050032// implements it.
33type enhancedWriter interface {
34 io.Writer
35 WriteRune(r rune) (n int, err error)
36 WriteString(s string) (n int, err error)
37 WriteByte(c byte) error
38}
39
40const (
khenaidooab1f7bd2019-11-14 14:00:27 -050041 initialNumBufSize = 24
42)
43
44var (
45 bufPool = sync.Pool{
46 New: func() interface{} {
khenaidood948f772021-08-11 17:49:24 -040047 return bufio.NewWriter(ioutil.Discard)
khenaidooab1f7bd2019-11-14 14:00:27 -050048 },
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
khenaidood948f772021-08-11 17:49:24 -040078 // bufio.Writer from the sync.Pool.
khenaidooab1f7bd2019-11-14 14:00:27 -050079 w, ok := out.(enhancedWriter)
80 if !ok {
khenaidood948f772021-08-11 17:49:24 -040081 b := bufPool.Get().(*bufio.Writer)
82 b.Reset(out)
khenaidooab1f7bd2019-11-14 14:00:27 -050083 w = b
84 defer func() {
khenaidood948f772021-08-11 17:49:24 -040085 bErr := b.Flush()
khenaidooab1f7bd2019-11-14 14:00:27 -050086 if err == nil {
87 err = bErr
88 }
89 bufPool.Put(b)
90 }()
91 }
92
93 var n int
94
95 // Comments, first HELP, then TYPE.
96 if in.Help != nil {
97 n, err = w.WriteString("# HELP ")
98 written += n
99 if err != nil {
100 return
101 }
102 n, err = w.WriteString(name)
103 written += n
104 if err != nil {
105 return
106 }
107 err = w.WriteByte(' ')
108 written++
109 if err != nil {
110 return
111 }
112 n, err = writeEscapedString(w, *in.Help, false)
113 written += n
114 if err != nil {
115 return
116 }
117 err = w.WriteByte('\n')
118 written++
119 if err != nil {
120 return
121 }
122 }
123 n, err = w.WriteString("# TYPE ")
124 written += n
125 if err != nil {
126 return
127 }
128 n, err = w.WriteString(name)
129 written += n
130 if err != nil {
131 return
132 }
133 metricType := in.GetType()
134 switch metricType {
135 case dto.MetricType_COUNTER:
136 n, err = w.WriteString(" counter\n")
137 case dto.MetricType_GAUGE:
138 n, err = w.WriteString(" gauge\n")
139 case dto.MetricType_SUMMARY:
140 n, err = w.WriteString(" summary\n")
141 case dto.MetricType_UNTYPED:
142 n, err = w.WriteString(" untyped\n")
143 case dto.MetricType_HISTOGRAM:
144 n, err = w.WriteString(" histogram\n")
145 default:
146 return written, fmt.Errorf("unknown metric type %s", metricType.String())
147 }
148 written += n
149 if err != nil {
150 return
151 }
152
153 // Finally the samples, one line for each.
154 for _, metric := range in.Metric {
155 switch metricType {
156 case dto.MetricType_COUNTER:
157 if metric.Counter == nil {
158 return written, fmt.Errorf(
159 "expected counter in metric %s %s", name, metric,
160 )
161 }
162 n, err = writeSample(
163 w, name, "", metric, "", 0,
164 metric.Counter.GetValue(),
165 )
166 case dto.MetricType_GAUGE:
167 if metric.Gauge == nil {
168 return written, fmt.Errorf(
169 "expected gauge in metric %s %s", name, metric,
170 )
171 }
172 n, err = writeSample(
173 w, name, "", metric, "", 0,
174 metric.Gauge.GetValue(),
175 )
176 case dto.MetricType_UNTYPED:
177 if metric.Untyped == nil {
178 return written, fmt.Errorf(
179 "expected untyped in metric %s %s", name, metric,
180 )
181 }
182 n, err = writeSample(
183 w, name, "", metric, "", 0,
184 metric.Untyped.GetValue(),
185 )
186 case dto.MetricType_SUMMARY:
187 if metric.Summary == nil {
188 return written, fmt.Errorf(
189 "expected summary in metric %s %s", name, metric,
190 )
191 }
192 for _, q := range metric.Summary.Quantile {
193 n, err = writeSample(
194 w, name, "", metric,
195 model.QuantileLabel, q.GetQuantile(),
196 q.GetValue(),
197 )
198 written += n
199 if err != nil {
200 return
201 }
202 }
203 n, err = writeSample(
204 w, name, "_sum", metric, "", 0,
205 metric.Summary.GetSampleSum(),
206 )
207 written += n
208 if err != nil {
209 return
210 }
211 n, err = writeSample(
212 w, name, "_count", metric, "", 0,
213 float64(metric.Summary.GetSampleCount()),
214 )
215 case dto.MetricType_HISTOGRAM:
216 if metric.Histogram == nil {
217 return written, fmt.Errorf(
218 "expected histogram in metric %s %s", name, metric,
219 )
220 }
221 infSeen := false
222 for _, b := range metric.Histogram.Bucket {
223 n, err = writeSample(
224 w, name, "_bucket", metric,
225 model.BucketLabel, b.GetUpperBound(),
226 float64(b.GetCumulativeCount()),
227 )
228 written += n
229 if err != nil {
230 return
231 }
232 if math.IsInf(b.GetUpperBound(), +1) {
233 infSeen = true
234 }
235 }
236 if !infSeen {
237 n, err = writeSample(
238 w, name, "_bucket", metric,
239 model.BucketLabel, math.Inf(+1),
240 float64(metric.Histogram.GetSampleCount()),
241 )
242 written += n
243 if err != nil {
244 return
245 }
246 }
247 n, err = writeSample(
248 w, name, "_sum", metric, "", 0,
249 metric.Histogram.GetSampleSum(),
250 )
251 written += n
252 if err != nil {
253 return
254 }
255 n, err = writeSample(
256 w, name, "_count", metric, "", 0,
257 float64(metric.Histogram.GetSampleCount()),
258 )
259 default:
260 return written, fmt.Errorf(
261 "unexpected type in metric %s %s", name, metric,
262 )
263 }
264 written += n
265 if err != nil {
266 return
267 }
268 }
269 return
270}
271
272// writeSample writes a single sample in text format to w, given the metric
273// name, the metric proto message itself, optionally an additional label name
274// with a float64 value (use empty string as label name if not required), and
275// the value. The function returns the number of bytes written and any error
276// encountered.
277func writeSample(
278 w enhancedWriter,
279 name, suffix string,
280 metric *dto.Metric,
281 additionalLabelName string, additionalLabelValue float64,
282 value float64,
283) (int, error) {
284 var written int
285 n, err := w.WriteString(name)
286 written += n
287 if err != nil {
288 return written, err
289 }
290 if suffix != "" {
291 n, err = w.WriteString(suffix)
292 written += n
293 if err != nil {
294 return written, err
295 }
296 }
297 n, err = writeLabelPairs(
298 w, metric.Label, additionalLabelName, additionalLabelValue,
299 )
300 written += n
301 if err != nil {
302 return written, err
303 }
304 err = w.WriteByte(' ')
305 written++
306 if err != nil {
307 return written, err
308 }
309 n, err = writeFloat(w, value)
310 written += n
311 if err != nil {
312 return written, err
313 }
314 if metric.TimestampMs != nil {
315 err = w.WriteByte(' ')
316 written++
317 if err != nil {
318 return written, err
319 }
320 n, err = writeInt(w, *metric.TimestampMs)
321 written += n
322 if err != nil {
323 return written, err
324 }
325 }
326 err = w.WriteByte('\n')
327 written++
328 if err != nil {
329 return written, err
330 }
331 return written, nil
332}
333
334// writeLabelPairs converts a slice of LabelPair proto messages plus the
335// explicitly given additional label pair into text formatted as required by the
336// text format and writes it to 'w'. An empty slice in combination with an empty
337// string 'additionalLabelName' results in nothing being written. Otherwise, the
338// label pairs are written, escaped as required by the text format, and enclosed
339// in '{...}'. The function returns the number of bytes written and any error
340// encountered.
341func writeLabelPairs(
342 w enhancedWriter,
343 in []*dto.LabelPair,
344 additionalLabelName string, additionalLabelValue float64,
345) (int, error) {
346 if len(in) == 0 && additionalLabelName == "" {
347 return 0, nil
348 }
349 var (
350 written int
351 separator byte = '{'
352 )
353 for _, lp := range in {
354 err := w.WriteByte(separator)
355 written++
356 if err != nil {
357 return written, err
358 }
359 n, err := w.WriteString(lp.GetName())
360 written += n
361 if err != nil {
362 return written, err
363 }
364 n, err = w.WriteString(`="`)
365 written += n
366 if err != nil {
367 return written, err
368 }
369 n, err = writeEscapedString(w, lp.GetValue(), true)
370 written += n
371 if err != nil {
372 return written, err
373 }
374 err = w.WriteByte('"')
375 written++
376 if err != nil {
377 return written, err
378 }
379 separator = ','
380 }
381 if additionalLabelName != "" {
382 err := w.WriteByte(separator)
383 written++
384 if err != nil {
385 return written, err
386 }
387 n, err := w.WriteString(additionalLabelName)
388 written += n
389 if err != nil {
390 return written, err
391 }
392 n, err = w.WriteString(`="`)
393 written += n
394 if err != nil {
395 return written, err
396 }
397 n, err = writeFloat(w, additionalLabelValue)
398 written += n
399 if err != nil {
400 return written, err
401 }
402 err = w.WriteByte('"')
403 written++
404 if err != nil {
405 return written, err
406 }
407 }
408 err := w.WriteByte('}')
409 written++
410 if err != nil {
411 return written, err
412 }
413 return written, nil
414}
415
416// writeEscapedString replaces '\' by '\\', new line character by '\n', and - if
417// includeDoubleQuote is true - '"' by '\"'.
418var (
419 escaper = strings.NewReplacer("\\", `\\`, "\n", `\n`)
420 quotedEscaper = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
421)
422
423func writeEscapedString(w enhancedWriter, v string, includeDoubleQuote bool) (int, error) {
424 if includeDoubleQuote {
425 return quotedEscaper.WriteString(w, v)
khenaidooab1f7bd2019-11-14 14:00:27 -0500426 }
khenaidood948f772021-08-11 17:49:24 -0400427 return escaper.WriteString(w, v)
khenaidooab1f7bd2019-11-14 14:00:27 -0500428}
429
430// writeFloat is equivalent to fmt.Fprint with a float64 argument but hardcodes
431// a few common cases for increased efficiency. For non-hardcoded cases, it uses
432// strconv.AppendFloat to avoid allocations, similar to writeInt.
433func writeFloat(w enhancedWriter, f float64) (int, error) {
434 switch {
435 case f == 1:
436 return 1, w.WriteByte('1')
437 case f == 0:
438 return 1, w.WriteByte('0')
439 case f == -1:
440 return w.WriteString("-1")
441 case math.IsNaN(f):
442 return w.WriteString("NaN")
443 case math.IsInf(f, +1):
444 return w.WriteString("+Inf")
445 case math.IsInf(f, -1):
446 return w.WriteString("-Inf")
447 default:
448 bp := numBufPool.Get().(*[]byte)
449 *bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
450 written, err := w.Write(*bp)
451 numBufPool.Put(bp)
452 return written, err
453 }
454}
455
456// writeInt is equivalent to fmt.Fprint with an int64 argument but uses
457// strconv.AppendInt with a byte slice taken from a sync.Pool to avoid
458// allocations.
459func writeInt(w enhancedWriter, i int64) (int, error) {
460 bp := numBufPool.Get().(*[]byte)
461 *bp = strconv.AppendInt((*bp)[:0], i, 10)
462 written, err := w.Write(*bp)
463 numBufPool.Put(bp)
464 return written, err
465}