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
6 // http://www.apache.org/licenses/LICENSE-2.0
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.
25 "github.com/prometheus/common/model"
27 dto "github.com/prometheus/client_model/go"
30 // enhancedWriter has all the enhanced write functions needed here. bytes.Buffer
32 type enhancedWriter interface {
34 WriteRune(r rune) (n int, err error)
35 WriteString(s string) (n int, err error)
36 WriteByte(c byte) error
41 initialNumBufSize = 24
46 New: func() interface{} {
47 return bytes.NewBuffer(make([]byte, 0, initialBufSize))
50 numBufPool = sync.Pool{
51 New: func() interface{} {
52 b := make([]byte, 0, initialNumBufSize)
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.
66 // This method fulfills the type 'prometheus.encoder'.
67 func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err error) {
69 if len(in.Metric) == 0 {
70 return 0, fmt.Errorf("MetricFamily has no metrics: %s", in)
74 return 0, fmt.Errorf("MetricFamily has no name: %s", in)
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)
82 b := bufPool.Get().(*bytes.Buffer)
86 bWritten, bErr := out.Write(b.Bytes())
97 // Comments, first HELP, then TYPE.
99 n, err = w.WriteString("# HELP ")
104 n, err = w.WriteString(name)
109 err = w.WriteByte(' ')
114 n, err = writeEscapedString(w, *in.Help, false)
119 err = w.WriteByte('\n')
125 n, err = w.WriteString("# TYPE ")
130 n, err = w.WriteString(name)
135 metricType := in.GetType()
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")
148 return written, fmt.Errorf("unknown metric type %s", metricType.String())
155 // Finally the samples, one line for each.
156 for _, metric := range in.Metric {
158 case dto.MetricType_COUNTER:
159 if metric.Counter == nil {
160 return written, fmt.Errorf(
161 "expected counter in metric %s %s", name, metric,
164 n, err = writeSample(
165 w, name, "", metric, "", 0,
166 metric.Counter.GetValue(),
168 case dto.MetricType_GAUGE:
169 if metric.Gauge == nil {
170 return written, fmt.Errorf(
171 "expected gauge in metric %s %s", name, metric,
174 n, err = writeSample(
175 w, name, "", metric, "", 0,
176 metric.Gauge.GetValue(),
178 case dto.MetricType_UNTYPED:
179 if metric.Untyped == nil {
180 return written, fmt.Errorf(
181 "expected untyped in metric %s %s", name, metric,
184 n, err = writeSample(
185 w, name, "", metric, "", 0,
186 metric.Untyped.GetValue(),
188 case dto.MetricType_SUMMARY:
189 if metric.Summary == nil {
190 return written, fmt.Errorf(
191 "expected summary in metric %s %s", name, metric,
194 for _, q := range metric.Summary.Quantile {
195 n, err = writeSample(
197 model.QuantileLabel, q.GetQuantile(),
205 n, err = writeSample(
206 w, name, "_sum", metric, "", 0,
207 metric.Summary.GetSampleSum(),
213 n, err = writeSample(
214 w, name, "_count", metric, "", 0,
215 float64(metric.Summary.GetSampleCount()),
217 case dto.MetricType_HISTOGRAM:
218 if metric.Histogram == nil {
219 return written, fmt.Errorf(
220 "expected histogram in metric %s %s", name, metric,
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()),
234 if math.IsInf(b.GetUpperBound(), +1) {
239 n, err = writeSample(
240 w, name, "_bucket", metric,
241 model.BucketLabel, math.Inf(+1),
242 float64(metric.Histogram.GetSampleCount()),
249 n, err = writeSample(
250 w, name, "_sum", metric, "", 0,
251 metric.Histogram.GetSampleSum(),
257 n, err = writeSample(
258 w, name, "_count", metric, "", 0,
259 float64(metric.Histogram.GetSampleCount()),
262 return written, fmt.Errorf(
263 "unexpected type in metric %s %s", name, metric,
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
283 additionalLabelName string, additionalLabelValue float64,
287 n, err := w.WriteString(name)
293 n, err = w.WriteString(suffix)
299 n, err = writeLabelPairs(
300 w, metric.Label, additionalLabelName, additionalLabelValue,
306 err = w.WriteByte(' ')
311 n, err = writeFloat(w, value)
316 if metric.TimestampMs != nil {
317 err = w.WriteByte(' ')
322 n, err = writeInt(w, *metric.TimestampMs)
328 err = w.WriteByte('\n')
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
343 func writeLabelPairs(
346 additionalLabelName string, additionalLabelValue float64,
348 if len(in) == 0 && additionalLabelName == "" {
355 for _, lp := range in {
356 err := w.WriteByte(separator)
361 n, err := w.WriteString(lp.GetName())
366 n, err = w.WriteString(`="`)
371 n, err = writeEscapedString(w, lp.GetValue(), true)
376 err = w.WriteByte('"')
383 if additionalLabelName != "" {
384 err := w.WriteByte(separator)
389 n, err := w.WriteString(additionalLabelName)
394 n, err = w.WriteString(`="`)
399 n, err = writeFloat(w, additionalLabelValue)
404 err = w.WriteByte('"')
410 err := w.WriteByte('}')
418 // writeEscapedString replaces '\' by '\\', new line character by '\n', and - if
419 // includeDoubleQuote is true - '"' by '\"'.
421 escaper = strings.NewReplacer("\\", `\\`, "\n", `\n`)
422 quotedEscaper = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
425 func writeEscapedString(w enhancedWriter, v string, includeDoubleQuote bool) (int, error) {
426 if includeDoubleQuote {
427 return quotedEscaper.WriteString(w, v)
429 return escaper.WriteString(w, v)
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.
436 func writeFloat(w enhancedWriter, f float64) (int, error) {
439 return 1, w.WriteByte('1')
441 return 1, w.WriteByte('0')
443 return w.WriteString("-1")
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")
451 bp := numBufPool.Get().(*[]byte)
452 *bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
453 written, err := w.Write(*bp)
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
462 func 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)