Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

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