github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/pkg/prometheus/prometheus.go (about)

     1  // Copyright 2022 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package prometheus contains Prometheus-compliant metric data structures and utilities.
    16  // It can export data in Prometheus data format, documented at:
    17  // https://prometheus.io/docs/instrumenting/exposition_formats/
    18  package prometheus
    19  
    20  import (
    21  	"bufio"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"math"
    26  	"reflect"
    27  	"sort"
    28  	"strings"
    29  	"time"
    30  )
    31  
    32  // timeNow is the time.Now() function. Can be mocked in tests.
    33  var timeNow = time.Now
    34  
    35  // Prometheus label names used to identify each sandbox.
    36  const (
    37  	SandboxIDLabel   = "sandbox"
    38  	PodNameLabel     = "pod_name"
    39  	NamespaceLabel   = "namespace_name"
    40  	IterationIDLabel = "iteration"
    41  )
    42  
    43  // Type is a Prometheus metric type.
    44  type Type int
    45  
    46  // List of supported Prometheus metric types.
    47  const (
    48  	TypeUntyped = Type(iota)
    49  	TypeGauge
    50  	TypeCounter
    51  	TypeHistogram
    52  )
    53  
    54  // Metric is a Prometheus metric metadata.
    55  type Metric struct {
    56  	// Name is the Prometheus metric name.
    57  	Name string `json:"name"`
    58  
    59  	// Type is the type of the metric.
    60  	Type Type `json:"type"`
    61  
    62  	// Help is an optional helpful string explaining what the metric is about.
    63  	Help string `json:"help"`
    64  }
    65  
    66  // writeHeaderTo writes the metric comment header to the given writer.
    67  func (m *Metric) writeHeaderTo(w io.Writer, options SnapshotExportOptions) error {
    68  	if m.Help != "" {
    69  		// This writes each string component one by one (rather than using fmt.Sprintf)
    70  		// in order to avoid allocating strings for each metric.
    71  		if _, err := io.WriteString(w, "# HELP "); err != nil {
    72  			return err
    73  		}
    74  		if _, err := io.WriteString(w, options.ExporterPrefix); err != nil {
    75  			return err
    76  		}
    77  		if _, err := io.WriteString(w, m.Name); err != nil {
    78  			return err
    79  		}
    80  		if _, err := io.WriteString(w, " "); err != nil {
    81  			return err
    82  		}
    83  		if _, err := writeEscapedString(w, m.Help, false); err != nil {
    84  			return err
    85  		}
    86  		if _, err := io.WriteString(w, "\n"); err != nil {
    87  			return err
    88  		}
    89  	}
    90  	var metricType string
    91  	switch m.Type {
    92  	case TypeGauge:
    93  		metricType = "gauge"
    94  	case TypeCounter:
    95  		metricType = "counter"
    96  	case TypeHistogram:
    97  		metricType = "histogram"
    98  	case TypeUntyped:
    99  		metricType = "untyped"
   100  	}
   101  	if metricType != "" {
   102  		if _, err := io.WriteString(w, "# TYPE "); err != nil {
   103  			return err
   104  		}
   105  		if _, err := io.WriteString(w, options.ExporterPrefix); err != nil {
   106  			return err
   107  		}
   108  		if _, err := io.WriteString(w, m.Name); err != nil {
   109  			return err
   110  		}
   111  		if _, err := io.WriteString(w, " "); err != nil {
   112  			return err
   113  		}
   114  		if _, err := io.WriteString(w, metricType); err != nil {
   115  			return err
   116  		}
   117  		if _, err := io.WriteString(w, "\n"); err != nil {
   118  			return err
   119  		}
   120  	}
   121  	return nil
   122  }
   123  
   124  // Number represents a numerical value.
   125  // In Prometheus, all numbers are float64s.
   126  // However, for the purpose of usage of this library, we support expressing numbers as integers,
   127  // which makes things like counters much easier and more precise.
   128  // At data export time (i.e. when written out in Prometheus data format), it is coalesced into
   129  // a float.
   130  type Number struct {
   131  	// Float is the float value of this number.
   132  	// Mutually exclusive with Int.
   133  	Float float64 `json:"float,omitempty"`
   134  
   135  	// Int is the integer value of this number.
   136  	// Mutually exclusive with Float.
   137  	Int int64 `json:"int,omitempty"`
   138  }
   139  
   140  // Common numbers which are reused and don't need their own memory allocations.
   141  var (
   142  	zero        = Number{}
   143  	intOne      = Number{Int: 1}
   144  	floatOne    = Number{Float: 1.0}
   145  	floatNaN    = Number{Float: math.NaN()}
   146  	floatInf    = Number{Float: math.Inf(1)}
   147  	floatNegInf = Number{Float: math.Inf(-1)}
   148  )
   149  
   150  // NewInt returns a new integer Number.
   151  func NewInt(val int64) *Number {
   152  	switch val {
   153  	case 0:
   154  		return &zero
   155  	case 1:
   156  		return &intOne
   157  	default:
   158  		return &Number{Int: val}
   159  	}
   160  }
   161  
   162  // NewFloat returns a new floating-point Number.
   163  func NewFloat(val float64) *Number {
   164  	if math.IsNaN(val) {
   165  		return &floatNaN
   166  	}
   167  	switch val {
   168  	case 0:
   169  		return &zero
   170  	case 1.0:
   171  		return &floatOne
   172  	case math.Inf(1.0):
   173  		return &floatInf
   174  	case math.Inf(-1.0):
   175  		return &floatNegInf
   176  	default:
   177  		return &Number{Float: val}
   178  	}
   179  }
   180  
   181  // IsInteger returns whether this number contains an integer value.
   182  // This is defined as either having the `Float` part set to zero (in which case the `Int` part takes
   183  // precedence), or having `Float` be a value equal to its own rounding and not a special float.
   184  //
   185  //go:nosplit
   186  func (n *Number) IsInteger() bool {
   187  	if n.Float == 0 {
   188  		return true
   189  	}
   190  	if math.IsNaN(n.Float) || n.Float == math.Inf(-1) || n.Float == math.Inf(1) {
   191  		return false
   192  	}
   193  	return n.Float < float64(math.MaxInt64) && n.Float > float64(math.MinInt64) && math.Round(n.Float) == n.Float
   194  }
   195  
   196  // ToFloat returns this number as a floating-point number, regardless of which
   197  // type the number was encoded as. An integer Number will have its value cast
   198  // to a float, while a floating-point Number will have its value returned
   199  // as-is.
   200  //
   201  //go:nosplit
   202  func (n *Number) ToFloat() float64 {
   203  	if n.Int != 0 {
   204  		return float64(n.Int)
   205  	}
   206  	return n.Float
   207  }
   208  
   209  // String returns a string representation of this number.
   210  func (n *Number) String() string {
   211  	var s strings.Builder
   212  	if err := n.writeTo(&s); err != nil {
   213  		panic(err)
   214  	}
   215  	return s.String()
   216  }
   217  
   218  // SameType returns true if `n` and `other` are either both floating-point or both integers.
   219  // If a `Number` is zero, it is considered of the same type as any other zero `Number`.
   220  //
   221  //go:nosplit
   222  func (n *Number) SameType(other *Number) bool {
   223  	// Within `n` and `other`, at least one of `Int` or `Float` must be set to zero.
   224  	// Therefore, this verifies that there is at least one shared zero between the two.
   225  	return n.Float == other.Float || n.Int == other.Int
   226  }
   227  
   228  // GreaterThan returns true if n > other.
   229  // Precondition: n.SameType(other) is true. Panics otherwise.
   230  //
   231  //go:nosplit
   232  func (n *Number) GreaterThan(other *Number) bool {
   233  	if !n.SameType(other) {
   234  		panic("tried to compare two numbers of different types")
   235  	}
   236  	if n.IsInteger() {
   237  		return n.Int > other.Int
   238  	}
   239  	return n.Float > other.Float
   240  }
   241  
   242  // WriteInteger writes the given integer to a writer without allocating strings.
   243  //
   244  //go:nosplit
   245  func WriteInteger(w io.Writer, val int64) (int, error) {
   246  	const decimalDigits = "0123456789"
   247  	if val == 0 {
   248  		return io.WriteString(w, decimalDigits[0:1])
   249  	}
   250  	var written int
   251  	if val < 0 {
   252  		n, err := io.WriteString(w, "-")
   253  		written += n
   254  		if err != nil {
   255  			return written, err
   256  		}
   257  		val = -val
   258  	}
   259  	decimal := int64(1)
   260  	for ; val/decimal != 0; decimal *= 10 {
   261  	}
   262  	for decimal /= 10; decimal > 0; decimal /= 10 {
   263  		digit := (val / decimal) % 10
   264  		n, err := io.WriteString(w, decimalDigits[digit:digit+1])
   265  		written += n
   266  		if err != nil {
   267  			return written, err
   268  		}
   269  	}
   270  	return written, nil
   271  }
   272  
   273  // writeTo writes the number to the given writer.
   274  // This only causes heap allocations when the number is a non-zero, non-special float.
   275  func (n *Number) writeTo(w io.Writer) error {
   276  	var s string
   277  	switch {
   278  	// Zero case:
   279  	case n.Int == 0 && n.Float == 0:
   280  		s = "0"
   281  
   282  	// Integer case:
   283  	case n.Int != 0:
   284  		_, err := WriteInteger(w, n.Int)
   285  		return err
   286  
   287  	// Special float cases:
   288  	case n.Float == math.Inf(-1):
   289  		s = "-Inf"
   290  	case n.Float == math.Inf(1):
   291  		s = "+Inf"
   292  	case math.IsNaN(n.Float):
   293  		s = "NaN"
   294  
   295  	// Regular float case:
   296  	default:
   297  		s = fmt.Sprintf("%f", n.Float)
   298  	}
   299  	_, err := io.WriteString(w, s)
   300  	return err
   301  }
   302  
   303  // Bucket is a single histogram bucket.
   304  type Bucket struct {
   305  	// UpperBound is the upper bound of the bucket.
   306  	// The lower bound of the bucket is the largest UpperBound within other Histogram Buckets that
   307  	// is smaller than this bucket's UpperBound.
   308  	// The bucket with the smallest UpperBound within a Histogram implicitly has -Inf as lower bound.
   309  	// This should be set to +Inf to mark the "last" bucket.
   310  	UpperBound Number `json:"le"`
   311  
   312  	// Samples is the number of samples in the bucket.
   313  	// Note: When exported to Prometheus, they are exported cumulatively, i.e. the count of samples
   314  	// exported in Bucket i is actually sum(histogram.Buckets[j].Samples for 0 <= j <= i).
   315  	Samples uint64 `json:"n,omitempty"`
   316  }
   317  
   318  // Histogram contains data about histogram values.
   319  type Histogram struct {
   320  	// Total is the sum of sample values across all buckets.
   321  	Total Number `json:"total"`
   322  	// Min is the minimum sample ever recorded in this histogram.
   323  	Min Number `json:"min"`
   324  	// Max is the maximum sample ever recorded in this histogram.
   325  	Max Number `json:"max"`
   326  	// SumOfSquaredDeviations is the number of squared deviations of all samples.
   327  	SumOfSquaredDeviations Number `json:"ssd"`
   328  	// Buckets contains per-bucket data.
   329  	// A distribution with n finite-boundary buckets should have n+2 entries here.
   330  	// The 0th entry is the underflow bucket (i.e. the one with -inf as lower bound),
   331  	// and the last aka (n+1)th entry is the overflow bucket (i.e. the one with +inf as upper bound).
   332  	Buckets []Bucket `json:"buckets,omitempty"`
   333  }
   334  
   335  // Data is an observation of the value of a single metric at a certain point in time.
   336  type Data struct {
   337  	// Metric is the metric for which the value is being reported.
   338  	Metric *Metric `json:"metric"`
   339  
   340  	// Labels is a key-value pair representing the labels set on this metric.
   341  	// This may be merged with other labels during export.
   342  	Labels map[string]string `json:"labels,omitempty"`
   343  
   344  	// ExternalLabels are more labels merged together with `Labels`.
   345  	// They can be set using SetExternalLabels.
   346  	// They are useful in the case where a single Data needs labels from two sources:
   347  	// labels specific to this data point (which should be in `Labels`), and labels
   348  	// that are shared between multiple data points (stored in `ExternalLabels`).
   349  	// This avoids allocating unique `Labels` maps for each Data struct, when
   350  	// most of the actual labels would be shared between them.
   351  	ExternalLabels map[string]string `json:"external_labels,omitempty"`
   352  
   353  	// At most one of the fields below may be set.
   354  	// Which one depends on the type of the metric.
   355  
   356  	// Number is used for all numerical types.
   357  	Number *Number `json:"val,omitempty"`
   358  
   359  	// Histogram is used for histogram-typed metrics.
   360  	HistogramValue *Histogram `json:"histogram,omitempty"`
   361  }
   362  
   363  // NewIntData returns a new Data struct with the given metric and value.
   364  func NewIntData(metric *Metric, val int64) *Data {
   365  	return LabeledIntData(metric, nil, val)
   366  }
   367  
   368  // LabeledIntData returns a new Data struct with the given metric, labels, and value.
   369  func LabeledIntData(metric *Metric, labels map[string]string, val int64) *Data {
   370  	return &Data{Metric: metric, Labels: labels, Number: NewInt(val)}
   371  }
   372  
   373  // NewFloatData returns a new Data struct with the given metric and value.
   374  func NewFloatData(metric *Metric, val float64) *Data {
   375  	return LabeledFloatData(metric, nil, val)
   376  }
   377  
   378  // LabeledFloatData returns a new Data struct with the given metric, labels, and value.
   379  func LabeledFloatData(metric *Metric, labels map[string]string, val float64) *Data {
   380  	return &Data{Metric: metric, Labels: labels, Number: NewFloat(val)}
   381  }
   382  
   383  // SetExternalLabels sets d.ExternalLabels. See its docstring for more information.
   384  // Returns `d` for chainability.
   385  func (d *Data) SetExternalLabels(externalLabels map[string]string) *Data {
   386  	d.ExternalLabels = externalLabels
   387  	return d
   388  }
   389  
   390  // ExportOptions contains options that control how metric data is exported in Prometheus format.
   391  type ExportOptions struct {
   392  	// CommentHeader is prepended as a comment before any metric data is exported.
   393  	CommentHeader string
   394  
   395  	// MetricsWritten memoizes written metric preambles (help/type comments)
   396  	// by metric name.
   397  	// If specified, this map can be used to avoid duplicate preambles across multiple snapshots.
   398  	// Note that this map is modified in-place during the writing process.
   399  	MetricsWritten map[string]bool
   400  }
   401  
   402  // SnapshotExportOptions contains options that control how metric data is exported for an
   403  // individual Snapshot.
   404  type SnapshotExportOptions struct {
   405  	// ExporterPrefix is prepended to all metric names.
   406  	ExporterPrefix string
   407  
   408  	// ExtraLabels is added as labels for all metric values.
   409  	ExtraLabels map[string]string
   410  }
   411  
   412  // writeEscapedString writes the given string in quotation marks and with some characters escaped,
   413  // per Prometheus spec. It does this without string allocations.
   414  // If `quoted` is true, quote characters will surround the string, and quote characters within `s`
   415  // will also be escaped.
   416  func writeEscapedString(w io.Writer, s string, quoted bool) (int, error) {
   417  	const (
   418  		quote            = '"'
   419  		backslash        = '\\'
   420  		newline          = '\n'
   421  		quoteStr         = `"`
   422  		escapedQuote     = `\\"`
   423  		escapedBackslash = "\\\\"
   424  		escapedNewline   = "\\\n"
   425  	)
   426  	written := 0
   427  	var n int
   428  	var err error
   429  	if quoted {
   430  		n, err = io.WriteString(w, quoteStr)
   431  		written += n
   432  		if err != nil {
   433  			return written, err
   434  		}
   435  	}
   436  	for _, r := range s {
   437  		switch r {
   438  		case quote:
   439  			if quoted {
   440  				n, err = io.WriteString(w, escapedQuote)
   441  			} else {
   442  				n, err = io.WriteString(w, quoteStr)
   443  			}
   444  		case backslash:
   445  			n, err = io.WriteString(w, escapedBackslash)
   446  		case newline:
   447  			n, err = io.WriteString(w, escapedNewline)
   448  		default:
   449  			n, err = io.WriteString(w, string(r))
   450  		}
   451  		written += n
   452  		if err != nil {
   453  			return written, err
   454  		}
   455  	}
   456  	if quoted {
   457  		n, err = io.WriteString(w, quoteStr)
   458  		written += n
   459  		if err != nil {
   460  			return written, err
   461  		}
   462  	}
   463  	return written, nil
   464  }
   465  
   466  // writeMetricPreambleTo writes the metric name to the io.Writer. It may also
   467  // write unwritten help and type comments of the metric if they haven't been
   468  // written to the io.Writer yet.
   469  func (d *Data) writeMetricPreambleTo(w io.Writer, options SnapshotExportOptions, metricsWritten map[string]bool) error {
   470  	// Metric header, if we haven't printed it yet.
   471  	if !metricsWritten[d.Metric.Name] {
   472  		// Extra newline before each preamble for aesthetic reasons.
   473  		if _, err := io.WriteString(w, "\n"); err != nil {
   474  			return err
   475  		}
   476  		if err := d.Metric.writeHeaderTo(w, options); err != nil {
   477  			return err
   478  		}
   479  		metricsWritten[d.Metric.Name] = true
   480  	}
   481  
   482  	// Metric name.
   483  	if options.ExporterPrefix != "" {
   484  		if _, err := io.WriteString(w, options.ExporterPrefix); err != nil {
   485  			return err
   486  		}
   487  	}
   488  	if _, err := io.WriteString(w, d.Metric.Name); err != nil {
   489  		return err
   490  	}
   491  	return nil
   492  }
   493  
   494  // keyVal is a key-value pair used in the function below.
   495  type keyVal struct{ Key, Value string }
   496  
   497  // sortedIterateLabels iterates through labels and outputs them to `out` in sorted key order,
   498  // or stops when cancelCh is written to. It runs in O(n^2) time but makes no heap allocations.
   499  func sortedIterateLabels(labels map[string]string, out chan<- keyVal, cancelCh <-chan struct{}) {
   500  	defer close(out)
   501  	if len(labels) == 0 {
   502  		return
   503  	}
   504  
   505  	// smallestKey is the smallest key that we've already sent to `out`.
   506  	// It starts as the empty string, which means we haven't sent anything to `out` yet.
   507  	smallestKey := ""
   508  	// Find the smallest key of the whole set and send it out.
   509  	for k := range labels {
   510  		if smallestKey == "" || k < smallestKey {
   511  			smallestKey = k
   512  		}
   513  	}
   514  	select {
   515  	case out <- keyVal{smallestKey, labels[smallestKey]}:
   516  	case <-cancelCh:
   517  		return
   518  	}
   519  
   520  	// Iterate until we've sent as many items as we have as input to the output channel.
   521  	// We start at 1 because the loop above already sent out the smallest key to `out`.
   522  	for numOutput := 1; numOutput < len(labels); numOutput++ {
   523  		// nextSmallestKey is the smallest key that is strictly larger than `smallestKey`.
   524  		nextSmallestKey := ""
   525  		for k := range labels {
   526  			if k > smallestKey && (nextSmallestKey == "" || k < nextSmallestKey) {
   527  				nextSmallestKey = k
   528  			}
   529  		}
   530  
   531  		// Update smallestKey and send it out.
   532  		smallestKey = nextSmallestKey
   533  		select {
   534  		case out <- keyVal{smallestKey, labels[smallestKey]}:
   535  		case <-cancelCh:
   536  			return
   537  		}
   538  	}
   539  }
   540  
   541  // LabelOrError is used in OrderedLabels.
   542  // It represents either a key-value pair, or an error.
   543  type LabelOrError struct {
   544  	Key, Value string
   545  	Error      error
   546  }
   547  
   548  // OrderedLabels streams the list of 'label_key="label_value"' in sorted order, except "le" which is
   549  // a reserved Prometheus label name and should go last.
   550  // If an error is encountered, it is returned as the Error field of LabelOrError, and no further
   551  // messages will be sent on the channel.
   552  func OrderedLabels(labels ...map[string]string) <-chan LabelOrError {
   553  	// This function is quite hot on the metric-rendering path, and its naive "just put all the
   554  	// strings in one map to ensure no dupes it, then in one slice and sort it" approach is very
   555  	// allocation-heavy. This approach is more computation-heavy (it runs in
   556  	// O(len(labels) * len(largest label map))), but the only heap allocations it does is for the
   557  	// following tiny slices and channels. In practice, the number of label maps and the size of
   558  	// each label map is tiny, so this is worth doing despite the theoretically-longer run time.
   559  
   560  	// Initialize the channels we'll use.
   561  	mapChannels := make([]chan keyVal, 0, len(labels))
   562  	lastKeyVal := make([]keyVal, len(labels))
   563  	resultCh := make(chan LabelOrError)
   564  	var cancelCh chan struct{}
   565  	// outputError is a helper function for when we have encountered an error mid-way.
   566  	outputError := func(err error) {
   567  		if cancelCh != nil {
   568  			for range mapChannels {
   569  				cancelCh <- struct{}{}
   570  			}
   571  			close(cancelCh)
   572  		}
   573  		resultCh <- LabelOrError{Error: err}
   574  		close(resultCh)
   575  	}
   576  
   577  	// Verify that no label is the empty string. It's not a valid label name,
   578  	// and we use the empty string later on in the function as a marker of having
   579  	// finished processing all labels from a given label map.
   580  	for _, labelMap := range labels {
   581  		for label := range labelMap {
   582  			if label == "" {
   583  				go outputError(errors.New("got empty-string label"))
   584  				return resultCh
   585  			}
   586  		}
   587  	}
   588  
   589  	// Each label map is processed in its own goroutine,
   590  	// which will stream it back to this function in sorted order.
   591  	cancelCh = make(chan struct{}, len(labels))
   592  	for _, labelMap := range labels {
   593  		ch := make(chan keyVal)
   594  		mapChannels = append(mapChannels, ch)
   595  		go sortedIterateLabels(labelMap, ch, cancelCh)
   596  	}
   597  
   598  	// This goroutine is the meat of this function; it iterates through
   599  	// the results being streamed from each `sortedIterateLabels` goroutine
   600  	// that we spawned earlier, until all of them are exhausted or until we
   601  	// hit an error.
   602  	go func() {
   603  		// The "le" label is special and goes last, not in sorted order.
   604  		// gotLe is the empty string if there is no "le" label,
   605  		// otherwise it's the value of the "le" label.
   606  		var gotLe string
   607  
   608  		// numChannelsLeft tracks the number of channels that are still live.
   609  		for numChannelsLeft := len(mapChannels); numChannelsLeft > 0; {
   610  			// Iterate over all channels and ensure we have the freshest (smallest)
   611  			// label from each of them.
   612  			for i, ch := range mapChannels {
   613  				// A nil channel is one that has been closed.
   614  				if ch == nil {
   615  					continue
   616  				}
   617  				// If we already have the latest value from this channel,
   618  				// keep it there instead of getting a new one,
   619  				if lastKeyVal[i].Key != "" {
   620  					continue
   621  				}
   622  				// Otherwise, get a new label.
   623  				kv, open := <-ch
   624  				if !open {
   625  					// Channel has been closed, no more to read from this one.
   626  					numChannelsLeft--
   627  					mapChannels[i] = nil
   628  					continue
   629  				}
   630  				if kv.Key == "le" {
   631  					if gotLe != "" {
   632  						outputError(errors.New("got duplicate 'le' label"))
   633  						return
   634  					}
   635  					gotLe = kv.Value
   636  					continue
   637  				}
   638  				lastKeyVal[i] = kv
   639  			}
   640  
   641  			// We have one key-value pair from each still-active channel now.
   642  			// Find the smallest one between them.
   643  			smallestKey := ""
   644  			indexForSmallest := -1
   645  			for i, kv := range lastKeyVal {
   646  				if kv.Key == "" {
   647  					continue
   648  				}
   649  				if smallestKey == "" || kv.Key < smallestKey {
   650  					smallestKey = kv.Key
   651  					indexForSmallest = i
   652  				} else if kv.Key == smallestKey {
   653  					outputError(fmt.Errorf("got duplicate label %q", smallestKey))
   654  					return
   655  				}
   656  			}
   657  
   658  			if indexForSmallest == -1 {
   659  				// There are no more key-value pairs to output. We're done.
   660  				break
   661  			}
   662  
   663  			// Output the smallest key-value pairs out of all the channels.
   664  			resultCh <- LabelOrError{
   665  				Key:   smallestKey,
   666  				Value: lastKeyVal[indexForSmallest].Value,
   667  			}
   668  			// Mark the last key-value pair from the channel that gave us the
   669  			// smallest key-value pair as no longer present, so that we get a new
   670  			// key-value pair from it in the next iteration.
   671  			lastKeyVal[indexForSmallest] = keyVal{}
   672  		}
   673  
   674  		// Output the "le" label last.
   675  		if gotLe != "" {
   676  			resultCh <- LabelOrError{
   677  				Key:   "le",
   678  				Value: gotLe,
   679  			}
   680  		}
   681  		close(resultCh)
   682  		close(cancelCh)
   683  	}()
   684  
   685  	return resultCh
   686  }
   687  
   688  // writeLabelsTo writes a set of metric labels.
   689  func (d *Data) writeLabelsTo(w io.Writer, extraLabels map[string]string, leLabel *Number) error {
   690  	if len(d.Labels)+len(d.ExternalLabels)+len(extraLabels) != 0 || leLabel != nil {
   691  		if _, err := io.WriteString(w, "{"); err != nil {
   692  			return err
   693  		}
   694  		var orderedLabels <-chan LabelOrError
   695  		if leLabel != nil {
   696  			orderedLabels = OrderedLabels(d.Labels, d.ExternalLabels, extraLabels, map[string]string{"le": leLabel.String()})
   697  		} else {
   698  			orderedLabels = OrderedLabels(d.Labels, d.ExternalLabels, extraLabels)
   699  		}
   700  		firstLabel := true
   701  		var foundError error
   702  		for labelOrError := range orderedLabels {
   703  			if foundError != nil {
   704  				continue
   705  			}
   706  			if labelOrError.Error != nil {
   707  				foundError = labelOrError.Error
   708  				continue
   709  			}
   710  			if !firstLabel {
   711  				if _, err := io.WriteString(w, ","); err != nil {
   712  					return err
   713  				}
   714  			}
   715  			firstLabel = false
   716  			if _, err := io.WriteString(w, labelOrError.Key); err != nil {
   717  				return err
   718  			}
   719  			if _, err := io.WriteString(w, "="); err != nil {
   720  				return err
   721  			}
   722  			if _, err := writeEscapedString(w, labelOrError.Value, true); err != nil {
   723  				return err
   724  			}
   725  		}
   726  		if foundError != nil {
   727  			return foundError
   728  		}
   729  		if _, err := io.WriteString(w, "}"); err != nil {
   730  			return err
   731  		}
   732  	}
   733  	return nil
   734  }
   735  
   736  // writeMetricLine writes a single line with a single number (val) to w.
   737  func (d *Data) writeMetricLine(w io.Writer, metricSuffix string, val *Number, when time.Time, options SnapshotExportOptions, leLabel *Number, metricsWritten map[string]bool) error {
   738  	if err := d.writeMetricPreambleTo(w, options, metricsWritten); err != nil {
   739  		return err
   740  	}
   741  	if metricSuffix != "" {
   742  		if _, err := io.WriteString(w, metricSuffix); err != nil {
   743  			return err
   744  		}
   745  	}
   746  	if err := d.writeLabelsTo(w, options.ExtraLabels, leLabel); err != nil {
   747  		return err
   748  	}
   749  	if _, err := io.WriteString(w, " "); err != nil {
   750  		return err
   751  	}
   752  	if err := val.writeTo(w); err != nil {
   753  		return err
   754  	}
   755  	if _, err := io.WriteString(w, " "); err != nil {
   756  		return err
   757  	}
   758  	if _, err := WriteInteger(w, when.UnixMilli()); err != nil {
   759  		return err
   760  	}
   761  	if _, err := io.WriteString(w, "\n"); err != nil {
   762  		return err
   763  	}
   764  	return nil
   765  }
   766  
   767  // writeTo writes the Data to the given writer in Prometheus format.
   768  func (d *Data) writeTo(w io.Writer, when time.Time, options SnapshotExportOptions, metricsWritten map[string]bool) error {
   769  	switch d.Metric.Type {
   770  	case TypeUntyped, TypeGauge, TypeCounter:
   771  		return d.writeMetricLine(w, "", d.Number, when, options, nil, metricsWritten)
   772  	case TypeHistogram:
   773  		// Write an empty line before and after histograms to easily distinguish them from
   774  		// other metric lines.
   775  		if _, err := io.WriteString(w, "\n"); err != nil {
   776  			return err
   777  		}
   778  		var numSamples uint64
   779  		var samples Number
   780  		for _, bucket := range d.HistogramValue.Buckets {
   781  			numSamples += bucket.Samples
   782  			samples.Int = int64(numSamples) // Prometheus distribution bucket counts are cumulative.
   783  			if err := d.writeMetricLine(w, "_bucket", &samples, when, options, &bucket.UpperBound, metricsWritten); err != nil {
   784  				return err
   785  			}
   786  		}
   787  		if err := d.writeMetricLine(w, "_sum", &d.HistogramValue.Total, when, options, nil, metricsWritten); err != nil {
   788  			return err
   789  		}
   790  		samples.Int = int64(numSamples)
   791  		if err := d.writeMetricLine(w, "_count", &samples, when, options, nil, metricsWritten); err != nil {
   792  			return err
   793  		}
   794  		if err := d.writeMetricLine(w, "_min", &d.HistogramValue.Min, when, options, nil, metricsWritten); err != nil {
   795  			return err
   796  		}
   797  		if err := d.writeMetricLine(w, "_max", &d.HistogramValue.Max, when, options, nil, metricsWritten); err != nil {
   798  			return err
   799  		}
   800  		if err := d.writeMetricLine(w, "_ssd", &d.HistogramValue.SumOfSquaredDeviations, when, options, nil, metricsWritten); err != nil {
   801  			return err
   802  		}
   803  		// Empty line after the histogram.
   804  		if _, err := io.WriteString(w, "\n"); err != nil {
   805  			return err
   806  		}
   807  		return nil
   808  	default:
   809  		return fmt.Errorf("unknown metric type for metric %s: %v", d.Metric.Name, d.Metric.Type)
   810  	}
   811  }
   812  
   813  // Snapshot is a snapshot of the values of all the metrics at a certain point in time.
   814  type Snapshot struct {
   815  	// When is the timestamp at which the snapshot was taken.
   816  	// Note that Prometheus ultimately encodes timestamps as millisecond-precision int64s from epoch.
   817  	When time.Time `json:"when,omitempty"`
   818  
   819  	// Data is the whole snapshot data.
   820  	// Each Data must be a unique combination of (Metric, Labels) within a Snapshot.
   821  	Data []*Data `json:"data,omitempty"`
   822  }
   823  
   824  // NewSnapshot returns a new Snapshot at the current time.
   825  func NewSnapshot() *Snapshot {
   826  	return &Snapshot{When: timeNow()}
   827  }
   828  
   829  // Add data point(s) to the snapshot.
   830  // Returns itself for chainability.
   831  func (s *Snapshot) Add(data ...*Data) *Snapshot {
   832  	s.Data = append(s.Data, data...)
   833  	return s
   834  }
   835  
   836  // countingWriter implements io.Writer, and counts the number of bytes written to it.
   837  // Useful in this file to keep track of total number of bytes without having to plumb this
   838  // everywhere in the writeX() functions in this file.
   839  type countingWriter struct {
   840  	w       *bufio.Writer
   841  	written int
   842  }
   843  
   844  // Write implements io.Writer.Write.
   845  func (w *countingWriter) Write(b []byte) (int, error) {
   846  	written, err := w.w.Write(b)
   847  	w.written += written
   848  	return written, err
   849  }
   850  
   851  // WriteString implements io.StringWriter.WriteString.
   852  // This avoids going into the slow, allocation-heavy path of io.WriteString.
   853  func (w *countingWriter) WriteString(s string) (int, error) {
   854  	written, err := w.w.WriteString(s)
   855  	w.written += written
   856  	return written, err
   857  }
   858  
   859  // Written returns the number of bytes written to the underlying writer (minus buffered writes).
   860  func (w *countingWriter) Written() int {
   861  	return w.written - w.w.Buffered()
   862  }
   863  
   864  // writeSingleMetric writes the data to the given writer in Prometheus format.
   865  // It returns the number of bytes written.
   866  func (s *Snapshot) writeSingleMetric(w io.Writer, options SnapshotExportOptions, metricName string, metricsWritten map[string]bool) error {
   867  	if !strings.HasPrefix(metricName, options.ExporterPrefix) {
   868  		return nil
   869  	}
   870  	wantMetricName := strings.TrimPrefix(metricName, options.ExporterPrefix)
   871  	for _, d := range s.Data {
   872  		if d.Metric.Name != wantMetricName {
   873  			continue
   874  		}
   875  		if err := d.writeTo(w, s.When, options, metricsWritten); err != nil {
   876  			return err
   877  		}
   878  	}
   879  	return nil
   880  }
   881  
   882  // Write writes one or more snapshots to the writer.
   883  // This ensures same-name metrics across different snapshots are printed together, per spec.
   884  func Write(w io.Writer, options ExportOptions, snapshotsToOptions map[*Snapshot]SnapshotExportOptions) (int, error) {
   885  	if len(snapshotsToOptions) == 0 {
   886  		return 0, nil
   887  	}
   888  	cw := &countingWriter{w: bufio.NewWriter(w)}
   889  	if options.CommentHeader != "" {
   890  		for _, commentLine := range strings.Split(options.CommentHeader, "\n") {
   891  			if _, err := io.WriteString(cw, "# "); err != nil {
   892  				return cw.Written(), err
   893  			}
   894  			if _, err := io.WriteString(cw, commentLine); err != nil {
   895  				return cw.Written(), err
   896  			}
   897  			if _, err := io.WriteString(cw, "\n"); err != nil {
   898  				return cw.Written(), err
   899  			}
   900  		}
   901  	}
   902  	snapshots := make([]*Snapshot, 0, len(snapshotsToOptions))
   903  	for snapshot := range snapshotsToOptions {
   904  		snapshots = append(snapshots, snapshot)
   905  	}
   906  	switch len(snapshots) {
   907  	case 1: // Single-snapshot case.
   908  		if _, err := io.WriteString(cw, fmt.Sprintf("# Writing data from snapshot containing %d data points taken at %v.\n", len(snapshots[0].Data), snapshots[0].When)); err != nil {
   909  			return cw.Written(), err
   910  		}
   911  	default: // Multi-snapshot case.
   912  		// Provide a consistent ordering of snapshots.
   913  		sort.Slice(snapshots, func(i, j int) bool {
   914  			return reflect.ValueOf(snapshots[i]).Pointer() < reflect.ValueOf(snapshots[j]).Pointer()
   915  		})
   916  		if _, err := io.WriteString(cw, fmt.Sprintf("# Writing data from %d snapshots:\n", len(snapshots))); err != nil {
   917  			return cw.Written(), err
   918  		}
   919  		for _, snapshot := range snapshots {
   920  			if _, err := io.WriteString(cw, fmt.Sprintf("#   - Snapshot with %d data points taken at %v: %v\n", len(snapshot.Data), snapshot.When, snapshotsToOptions[snapshot].ExtraLabels)); err != nil {
   921  				return cw.Written(), err
   922  			}
   923  		}
   924  	}
   925  	if _, err := io.WriteString(cw, "\n"); err != nil {
   926  		return cw.Written(), err
   927  	}
   928  	if options.MetricsWritten == nil {
   929  		options.MetricsWritten = make(map[string]bool)
   930  	}
   931  	metricNamesMap := make(map[string]bool, len(options.MetricsWritten))
   932  	metricNames := make([]string, 0, len(options.MetricsWritten))
   933  	for _, snapshot := range snapshots {
   934  		for _, data := range snapshot.Data {
   935  			metricName := snapshotsToOptions[snapshot].ExporterPrefix + data.Metric.Name
   936  			if !metricNamesMap[metricName] {
   937  				metricNamesMap[metricName] = true
   938  				metricNames = append(metricNames, metricName)
   939  			}
   940  		}
   941  	}
   942  	sort.Strings(metricNames)
   943  	for _, metricName := range metricNames {
   944  		for _, snapshot := range snapshots {
   945  			snapshot.writeSingleMetric(cw, snapshotsToOptions[snapshot], metricName, options.MetricsWritten)
   946  		}
   947  	}
   948  	if _, err := io.WriteString(cw, "\n# End of metric data.\n"); err != nil {
   949  		return cw.Written(), err
   950  	}
   951  	if err := cw.w.Flush(); err != nil {
   952  		return cw.Written(), err
   953  	}
   954  	return cw.Written(), nil
   955  }