1// 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	"bufio"
18	"fmt"
19	"io"
20	"io/ioutil"
21	"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
31// enhancedWriter has all the enhanced write functions needed here. bufio.Writer
32// 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 (
41	initialNumBufSize = 24
42)
43
44var (
45	bufPool = sync.Pool{
46		New: func() interface{} {
47			return bufio.NewWriter(ioutil.Discard)
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	// bufio.Writer from the sync.Pool.
79	w, ok := out.(enhancedWriter)
80	if !ok {
81		b := bufPool.Get().(*bufio.Writer)
82		b.Reset(out)
83		w = b
84		defer func() {
85			bErr := b.Flush()
86			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)
426	}
427	return escaper.WriteString(w, v)
428}
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}
466