You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

528 lines
13 KiB

  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. "github.com/prometheus/common/model"
  23. dto "github.com/prometheus/client_model/go"
  24. )
  25. // MetricFamilyToOpenMetrics converts a MetricFamily proto message into the
  26. // OpenMetrics text format and writes the resulting lines to 'out'. It returns
  27. // the number of bytes written and any error encountered. The output will have
  28. // the same order as the input, no further sorting is performed. Furthermore,
  29. // this function assumes the input is already sanitized and does not perform any
  30. // sanity checks. If the input contains duplicate metrics or invalid metric or
  31. // label names, the conversion will result in invalid text format output.
  32. //
  33. // This function fulfills the type 'expfmt.encoder'.
  34. //
  35. // Note that OpenMetrics requires a final `# EOF` line. Since this function acts
  36. // on individual metric families, it is the responsibility of the caller to
  37. // append this line to 'out' once all metric families have been written.
  38. // Conveniently, this can be done by calling FinalizeOpenMetrics.
  39. //
  40. // The output should be fully OpenMetrics compliant. However, there are a few
  41. // missing features and peculiarities to avoid complications when switching from
  42. // Prometheus to OpenMetrics or vice versa:
  43. //
  44. // - Counters are expected to have the `_total` suffix in their metric name. In
  45. // the output, the suffix will be truncated from the `# TYPE` and `# HELP`
  46. // line. A counter with a missing `_total` suffix is not an error. However,
  47. // its type will be set to `unknown` in that case to avoid invalid OpenMetrics
  48. // output.
  49. //
  50. // - No support for the following (optional) features: `# UNIT` line, `_created`
  51. // line, info type, stateset type, gaugehistogram type.
  52. //
  53. // - The size of exemplar labels is not checked (i.e. it's possible to create
  54. // exemplars that are larger than allowed by the OpenMetrics specification).
  55. //
  56. // - The value of Counters is not checked. (OpenMetrics doesn't allow counters
  57. // with a `NaN` value.)
  58. func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int, err error) {
  59. name := in.GetName()
  60. if name == "" {
  61. return 0, fmt.Errorf("MetricFamily has no name: %s", in)
  62. }
  63. // Try the interface upgrade. If it doesn't work, we'll use a
  64. // bufio.Writer from the sync.Pool.
  65. w, ok := out.(enhancedWriter)
  66. if !ok {
  67. b := bufPool.Get().(*bufio.Writer)
  68. b.Reset(out)
  69. w = b
  70. defer func() {
  71. bErr := b.Flush()
  72. if err == nil {
  73. err = bErr
  74. }
  75. bufPool.Put(b)
  76. }()
  77. }
  78. var (
  79. n int
  80. metricType = in.GetType()
  81. shortName = name
  82. )
  83. if metricType == dto.MetricType_COUNTER && strings.HasSuffix(shortName, "_total") {
  84. shortName = name[:len(name)-6]
  85. }
  86. // Comments, first HELP, then TYPE.
  87. if in.Help != nil {
  88. n, err = w.WriteString("# HELP ")
  89. written += n
  90. if err != nil {
  91. return
  92. }
  93. n, err = w.WriteString(shortName)
  94. written += n
  95. if err != nil {
  96. return
  97. }
  98. err = w.WriteByte(' ')
  99. written++
  100. if err != nil {
  101. return
  102. }
  103. n, err = writeEscapedString(w, *in.Help, true)
  104. written += n
  105. if err != nil {
  106. return
  107. }
  108. err = w.WriteByte('\n')
  109. written++
  110. if err != nil {
  111. return
  112. }
  113. }
  114. n, err = w.WriteString("# TYPE ")
  115. written += n
  116. if err != nil {
  117. return
  118. }
  119. n, err = w.WriteString(shortName)
  120. written += n
  121. if err != nil {
  122. return
  123. }
  124. switch metricType {
  125. case dto.MetricType_COUNTER:
  126. if strings.HasSuffix(name, "_total") {
  127. n, err = w.WriteString(" counter\n")
  128. } else {
  129. n, err = w.WriteString(" unknown\n")
  130. }
  131. case dto.MetricType_GAUGE:
  132. n, err = w.WriteString(" gauge\n")
  133. case dto.MetricType_SUMMARY:
  134. n, err = w.WriteString(" summary\n")
  135. case dto.MetricType_UNTYPED:
  136. n, err = w.WriteString(" unknown\n")
  137. case dto.MetricType_HISTOGRAM:
  138. n, err = w.WriteString(" histogram\n")
  139. default:
  140. return written, fmt.Errorf("unknown metric type %s", metricType.String())
  141. }
  142. written += n
  143. if err != nil {
  144. return
  145. }
  146. // Finally the samples, one line for each.
  147. for _, metric := range in.Metric {
  148. switch metricType {
  149. case dto.MetricType_COUNTER:
  150. if metric.Counter == nil {
  151. return written, fmt.Errorf(
  152. "expected counter in metric %s %s", name, metric,
  153. )
  154. }
  155. // Note that we have ensured above that either the name
  156. // ends on `_total` or that the rendered type is
  157. // `unknown`. Therefore, no `_total` must be added here.
  158. n, err = writeOpenMetricsSample(
  159. w, name, "", metric, "", 0,
  160. metric.Counter.GetValue(), 0, false,
  161. metric.Counter.Exemplar,
  162. )
  163. case dto.MetricType_GAUGE:
  164. if metric.Gauge == nil {
  165. return written, fmt.Errorf(
  166. "expected gauge in metric %s %s", name, metric,
  167. )
  168. }
  169. n, err = writeOpenMetricsSample(
  170. w, name, "", metric, "", 0,
  171. metric.Gauge.GetValue(), 0, false,
  172. nil,
  173. )
  174. case dto.MetricType_UNTYPED:
  175. if metric.Untyped == nil {
  176. return written, fmt.Errorf(
  177. "expected untyped in metric %s %s", name, metric,
  178. )
  179. }
  180. n, err = writeOpenMetricsSample(
  181. w, name, "", metric, "", 0,
  182. metric.Untyped.GetValue(), 0, false,
  183. nil,
  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 = writeOpenMetricsSample(
  193. w, name, "", metric,
  194. model.QuantileLabel, q.GetQuantile(),
  195. q.GetValue(), 0, false,
  196. nil,
  197. )
  198. written += n
  199. if err != nil {
  200. return
  201. }
  202. }
  203. n, err = writeOpenMetricsSample(
  204. w, name, "_sum", metric, "", 0,
  205. metric.Summary.GetSampleSum(), 0, false,
  206. nil,
  207. )
  208. written += n
  209. if err != nil {
  210. return
  211. }
  212. n, err = writeOpenMetricsSample(
  213. w, name, "_count", metric, "", 0,
  214. 0, metric.Summary.GetSampleCount(), true,
  215. nil,
  216. )
  217. case dto.MetricType_HISTOGRAM:
  218. if metric.Histogram == nil {
  219. return written, fmt.Errorf(
  220. "expected histogram in metric %s %s", name, metric,
  221. )
  222. }
  223. infSeen := false
  224. for _, b := range metric.Histogram.Bucket {
  225. n, err = writeOpenMetricsSample(
  226. w, name, "_bucket", metric,
  227. model.BucketLabel, b.GetUpperBound(),
  228. 0, b.GetCumulativeCount(), true,
  229. b.Exemplar,
  230. )
  231. written += n
  232. if err != nil {
  233. return
  234. }
  235. if math.IsInf(b.GetUpperBound(), +1) {
  236. infSeen = true
  237. }
  238. }
  239. if !infSeen {
  240. n, err = writeOpenMetricsSample(
  241. w, name, "_bucket", metric,
  242. model.BucketLabel, math.Inf(+1),
  243. 0, metric.Histogram.GetSampleCount(), true,
  244. nil,
  245. )
  246. written += n
  247. if err != nil {
  248. return
  249. }
  250. }
  251. n, err = writeOpenMetricsSample(
  252. w, name, "_sum", metric, "", 0,
  253. metric.Histogram.GetSampleSum(), 0, false,
  254. nil,
  255. )
  256. written += n
  257. if err != nil {
  258. return
  259. }
  260. n, err = writeOpenMetricsSample(
  261. w, name, "_count", metric, "", 0,
  262. 0, metric.Histogram.GetSampleCount(), true,
  263. nil,
  264. )
  265. default:
  266. return written, fmt.Errorf(
  267. "unexpected type in metric %s %s", name, metric,
  268. )
  269. }
  270. written += n
  271. if err != nil {
  272. return
  273. }
  274. }
  275. return
  276. }
  277. // FinalizeOpenMetrics writes the final `# EOF\n` line required by OpenMetrics.
  278. func FinalizeOpenMetrics(w io.Writer) (written int, err error) {
  279. return w.Write([]byte("# EOF\n"))
  280. }
  281. // writeOpenMetricsSample writes a single sample in OpenMetrics text format to
  282. // w, given the metric name, the metric proto message itself, optionally an
  283. // additional label name with a float64 value (use empty string as label name if
  284. // not required), the value (optionally as float64 or uint64, determined by
  285. // useIntValue), and optionally an exemplar (use nil if not required). The
  286. // function returns the number of bytes written and any error encountered.
  287. func writeOpenMetricsSample(
  288. w enhancedWriter,
  289. name, suffix string,
  290. metric *dto.Metric,
  291. additionalLabelName string, additionalLabelValue float64,
  292. floatValue float64, intValue uint64, useIntValue bool,
  293. exemplar *dto.Exemplar,
  294. ) (int, error) {
  295. var written int
  296. n, err := w.WriteString(name)
  297. written += n
  298. if err != nil {
  299. return written, err
  300. }
  301. if suffix != "" {
  302. n, err = w.WriteString(suffix)
  303. written += n
  304. if err != nil {
  305. return written, err
  306. }
  307. }
  308. n, err = writeOpenMetricsLabelPairs(
  309. w, metric.Label, additionalLabelName, additionalLabelValue,
  310. )
  311. written += n
  312. if err != nil {
  313. return written, err
  314. }
  315. err = w.WriteByte(' ')
  316. written++
  317. if err != nil {
  318. return written, err
  319. }
  320. if useIntValue {
  321. n, err = writeUint(w, intValue)
  322. } else {
  323. n, err = writeOpenMetricsFloat(w, floatValue)
  324. }
  325. written += n
  326. if err != nil {
  327. return written, err
  328. }
  329. if metric.TimestampMs != nil {
  330. err = w.WriteByte(' ')
  331. written++
  332. if err != nil {
  333. return written, err
  334. }
  335. // TODO(beorn7): Format this directly without converting to a float first.
  336. n, err = writeOpenMetricsFloat(w, float64(*metric.TimestampMs)/1000)
  337. written += n
  338. if err != nil {
  339. return written, err
  340. }
  341. }
  342. if exemplar != nil {
  343. n, err = writeExemplar(w, exemplar)
  344. written += n
  345. if err != nil {
  346. return written, err
  347. }
  348. }
  349. err = w.WriteByte('\n')
  350. written++
  351. if err != nil {
  352. return written, err
  353. }
  354. return written, nil
  355. }
  356. // writeOpenMetricsLabelPairs works like writeOpenMetrics but formats the float
  357. // in OpenMetrics style.
  358. func writeOpenMetricsLabelPairs(
  359. w enhancedWriter,
  360. in []*dto.LabelPair,
  361. additionalLabelName string, additionalLabelValue float64,
  362. ) (int, error) {
  363. if len(in) == 0 && additionalLabelName == "" {
  364. return 0, nil
  365. }
  366. var (
  367. written int
  368. separator byte = '{'
  369. )
  370. for _, lp := range in {
  371. err := w.WriteByte(separator)
  372. written++
  373. if err != nil {
  374. return written, err
  375. }
  376. n, err := w.WriteString(lp.GetName())
  377. written += n
  378. if err != nil {
  379. return written, err
  380. }
  381. n, err = w.WriteString(`="`)
  382. written += n
  383. if err != nil {
  384. return written, err
  385. }
  386. n, err = writeEscapedString(w, lp.GetValue(), true)
  387. written += n
  388. if err != nil {
  389. return written, err
  390. }
  391. err = w.WriteByte('"')
  392. written++
  393. if err != nil {
  394. return written, err
  395. }
  396. separator = ','
  397. }
  398. if additionalLabelName != "" {
  399. err := w.WriteByte(separator)
  400. written++
  401. if err != nil {
  402. return written, err
  403. }
  404. n, err := w.WriteString(additionalLabelName)
  405. written += n
  406. if err != nil {
  407. return written, err
  408. }
  409. n, err = w.WriteString(`="`)
  410. written += n
  411. if err != nil {
  412. return written, err
  413. }
  414. n, err = writeOpenMetricsFloat(w, additionalLabelValue)
  415. written += n
  416. if err != nil {
  417. return written, err
  418. }
  419. err = w.WriteByte('"')
  420. written++
  421. if err != nil {
  422. return written, err
  423. }
  424. }
  425. err := w.WriteByte('}')
  426. written++
  427. if err != nil {
  428. return written, err
  429. }
  430. return written, nil
  431. }
  432. // writeExemplar writes the provided exemplar in OpenMetrics format to w. The
  433. // function returns the number of bytes written and any error encountered.
  434. func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) {
  435. written := 0
  436. n, err := w.WriteString(" # ")
  437. written += n
  438. if err != nil {
  439. return written, err
  440. }
  441. n, err = writeOpenMetricsLabelPairs(w, e.Label, "", 0)
  442. written += n
  443. if err != nil {
  444. return written, err
  445. }
  446. err = w.WriteByte(' ')
  447. written++
  448. if err != nil {
  449. return written, err
  450. }
  451. n, err = writeOpenMetricsFloat(w, e.GetValue())
  452. written += n
  453. if err != nil {
  454. return written, err
  455. }
  456. if e.Timestamp != nil {
  457. err = w.WriteByte(' ')
  458. written++
  459. if err != nil {
  460. return written, err
  461. }
  462. err = (*e).Timestamp.CheckValid()
  463. if err != nil {
  464. return written, err
  465. }
  466. ts := (*e).Timestamp.AsTime()
  467. // TODO(beorn7): Format this directly from components of ts to
  468. // avoid overflow/underflow and precision issues of the float
  469. // conversion.
  470. n, err = writeOpenMetricsFloat(w, float64(ts.UnixNano())/1e9)
  471. written += n
  472. if err != nil {
  473. return written, err
  474. }
  475. }
  476. return written, nil
  477. }
  478. // writeOpenMetricsFloat works like writeFloat but appends ".0" if the resulting
  479. // number would otherwise contain neither a "." nor an "e".
  480. func writeOpenMetricsFloat(w enhancedWriter, f float64) (int, error) {
  481. switch {
  482. case f == 1:
  483. return w.WriteString("1.0")
  484. case f == 0:
  485. return w.WriteString("0.0")
  486. case f == -1:
  487. return w.WriteString("-1.0")
  488. case math.IsNaN(f):
  489. return w.WriteString("NaN")
  490. case math.IsInf(f, +1):
  491. return w.WriteString("+Inf")
  492. case math.IsInf(f, -1):
  493. return w.WriteString("-Inf")
  494. default:
  495. bp := numBufPool.Get().(*[]byte)
  496. *bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
  497. if !bytes.ContainsAny(*bp, "e.") {
  498. *bp = append(*bp, '.', '0')
  499. }
  500. written, err := w.Write(*bp)
  501. numBufPool.Put(bp)
  502. return written, err
  503. }
  504. }
  505. // writeUint is like writeInt just for uint64.
  506. func writeUint(w enhancedWriter, u uint64) (int, error) {
  507. bp := numBufPool.Get().(*[]byte)
  508. *bp = strconv.AppendUint((*bp)[:0], u, 10)
  509. written, err := w.Write(*bp)
  510. numBufPool.Put(bp)
  511. return written, err
  512. }