openmetrics_create.go 19 KB

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