...

Source file src/github.com/prometheus/common/expfmt/openmetrics_create.go

Documentation: github.com/prometheus/common/expfmt

     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  	"google.golang.org/protobuf/types/known/timestamppb"
    26  
    27  	"github.com/prometheus/common/model"
    28  
    29  	dto "github.com/prometheus/client_model/go"
    30  )
    31  
    32  type encoderOption struct {
    33  	withCreatedLines bool
    34  	withUnit         bool
    35  }
    36  
    37  type EncoderOption func(*encoderOption)
    38  
    39  // WithCreatedLines is an EncoderOption that configures the OpenMetrics encoder
    40  // to include _created lines (See
    41  // https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#counter-1).
    42  // Created timestamps can improve the accuracy of series reset detection, but
    43  // come with a bandwidth cost.
    44  //
    45  // At the time of writing, created timestamp ingestion is still experimental in
    46  // Prometheus and need to be enabled with the feature-flag
    47  // `--feature-flag=created-timestamp-zero-ingestion`, and breaking changes are
    48  // still possible. Therefore, it is recommended to use this feature with caution.
    49  func WithCreatedLines() EncoderOption {
    50  	return func(t *encoderOption) {
    51  		t.withCreatedLines = true
    52  	}
    53  }
    54  
    55  // WithUnit is an EncoderOption enabling a set unit to be written to the output
    56  // and to be added to the metric name, if it's not there already, as a suffix.
    57  // Without opting in this way, the unit will not be added to the metric name and,
    58  // on top of that, the unit will not be passed onto the output, even if it
    59  // were declared in the *dto.MetricFamily struct, i.e. even if in.Unit !=nil.
    60  func WithUnit() EncoderOption {
    61  	return func(t *encoderOption) {
    62  		t.withUnit = true
    63  	}
    64  }
    65  
    66  // MetricFamilyToOpenMetrics converts a MetricFamily proto message into the
    67  // OpenMetrics text format and writes the resulting lines to 'out'. It returns
    68  // the number of bytes written and any error encountered. The output will have
    69  // the same order as the input, no further sorting is performed. Furthermore,
    70  // this function assumes the input is already sanitized and does not perform any
    71  // sanity checks. If the input contains duplicate metrics or invalid metric or
    72  // label names, the conversion will result in invalid text format output.
    73  //
    74  // If metric names conform to the legacy validation pattern, they will be placed
    75  // outside the brackets in the traditional way, like `foo{}`. If the metric name
    76  // fails the legacy validation check, it will be placed quoted inside the
    77  // brackets: `{"foo"}`. As stated above, the input is assumed to be santized and
    78  // no error will be thrown in this case.
    79  //
    80  // Similar to metric names, if label names conform to the legacy validation
    81  // pattern, they will be unquoted as normal, like `foo{bar="baz"}`. If the label
    82  // name fails the legacy validation check, it will be quoted:
    83  // `foo{"bar"="baz"}`. As stated above, the input is assumed to be santized and
    84  // no error will be thrown in this case.
    85  //
    86  // This function fulfills the type 'expfmt.encoder'.
    87  //
    88  // Note that OpenMetrics requires a final `# EOF` line. Since this function acts
    89  // on individual metric families, it is the responsibility of the caller to
    90  // append this line to 'out' once all metric families have been written.
    91  // Conveniently, this can be done by calling FinalizeOpenMetrics.
    92  //
    93  // The output should be fully OpenMetrics compliant. However, there are a few
    94  // missing features and peculiarities to avoid complications when switching from
    95  // Prometheus to OpenMetrics or vice versa:
    96  //
    97  //   - Counters are expected to have the `_total` suffix in their metric name. In
    98  //     the output, the suffix will be truncated from the `# TYPE`, `# HELP` and `# UNIT`
    99  //     lines. A counter with a missing `_total` suffix is not an error. However,
   100  //     its type will be set to `unknown` in that case to avoid invalid OpenMetrics
   101  //     output.
   102  //
   103  //   - According to the OM specs, the `# UNIT` line is optional, but if populated,
   104  //     the unit has to be present in the metric name as its suffix:
   105  //     (see https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#unit).
   106  //     However, in order to accommodate any potential scenario where such a change in the
   107  //     metric name is not desirable, the users are here given the choice of either explicitly
   108  //     opt in, in case they wish for the unit to be included in the output AND in the metric name
   109  //     as a suffix (see the description of the WithUnit function above),
   110  //     or not to opt in, in case they don't want for any of that to happen.
   111  //
   112  //   - No support for the following (optional) features: info type,
   113  //     stateset type, gaugehistogram type.
   114  //
   115  //   - The size of exemplar labels is not checked (i.e. it's possible to create
   116  //     exemplars that are larger than allowed by the OpenMetrics specification).
   117  //
   118  //   - The value of Counters is not checked. (OpenMetrics doesn't allow counters
   119  //     with a `NaN` value.)
   120  func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...EncoderOption) (written int, err error) {
   121  	toOM := encoderOption{}
   122  	for _, option := range options {
   123  		option(&toOM)
   124  	}
   125  
   126  	name := in.GetName()
   127  	if name == "" {
   128  		return 0, fmt.Errorf("MetricFamily has no name: %s", in)
   129  	}
   130  
   131  	// Try the interface upgrade. If it doesn't work, we'll use a
   132  	// bufio.Writer from the sync.Pool.
   133  	w, ok := out.(enhancedWriter)
   134  	if !ok {
   135  		b := bufPool.Get().(*bufio.Writer)
   136  		b.Reset(out)
   137  		w = b
   138  		defer func() {
   139  			bErr := b.Flush()
   140  			if err == nil {
   141  				err = bErr
   142  			}
   143  			bufPool.Put(b)
   144  		}()
   145  	}
   146  
   147  	var (
   148  		n             int
   149  		metricType    = in.GetType()
   150  		compliantName = name
   151  	)
   152  	if metricType == dto.MetricType_COUNTER && strings.HasSuffix(compliantName, "_total") {
   153  		compliantName = name[:len(name)-6]
   154  	}
   155  	if toOM.withUnit && in.Unit != nil && !strings.HasSuffix(compliantName, fmt.Sprintf("_%s", *in.Unit)) {
   156  		compliantName = compliantName + fmt.Sprintf("_%s", *in.Unit)
   157  	}
   158  
   159  	// Comments, first HELP, then TYPE.
   160  	if in.Help != nil {
   161  		n, err = w.WriteString("# HELP ")
   162  		written += n
   163  		if err != nil {
   164  			return
   165  		}
   166  		n, err = writeName(w, compliantName)
   167  		written += n
   168  		if err != nil {
   169  			return
   170  		}
   171  		err = w.WriteByte(' ')
   172  		written++
   173  		if err != nil {
   174  			return
   175  		}
   176  		n, err = writeEscapedString(w, *in.Help, true)
   177  		written += n
   178  		if err != nil {
   179  			return
   180  		}
   181  		err = w.WriteByte('\n')
   182  		written++
   183  		if err != nil {
   184  			return
   185  		}
   186  	}
   187  	n, err = w.WriteString("# TYPE ")
   188  	written += n
   189  	if err != nil {
   190  		return
   191  	}
   192  	n, err = writeName(w, compliantName)
   193  	written += n
   194  	if err != nil {
   195  		return
   196  	}
   197  	switch metricType {
   198  	case dto.MetricType_COUNTER:
   199  		if strings.HasSuffix(name, "_total") {
   200  			n, err = w.WriteString(" counter\n")
   201  		} else {
   202  			n, err = w.WriteString(" unknown\n")
   203  		}
   204  	case dto.MetricType_GAUGE:
   205  		n, err = w.WriteString(" gauge\n")
   206  	case dto.MetricType_SUMMARY:
   207  		n, err = w.WriteString(" summary\n")
   208  	case dto.MetricType_UNTYPED:
   209  		n, err = w.WriteString(" unknown\n")
   210  	case dto.MetricType_HISTOGRAM:
   211  		n, err = w.WriteString(" histogram\n")
   212  	default:
   213  		return written, fmt.Errorf("unknown metric type %s", metricType.String())
   214  	}
   215  	written += n
   216  	if err != nil {
   217  		return
   218  	}
   219  	if toOM.withUnit && in.Unit != nil {
   220  		n, err = w.WriteString("# UNIT ")
   221  		written += n
   222  		if err != nil {
   223  			return
   224  		}
   225  		n, err = writeName(w, compliantName)
   226  		written += n
   227  		if err != nil {
   228  			return
   229  		}
   230  
   231  		err = w.WriteByte(' ')
   232  		written++
   233  		if err != nil {
   234  			return
   235  		}
   236  		n, err = writeEscapedString(w, *in.Unit, true)
   237  		written += n
   238  		if err != nil {
   239  			return
   240  		}
   241  		err = w.WriteByte('\n')
   242  		written++
   243  		if err != nil {
   244  			return
   245  		}
   246  	}
   247  
   248  	var createdTsBytesWritten int
   249  
   250  	// Finally the samples, one line for each.
   251  	if metricType == dto.MetricType_COUNTER && strings.HasSuffix(name, "_total") {
   252  		compliantName = compliantName + "_total"
   253  	}
   254  	for _, metric := range in.Metric {
   255  		switch metricType {
   256  		case dto.MetricType_COUNTER:
   257  			if metric.Counter == nil {
   258  				return written, fmt.Errorf(
   259  					"expected counter in metric %s %s", compliantName, metric,
   260  				)
   261  			}
   262  			n, err = writeOpenMetricsSample(
   263  				w, compliantName, "", metric, "", 0,
   264  				metric.Counter.GetValue(), 0, false,
   265  				metric.Counter.Exemplar,
   266  			)
   267  			if toOM.withCreatedLines && metric.Counter.CreatedTimestamp != nil {
   268  				createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "_total", metric, "", 0, metric.Counter.GetCreatedTimestamp())
   269  				n += createdTsBytesWritten
   270  			}
   271  		case dto.MetricType_GAUGE:
   272  			if metric.Gauge == nil {
   273  				return written, fmt.Errorf(
   274  					"expected gauge in metric %s %s", compliantName, metric,
   275  				)
   276  			}
   277  			n, err = writeOpenMetricsSample(
   278  				w, compliantName, "", metric, "", 0,
   279  				metric.Gauge.GetValue(), 0, false,
   280  				nil,
   281  			)
   282  		case dto.MetricType_UNTYPED:
   283  			if metric.Untyped == nil {
   284  				return written, fmt.Errorf(
   285  					"expected untyped in metric %s %s", compliantName, metric,
   286  				)
   287  			}
   288  			n, err = writeOpenMetricsSample(
   289  				w, compliantName, "", metric, "", 0,
   290  				metric.Untyped.GetValue(), 0, false,
   291  				nil,
   292  			)
   293  		case dto.MetricType_SUMMARY:
   294  			if metric.Summary == nil {
   295  				return written, fmt.Errorf(
   296  					"expected summary in metric %s %s", compliantName, metric,
   297  				)
   298  			}
   299  			for _, q := range metric.Summary.Quantile {
   300  				n, err = writeOpenMetricsSample(
   301  					w, compliantName, "", metric,
   302  					model.QuantileLabel, q.GetQuantile(),
   303  					q.GetValue(), 0, false,
   304  					nil,
   305  				)
   306  				written += n
   307  				if err != nil {
   308  					return
   309  				}
   310  			}
   311  			n, err = writeOpenMetricsSample(
   312  				w, compliantName, "_sum", metric, "", 0,
   313  				metric.Summary.GetSampleSum(), 0, false,
   314  				nil,
   315  			)
   316  			written += n
   317  			if err != nil {
   318  				return
   319  			}
   320  			n, err = writeOpenMetricsSample(
   321  				w, compliantName, "_count", metric, "", 0,
   322  				0, metric.Summary.GetSampleCount(), true,
   323  				nil,
   324  			)
   325  			if toOM.withCreatedLines && metric.Summary.CreatedTimestamp != nil {
   326  				createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "", metric, "", 0, metric.Summary.GetCreatedTimestamp())
   327  				n += createdTsBytesWritten
   328  			}
   329  		case dto.MetricType_HISTOGRAM:
   330  			if metric.Histogram == nil {
   331  				return written, fmt.Errorf(
   332  					"expected histogram in metric %s %s", compliantName, metric,
   333  				)
   334  			}
   335  			infSeen := false
   336  			for _, b := range metric.Histogram.Bucket {
   337  				n, err = writeOpenMetricsSample(
   338  					w, compliantName, "_bucket", metric,
   339  					model.BucketLabel, b.GetUpperBound(),
   340  					0, b.GetCumulativeCount(), true,
   341  					b.Exemplar,
   342  				)
   343  				written += n
   344  				if err != nil {
   345  					return
   346  				}
   347  				if math.IsInf(b.GetUpperBound(), +1) {
   348  					infSeen = true
   349  				}
   350  			}
   351  			if !infSeen {
   352  				n, err = writeOpenMetricsSample(
   353  					w, compliantName, "_bucket", metric,
   354  					model.BucketLabel, math.Inf(+1),
   355  					0, metric.Histogram.GetSampleCount(), true,
   356  					nil,
   357  				)
   358  				written += n
   359  				if err != nil {
   360  					return
   361  				}
   362  			}
   363  			n, err = writeOpenMetricsSample(
   364  				w, compliantName, "_sum", metric, "", 0,
   365  				metric.Histogram.GetSampleSum(), 0, false,
   366  				nil,
   367  			)
   368  			written += n
   369  			if err != nil {
   370  				return
   371  			}
   372  			n, err = writeOpenMetricsSample(
   373  				w, compliantName, "_count", metric, "", 0,
   374  				0, metric.Histogram.GetSampleCount(), true,
   375  				nil,
   376  			)
   377  			if toOM.withCreatedLines && metric.Histogram.CreatedTimestamp != nil {
   378  				createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "", metric, "", 0, metric.Histogram.GetCreatedTimestamp())
   379  				n += createdTsBytesWritten
   380  			}
   381  		default:
   382  			return written, fmt.Errorf(
   383  				"unexpected type in metric %s %s", compliantName, metric,
   384  			)
   385  		}
   386  		written += n
   387  		if err != nil {
   388  			return
   389  		}
   390  	}
   391  	return
   392  }
   393  
   394  // FinalizeOpenMetrics writes the final `# EOF\n` line required by OpenMetrics.
   395  func FinalizeOpenMetrics(w io.Writer) (written int, err error) {
   396  	return w.Write([]byte("# EOF\n"))
   397  }
   398  
   399  // writeOpenMetricsSample writes a single sample in OpenMetrics text format to
   400  // w, given the metric name, the metric proto message itself, optionally an
   401  // additional label name with a float64 value (use empty string as label name if
   402  // not required), the value (optionally as float64 or uint64, determined by
   403  // useIntValue), and optionally an exemplar (use nil if not required). The
   404  // function returns the number of bytes written and any error encountered.
   405  func writeOpenMetricsSample(
   406  	w enhancedWriter,
   407  	name, suffix string,
   408  	metric *dto.Metric,
   409  	additionalLabelName string, additionalLabelValue float64,
   410  	floatValue float64, intValue uint64, useIntValue bool,
   411  	exemplar *dto.Exemplar,
   412  ) (int, error) {
   413  	written := 0
   414  	n, err := writeOpenMetricsNameAndLabelPairs(
   415  		w, name+suffix, metric.Label, additionalLabelName, additionalLabelValue,
   416  	)
   417  	written += n
   418  	if err != nil {
   419  		return written, err
   420  	}
   421  	err = w.WriteByte(' ')
   422  	written++
   423  	if err != nil {
   424  		return written, err
   425  	}
   426  	if useIntValue {
   427  		n, err = writeUint(w, intValue)
   428  	} else {
   429  		n, err = writeOpenMetricsFloat(w, floatValue)
   430  	}
   431  	written += n
   432  	if err != nil {
   433  		return written, err
   434  	}
   435  	if metric.TimestampMs != nil {
   436  		err = w.WriteByte(' ')
   437  		written++
   438  		if err != nil {
   439  			return written, err
   440  		}
   441  		// TODO(beorn7): Format this directly without converting to a float first.
   442  		n, err = writeOpenMetricsFloat(w, float64(*metric.TimestampMs)/1000)
   443  		written += n
   444  		if err != nil {
   445  			return written, err
   446  		}
   447  	}
   448  	if exemplar != nil && len(exemplar.Label) > 0 {
   449  		n, err = writeExemplar(w, exemplar)
   450  		written += n
   451  		if err != nil {
   452  			return written, err
   453  		}
   454  	}
   455  	err = w.WriteByte('\n')
   456  	written++
   457  	if err != nil {
   458  		return written, err
   459  	}
   460  	return written, nil
   461  }
   462  
   463  // writeOpenMetricsNameAndLabelPairs works like writeOpenMetricsSample but
   464  // formats the float in OpenMetrics style.
   465  func writeOpenMetricsNameAndLabelPairs(
   466  	w enhancedWriter,
   467  	name string,
   468  	in []*dto.LabelPair,
   469  	additionalLabelName string, additionalLabelValue float64,
   470  ) (int, error) {
   471  	var (
   472  		written            int
   473  		separator          byte = '{'
   474  		metricInsideBraces      = false
   475  	)
   476  
   477  	if name != "" {
   478  		// If the name does not pass the legacy validity check, we must put the
   479  		// metric name inside the braces, quoted.
   480  		if !model.IsValidLegacyMetricName(model.LabelValue(name)) {
   481  			metricInsideBraces = true
   482  			err := w.WriteByte(separator)
   483  			written++
   484  			if err != nil {
   485  				return written, err
   486  			}
   487  			separator = ','
   488  		}
   489  
   490  		n, err := writeName(w, name)
   491  		written += n
   492  		if err != nil {
   493  			return written, err
   494  		}
   495  	}
   496  
   497  	if len(in) == 0 && additionalLabelName == "" {
   498  		if metricInsideBraces {
   499  			err := w.WriteByte('}')
   500  			written++
   501  			if err != nil {
   502  				return written, err
   503  			}
   504  		}
   505  		return written, nil
   506  	}
   507  
   508  	for _, lp := range in {
   509  		err := w.WriteByte(separator)
   510  		written++
   511  		if err != nil {
   512  			return written, err
   513  		}
   514  		n, err := writeName(w, lp.GetName())
   515  		written += n
   516  		if err != nil {
   517  			return written, err
   518  		}
   519  		n, err = w.WriteString(`="`)
   520  		written += n
   521  		if err != nil {
   522  			return written, err
   523  		}
   524  		n, err = writeEscapedString(w, lp.GetValue(), true)
   525  		written += n
   526  		if err != nil {
   527  			return written, err
   528  		}
   529  		err = w.WriteByte('"')
   530  		written++
   531  		if err != nil {
   532  			return written, err
   533  		}
   534  		separator = ','
   535  	}
   536  	if additionalLabelName != "" {
   537  		err := w.WriteByte(separator)
   538  		written++
   539  		if err != nil {
   540  			return written, err
   541  		}
   542  		n, err := w.WriteString(additionalLabelName)
   543  		written += n
   544  		if err != nil {
   545  			return written, err
   546  		}
   547  		n, err = w.WriteString(`="`)
   548  		written += n
   549  		if err != nil {
   550  			return written, err
   551  		}
   552  		n, err = writeOpenMetricsFloat(w, additionalLabelValue)
   553  		written += n
   554  		if err != nil {
   555  			return written, err
   556  		}
   557  		err = w.WriteByte('"')
   558  		written++
   559  		if err != nil {
   560  			return written, err
   561  		}
   562  	}
   563  	err := w.WriteByte('}')
   564  	written++
   565  	if err != nil {
   566  		return written, err
   567  	}
   568  	return written, nil
   569  }
   570  
   571  // writeOpenMetricsCreated writes the created timestamp for a single time series
   572  // following OpenMetrics text format to w, given the metric name, the metric proto
   573  // message itself, optionally a suffix to be removed, e.g. '_total' for counters,
   574  // an additional label name with a float64 value (use empty string as label name if
   575  // not required) and the timestamp that represents the created timestamp.
   576  // The function returns the number of bytes written and any error encountered.
   577  func writeOpenMetricsCreated(w enhancedWriter,
   578  	name, suffixToTrim string, metric *dto.Metric,
   579  	additionalLabelName string, additionalLabelValue float64,
   580  	createdTimestamp *timestamppb.Timestamp,
   581  ) (int, error) {
   582  	written := 0
   583  	n, err := writeOpenMetricsNameAndLabelPairs(
   584  		w, strings.TrimSuffix(name, suffixToTrim)+"_created", metric.Label, additionalLabelName, additionalLabelValue,
   585  	)
   586  	written += n
   587  	if err != nil {
   588  		return written, err
   589  	}
   590  
   591  	err = w.WriteByte(' ')
   592  	written++
   593  	if err != nil {
   594  		return written, err
   595  	}
   596  
   597  	// TODO(beorn7): Format this directly from components of ts to
   598  	// avoid overflow/underflow and precision issues of the float
   599  	// conversion.
   600  	n, err = writeOpenMetricsFloat(w, float64(createdTimestamp.AsTime().UnixNano())/1e9)
   601  	written += n
   602  	if err != nil {
   603  		return written, err
   604  	}
   605  
   606  	err = w.WriteByte('\n')
   607  	written++
   608  	if err != nil {
   609  		return written, err
   610  	}
   611  	return written, nil
   612  }
   613  
   614  // writeExemplar writes the provided exemplar in OpenMetrics format to w. The
   615  // function returns the number of bytes written and any error encountered.
   616  func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) {
   617  	written := 0
   618  	n, err := w.WriteString(" # ")
   619  	written += n
   620  	if err != nil {
   621  		return written, err
   622  	}
   623  	n, err = writeOpenMetricsNameAndLabelPairs(w, "", e.Label, "", 0)
   624  	written += n
   625  	if err != nil {
   626  		return written, err
   627  	}
   628  	err = w.WriteByte(' ')
   629  	written++
   630  	if err != nil {
   631  		return written, err
   632  	}
   633  	n, err = writeOpenMetricsFloat(w, e.GetValue())
   634  	written += n
   635  	if err != nil {
   636  		return written, err
   637  	}
   638  	if e.Timestamp != nil {
   639  		err = w.WriteByte(' ')
   640  		written++
   641  		if err != nil {
   642  			return written, err
   643  		}
   644  		err = (*e).Timestamp.CheckValid()
   645  		if err != nil {
   646  			return written, err
   647  		}
   648  		ts := (*e).Timestamp.AsTime()
   649  		// TODO(beorn7): Format this directly from components of ts to
   650  		// avoid overflow/underflow and precision issues of the float
   651  		// conversion.
   652  		n, err = writeOpenMetricsFloat(w, float64(ts.UnixNano())/1e9)
   653  		written += n
   654  		if err != nil {
   655  			return written, err
   656  		}
   657  	}
   658  	return written, nil
   659  }
   660  
   661  // writeOpenMetricsFloat works like writeFloat but appends ".0" if the resulting
   662  // number would otherwise contain neither a "." nor an "e".
   663  func writeOpenMetricsFloat(w enhancedWriter, f float64) (int, error) {
   664  	switch {
   665  	case f == 1:
   666  		return w.WriteString("1.0")
   667  	case f == 0:
   668  		return w.WriteString("0.0")
   669  	case f == -1:
   670  		return w.WriteString("-1.0")
   671  	case math.IsNaN(f):
   672  		return w.WriteString("NaN")
   673  	case math.IsInf(f, +1):
   674  		return w.WriteString("+Inf")
   675  	case math.IsInf(f, -1):
   676  		return w.WriteString("-Inf")
   677  	default:
   678  		bp := numBufPool.Get().(*[]byte)
   679  		*bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
   680  		if !bytes.ContainsAny(*bp, "e.") {
   681  			*bp = append(*bp, '.', '0')
   682  		}
   683  		written, err := w.Write(*bp)
   684  		numBufPool.Put(bp)
   685  		return written, err
   686  	}
   687  }
   688  
   689  // writeUint is like writeInt just for uint64.
   690  func writeUint(w enhancedWriter, u uint64) (int, error) {
   691  	bp := numBufPool.Get().(*[]byte)
   692  	*bp = strconv.AppendUint((*bp)[:0], u, 10)
   693  	written, err := w.Write(*bp)
   694  	numBufPool.Put(bp)
   695  	return written, err
   696  }
   697  

View as plain text