text_create.go 7.4 KB

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