gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/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  	"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.
    33  type 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  
    40  const (
    41  	initialNumBufSize = 24
    42  )
    43  
    44  var (
    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'.
    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  	// 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.
   277  func 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.
   341  func 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 '\"'.
   418  var (
   419  	escaper       = strings.NewReplacer("\\", `\\`, "\n", `\n`)
   420  	quotedEscaper = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
   421  )
   422  
   423  func 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.
   433  func 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.
   459  func 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  }