text_create.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. // Copyright 2014 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. "fmt"
  17. "io"
  18. "math"
  19. "strconv"
  20. "strings"
  21. "sync"
  22. "github.com/prometheus/common/model"
  23. dto "github.com/prometheus/client_model/go"
  24. )
  25. // enhancedWriter has all the enhanced write functions needed here. bufio.Writer
  26. // implements it.
  27. type enhancedWriter interface {
  28. io.Writer
  29. WriteRune(r rune) (n int, err error)
  30. WriteString(s string) (n int, err error)
  31. WriteByte(c byte) error
  32. }
  33. const (
  34. initialNumBufSize = 24
  35. )
  36. var (
  37. bufPool = sync.Pool{
  38. New: func() interface{} {
  39. return bufio.NewWriter(io.Discard)
  40. },
  41. }
  42. numBufPool = sync.Pool{
  43. New: func() interface{} {
  44. b := make([]byte, 0, initialNumBufSize)
  45. return &b
  46. },
  47. }
  48. )
  49. // MetricFamilyToText converts a MetricFamily proto message into text format and
  50. // writes the resulting lines to 'out'. It returns the number of bytes written
  51. // and any error encountered. The output will have the same order as the input,
  52. // no further sorting is performed. Furthermore, this function assumes the input
  53. // is already sanitized and does not perform any sanity checks. If the input
  54. // contains duplicate metrics or invalid metric or label names, the conversion
  55. // will result in invalid text format output.
  56. //
  57. // If metric names conform to the legacy validation pattern, they will be placed
  58. // outside the brackets in the traditional way, like `foo{}`. If the metric name
  59. // fails the legacy validation check, it will be placed quoted inside the
  60. // brackets: `{"foo"}`. As stated above, the input is assumed to be santized and
  61. // no error will be thrown in this case.
  62. //
  63. // Similar to metric names, if label names conform to the legacy validation
  64. // pattern, they will be unquoted as normal, like `foo{bar="baz"}`. If the label
  65. // name fails the legacy validation check, it will be quoted:
  66. // `foo{"bar"="baz"}`. As stated above, the input is assumed to be santized and
  67. // no error will be thrown in this case.
  68. //
  69. // This method fulfills the type 'prometheus.encoder'.
  70. func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err error) {
  71. // Fail-fast checks.
  72. if len(in.Metric) == 0 {
  73. return 0, fmt.Errorf("MetricFamily has no metrics: %s", in)
  74. }
  75. name := in.GetName()
  76. if name == "" {
  77. return 0, fmt.Errorf("MetricFamily has no name: %s", in)
  78. }
  79. // Try the interface upgrade. If it doesn't work, we'll use a
  80. // bufio.Writer from the sync.Pool.
  81. w, ok := out.(enhancedWriter)
  82. if !ok {
  83. b := bufPool.Get().(*bufio.Writer)
  84. b.Reset(out)
  85. w = b
  86. defer func() {
  87. bErr := b.Flush()
  88. if err == nil {
  89. err = bErr
  90. }
  91. bufPool.Put(b)
  92. }()
  93. }
  94. var n int
  95. // Comments, first HELP, then TYPE.
  96. if in.Help != nil {
  97. n, err = w.WriteString("# HELP ")
  98. written += n
  99. if err != nil {
  100. return
  101. }
  102. n, err = writeName(w, name)
  103. written += n
  104. if err != nil {
  105. return
  106. }
  107. err = w.WriteByte(' ')
  108. written++
  109. if err != nil {
  110. return
  111. }
  112. n, err = writeEscapedString(w, *in.Help, false)
  113. written += n
  114. if err != nil {
  115. return
  116. }
  117. err = w.WriteByte('\n')
  118. written++
  119. if err != nil {
  120. return
  121. }
  122. }
  123. n, err = w.WriteString("# TYPE ")
  124. written += n
  125. if err != nil {
  126. return
  127. }
  128. n, err = writeName(w, name)
  129. written += n
  130. if err != nil {
  131. return
  132. }
  133. metricType := in.GetType()
  134. switch metricType {
  135. case dto.MetricType_COUNTER:
  136. n, err = w.WriteString(" counter\n")
  137. case dto.MetricType_GAUGE:
  138. n, err = w.WriteString(" gauge\n")
  139. case dto.MetricType_SUMMARY:
  140. n, err = w.WriteString(" summary\n")
  141. case dto.MetricType_UNTYPED:
  142. n, err = w.WriteString(" untyped\n")
  143. case dto.MetricType_HISTOGRAM:
  144. n, err = w.WriteString(" histogram\n")
  145. default:
  146. return written, fmt.Errorf("unknown metric type %s", metricType.String())
  147. }
  148. written += n
  149. if err != nil {
  150. return
  151. }
  152. // Finally the samples, one line for each.
  153. for _, metric := range in.Metric {
  154. switch metricType {
  155. case dto.MetricType_COUNTER:
  156. if metric.Counter == nil {
  157. return written, fmt.Errorf(
  158. "expected counter in metric %s %s", name, metric,
  159. )
  160. }
  161. n, err = writeSample(
  162. w, name, "", metric, "", 0,
  163. metric.Counter.GetValue(),
  164. )
  165. case dto.MetricType_GAUGE:
  166. if metric.Gauge == nil {
  167. return written, fmt.Errorf(
  168. "expected gauge in metric %s %s", name, metric,
  169. )
  170. }
  171. n, err = writeSample(
  172. w, name, "", metric, "", 0,
  173. metric.Gauge.GetValue(),
  174. )
  175. case dto.MetricType_UNTYPED:
  176. if metric.Untyped == nil {
  177. return written, fmt.Errorf(
  178. "expected untyped in metric %s %s", name, metric,
  179. )
  180. }
  181. n, err = writeSample(
  182. w, name, "", metric, "", 0,
  183. metric.Untyped.GetValue(),
  184. )
  185. case dto.MetricType_SUMMARY:
  186. if metric.Summary == nil {
  187. return written, fmt.Errorf(
  188. "expected summary in metric %s %s", name, metric,
  189. )
  190. }
  191. for _, q := range metric.Summary.Quantile {
  192. n, err = writeSample(
  193. w, name, "", metric,
  194. model.QuantileLabel, q.GetQuantile(),
  195. q.GetValue(),
  196. )
  197. written += n
  198. if err != nil {
  199. return
  200. }
  201. }
  202. n, err = writeSample(
  203. w, name, "_sum", metric, "", 0,
  204. metric.Summary.GetSampleSum(),
  205. )
  206. written += n
  207. if err != nil {
  208. return
  209. }
  210. n, err = writeSample(
  211. w, name, "_count", metric, "", 0,
  212. float64(metric.Summary.GetSampleCount()),
  213. )
  214. case dto.MetricType_HISTOGRAM:
  215. if metric.Histogram == nil {
  216. return written, fmt.Errorf(
  217. "expected histogram in metric %s %s", name, metric,
  218. )
  219. }
  220. infSeen := false
  221. for _, b := range metric.Histogram.Bucket {
  222. n, err = writeSample(
  223. w, name, "_bucket", metric,
  224. model.BucketLabel, b.GetUpperBound(),
  225. float64(b.GetCumulativeCount()),
  226. )
  227. written += n
  228. if err != nil {
  229. return
  230. }
  231. if math.IsInf(b.GetUpperBound(), +1) {
  232. infSeen = true
  233. }
  234. }
  235. if !infSeen {
  236. n, err = writeSample(
  237. w, name, "_bucket", metric,
  238. model.BucketLabel, math.Inf(+1),
  239. float64(metric.Histogram.GetSampleCount()),
  240. )
  241. written += n
  242. if err != nil {
  243. return
  244. }
  245. }
  246. n, err = writeSample(
  247. w, name, "_sum", metric, "", 0,
  248. metric.Histogram.GetSampleSum(),
  249. )
  250. written += n
  251. if err != nil {
  252. return
  253. }
  254. n, err = writeSample(
  255. w, name, "_count", metric, "", 0,
  256. float64(metric.Histogram.GetSampleCount()),
  257. )
  258. default:
  259. return written, fmt.Errorf(
  260. "unexpected type in metric %s %s", name, metric,
  261. )
  262. }
  263. written += n
  264. if err != nil {
  265. return
  266. }
  267. }
  268. return
  269. }
  270. // writeSample writes a single sample in text format to w, given the metric
  271. // name, the metric proto message itself, optionally an additional label name
  272. // with a float64 value (use empty string as label name if not required), and
  273. // the value. The function returns the number of bytes written and any error
  274. // encountered.
  275. func writeSample(
  276. w enhancedWriter,
  277. name, suffix string,
  278. metric *dto.Metric,
  279. additionalLabelName string, additionalLabelValue float64,
  280. value float64,
  281. ) (int, error) {
  282. written := 0
  283. n, err := writeNameAndLabelPairs(
  284. w, name+suffix, metric.Label, additionalLabelName, additionalLabelValue,
  285. )
  286. written += n
  287. if err != nil {
  288. return written, err
  289. }
  290. err = w.WriteByte(' ')
  291. written++
  292. if err != nil {
  293. return written, err
  294. }
  295. n, err = writeFloat(w, value)
  296. written += n
  297. if err != nil {
  298. return written, err
  299. }
  300. if metric.TimestampMs != nil {
  301. err = w.WriteByte(' ')
  302. written++
  303. if err != nil {
  304. return written, err
  305. }
  306. n, err = writeInt(w, *metric.TimestampMs)
  307. written += n
  308. if err != nil {
  309. return written, err
  310. }
  311. }
  312. err = w.WriteByte('\n')
  313. written++
  314. if err != nil {
  315. return written, err
  316. }
  317. return written, nil
  318. }
  319. // writeNameAndLabelPairs converts a slice of LabelPair proto messages plus the
  320. // explicitly given metric name and additional label pair into text formatted as
  321. // required by the text format and writes it to 'w'. An empty slice in
  322. // combination with an empty string 'additionalLabelName' results in nothing
  323. // being written. Otherwise, the label pairs are written, escaped as required by
  324. // the text format, and enclosed in '{...}'. The function returns the number of
  325. // bytes written and any error encountered. If the metric name is not
  326. // legacy-valid, it will be put inside the brackets as well. Legacy-invalid
  327. // label names will also be quoted.
  328. func writeNameAndLabelPairs(
  329. w enhancedWriter,
  330. name string,
  331. in []*dto.LabelPair,
  332. additionalLabelName string, additionalLabelValue float64,
  333. ) (int, error) {
  334. var (
  335. written int
  336. separator byte = '{'
  337. metricInsideBraces = false
  338. )
  339. if name != "" {
  340. // If the name does not pass the legacy validity check, we must put the
  341. // metric name inside the braces.
  342. if !model.IsValidLegacyMetricName(model.LabelValue(name)) {
  343. metricInsideBraces = true
  344. err := w.WriteByte(separator)
  345. written++
  346. if err != nil {
  347. return written, err
  348. }
  349. separator = ','
  350. }
  351. n, err := writeName(w, name)
  352. written += n
  353. if err != nil {
  354. return written, err
  355. }
  356. }
  357. if len(in) == 0 && additionalLabelName == "" {
  358. if metricInsideBraces {
  359. err := w.WriteByte('}')
  360. written++
  361. if err != nil {
  362. return written, err
  363. }
  364. }
  365. return written, nil
  366. }
  367. for _, lp := range in {
  368. err := w.WriteByte(separator)
  369. written++
  370. if err != nil {
  371. return written, err
  372. }
  373. n, err := writeName(w, lp.GetName())
  374. written += n
  375. if err != nil {
  376. return written, err
  377. }
  378. n, err = w.WriteString(`="`)
  379. written += n
  380. if err != nil {
  381. return written, err
  382. }
  383. n, err = writeEscapedString(w, lp.GetValue(), true)
  384. written += n
  385. if err != nil {
  386. return written, err
  387. }
  388. err = w.WriteByte('"')
  389. written++
  390. if err != nil {
  391. return written, err
  392. }
  393. separator = ','
  394. }
  395. if additionalLabelName != "" {
  396. err := w.WriteByte(separator)
  397. written++
  398. if err != nil {
  399. return written, err
  400. }
  401. n, err := w.WriteString(additionalLabelName)
  402. written += n
  403. if err != nil {
  404. return written, err
  405. }
  406. n, err = w.WriteString(`="`)
  407. written += n
  408. if err != nil {
  409. return written, err
  410. }
  411. n, err = writeFloat(w, additionalLabelValue)
  412. written += n
  413. if err != nil {
  414. return written, err
  415. }
  416. err = w.WriteByte('"')
  417. written++
  418. if err != nil {
  419. return written, err
  420. }
  421. }
  422. err := w.WriteByte('}')
  423. written++
  424. if err != nil {
  425. return written, err
  426. }
  427. return written, nil
  428. }
  429. // writeEscapedString replaces '\' by '\\', new line character by '\n', and - if
  430. // includeDoubleQuote is true - '"' by '\"'.
  431. var (
  432. escaper = strings.NewReplacer("\\", `\\`, "\n", `\n`)
  433. quotedEscaper = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
  434. )
  435. func writeEscapedString(w enhancedWriter, v string, includeDoubleQuote bool) (int, error) {
  436. if includeDoubleQuote {
  437. return quotedEscaper.WriteString(w, v)
  438. }
  439. return escaper.WriteString(w, v)
  440. }
  441. // writeFloat is equivalent to fmt.Fprint with a float64 argument but hardcodes
  442. // a few common cases for increased efficiency. For non-hardcoded cases, it uses
  443. // strconv.AppendFloat to avoid allocations, similar to writeInt.
  444. func writeFloat(w enhancedWriter, f float64) (int, error) {
  445. switch {
  446. case f == 1:
  447. return 1, w.WriteByte('1')
  448. case f == 0:
  449. return 1, w.WriteByte('0')
  450. case f == -1:
  451. return w.WriteString("-1")
  452. case math.IsNaN(f):
  453. return w.WriteString("NaN")
  454. case math.IsInf(f, +1):
  455. return w.WriteString("+Inf")
  456. case math.IsInf(f, -1):
  457. return w.WriteString("-Inf")
  458. default:
  459. bp := numBufPool.Get().(*[]byte)
  460. *bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
  461. written, err := w.Write(*bp)
  462. numBufPool.Put(bp)
  463. return written, err
  464. }
  465. }
  466. // writeInt is equivalent to fmt.Fprint with an int64 argument but uses
  467. // strconv.AppendInt with a byte slice taken from a sync.Pool to avoid
  468. // allocations.
  469. func writeInt(w enhancedWriter, i int64) (int, error) {
  470. bp := numBufPool.Get().(*[]byte)
  471. *bp = strconv.AppendInt((*bp)[:0], i, 10)
  472. written, err := w.Write(*bp)
  473. numBufPool.Put(bp)
  474. return written, err
  475. }
  476. // writeName writes a string as-is if it complies with the legacy naming
  477. // scheme, or escapes it in double quotes if not.
  478. func writeName(w enhancedWriter, name string) (int, error) {
  479. if model.IsValidLegacyMetricName(model.LabelValue(name)) {
  480. return w.WriteString(name)
  481. }
  482. var written int
  483. var err error
  484. err = w.WriteByte('"')
  485. written++
  486. if err != nil {
  487. return written, err
  488. }
  489. var n int
  490. n, err = writeEscapedString(w, name, true)
  491. written += n
  492. if err != nil {
  493. return written, err
  494. }
  495. err = w.WriteByte('"')
  496. written++
  497. return written, err
  498. }