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.
 
 

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