text_create.go 7.5 KB

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