github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/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 coallesced 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  func writeInteger(w io.Writer, val int64) (int, error) {
   244  	const decimalDigits = "0123456789"
   245  	if val == 0 {
   246  		return io.WriteString(w, decimalDigits[0:1])
   247  	}
   248  	var written int
   249  	if val < 0 {
   250  		n, err := io.WriteString(w, "-")
   251  		written += n
   252  		if err != nil {
   253  			return written, err
   254  		}
   255  		val = -val
   256  	}
   257  	decimal := int64(1)
   258  	for ; val/decimal != 0; decimal *= 10 {
   259  	}
   260  	for decimal /= 10; decimal > 0; decimal /= 10 {
   261  		digit := (val / decimal) % 10
   262  		n, err := io.WriteString(w, decimalDigits[digit:digit+1])
   263  		written += n
   264  		if err != nil {
   265  			return written, err
   266  		}
   267  	}
   268  	return written, nil
   269  }
   270  
   271  // writeTo writes the number to the given writer.
   272  // This only causes heap allocations when the number is a non-zero, non-special float.
   273  func (n *Number) writeTo(w io.Writer) error {
   274  	var s string
   275  	switch {
   276  	// Zero case:
   277  	case n.Int == 0 && n.Float == 0:
   278  		s = "0"
   279  
   280  	// Integer case:
   281  	case n.Int != 0:
   282  		_, err := writeInteger(w, n.Int)
   283  		return err
   284  
   285  	// Special float cases:
   286  	case n.Float == math.Inf(-1):
   287  		s = "-Inf"
   288  	case n.Float == math.Inf(1):
   289  		s = "+Inf"
   290  	case math.IsNaN(n.Float):
   291  		s = "NaN"
   292  
   293  	// Regular float case:
   294  	default:
   295  		s = fmt.Sprintf("%f", n.Float)
   296  	}
   297  	_, err := io.WriteString(w, s)
   298  	return err
   299  }
   300  
   301  // Bucket is a single histogram bucket.
   302  type Bucket struct {
   303  	// UpperBound is the upper bound of the bucket.
   304  	// The lower bound of the bucket is the largest UpperBound within other Histogram Buckets that
   305  	// is smaller than this bucket's UpperBound.
   306  	// The bucket with the smallest UpperBound within a Histogram implicitly has -Inf as lower bound.
   307  	// This should be set to +Inf to mark the "last" bucket.
   308  	UpperBound Number `json:"le"`
   309  
   310  	// Samples is the number of samples in the bucket.
   311  	// Note: When exported to Prometheus, they are exported cumulatively, i.e. the count of samples
   312  	// exported in Bucket i is actually sum(histogram.Buckets[j].Samples for 0 <= j <= i).
   313  	Samples uint64 `json:"n,omitempty"`
   314  }
   315  
   316  // Histogram contains data about histogram values.
   317  type Histogram struct {
   318  	// Total is the sum of sample values across all buckets.
   319  	Total Number `json:"total"`
   320  	// Min is the minimum sample ever recorded in this histogram.
   321  	Min Number `json:"min"`
   322  	// Max is the maximum sample ever recorded in this histogram.
   323  	Max Number `json:"max"`
   324  	// SumOfSquaredDeviations is the number of squared deviations of all samples.
   325  	SumOfSquaredDeviations Number `json:"ssd"`
   326  	// Buckets contains per-bucket data.
   327  	// A distribution with n finite-boundary buckets should have n+2 entries here.
   328  	// The 0th entry is the underflow bucket (i.e. the one with -inf as lower bound),
   329  	// and the last aka (n+1)th entry is the overflow bucket (i.e. the one with +inf as upper bound).
   330  	Buckets []Bucket `json:"buckets,omitempty"`
   331  }
   332  
   333  // Data is an observation of the value of a single metric at a certain point in time.
   334  type Data struct {
   335  	// Metric is the metric for which the value is being reported.
   336  	Metric *Metric `json:"metric"`
   337  
   338  	// Labels is a key-value pair representing the labels set on this metric.
   339  	// This may be merged with other labels during export.
   340  	Labels map[string]string `json:"labels,omitempty"`
   341  
   342  	// ExternalLabels are more labels merged together with `Labels`.
   343  	// They can be set using SetExternalLabels.
   344  	// They are useful in the case where a single Data needs labels from two sources:
   345  	// labels specific to this data point (which should be in `Labels`), and labels
   346  	// that are shared between multiple data points (stored in `ExternalLabels`).
   347  	// This avoids allocating unique `Labels` maps for each Data struct, when
   348  	// most of the actual labels would be shared between them.
   349  	ExternalLabels map[string]string `json:"external_labels,omitempty"`
   350  
   351  	// At most one of the fields below may be set.
   352  	// Which one depends on the type of the metric.
   353  
   354  	// Number is used for all numerical types.
   355  	Number *Number `json:"val,omitempty"`
   356  
   357  	// Histogram is used for histogram-typed metrics.
   358  	HistogramValue *Histogram `json:"histogram,omitempty"`
   359  }
   360  
   361  // NewIntData returns a new Data struct with the given metric and value.
   362  func NewIntData(metric *Metric, val int64) *Data {
   363  	return LabeledIntData(metric, nil, val)
   364  }
   365  
   366  // LabeledIntData returns a new Data struct with the given metric, labels, and value.
   367  func LabeledIntData(metric *Metric, labels map[string]string, val int64) *Data {
   368  	return &Data{Metric: metric, Labels: labels, Number: NewInt(val)}
   369  }
   370  
   371  // NewFloatData returns a new Data struct with the given metric and value.
   372  func NewFloatData(metric *Metric, val float64) *Data {
   373  	return LabeledFloatData(metric, nil, val)
   374  }
   375  
   376  // LabeledFloatData returns a new Data struct with the given metric, labels, and value.
   377  func LabeledFloatData(metric *Metric, labels map[string]string, val float64) *Data {
   378  	return &Data{Metric: metric, Labels: labels, Number: NewFloat(val)}
   379  }
   380  
   381  // SetExternalLabels sets d.ExternalLabels. See its docstring for more information.
   382  // Returns `d` for chainability.
   383  func (d *Data) SetExternalLabels(externalLabels map[string]string) *Data {
   384  	d.ExternalLabels = externalLabels
   385  	return d
   386  }
   387  
   388  // ExportOptions contains options that control how metric data is exported in Prometheus format.
   389  type ExportOptions struct {
   390  	// CommentHeader is prepended as a comment before any metric data is exported.
   391  	CommentHeader string
   392  
   393  	// MetricsWritten memoizes written metric preambles (help/type comments)
   394  	// by metric name.
   395  	// If specified, this map can be used to avoid duplicate preambles across multiple snapshots.
   396  	// Note that this map is modified in-place during the writing process.
   397  	MetricsWritten map[string]bool
   398  }
   399  
   400  // SnapshotExportOptions contains options that control how metric data is exported for an
   401  // individual Snapshot.
   402  type SnapshotExportOptions struct {
   403  	// ExporterPrefix is prepended to all metric names.
   404  	ExporterPrefix string
   405  
   406  	// ExtraLabels is added as labels for all metric values.
   407  	ExtraLabels map[string]string
   408  }
   409  
   410  // writeEscapedString writes the given string in quotation marks and with some characters escaped,
   411  // per Prometheus spec. It does this without string allocations.
   412  // If `quoted` is true, quote characters will surround the string, and quote characters within `s`
   413  // will also be escaped.
   414  func writeEscapedString(w io.Writer, s string, quoted bool) (int, error) {
   415  	const (
   416  		quote            = '"'
   417  		backslash        = '\\'
   418  		newline          = '\n'
   419  		quoteStr         = `"`
   420  		escapedQuote     = `\\"`
   421  		escapedBackslash = "\\\\"
   422  		escapedNewline   = "\\\n"
   423  	)
   424  	written := 0
   425  	var n int
   426  	var err error
   427  	if quoted {
   428  		n, err = io.WriteString(w, quoteStr)
   429  		written += n
   430  		if err != nil {
   431  			return written, err
   432  		}
   433  	}
   434  	for _, r := range s {
   435  		switch r {
   436  		case quote:
   437  			if quoted {
   438  				n, err = io.WriteString(w, escapedQuote)
   439  			} else {
   440  				n, err = io.WriteString(w, quoteStr)
   441  			}
   442  		case backslash:
   443  			n, err = io.WriteString(w, escapedBackslash)
   444  		case newline:
   445  			n, err = io.WriteString(w, escapedNewline)
   446  		default:
   447  			n, err = io.WriteString(w, string(r))
   448  		}
   449  		written += n
   450  		if err != nil {
   451  			return written, err
   452  		}
   453  	}
   454  	if quoted {
   455  		n, err = io.WriteString(w, quoteStr)
   456  		written += n
   457  		if err != nil {
   458  			return written, err
   459  		}
   460  	}
   461  	return written, nil
   462  }
   463  
   464  // writeMetricPreambleTo writes the metric name to the io.Writer. It may also
   465  // write unwritten help and type comments of the metric if they haven't been
   466  // written to the io.Writer yet.
   467  func (d *Data) writeMetricPreambleTo(w io.Writer, options SnapshotExportOptions, metricsWritten map[string]bool) error {
   468  	// Metric header, if we haven't printed it yet.
   469  	if !metricsWritten[d.Metric.Name] {
   470  		// Extra newline before each preamble for aesthetic reasons.
   471  		if _, err := io.WriteString(w, "\n"); err != nil {
   472  			return err
   473  		}
   474  		if err := d.Metric.writeHeaderTo(w, options); err != nil {
   475  			return err
   476  		}
   477  		metricsWritten[d.Metric.Name] = true
   478  	}
   479  
   480  	// Metric name.
   481  	if options.ExporterPrefix != "" {
   482  		if _, err := io.WriteString(w, options.ExporterPrefix); err != nil {
   483  			return err
   484  		}
   485  	}
   486  	if _, err := io.WriteString(w, d.Metric.Name); err != nil {
   487  		return err
   488  	}
   489  	return nil
   490  }
   491  
   492  // keyVal is a key-value pair used in the function below.
   493  type keyVal struct{ Key, Value string }
   494  
   495  // sortedIterateLabels iterates through labels and outputs them to `out` in sorted key order,
   496  // or stops when cancelCh is written to. It runs in O(n^2) time but makes no heap allocations.
   497  func sortedIterateLabels(labels map[string]string, out chan<- keyVal, cancelCh <-chan struct{}) {
   498  	defer close(out)
   499  	if len(labels) == 0 {
   500  		return
   501  	}
   502  
   503  	// smallestKey is the smallest key that we've already sent to `out`.
   504  	// It starts as the empty string, which means we haven't sent anything to `out` yet.
   505  	smallestKey := ""
   506  	// Find the smallest key of the whole set and send it out.
   507  	for k := range labels {
   508  		if smallestKey == "" || k < smallestKey {
   509  			smallestKey = k
   510  		}
   511  	}
   512  	select {
   513  	case out <- keyVal{smallestKey, labels[smallestKey]}:
   514  	case <-cancelCh:
   515  		return
   516  	}
   517  
   518  	// Iterate until we've sent as many items as we have as input to the output channel.
   519  	// We start at 1 because the loop above already sent out the smallest key to `out`.
   520  	for numOutput := 1; numOutput < len(labels); numOutput++ {
   521  		// nextSmallestKey is the smallest key that is strictly larger than `smallestKey`.
   522  		nextSmallestKey := ""
   523  		for k := range labels {
   524  			if k > smallestKey && (nextSmallestKey == "" || k < nextSmallestKey) {
   525  				nextSmallestKey = k
   526  			}
   527  		}
   528  
   529  		// Update smallestKey and send it out.
   530  		smallestKey = nextSmallestKey
   531  		select {
   532  		case out <- keyVal{smallestKey, labels[smallestKey]}:
   533  		case <-cancelCh:
   534  			return
   535  		}
   536  	}
   537  }
   538  
   539  // LabelOrError is used in OrderedLabels.
   540  // It represents either a key-value pair, or an error.
   541  type LabelOrError struct {
   542  	Key, Value string
   543  	Error      error
   544  }
   545  
   546  // OrderedLabels streams the list of 'label_key="label_value"' in sorted order, except "le" which is
   547  // a reserved Prometheus label name and should go last.
   548  // If an error is encountered, it is returned as the Error field of LabelOrError, and no further
   549  // messages will be sent on the channel.
   550  func OrderedLabels(labels ...map[string]string) <-chan LabelOrError {
   551  	// This function is quite hot on the metric-rendering path, and its naive "just put all the
   552  	// strings in one map to ensure no dupes it, then in one slice and sort it" approach is very
   553  	// allocation-heavy. This approach is more computation-heavy (it runs in
   554  	// O(len(labels) * len(largest label map))), but the only heap allocations it does is for the
   555  	// following tiny slices and channels. In practice, the number of label maps and the size of
   556  	// each label map is tiny, so this is worth doing despite the theoretically-longer run time.
   557  
   558  	// Initialize the channels we'll use.
   559  	mapChannels := make([]chan keyVal, 0, len(labels))
   560  	lastKeyVal := make([]keyVal, len(labels))
   561  	resultCh := make(chan LabelOrError)
   562  	var cancelCh chan struct{}
   563  	// outputError is a helper function for when we have encountered an error mid-way.
   564  	outputError := func(err error) {
   565  		if cancelCh != nil {
   566  			for range mapChannels {
   567  				cancelCh <- struct{}{}
   568  			}
   569  			close(cancelCh)
   570  		}
   571  		resultCh <- LabelOrError{Error: err}
   572  		close(resultCh)
   573  	}
   574  
   575  	// Verify that no label is the empty string. It's not a valid label name,
   576  	// and we use the empty string later on in the function as a marker of having
   577  	// finished processing all labels from a given label map.
   578  	for _, labelMap := range labels {
   579  		for label := range labelMap {
   580  			if label == "" {
   581  				go outputError(errors.New("got empty-string label"))
   582  				return resultCh
   583  			}
   584  		}
   585  	}
   586  
   587  	// Each label map is processed in its own goroutine,
   588  	// which will stream it back to this function in sorted order.
   589  	cancelCh = make(chan struct{}, len(labels))
   590  	for _, labelMap := range labels {
   591  		ch := make(chan keyVal)
   592  		mapChannels = append(mapChannels, ch)
   593  		go sortedIterateLabels(labelMap, ch, cancelCh)
   594  	}
   595  
   596  	// This goroutine is the meat of this function; it iterates through
   597  	// the results being streamed from each `sortedIterateLabels` goroutine
   598  	// that we spawned earlier, until all of them are exhausted or until we
   599  	// hit an error.
   600  	go func() {
   601  		// The "le" label is special and goes last, not in sorted order.
   602  		// gotLe is the empty string if there is no "le" label,
   603  		// otherwise it's the value of the "le" label.
   604  		var gotLe string
   605  
   606  		// numChannelsLeft tracks the number of channels that are still live.
   607  		for numChannelsLeft := len(mapChannels); numChannelsLeft > 0; {
   608  			// Iterate over all channels and ensure we have the freshest (smallest)
   609  			// label from each of them.
   610  			for i, ch := range mapChannels {
   611  				// A nil channel is one that has been closed.
   612  				if ch == nil {
   613  					continue
   614  				}
   615  				// If we already have the latest value from this channel,
   616  				// keep it there instead of getting a new one,
   617  				if lastKeyVal[i].Key != "" {
   618  					continue
   619  				}
   620  				// Otherwise, get a new label.
   621  				kv, open := <-ch
   622  				if !open {
   623  					// Channel has been closed, no more to read from this one.
   624  					numChannelsLeft--
   625  					mapChannels[i] = nil
   626  					continue
   627  				}
   628  				if kv.Key == "le" {
   629  					if gotLe != "" {
   630  						outputError(errors.New("got duplicate 'le' label"))
   631  						return
   632  					}
   633  					gotLe = kv.Value
   634  					continue
   635  				}
   636  				lastKeyVal[i] = kv
   637  			}
   638  
   639  			// We have one key-value pair from each still-active channel now.
   640  			// Find the smallest one between them.
   641  			smallestKey := ""
   642  			indexForSmallest := -1
   643  			for i, kv := range lastKeyVal {
   644  				if kv.Key == "" {
   645  					continue
   646  				}
   647  				if smallestKey == "" || kv.Key < smallestKey {
   648  					smallestKey = kv.Key
   649  					indexForSmallest = i
   650  				} else if kv.Key == smallestKey {
   651  					outputError(fmt.Errorf("got duplicate label %q", smallestKey))
   652  					return
   653  				}
   654  			}
   655  
   656  			if indexForSmallest == -1 {
   657  				// There are no more key-value pairs to output. We're done.
   658  				break
   659  			}
   660  
   661  			// Output the smallest key-value pairs out of all the channels.
   662  			resultCh <- LabelOrError{
   663  				Key:   smallestKey,
   664  				Value: lastKeyVal[indexForSmallest].Value,
   665  			}
   666  			// Mark the last key-value pair from the channel that gave us the
   667  			// smallest key-value pair as no longer present, so that we get a new
   668  			// key-value pair from it in the next iteration.
   669  			lastKeyVal[indexForSmallest] = keyVal{}
   670  		}
   671  
   672  		// Output the "le" label last.
   673  		if gotLe != "" {
   674  			resultCh <- LabelOrError{
   675  				Key:   "le",
   676  				Value: gotLe,
   677  			}
   678  		}
   679  		close(resultCh)
   680  		close(cancelCh)
   681  	}()
   682  
   683  	return resultCh
   684  }
   685  
   686  // writeLabelsTo writes a set of metric labels.
   687  func (d *Data) writeLabelsTo(w io.Writer, extraLabels map[string]string, leLabel *Number) error {
   688  	if len(d.Labels)+len(d.ExternalLabels)+len(extraLabels) != 0 || leLabel != nil {
   689  		if _, err := io.WriteString(w, "{"); err != nil {
   690  			return err
   691  		}
   692  		var orderedLabels <-chan LabelOrError
   693  		if leLabel != nil {
   694  			orderedLabels = OrderedLabels(d.Labels, d.ExternalLabels, extraLabels, map[string]string{"le": leLabel.String()})
   695  		} else {
   696  			orderedLabels = OrderedLabels(d.Labels, d.ExternalLabels, extraLabels)
   697  		}
   698  		firstLabel := true
   699  		var foundError error
   700  		for labelOrError := range orderedLabels {
   701  			if foundError != nil {
   702  				continue
   703  			}
   704  			if labelOrError.Error != nil {
   705  				foundError = labelOrError.Error
   706  				continue
   707  			}
   708  			if !firstLabel {
   709  				if _, err := io.WriteString(w, ","); err != nil {
   710  					return err
   711  				}
   712  			}
   713  			firstLabel = false
   714  			if _, err := io.WriteString(w, labelOrError.Key); err != nil {
   715  				return err
   716  			}
   717  			if _, err := io.WriteString(w, "="); err != nil {
   718  				return err
   719  			}
   720  			if _, err := writeEscapedString(w, labelOrError.Value, true); err != nil {
   721  				return err
   722  			}
   723  		}
   724  		if foundError != nil {
   725  			return foundError
   726  		}
   727  		if _, err := io.WriteString(w, "}"); err != nil {
   728  			return err
   729  		}
   730  	}
   731  	return nil
   732  }
   733  
   734  // writeMetricLine writes a single line with a single number (val) to w.
   735  func (d *Data) writeMetricLine(w io.Writer, metricSuffix string, val *Number, when time.Time, options SnapshotExportOptions, leLabel *Number, metricsWritten map[string]bool) error {
   736  	if err := d.writeMetricPreambleTo(w, options, metricsWritten); err != nil {
   737  		return err
   738  	}
   739  	if metricSuffix != "" {
   740  		if _, err := io.WriteString(w, metricSuffix); err != nil {
   741  			return err
   742  		}
   743  	}
   744  	if err := d.writeLabelsTo(w, options.ExtraLabels, leLabel); err != nil {
   745  		return err
   746  	}
   747  	if _, err := io.WriteString(w, " "); err != nil {
   748  		return err
   749  	}
   750  	if err := val.writeTo(w); err != nil {
   751  		return err
   752  	}
   753  	if _, err := io.WriteString(w, " "); err != nil {
   754  		return err
   755  	}
   756  	if _, err := writeInteger(w, when.UnixMilli()); err != nil {
   757  		return err
   758  	}
   759  	if _, err := io.WriteString(w, "\n"); err != nil {
   760  		return err
   761  	}
   762  	return nil
   763  }
   764  
   765  // writeTo writes the Data to the given writer in Prometheus format.
   766  func (d *Data) writeTo(w io.Writer, when time.Time, options SnapshotExportOptions, metricsWritten map[string]bool) error {
   767  	switch d.Metric.Type {
   768  	case TypeUntyped, TypeGauge, TypeCounter:
   769  		return d.writeMetricLine(w, "", d.Number, when, options, nil, metricsWritten)
   770  	case TypeHistogram:
   771  		// Write an empty line before and after histograms to easily distinguish them from
   772  		// other metric lines.
   773  		if _, err := io.WriteString(w, "\n"); err != nil {
   774  			return err
   775  		}
   776  		var numSamples uint64
   777  		var samples Number
   778  		for _, bucket := range d.HistogramValue.Buckets {
   779  			numSamples += bucket.Samples
   780  			samples.Int = int64(numSamples) // Prometheus distribution bucket counts are cumulative.
   781  			if err := d.writeMetricLine(w, "_bucket", &samples, when, options, &bucket.UpperBound, metricsWritten); err != nil {
   782  				return err
   783  			}
   784  		}
   785  		if err := d.writeMetricLine(w, "_sum", &d.HistogramValue.Total, when, options, nil, metricsWritten); err != nil {
   786  			return err
   787  		}
   788  		samples.Int = int64(numSamples)
   789  		if err := d.writeMetricLine(w, "_count", &samples, when, options, nil, metricsWritten); err != nil {
   790  			return err
   791  		}
   792  		if err := d.writeMetricLine(w, "_min", &d.HistogramValue.Min, when, options, nil, metricsWritten); err != nil {
   793  			return err
   794  		}
   795  		if err := d.writeMetricLine(w, "_max", &d.HistogramValue.Max, when, options, nil, metricsWritten); err != nil {
   796  			return err
   797  		}
   798  		if err := d.writeMetricLine(w, "_ssd", &d.HistogramValue.SumOfSquaredDeviations, when, options, nil, metricsWritten); err != nil {
   799  			return err
   800  		}
   801  		// Empty line after the histogram.
   802  		if _, err := io.WriteString(w, "\n"); err != nil {
   803  			return err
   804  		}
   805  		return nil
   806  	default:
   807  		return fmt.Errorf("unknown metric type for metric %s: %v", d.Metric.Name, d.Metric.Type)
   808  	}
   809  }
   810  
   811  // Snapshot is a snapshot of the values of all the metrics at a certain point in time.
   812  type Snapshot struct {
   813  	// When is the timestamp at which the snapshot was taken.
   814  	// Note that Prometheus ultimately encodes timestamps as millisecond-precision int64s from epoch.
   815  	When time.Time `json:"when,omitempty"`
   816  
   817  	// Data is the whole snapshot data.
   818  	// Each Data must be a unique combination of (Metric, Labels) within a Snapshot.
   819  	Data []*Data `json:"data,omitempty"`
   820  }
   821  
   822  // NewSnapshot returns a new Snapshot at the current time.
   823  func NewSnapshot() *Snapshot {
   824  	return &Snapshot{When: timeNow()}
   825  }
   826  
   827  // Add data point(s) to the snapshot.
   828  // Returns itself for chainability.
   829  func (s *Snapshot) Add(data ...*Data) *Snapshot {
   830  	s.Data = append(s.Data, data...)
   831  	return s
   832  }
   833  
   834  // countingWriter implements io.Writer, and counts the number of bytes written to it.
   835  // Useful in this file to keep track of total number of bytes without having to plumb this
   836  // everywhere in the writeX() functions in this file.
   837  type countingWriter struct {
   838  	w       *bufio.Writer
   839  	written int
   840  }
   841  
   842  // Write implements io.Writer.Write.
   843  func (w *countingWriter) Write(b []byte) (int, error) {
   844  	written, err := w.w.Write(b)
   845  	w.written += written
   846  	return written, err
   847  }
   848  
   849  // WriteString implements io.StringWriter.WriteString.
   850  // This avoids going into the slow, allocation-heavy path of io.WriteString.
   851  func (w *countingWriter) WriteString(s string) (int, error) {
   852  	written, err := w.w.WriteString(s)
   853  	w.written += written
   854  	return written, err
   855  }
   856  
   857  // Written returns the number of bytes written to the underlying writer (minus buffered writes).
   858  func (w *countingWriter) Written() int {
   859  	return w.written - w.w.Buffered()
   860  }
   861  
   862  // writeSingleMetric writes the data to the given writer in Prometheus format.
   863  // It returns the number of bytes written.
   864  func (s *Snapshot) writeSingleMetric(w io.Writer, options SnapshotExportOptions, metricName string, metricsWritten map[string]bool) error {
   865  	if !strings.HasPrefix(metricName, options.ExporterPrefix) {
   866  		return nil
   867  	}
   868  	wantMetricName := strings.TrimPrefix(metricName, options.ExporterPrefix)
   869  	for _, d := range s.Data {
   870  		if d.Metric.Name != wantMetricName {
   871  			continue
   872  		}
   873  		if err := d.writeTo(w, s.When, options, metricsWritten); err != nil {
   874  			return err
   875  		}
   876  	}
   877  	return nil
   878  }
   879  
   880  // Write writes one or more snapshots to the writer.
   881  // This ensures same-name metrics across different snapshots are printed together, per spec.
   882  func Write(w io.Writer, options ExportOptions, snapshotsToOptions map[*Snapshot]SnapshotExportOptions) (int, error) {
   883  	if len(snapshotsToOptions) == 0 {
   884  		return 0, nil
   885  	}
   886  	cw := &countingWriter{w: bufio.NewWriter(w)}
   887  	if options.CommentHeader != "" {
   888  		for _, commentLine := range strings.Split(options.CommentHeader, "\n") {
   889  			if _, err := io.WriteString(cw, "# "); err != nil {
   890  				return cw.Written(), err
   891  			}
   892  			if _, err := io.WriteString(cw, commentLine); err != nil {
   893  				return cw.Written(), err
   894  			}
   895  			if _, err := io.WriteString(cw, "\n"); err != nil {
   896  				return cw.Written(), err
   897  			}
   898  		}
   899  	}
   900  	snapshots := make([]*Snapshot, 0, len(snapshotsToOptions))
   901  	for snapshot := range snapshotsToOptions {
   902  		snapshots = append(snapshots, snapshot)
   903  	}
   904  	switch len(snapshots) {
   905  	case 1: // Single-snapshot case.
   906  		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 {
   907  			return cw.Written(), err
   908  		}
   909  	default: // Multi-snapshot case.
   910  		// Provide a consistent ordering of snapshots.
   911  		sort.Slice(snapshots, func(i, j int) bool {
   912  			return reflect.ValueOf(snapshots[i]).Pointer() < reflect.ValueOf(snapshots[j]).Pointer()
   913  		})
   914  		if _, err := io.WriteString(cw, fmt.Sprintf("# Writing data from %d snapshots:\n", len(snapshots))); err != nil {
   915  			return cw.Written(), err
   916  		}
   917  		for _, snapshot := range snapshots {
   918  			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 {
   919  				return cw.Written(), err
   920  			}
   921  		}
   922  	}
   923  	if _, err := io.WriteString(cw, "\n"); err != nil {
   924  		return cw.Written(), err
   925  	}
   926  	if options.MetricsWritten == nil {
   927  		options.MetricsWritten = make(map[string]bool)
   928  	}
   929  	metricNamesMap := make(map[string]bool, len(options.MetricsWritten))
   930  	metricNames := make([]string, 0, len(options.MetricsWritten))
   931  	for _, snapshot := range snapshots {
   932  		for _, data := range snapshot.Data {
   933  			metricName := snapshotsToOptions[snapshot].ExporterPrefix + data.Metric.Name
   934  			if !metricNamesMap[metricName] {
   935  				metricNamesMap[metricName] = true
   936  				metricNames = append(metricNames, metricName)
   937  			}
   938  		}
   939  	}
   940  	sort.Strings(metricNames)
   941  	for _, metricName := range metricNames {
   942  		for _, snapshot := range snapshots {
   943  			snapshot.writeSingleMetric(cw, snapshotsToOptions[snapshot], metricName, options.MetricsWritten)
   944  		}
   945  	}
   946  	if _, err := io.WriteString(cw, "\n# End of metric data.\n"); err != nil {
   947  		return cw.Written(), err
   948  	}
   949  	if err := cw.w.Flush(); err != nil {
   950  		return cw.Written(), err
   951  	}
   952  	return cw.Written(), nil
   953  }