本站源代码
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.

469 lines
11KB

  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. "bytes"
  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. bytes.Buffer
  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. initialBufSize = 512
  35. initialNumBufSize = 24
  36. )
  37. var (
  38. bufPool = sync.Pool{
  39. New: func() interface{} {
  40. return bytes.NewBuffer(make([]byte, 0, initialBufSize))
  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. // bytes.Buffer from the sync.Pool and write out its content to out in a
  70. // single go in the end.
  71. w, ok := out.(enhancedWriter)
  72. if !ok {
  73. b := bufPool.Get().(*bytes.Buffer)
  74. b.Reset()
  75. w = b
  76. defer func() {
  77. bWritten, bErr := out.Write(b.Bytes())
  78. written = bWritten
  79. if err == nil {
  80. err = bErr
  81. }
  82. bufPool.Put(b)
  83. }()
  84. }
  85. var n int
  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(name)
  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, false)
  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(name)
  120. written += n
  121. if err != nil {
  122. return
  123. }
  124. metricType := in.GetType()
  125. switch metricType {
  126. case dto.MetricType_COUNTER:
  127. n, err = w.WriteString(" counter\n")
  128. case dto.MetricType_GAUGE:
  129. n, err = w.WriteString(" gauge\n")
  130. case dto.MetricType_SUMMARY:
  131. n, err = w.WriteString(" summary\n")
  132. case dto.MetricType_UNTYPED:
  133. n, err = w.WriteString(" untyped\n")
  134. case dto.MetricType_HISTOGRAM:
  135. n, err = w.WriteString(" histogram\n")
  136. default:
  137. return written, fmt.Errorf("unknown metric type %s", metricType.String())
  138. }
  139. written += n
  140. if err != nil {
  141. return
  142. }
  143. // Finally the samples, one line for each.
  144. for _, metric := range in.Metric {
  145. switch metricType {
  146. case dto.MetricType_COUNTER:
  147. if metric.Counter == nil {
  148. return written, fmt.Errorf(
  149. "expected counter in metric %s %s", name, metric,
  150. )
  151. }
  152. n, err = writeSample(
  153. w, name, "", metric, "", 0,
  154. metric.Counter.GetValue(),
  155. )
  156. case dto.MetricType_GAUGE:
  157. if metric.Gauge == nil {
  158. return written, fmt.Errorf(
  159. "expected gauge in metric %s %s", name, metric,
  160. )
  161. }
  162. n, err = writeSample(
  163. w, name, "", metric, "", 0,
  164. metric.Gauge.GetValue(),
  165. )
  166. case dto.MetricType_UNTYPED:
  167. if metric.Untyped == nil {
  168. return written, fmt.Errorf(
  169. "expected untyped in metric %s %s", name, metric,
  170. )
  171. }
  172. n, err = writeSample(
  173. w, name, "", metric, "", 0,
  174. metric.Untyped.GetValue(),
  175. )
  176. case dto.MetricType_SUMMARY:
  177. if metric.Summary == nil {
  178. return written, fmt.Errorf(
  179. "expected summary in metric %s %s", name, metric,
  180. )
  181. }
  182. for _, q := range metric.Summary.Quantile {
  183. n, err = writeSample(
  184. w, name, "", metric,
  185. model.QuantileLabel, q.GetQuantile(),
  186. q.GetValue(),
  187. )
  188. written += n
  189. if err != nil {
  190. return
  191. }
  192. }
  193. n, err = writeSample(
  194. w, name, "_sum", metric, "", 0,
  195. metric.Summary.GetSampleSum(),
  196. )
  197. written += n
  198. if err != nil {
  199. return
  200. }
  201. n, err = writeSample(
  202. w, name, "_count", metric, "", 0,
  203. float64(metric.Summary.GetSampleCount()),
  204. )
  205. case dto.MetricType_HISTOGRAM:
  206. if metric.Histogram == nil {
  207. return written, fmt.Errorf(
  208. "expected histogram in metric %s %s", name, metric,
  209. )
  210. }
  211. infSeen := false
  212. for _, b := range metric.Histogram.Bucket {
  213. n, err = writeSample(
  214. w, name, "_bucket", metric,
  215. model.BucketLabel, b.GetUpperBound(),
  216. float64(b.GetCumulativeCount()),
  217. )
  218. written += n
  219. if err != nil {
  220. return
  221. }
  222. if math.IsInf(b.GetUpperBound(), +1) {
  223. infSeen = true
  224. }
  225. }
  226. if !infSeen {
  227. n, err = writeSample(
  228. w, name, "_bucket", metric,
  229. model.BucketLabel, math.Inf(+1),
  230. float64(metric.Histogram.GetSampleCount()),
  231. )
  232. written += n
  233. if err != nil {
  234. return
  235. }
  236. }
  237. n, err = writeSample(
  238. w, name, "_sum", metric, "", 0,
  239. metric.Histogram.GetSampleSum(),
  240. )
  241. written += n
  242. if err != nil {
  243. return
  244. }
  245. n, err = writeSample(
  246. w, name, "_count", metric, "", 0,
  247. float64(metric.Histogram.GetSampleCount()),
  248. )
  249. default:
  250. return written, fmt.Errorf(
  251. "unexpected type in metric %s %s", name, metric,
  252. )
  253. }
  254. written += n
  255. if err != nil {
  256. return
  257. }
  258. }
  259. return
  260. }
  261. // writeSample writes a single sample in text format to w, given the metric
  262. // name, the metric proto message itself, optionally an additional label name
  263. // with a float64 value (use empty string as label name if not required), and
  264. // the value. The function returns the number of bytes written and any error
  265. // encountered.
  266. func writeSample(
  267. w enhancedWriter,
  268. name, suffix string,
  269. metric *dto.Metric,
  270. additionalLabelName string, additionalLabelValue float64,
  271. value float64,
  272. ) (int, error) {
  273. var written int
  274. n, err := w.WriteString(name)
  275. written += n
  276. if err != nil {
  277. return written, err
  278. }
  279. if suffix != "" {
  280. n, err = w.WriteString(suffix)
  281. written += n
  282. if err != nil {
  283. return written, err
  284. }
  285. }
  286. n, err = writeLabelPairs(
  287. w, metric.Label, additionalLabelName, additionalLabelValue,
  288. )
  289. written += n
  290. if err != nil {
  291. return written, err
  292. }
  293. err = w.WriteByte(' ')
  294. written++
  295. if err != nil {
  296. return written, err
  297. }
  298. n, err = writeFloat(w, value)
  299. written += n
  300. if err != nil {
  301. return written, err
  302. }
  303. if metric.TimestampMs != nil {
  304. err = w.WriteByte(' ')
  305. written++
  306. if err != nil {
  307. return written, err
  308. }
  309. n, err = writeInt(w, *metric.TimestampMs)
  310. written += n
  311. if err != nil {
  312. return written, err
  313. }
  314. }
  315. err = w.WriteByte('\n')
  316. written++
  317. if err != nil {
  318. return written, err
  319. }
  320. return written, nil
  321. }
  322. // writeLabelPairs converts a slice of LabelPair proto messages plus the
  323. // explicitly given additional label pair into text formatted as required by the
  324. // text format and writes it to 'w'. An empty slice in combination with an empty
  325. // string 'additionalLabelName' results in nothing being written. Otherwise, the
  326. // label pairs are written, escaped as required by the text format, and enclosed
  327. // in '{...}'. The function returns the number of bytes written and any error
  328. // encountered.
  329. func writeLabelPairs(
  330. w enhancedWriter,
  331. in []*dto.LabelPair,
  332. additionalLabelName string, additionalLabelValue float64,
  333. ) (int, error) {
  334. if len(in) == 0 && additionalLabelName == "" {
  335. return 0, nil
  336. }
  337. var (
  338. written int
  339. separator byte = '{'
  340. )
  341. for _, lp := range in {
  342. err := w.WriteByte(separator)
  343. written++
  344. if err != nil {
  345. return written, err
  346. }
  347. n, err := w.WriteString(lp.GetName())
  348. written += n
  349. if err != nil {
  350. return written, err
  351. }
  352. n, err = w.WriteString(`="`)
  353. written += n
  354. if err != nil {
  355. return written, err
  356. }
  357. n, err = writeEscapedString(w, lp.GetValue(), true)
  358. written += n
  359. if err != nil {
  360. return written, err
  361. }
  362. err = w.WriteByte('"')
  363. written++
  364. if err != nil {
  365. return written, err
  366. }
  367. separator = ','
  368. }
  369. if additionalLabelName != "" {
  370. err := w.WriteByte(separator)
  371. written++
  372. if err != nil {
  373. return written, err
  374. }
  375. n, err := w.WriteString(additionalLabelName)
  376. written += n
  377. if err != nil {
  378. return written, err
  379. }
  380. n, err = w.WriteString(`="`)
  381. written += n
  382. if err != nil {
  383. return written, err
  384. }
  385. n, err = writeFloat(w, additionalLabelValue)
  386. written += n
  387. if err != nil {
  388. return written, err
  389. }
  390. err = w.WriteByte('"')
  391. written++
  392. if err != nil {
  393. return written, err
  394. }
  395. }
  396. err := w.WriteByte('}')
  397. written++
  398. if err != nil {
  399. return written, err
  400. }
  401. return written, nil
  402. }
  403. // writeEscapedString replaces '\' by '\\', new line character by '\n', and - if
  404. // includeDoubleQuote is true - '"' by '\"'.
  405. var (
  406. escaper = strings.NewReplacer("\\", `\\`, "\n", `\n`)
  407. quotedEscaper = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
  408. )
  409. func writeEscapedString(w enhancedWriter, v string, includeDoubleQuote bool) (int, error) {
  410. if includeDoubleQuote {
  411. return quotedEscaper.WriteString(w, v)
  412. } else {
  413. return escaper.WriteString(w, v)
  414. }
  415. }
  416. // writeFloat is equivalent to fmt.Fprint with a float64 argument but hardcodes
  417. // a few common cases for increased efficiency. For non-hardcoded cases, it uses
  418. // strconv.AppendFloat to avoid allocations, similar to writeInt.
  419. func writeFloat(w enhancedWriter, f float64) (int, error) {
  420. switch {
  421. case f == 1:
  422. return 1, w.WriteByte('1')
  423. case f == 0:
  424. return 1, w.WriteByte('0')
  425. case f == -1:
  426. return w.WriteString("-1")
  427. case math.IsNaN(f):
  428. return w.WriteString("NaN")
  429. case math.IsInf(f, +1):
  430. return w.WriteString("+Inf")
  431. case math.IsInf(f, -1):
  432. return w.WriteString("-Inf")
  433. default:
  434. bp := numBufPool.Get().(*[]byte)
  435. *bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
  436. written, err := w.Write(*bp)
  437. numBufPool.Put(bp)
  438. return written, err
  439. }
  440. }
  441. // writeInt is equivalent to fmt.Fprint with an int64 argument but uses
  442. // strconv.AppendInt with a byte slice taken from a sync.Pool to avoid
  443. // allocations.
  444. func writeInt(w enhancedWriter, i int64) (int, error) {
  445. bp := numBufPool.Get().(*[]byte)
  446. *bp = strconv.AppendInt((*bp)[:0], i, 10)
  447. written, err := w.Write(*bp)
  448. numBufPool.Put(bp)
  449. return written, err
  450. }
上海开阖软件有限公司 沪ICP备12045867号-1