text_create.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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. "fmt"
  16. "io"
  17. "math"
  18. "strings"
  19. dto "github.com/prometheus/client_model/go"
  20. "github.com/prometheus/common/model"
  21. )
  22. // MetricFamilyToText converts a MetricFamily proto message into text format and
  23. // writes the resulting lines to 'out'. It returns the number of bytes written
  24. // and any error encountered. This function does not perform checks on the
  25. // content of the metric and label names, i.e. invalid metric or label names
  26. // will result in invalid text format output.
  27. // This method fulfills the type 'prometheus.encoder'.
  28. func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
  29. var written int
  30. // Fail-fast checks.
  31. if len(in.Metric) == 0 {
  32. return written, fmt.Errorf("MetricFamily has no metrics: %s", in)
  33. }
  34. name := in.GetName()
  35. if name == "" {
  36. return written, fmt.Errorf("MetricFamily has no name: %s", in)
  37. }
  38. // Comments, first HELP, then TYPE.
  39. if in.Help != nil {
  40. n, err := fmt.Fprintf(
  41. out, "# HELP %s %s\n",
  42. name, escapeString(*in.Help, false),
  43. )
  44. written += n
  45. if err != nil {
  46. return written, err
  47. }
  48. }
  49. metricType := in.GetType()
  50. n, err := fmt.Fprintf(
  51. out, "# TYPE %s %s\n",
  52. name, strings.ToLower(metricType.String()),
  53. )
  54. written += n
  55. if err != nil {
  56. return written, err
  57. }
  58. // Finally the samples, one line for each.
  59. for _, metric := range in.Metric {
  60. switch metricType {
  61. case dto.MetricType_COUNTER:
  62. if metric.Counter == nil {
  63. return written, fmt.Errorf(
  64. "expected counter in metric %s %s", name, metric,
  65. )
  66. }
  67. n, err = writeSample(
  68. name, metric, "", "",
  69. metric.Counter.GetValue(),
  70. out,
  71. )
  72. case dto.MetricType_GAUGE:
  73. if metric.Gauge == nil {
  74. return written, fmt.Errorf(
  75. "expected gauge in metric %s %s", name, metric,
  76. )
  77. }
  78. n, err = writeSample(
  79. name, metric, "", "",
  80. metric.Gauge.GetValue(),
  81. out,
  82. )
  83. case dto.MetricType_UNTYPED:
  84. if metric.Untyped == nil {
  85. return written, fmt.Errorf(
  86. "expected untyped in metric %s %s", name, metric,
  87. )
  88. }
  89. n, err = writeSample(
  90. name, metric, "", "",
  91. metric.Untyped.GetValue(),
  92. out,
  93. )
  94. case dto.MetricType_SUMMARY:
  95. if metric.Summary == nil {
  96. return written, fmt.Errorf(
  97. "expected summary in metric %s %s", name, metric,
  98. )
  99. }
  100. for _, q := range metric.Summary.Quantile {
  101. n, err = writeSample(
  102. name, metric,
  103. model.QuantileLabel, fmt.Sprint(q.GetQuantile()),
  104. q.GetValue(),
  105. out,
  106. )
  107. written += n
  108. if err != nil {
  109. return written, err
  110. }
  111. }
  112. n, err = writeSample(
  113. name+"_sum", metric, "", "",
  114. metric.Summary.GetSampleSum(),
  115. out,
  116. )
  117. if err != nil {
  118. return written, err
  119. }
  120. written += n
  121. n, err = writeSample(
  122. name+"_count", metric, "", "",
  123. float64(metric.Summary.GetSampleCount()),
  124. out,
  125. )
  126. case dto.MetricType_HISTOGRAM:
  127. if metric.Histogram == nil {
  128. return written, fmt.Errorf(
  129. "expected histogram in metric %s %s", name, metric,
  130. )
  131. }
  132. infSeen := false
  133. for _, q := range metric.Histogram.Bucket {
  134. n, err = writeSample(
  135. name+"_bucket", metric,
  136. model.BucketLabel, fmt.Sprint(q.GetUpperBound()),
  137. float64(q.GetCumulativeCount()),
  138. out,
  139. )
  140. written += n
  141. if err != nil {
  142. return written, err
  143. }
  144. if math.IsInf(q.GetUpperBound(), +1) {
  145. infSeen = true
  146. }
  147. }
  148. if !infSeen {
  149. n, err = writeSample(
  150. name+"_bucket", metric,
  151. model.BucketLabel, "+Inf",
  152. float64(metric.Histogram.GetSampleCount()),
  153. out,
  154. )
  155. if err != nil {
  156. return written, err
  157. }
  158. written += n
  159. }
  160. n, err = writeSample(
  161. name+"_sum", metric, "", "",
  162. metric.Histogram.GetSampleSum(),
  163. out,
  164. )
  165. if err != nil {
  166. return written, err
  167. }
  168. written += n
  169. n, err = writeSample(
  170. name+"_count", metric, "", "",
  171. float64(metric.Histogram.GetSampleCount()),
  172. out,
  173. )
  174. default:
  175. return written, fmt.Errorf(
  176. "unexpected type in metric %s %s", name, metric,
  177. )
  178. }
  179. written += n
  180. if err != nil {
  181. return written, err
  182. }
  183. }
  184. return written, nil
  185. }
  186. // writeSample writes a single sample in text format to out, given the metric
  187. // name, the metric proto message itself, optionally an additional label name
  188. // and value (use empty strings if not required), and the value. The function
  189. // returns the number of bytes written and any error encountered.
  190. func writeSample(
  191. name string,
  192. metric *dto.Metric,
  193. additionalLabelName, additionalLabelValue string,
  194. value float64,
  195. out io.Writer,
  196. ) (int, error) {
  197. var written int
  198. n, err := fmt.Fprint(out, name)
  199. written += n
  200. if err != nil {
  201. return written, err
  202. }
  203. n, err = labelPairsToText(
  204. metric.Label,
  205. additionalLabelName, additionalLabelValue,
  206. out,
  207. )
  208. written += n
  209. if err != nil {
  210. return written, err
  211. }
  212. n, err = fmt.Fprintf(out, " %v", value)
  213. written += n
  214. if err != nil {
  215. return written, err
  216. }
  217. if metric.TimestampMs != nil {
  218. n, err = fmt.Fprintf(out, " %v", *metric.TimestampMs)
  219. written += n
  220. if err != nil {
  221. return written, err
  222. }
  223. }
  224. n, err = out.Write([]byte{'\n'})
  225. written += n
  226. if err != nil {
  227. return written, err
  228. }
  229. return written, nil
  230. }
  231. // labelPairsToText converts a slice of LabelPair proto messages plus the
  232. // explicitly given additional label pair into text formatted as required by the
  233. // text format and writes it to 'out'. An empty slice in combination with an
  234. // empty string 'additionalLabelName' results in nothing being
  235. // written. Otherwise, the label pairs are written, escaped as required by the
  236. // text format, and enclosed in '{...}'. The function returns the number of
  237. // bytes written and any error encountered.
  238. func labelPairsToText(
  239. in []*dto.LabelPair,
  240. additionalLabelName, additionalLabelValue string,
  241. out io.Writer,
  242. ) (int, error) {
  243. if len(in) == 0 && additionalLabelName == "" {
  244. return 0, nil
  245. }
  246. var written int
  247. separator := '{'
  248. for _, lp := range in {
  249. n, err := fmt.Fprintf(
  250. out, `%c%s="%s"`,
  251. separator, lp.GetName(), escapeString(lp.GetValue(), true),
  252. )
  253. written += n
  254. if err != nil {
  255. return written, err
  256. }
  257. separator = ','
  258. }
  259. if additionalLabelName != "" {
  260. n, err := fmt.Fprintf(
  261. out, `%c%s="%s"`,
  262. separator, additionalLabelName,
  263. escapeString(additionalLabelValue, true),
  264. )
  265. written += n
  266. if err != nil {
  267. return written, err
  268. }
  269. }
  270. n, err := out.Write([]byte{'}'})
  271. written += n
  272. if err != nil {
  273. return written, err
  274. }
  275. return written, nil
  276. }
  277. var (
  278. escape = strings.NewReplacer("\\", `\\`, "\n", `\n`)
  279. escapeWithDoubleQuote = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
  280. )
  281. // escapeString replaces '\' by '\\', new line character by '\n', and - if
  282. // includeDoubleQuote is true - '"' by '\"'.
  283. func escapeString(v string, includeDoubleQuote bool) string {
  284. if includeDoubleQuote {
  285. return escapeWithDoubleQuote.Replace(v)
  286. }
  287. return escape.Replace(v)
  288. }