gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/prometheus/common/expfmt/openmetrics_create.go (about)

     1  // Copyright 2020 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  	"bytes"
    19  	"fmt"
    20  	"io"
    21  	"math"
    22  	"strconv"
    23  	"strings"
    24  
    25  	"github.com/prometheus/common/model"
    26  
    27  	dto "github.com/prometheus/client_model/go"
    28  )
    29  
    30  // MetricFamilyToOpenMetrics converts a MetricFamily proto message into the
    31  // OpenMetrics text format and writes the resulting lines to 'out'. It returns
    32  // the number of bytes written and any error encountered. The output will have
    33  // the same order as the input, no further sorting is performed. Furthermore,
    34  // this function assumes the input is already sanitized and does not perform any
    35  // sanity checks. If the input contains duplicate metrics or invalid metric or
    36  // label names, the conversion will result in invalid text format output.
    37  //
    38  // This function fulfills the type 'expfmt.encoder'.
    39  //
    40  // Note that OpenMetrics requires a final `# EOF` line. Since this function acts
    41  // on individual metric families, it is the responsibility of the caller to
    42  // append this line to 'out' once all metric families have been written.
    43  // Conveniently, this can be done by calling FinalizeOpenMetrics.
    44  //
    45  // The output should be fully OpenMetrics compliant. However, there are a few
    46  // missing features and peculiarities to avoid complications when switching from
    47  // Prometheus to OpenMetrics or vice versa:
    48  //
    49  // - Counters are expected to have the `_total` suffix in their metric name. In
    50  //   the output, the suffix will be truncated from the `# TYPE` and `# HELP`
    51  //   line. A counter with a missing `_total` suffix is not an error. However,
    52  //   its type will be set to `unknown` in that case to avoid invalid OpenMetrics
    53  //   output.
    54  //
    55  // - No support for the following (optional) features: `# UNIT` line, `_created`
    56  //   line, info type, stateset type, gaugehistogram type.
    57  //
    58  // - The size of exemplar labels is not checked (i.e. it's possible to create
    59  //   exemplars that are larger than allowed by the OpenMetrics specification).
    60  //
    61  // - The value of Counters is not checked. (OpenMetrics doesn't allow counters
    62  //   with a `NaN` value.)
    63  func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int, err error) {
    64  	name := in.GetName()
    65  	if name == "" {
    66  		return 0, fmt.Errorf("MetricFamily has no name: %s", in)
    67  	}
    68  
    69  	// Try the interface upgrade. If it doesn't work, we'll use a
    70  	// bufio.Writer from the sync.Pool.
    71  	w, ok := out.(enhancedWriter)
    72  	if !ok {
    73  		b := bufPool.Get().(*bufio.Writer)
    74  		b.Reset(out)
    75  		w = b
    76  		defer func() {
    77  			bErr := b.Flush()
    78  			if err == nil {
    79  				err = bErr
    80  			}
    81  			bufPool.Put(b)
    82  		}()
    83  	}
    84  
    85  	var (
    86  		n          int
    87  		metricType = in.GetType()
    88  		shortName  = name
    89  	)
    90  	if metricType == dto.MetricType_COUNTER && strings.HasSuffix(shortName, "_total") {
    91  		shortName = name[:len(name)-6]
    92  	}
    93  
    94  	// Comments, first HELP, then TYPE.
    95  	if in.Help != nil {
    96  		n, err = w.WriteString("# HELP ")
    97  		written += n
    98  		if err != nil {
    99  			return
   100  		}
   101  		n, err = w.WriteString(shortName)
   102  		written += n
   103  		if err != nil {
   104  			return
   105  		}
   106  		err = w.WriteByte(' ')
   107  		written++
   108  		if err != nil {
   109  			return
   110  		}
   111  		n, err = writeEscapedString(w, *in.Help, true)
   112  		written += n
   113  		if err != nil {
   114  			return
   115  		}
   116  		err = w.WriteByte('\n')
   117  		written++
   118  		if err != nil {
   119  			return
   120  		}
   121  	}
   122  	n, err = w.WriteString("# TYPE ")
   123  	written += n
   124  	if err != nil {
   125  		return
   126  	}
   127  	n, err = w.WriteString(shortName)
   128  	written += n
   129  	if err != nil {
   130  		return
   131  	}
   132  	switch metricType {
   133  	case dto.MetricType_COUNTER:
   134  		if strings.HasSuffix(name, "_total") {
   135  			n, err = w.WriteString(" counter\n")
   136  		} else {
   137  			n, err = w.WriteString(" unknown\n")
   138  		}
   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(" unknown\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  			// Note that we have ensured above that either the name
   165  			// ends on `_total` or that the rendered type is
   166  			// `unknown`. Therefore, no `_total` must be added here.
   167  			n, err = writeOpenMetricsSample(
   168  				w, name, "", metric, "", 0,
   169  				metric.Counter.GetValue(), 0, false,
   170  				metric.Counter.Exemplar,
   171  			)
   172  		case dto.MetricType_GAUGE:
   173  			if metric.Gauge == nil {
   174  				return written, fmt.Errorf(
   175  					"expected gauge in metric %s %s", name, metric,
   176  				)
   177  			}
   178  			n, err = writeOpenMetricsSample(
   179  				w, name, "", metric, "", 0,
   180  				metric.Gauge.GetValue(), 0, false,
   181  				nil,
   182  			)
   183  		case dto.MetricType_UNTYPED:
   184  			if metric.Untyped == nil {
   185  				return written, fmt.Errorf(
   186  					"expected untyped in metric %s %s", name, metric,
   187  				)
   188  			}
   189  			n, err = writeOpenMetricsSample(
   190  				w, name, "", metric, "", 0,
   191  				metric.Untyped.GetValue(), 0, false,
   192  				nil,
   193  			)
   194  		case dto.MetricType_SUMMARY:
   195  			if metric.Summary == nil {
   196  				return written, fmt.Errorf(
   197  					"expected summary in metric %s %s", name, metric,
   198  				)
   199  			}
   200  			for _, q := range metric.Summary.Quantile {
   201  				n, err = writeOpenMetricsSample(
   202  					w, name, "", metric,
   203  					model.QuantileLabel, q.GetQuantile(),
   204  					q.GetValue(), 0, false,
   205  					nil,
   206  				)
   207  				written += n
   208  				if err != nil {
   209  					return
   210  				}
   211  			}
   212  			n, err = writeOpenMetricsSample(
   213  				w, name, "_sum", metric, "", 0,
   214  				metric.Summary.GetSampleSum(), 0, false,
   215  				nil,
   216  			)
   217  			written += n
   218  			if err != nil {
   219  				return
   220  			}
   221  			n, err = writeOpenMetricsSample(
   222  				w, name, "_count", metric, "", 0,
   223  				0, metric.Summary.GetSampleCount(), true,
   224  				nil,
   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 = writeOpenMetricsSample(
   235  					w, name, "_bucket", metric,
   236  					model.BucketLabel, b.GetUpperBound(),
   237  					0, b.GetCumulativeCount(), true,
   238  					b.Exemplar,
   239  				)
   240  				written += n
   241  				if err != nil {
   242  					return
   243  				}
   244  				if math.IsInf(b.GetUpperBound(), +1) {
   245  					infSeen = true
   246  				}
   247  			}
   248  			if !infSeen {
   249  				n, err = writeOpenMetricsSample(
   250  					w, name, "_bucket", metric,
   251  					model.BucketLabel, math.Inf(+1),
   252  					0, metric.Histogram.GetSampleCount(), true,
   253  					nil,
   254  				)
   255  				written += n
   256  				if err != nil {
   257  					return
   258  				}
   259  			}
   260  			n, err = writeOpenMetricsSample(
   261  				w, name, "_sum", metric, "", 0,
   262  				metric.Histogram.GetSampleSum(), 0, false,
   263  				nil,
   264  			)
   265  			written += n
   266  			if err != nil {
   267  				return
   268  			}
   269  			n, err = writeOpenMetricsSample(
   270  				w, name, "_count", metric, "", 0,
   271  				0, metric.Histogram.GetSampleCount(), true,
   272  				nil,
   273  			)
   274  		default:
   275  			return written, fmt.Errorf(
   276  				"unexpected type in metric %s %s", name, metric,
   277  			)
   278  		}
   279  		written += n
   280  		if err != nil {
   281  			return
   282  		}
   283  	}
   284  	return
   285  }
   286  
   287  // FinalizeOpenMetrics writes the final `# EOF\n` line required by OpenMetrics.
   288  func FinalizeOpenMetrics(w io.Writer) (written int, err error) {
   289  	return w.Write([]byte("# EOF\n"))
   290  }
   291  
   292  // writeOpenMetricsSample writes a single sample in OpenMetrics text format to
   293  // w, given the metric name, the metric proto message itself, optionally an
   294  // additional label name with a float64 value (use empty string as label name if
   295  // not required), the value (optionally as float64 or uint64, determined by
   296  // useIntValue), and optionally an exemplar (use nil if not required). The
   297  // function returns the number of bytes written and any error encountered.
   298  func writeOpenMetricsSample(
   299  	w enhancedWriter,
   300  	name, suffix string,
   301  	metric *dto.Metric,
   302  	additionalLabelName string, additionalLabelValue float64,
   303  	floatValue float64, intValue uint64, useIntValue bool,
   304  	exemplar *dto.Exemplar,
   305  ) (int, error) {
   306  	var written int
   307  	n, err := w.WriteString(name)
   308  	written += n
   309  	if err != nil {
   310  		return written, err
   311  	}
   312  	if suffix != "" {
   313  		n, err = w.WriteString(suffix)
   314  		written += n
   315  		if err != nil {
   316  			return written, err
   317  		}
   318  	}
   319  	n, err = writeOpenMetricsLabelPairs(
   320  		w, metric.Label, additionalLabelName, additionalLabelValue,
   321  	)
   322  	written += n
   323  	if err != nil {
   324  		return written, err
   325  	}
   326  	err = w.WriteByte(' ')
   327  	written++
   328  	if err != nil {
   329  		return written, err
   330  	}
   331  	if useIntValue {
   332  		n, err = writeUint(w, intValue)
   333  	} else {
   334  		n, err = writeOpenMetricsFloat(w, floatValue)
   335  	}
   336  	written += n
   337  	if err != nil {
   338  		return written, err
   339  	}
   340  	if metric.TimestampMs != nil {
   341  		err = w.WriteByte(' ')
   342  		written++
   343  		if err != nil {
   344  			return written, err
   345  		}
   346  		// TODO(beorn7): Format this directly without converting to a float first.
   347  		n, err = writeOpenMetricsFloat(w, float64(*metric.TimestampMs)/1000)
   348  		written += n
   349  		if err != nil {
   350  			return written, err
   351  		}
   352  	}
   353  	if exemplar != nil {
   354  		n, err = writeExemplar(w, exemplar)
   355  		written += n
   356  		if err != nil {
   357  			return written, err
   358  		}
   359  	}
   360  	err = w.WriteByte('\n')
   361  	written++
   362  	if err != nil {
   363  		return written, err
   364  	}
   365  	return written, nil
   366  }
   367  
   368  // writeOpenMetricsLabelPairs works like writeOpenMetrics but formats the float
   369  // in OpenMetrics style.
   370  func writeOpenMetricsLabelPairs(
   371  	w enhancedWriter,
   372  	in []*dto.LabelPair,
   373  	additionalLabelName string, additionalLabelValue float64,
   374  ) (int, error) {
   375  	if len(in) == 0 && additionalLabelName == "" {
   376  		return 0, nil
   377  	}
   378  	var (
   379  		written   int
   380  		separator byte = '{'
   381  	)
   382  	for _, lp := range in {
   383  		err := w.WriteByte(separator)
   384  		written++
   385  		if err != nil {
   386  			return written, err
   387  		}
   388  		n, err := w.WriteString(lp.GetName())
   389  		written += n
   390  		if err != nil {
   391  			return written, err
   392  		}
   393  		n, err = w.WriteString(`="`)
   394  		written += n
   395  		if err != nil {
   396  			return written, err
   397  		}
   398  		n, err = writeEscapedString(w, lp.GetValue(), true)
   399  		written += n
   400  		if err != nil {
   401  			return written, err
   402  		}
   403  		err = w.WriteByte('"')
   404  		written++
   405  		if err != nil {
   406  			return written, err
   407  		}
   408  		separator = ','
   409  	}
   410  	if additionalLabelName != "" {
   411  		err := w.WriteByte(separator)
   412  		written++
   413  		if err != nil {
   414  			return written, err
   415  		}
   416  		n, err := w.WriteString(additionalLabelName)
   417  		written += n
   418  		if err != nil {
   419  			return written, err
   420  		}
   421  		n, err = w.WriteString(`="`)
   422  		written += n
   423  		if err != nil {
   424  			return written, err
   425  		}
   426  		n, err = writeOpenMetricsFloat(w, additionalLabelValue)
   427  		written += n
   428  		if err != nil {
   429  			return written, err
   430  		}
   431  		err = w.WriteByte('"')
   432  		written++
   433  		if err != nil {
   434  			return written, err
   435  		}
   436  	}
   437  	err := w.WriteByte('}')
   438  	written++
   439  	if err != nil {
   440  		return written, err
   441  	}
   442  	return written, nil
   443  }
   444  
   445  // writeExemplar writes the provided exemplar in OpenMetrics format to w. The
   446  // function returns the number of bytes written and any error encountered.
   447  func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) {
   448  	written := 0
   449  	n, err := w.WriteString(" # ")
   450  	written += n
   451  	if err != nil {
   452  		return written, err
   453  	}
   454  	n, err = writeOpenMetricsLabelPairs(w, e.Label, "", 0)
   455  	written += n
   456  	if err != nil {
   457  		return written, err
   458  	}
   459  	err = w.WriteByte(' ')
   460  	written++
   461  	if err != nil {
   462  		return written, err
   463  	}
   464  	n, err = writeOpenMetricsFloat(w, e.GetValue())
   465  	written += n
   466  	if err != nil {
   467  		return written, err
   468  	}
   469  	if e.Timestamp != nil {
   470  		err = w.WriteByte(' ')
   471  		written++
   472  		if err != nil {
   473  			return written, err
   474  		}
   475  		err = (*e).Timestamp.CheckValid()
   476  		if err != nil {
   477  			return written, err
   478  		}
   479  		ts := (*e).Timestamp.AsTime()
   480  		// TODO(beorn7): Format this directly from components of ts to
   481  		// avoid overflow/underflow and precision issues of the float
   482  		// conversion.
   483  		n, err = writeOpenMetricsFloat(w, float64(ts.UnixNano())/1e9)
   484  		written += n
   485  		if err != nil {
   486  			return written, err
   487  		}
   488  	}
   489  	return written, nil
   490  }
   491  
   492  // writeOpenMetricsFloat works like writeFloat but appends ".0" if the resulting
   493  // number would otherwise contain neither a "." nor an "e".
   494  func writeOpenMetricsFloat(w enhancedWriter, f float64) (int, error) {
   495  	switch {
   496  	case f == 1:
   497  		return w.WriteString("1.0")
   498  	case f == 0:
   499  		return w.WriteString("0.0")
   500  	case f == -1:
   501  		return w.WriteString("-1.0")
   502  	case math.IsNaN(f):
   503  		return w.WriteString("NaN")
   504  	case math.IsInf(f, +1):
   505  		return w.WriteString("+Inf")
   506  	case math.IsInf(f, -1):
   507  		return w.WriteString("-Inf")
   508  	default:
   509  		bp := numBufPool.Get().(*[]byte)
   510  		*bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
   511  		if !bytes.ContainsAny(*bp, "e.") {
   512  			*bp = append(*bp, '.', '0')
   513  		}
   514  		written, err := w.Write(*bp)
   515  		numBufPool.Put(bp)
   516  		return written, err
   517  	}
   518  }
   519  
   520  // writeUint is like writeInt just for uint64.
   521  func writeUint(w enhancedWriter, u uint64) (int, error) {
   522  	bp := numBufPool.Get().(*[]byte)
   523  	*bp = strconv.AppendUint((*bp)[:0], u, 10)
   524  	written, err := w.Write(*bp)
   525  	numBufPool.Put(bp)
   526  	return written, err
   527  }