...

Source file src/github.com/prometheus/common/expfmt/text_create.go

Documentation: github.com/prometheus/common/expfmt

     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  
    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  // enhancedWriter has all the enhanced write functions needed here. bufio.Writer
    31  // implements it.
    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  // MetricFamilyToText converts a MetricFamily proto message into text format and
    58  // writes the resulting lines to 'out'. It returns the number of bytes written
    59  // and any error encountered. The output will have the same order as the input,
    60  // no further sorting is performed. Furthermore, this function assumes the input
    61  // is already sanitized and does not perform any sanity checks. If the input
    62  // contains duplicate metrics or invalid metric or label names, the conversion
    63  // will result in invalid text format output.
    64  //
    65  // If metric names conform to the legacy validation pattern, they will be placed
    66  // outside the brackets in the traditional way, like `foo{}`. If the metric name
    67  // fails the legacy validation check, it will be placed quoted inside the
    68  // brackets: `{"foo"}`. As stated above, the input is assumed to be santized and
    69  // no error will be thrown in this case.
    70  //
    71  // Similar to metric names, if label names conform to the legacy validation
    72  // pattern, they will be unquoted as normal, like `foo{bar="baz"}`. If the label
    73  // name fails the legacy validation check, it will be quoted:
    74  // `foo{"bar"="baz"}`. As stated above, the input is assumed to be santized and
    75  // no error will be thrown in this case.
    76  //
    77  // This method fulfills the type 'prometheus.encoder'.
    78  func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err error) {
    79  	// Fail-fast checks.
    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  	// Try the interface upgrade. If it doesn't work, we'll use a
    89  	// bufio.Writer from the sync.Pool.
    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  	// Comments, first HELP, then TYPE.
   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  	// Finally the samples, one line for each.
   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  // writeSample writes a single sample in text format to w, given the metric
   284  // name, the metric proto message itself, optionally an additional label name
   285  // with a float64 value (use empty string as label name if not required), and
   286  // the value. The function returns the number of bytes written and any error
   287  // encountered.
   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  // writeNameAndLabelPairs converts a slice of LabelPair proto messages plus the
   334  // explicitly given metric name and additional label pair into text formatted as
   335  // required by the text format and writes it to 'w'. An empty slice in
   336  // combination with an empty string 'additionalLabelName' results in nothing
   337  // being written. Otherwise, the label pairs are written, escaped as required by
   338  // the text format, and enclosed in '{...}'. The function returns the number of
   339  // bytes written and any error encountered. If the metric name is not
   340  // legacy-valid, it will be put inside the brackets as well. Legacy-invalid
   341  // label names will also be quoted.
   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  		// If the name does not pass the legacy validity check, we must put the
   356  		// metric name inside the braces.
   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  // writeEscapedString replaces '\' by '\\', new line character by '\n', and - if
   448  // includeDoubleQuote is true - '"' by '\"'.
   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  // writeFloat is equivalent to fmt.Fprint with a float64 argument but hardcodes
   462  // a few common cases for increased efficiency. For non-hardcoded cases, it uses
   463  // strconv.AppendFloat to avoid allocations, similar to writeInt.
   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  // writeInt is equivalent to fmt.Fprint with an int64 argument but uses
   488  // strconv.AppendInt with a byte slice taken from a sync.Pool to avoid
   489  // allocations.
   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  // writeName writes a string as-is if it complies with the legacy naming
   499  // scheme, or escapes it in double quotes if not.
   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