github.com/hellobchain/third_party@v0.0.0-20230331131523-deb0478a2e52/prometheus/common/expfmt/text_create.go (about)

     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  	"bytes"
    18  	"fmt"
    19  	"io"
    20  	"math"
    21  	"strconv"
    22  	"strings"
    23  	"sync"
    24  
    25  	"github.com/hellobchain/third_party/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.
    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  	initialBufSize    = 512
    41  	initialNumBufSize = 24
    42  )
    43  
    44  var (
    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'.
    67  func 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.
   279  func 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.
   343  func 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 '\"'.
   420  var (
   421  	escaper       = strings.NewReplacer("\\", `\\`, "\n", `\n`)
   422  	quotedEscaper = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
   423  )
   424  
   425  func 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.
   436  func writeFloat(w enhancedWriter, f float64) (int, error) {
   437  	switch {
   438  	case f == 1:
   439  		return w.WriteString("1.0")
   440  	case f == 0:
   441  		return w.WriteString("0.0")
   442  	case f == -1:
   443  		return w.WriteString("-1.0")
   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  		// Add a .0 if used fixed point and there is no decimal
   454  		// point already. This is for future proofing with OpenMetrics,
   455  		// where floats always contain either an exponent or decimal.
   456  		if !bytes.ContainsAny(*bp, "e.") {
   457  			*bp = append(*bp, '.', '0')
   458  		}
   459  		written, err := w.Write(*bp)
   460  		numBufPool.Put(bp)
   461  		return written, err
   462  	}
   463  }
   464  
   465  // writeInt is equivalent to fmt.Fprint with an int64 argument but uses
   466  // strconv.AppendInt with a byte slice taken from a sync.Pool to avoid
   467  // allocations.
   468  func writeInt(w enhancedWriter, i int64) (int, error) {
   469  	bp := numBufPool.Get().(*[]byte)
   470  	*bp = strconv.AppendInt((*bp)[:0], i, 10)
   471  	written, err := w.Write(*bp)
   472  	numBufPool.Put(bp)
   473  	return written, err
   474  }