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.
 
 

465 lines
11 KiB

  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. // This method fulfills the type 'prometheus.encoder'.
  58. func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err error) {
  59. // Fail-fast checks.
  60. if len(in.Metric) == 0 {
  61. return 0, fmt.Errorf("MetricFamily has no metrics: %s", in)
  62. }
  63. name := in.GetName()
  64. if name == "" {
  65. return 0, fmt.Errorf("MetricFamily has no name: %s", in)
  66. }
  67. // Try the interface upgrade. If it doesn't work, we'll use a
  68. // bufio.Writer from the sync.Pool.
  69. w, ok := out.(enhancedWriter)
  70. if !ok {
  71. b := bufPool.Get().(*bufio.Writer)
  72. b.Reset(out)
  73. w = b
  74. defer func() {
  75. bErr := b.Flush()
  76. if err == nil {
  77. err = bErr
  78. }
  79. bufPool.Put(b)
  80. }()
  81. }
  82. var n int
  83. // Comments, first HELP, then TYPE.
  84. if in.Help != nil {
  85. n, err = w.WriteString("# HELP ")
  86. written += n
  87. if err != nil {
  88. return
  89. }
  90. n, err = w.WriteString(name)
  91. written += n
  92. if err != nil {
  93. return
  94. }
  95. err = w.WriteByte(' ')
  96. written++
  97. if err != nil {
  98. return
  99. }
  100. n, err = writeEscapedString(w, *in.Help, false)
  101. written += n
  102. if err != nil {
  103. return
  104. }
  105. err = w.WriteByte('\n')
  106. written++
  107. if err != nil {
  108. return
  109. }
  110. }
  111. n, err = w.WriteString("# TYPE ")
  112. written += n
  113. if err != nil {
  114. return
  115. }
  116. n, err = w.WriteString(name)
  117. written += n
  118. if err != nil {
  119. return
  120. }
  121. metricType := in.GetType()
  122. switch metricType {
  123. case dto.MetricType_COUNTER:
  124. n, err = w.WriteString(" counter\n")
  125. case dto.MetricType_GAUGE:
  126. n, err = w.WriteString(" gauge\n")
  127. case dto.MetricType_SUMMARY:
  128. n, err = w.WriteString(" summary\n")
  129. case dto.MetricType_UNTYPED:
  130. n, err = w.WriteString(" untyped\n")
  131. case dto.MetricType_HISTOGRAM:
  132. n, err = w.WriteString(" histogram\n")
  133. default:
  134. return written, fmt.Errorf("unknown metric type %s", metricType.String())
  135. }
  136. written += n
  137. if err != nil {
  138. return
  139. }
  140. // Finally the samples, one line for each.
  141. for _, metric := range in.Metric {
  142. switch metricType {
  143. case dto.MetricType_COUNTER:
  144. if metric.Counter == nil {
  145. return written, fmt.Errorf(
  146. "expected counter in metric %s %s", name, metric,
  147. )
  148. }
  149. n, err = writeSample(
  150. w, name, "", metric, "", 0,
  151. metric.Counter.GetValue(),
  152. )
  153. case dto.MetricType_GAUGE:
  154. if metric.Gauge == nil {
  155. return written, fmt.Errorf(
  156. "expected gauge in metric %s %s", name, metric,
  157. )
  158. }
  159. n, err = writeSample(
  160. w, name, "", metric, "", 0,
  161. metric.Gauge.GetValue(),
  162. )
  163. case dto.MetricType_UNTYPED:
  164. if metric.Untyped == nil {
  165. return written, fmt.Errorf(
  166. "expected untyped in metric %s %s", name, metric,
  167. )
  168. }
  169. n, err = writeSample(
  170. w, name, "", metric, "", 0,
  171. metric.Untyped.GetValue(),
  172. )
  173. case dto.MetricType_SUMMARY:
  174. if metric.Summary == nil {
  175. return written, fmt.Errorf(
  176. "expected summary in metric %s %s", name, metric,
  177. )
  178. }
  179. for _, q := range metric.Summary.Quantile {
  180. n, err = writeSample(
  181. w, name, "", metric,
  182. model.QuantileLabel, q.GetQuantile(),
  183. q.GetValue(),
  184. )
  185. written += n
  186. if err != nil {
  187. return
  188. }
  189. }
  190. n, err = writeSample(
  191. w, name, "_sum", metric, "", 0,
  192. metric.Summary.GetSampleSum(),
  193. )
  194. written += n
  195. if err != nil {
  196. return
  197. }
  198. n, err = writeSample(
  199. w, name, "_count", metric, "", 0,
  200. float64(metric.Summary.GetSampleCount()),
  201. )
  202. case dto.MetricType_HISTOGRAM:
  203. if metric.Histogram == nil {
  204. return written, fmt.Errorf(
  205. "expected histogram in metric %s %s", name, metric,
  206. )
  207. }
  208. infSeen := false
  209. for _, b := range metric.Histogram.Bucket {
  210. n, err = writeSample(
  211. w, name, "_bucket", metric,
  212. model.BucketLabel, b.GetUpperBound(),
  213. float64(b.GetCumulativeCount()),
  214. )
  215. written += n
  216. if err != nil {
  217. return
  218. }
  219. if math.IsInf(b.GetUpperBound(), +1) {
  220. infSeen = true
  221. }
  222. }
  223. if !infSeen {
  224. n, err = writeSample(
  225. w, name, "_bucket", metric,
  226. model.BucketLabel, math.Inf(+1),
  227. float64(metric.Histogram.GetSampleCount()),
  228. )
  229. written += n
  230. if err != nil {
  231. return
  232. }
  233. }
  234. n, err = writeSample(
  235. w, name, "_sum", metric, "", 0,
  236. metric.Histogram.GetSampleSum(),
  237. )
  238. written += n
  239. if err != nil {
  240. return
  241. }
  242. n, err = writeSample(
  243. w, name, "_count", metric, "", 0,
  244. float64(metric.Histogram.GetSampleCount()),
  245. )
  246. default:
  247. return written, fmt.Errorf(
  248. "unexpected type in metric %s %s", name, metric,
  249. )
  250. }
  251. written += n
  252. if err != nil {
  253. return
  254. }
  255. }
  256. return
  257. }
  258. // writeSample writes a single sample in text format to w, given the metric
  259. // name, the metric proto message itself, optionally an additional label name
  260. // with a float64 value (use empty string as label name if not required), and
  261. // the value. The function returns the number of bytes written and any error
  262. // encountered.
  263. func writeSample(
  264. w enhancedWriter,
  265. name, suffix string,
  266. metric *dto.Metric,
  267. additionalLabelName string, additionalLabelValue float64,
  268. value float64,
  269. ) (int, error) {
  270. var written int
  271. n, err := w.WriteString(name)
  272. written += n
  273. if err != nil {
  274. return written, err
  275. }
  276. if suffix != "" {
  277. n, err = w.WriteString(suffix)
  278. written += n
  279. if err != nil {
  280. return written, err
  281. }
  282. }
  283. n, err = writeLabelPairs(
  284. w, 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. // writeLabelPairs converts a slice of LabelPair proto messages plus the
  320. // explicitly given additional label pair into text formatted as required by the
  321. // text format and writes it to 'w'. An empty slice in combination with an empty
  322. // string 'additionalLabelName' results in nothing being written. Otherwise, the
  323. // label pairs are written, escaped as required by the text format, and enclosed
  324. // in '{...}'. The function returns the number of bytes written and any error
  325. // encountered.
  326. func writeLabelPairs(
  327. w enhancedWriter,
  328. in []*dto.LabelPair,
  329. additionalLabelName string, additionalLabelValue float64,
  330. ) (int, error) {
  331. if len(in) == 0 && additionalLabelName == "" {
  332. return 0, nil
  333. }
  334. var (
  335. written int
  336. separator byte = '{'
  337. )
  338. for _, lp := range in {
  339. err := w.WriteByte(separator)
  340. written++
  341. if err != nil {
  342. return written, err
  343. }
  344. n, err := w.WriteString(lp.GetName())
  345. written += n
  346. if err != nil {
  347. return written, err
  348. }
  349. n, err = w.WriteString(`="`)
  350. written += n
  351. if err != nil {
  352. return written, err
  353. }
  354. n, err = writeEscapedString(w, lp.GetValue(), true)
  355. written += n
  356. if err != nil {
  357. return written, err
  358. }
  359. err = w.WriteByte('"')
  360. written++
  361. if err != nil {
  362. return written, err
  363. }
  364. separator = ','
  365. }
  366. if additionalLabelName != "" {
  367. err := w.WriteByte(separator)
  368. written++
  369. if err != nil {
  370. return written, err
  371. }
  372. n, err := w.WriteString(additionalLabelName)
  373. written += n
  374. if err != nil {
  375. return written, err
  376. }
  377. n, err = w.WriteString(`="`)
  378. written += n
  379. if err != nil {
  380. return written, err
  381. }
  382. n, err = writeFloat(w, additionalLabelValue)
  383. written += n
  384. if err != nil {
  385. return written, err
  386. }
  387. err = w.WriteByte('"')
  388. written++
  389. if err != nil {
  390. return written, err
  391. }
  392. }
  393. err := w.WriteByte('}')
  394. written++
  395. if err != nil {
  396. return written, err
  397. }
  398. return written, nil
  399. }
  400. // writeEscapedString replaces '\' by '\\', new line character by '\n', and - if
  401. // includeDoubleQuote is true - '"' by '\"'.
  402. var (
  403. escaper = strings.NewReplacer("\\", `\\`, "\n", `\n`)
  404. quotedEscaper = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
  405. )
  406. func writeEscapedString(w enhancedWriter, v string, includeDoubleQuote bool) (int, error) {
  407. if includeDoubleQuote {
  408. return quotedEscaper.WriteString(w, v)
  409. }
  410. return escaper.WriteString(w, v)
  411. }
  412. // writeFloat is equivalent to fmt.Fprint with a float64 argument but hardcodes
  413. // a few common cases for increased efficiency. For non-hardcoded cases, it uses
  414. // strconv.AppendFloat to avoid allocations, similar to writeInt.
  415. func writeFloat(w enhancedWriter, f float64) (int, error) {
  416. switch {
  417. case f == 1:
  418. return 1, w.WriteByte('1')
  419. case f == 0:
  420. return 1, w.WriteByte('0')
  421. case f == -1:
  422. return w.WriteString("-1")
  423. case math.IsNaN(f):
  424. return w.WriteString("NaN")
  425. case math.IsInf(f, +1):
  426. return w.WriteString("+Inf")
  427. case math.IsInf(f, -1):
  428. return w.WriteString("-Inf")
  429. default:
  430. bp := numBufPool.Get().(*[]byte)
  431. *bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
  432. written, err := w.Write(*bp)
  433. numBufPool.Put(bp)
  434. return written, err
  435. }
  436. }
  437. // writeInt is equivalent to fmt.Fprint with an int64 argument but uses
  438. // strconv.AppendInt with a byte slice taken from a sync.Pool to avoid
  439. // allocations.
  440. func writeInt(w enhancedWriter, i int64) (int, error) {
  441. bp := numBufPool.Get().(*[]byte)
  442. *bp = strconv.AppendInt((*bp)[:0], i, 10)
  443. written, err := w.Write(*bp)
  444. numBufPool.Put(bp)
  445. return written, err
  446. }