source: code/trunk/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go@ 822

Last change on this file since 822 was 822, checked in by yakumo.izuru, 22 months ago

Prefer immortal.run over runit and rc.d, use vendored modules
for convenience.

Signed-off-by: Izuru Yakumo <yakumo.izuru@…>

File size: 13.1 KB
Line 
1// Copyright 2020 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
14package expfmt
15
16import (
17 "bufio"
18 "bytes"
19 "fmt"
20 "io"
21 "math"
22 "strconv"
23 "strings"
24
25 "github.com/prometheus/common/model"
26
27 dto "github.com/prometheus/client_model/go"
28)
29
30// MetricFamilyToOpenMetrics converts a MetricFamily proto message into the
31// OpenMetrics text format and writes the resulting lines to 'out'. It returns
32// the number of bytes written and any error encountered. The output will have
33// the same order as the input, no further sorting is performed. Furthermore,
34// this function assumes the input is already sanitized and does not perform any
35// sanity checks. If the input contains duplicate metrics or invalid metric or
36// label names, the conversion will result in invalid text format output.
37//
38// This function fulfills the type 'expfmt.encoder'.
39//
40// Note that OpenMetrics requires a final `# EOF` line. Since this function acts
41// on individual metric families, it is the responsibility of the caller to
42// append this line to 'out' once all metric families have been written.
43// Conveniently, this can be done by calling FinalizeOpenMetrics.
44//
45// The output should be fully OpenMetrics compliant. However, there are a few
46// missing features and peculiarities to avoid complications when switching from
47// Prometheus to OpenMetrics or vice versa:
48//
49// - Counters are expected to have the `_total` suffix in their metric name. In
50// the output, the suffix will be truncated from the `# TYPE` and `# HELP`
51// line. A counter with a missing `_total` suffix is not an error. However,
52// its type will be set to `unknown` in that case to avoid invalid OpenMetrics
53// output.
54//
55// - No support for the following (optional) features: `# UNIT` line, `_created`
56// line, info type, stateset type, gaugehistogram type.
57//
58// - The size of exemplar labels is not checked (i.e. it's possible to create
59// exemplars that are larger than allowed by the OpenMetrics specification).
60//
61// - The value of Counters is not checked. (OpenMetrics doesn't allow counters
62// with a `NaN` value.)
63func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int, err error) {
64 name := in.GetName()
65 if name == "" {
66 return 0, fmt.Errorf("MetricFamily has no name: %s", in)
67 }
68
69 // Try the interface upgrade. If it doesn't work, we'll use a
70 // bufio.Writer from the sync.Pool.
71 w, ok := out.(enhancedWriter)
72 if !ok {
73 b := bufPool.Get().(*bufio.Writer)
74 b.Reset(out)
75 w = b
76 defer func() {
77 bErr := b.Flush()
78 if err == nil {
79 err = bErr
80 }
81 bufPool.Put(b)
82 }()
83 }
84
85 var (
86 n int
87 metricType = in.GetType()
88 shortName = name
89 )
90 if metricType == dto.MetricType_COUNTER && strings.HasSuffix(shortName, "_total") {
91 shortName = name[:len(name)-6]
92 }
93
94 // Comments, first HELP, then TYPE.
95 if in.Help != nil {
96 n, err = w.WriteString("# HELP ")
97 written += n
98 if err != nil {
99 return
100 }
101 n, err = w.WriteString(shortName)
102 written += n
103 if err != nil {
104 return
105 }
106 err = w.WriteByte(' ')
107 written++
108 if err != nil {
109 return
110 }
111 n, err = writeEscapedString(w, *in.Help, true)
112 written += n
113 if err != nil {
114 return
115 }
116 err = w.WriteByte('\n')
117 written++
118 if err != nil {
119 return
120 }
121 }
122 n, err = w.WriteString("# TYPE ")
123 written += n
124 if err != nil {
125 return
126 }
127 n, err = w.WriteString(shortName)
128 written += n
129 if err != nil {
130 return
131 }
132 switch metricType {
133 case dto.MetricType_COUNTER:
134 if strings.HasSuffix(name, "_total") {
135 n, err = w.WriteString(" counter\n")
136 } else {
137 n, err = w.WriteString(" unknown\n")
138 }
139 case dto.MetricType_GAUGE:
140 n, err = w.WriteString(" gauge\n")
141 case dto.MetricType_SUMMARY:
142 n, err = w.WriteString(" summary\n")
143 case dto.MetricType_UNTYPED:
144 n, err = w.WriteString(" unknown\n")
145 case dto.MetricType_HISTOGRAM:
146 n, err = w.WriteString(" histogram\n")
147 default:
148 return written, fmt.Errorf("unknown metric type %s", metricType.String())
149 }
150 written += n
151 if err != nil {
152 return
153 }
154
155 // Finally the samples, one line for each.
156 for _, metric := range in.Metric {
157 switch metricType {
158 case dto.MetricType_COUNTER:
159 if metric.Counter == nil {
160 return written, fmt.Errorf(
161 "expected counter in metric %s %s", name, metric,
162 )
163 }
164 // Note that we have ensured above that either the name
165 // ends on `_total` or that the rendered type is
166 // `unknown`. Therefore, no `_total` must be added here.
167 n, err = writeOpenMetricsSample(
168 w, name, "", metric, "", 0,
169 metric.Counter.GetValue(), 0, false,
170 metric.Counter.Exemplar,
171 )
172 case dto.MetricType_GAUGE:
173 if metric.Gauge == nil {
174 return written, fmt.Errorf(
175 "expected gauge in metric %s %s", name, metric,
176 )
177 }
178 n, err = writeOpenMetricsSample(
179 w, name, "", metric, "", 0,
180 metric.Gauge.GetValue(), 0, false,
181 nil,
182 )
183 case dto.MetricType_UNTYPED:
184 if metric.Untyped == nil {
185 return written, fmt.Errorf(
186 "expected untyped in metric %s %s", name, metric,
187 )
188 }
189 n, err = writeOpenMetricsSample(
190 w, name, "", metric, "", 0,
191 metric.Untyped.GetValue(), 0, false,
192 nil,
193 )
194 case dto.MetricType_SUMMARY:
195 if metric.Summary == nil {
196 return written, fmt.Errorf(
197 "expected summary in metric %s %s", name, metric,
198 )
199 }
200 for _, q := range metric.Summary.Quantile {
201 n, err = writeOpenMetricsSample(
202 w, name, "", metric,
203 model.QuantileLabel, q.GetQuantile(),
204 q.GetValue(), 0, false,
205 nil,
206 )
207 written += n
208 if err != nil {
209 return
210 }
211 }
212 n, err = writeOpenMetricsSample(
213 w, name, "_sum", metric, "", 0,
214 metric.Summary.GetSampleSum(), 0, false,
215 nil,
216 )
217 written += n
218 if err != nil {
219 return
220 }
221 n, err = writeOpenMetricsSample(
222 w, name, "_count", metric, "", 0,
223 0, metric.Summary.GetSampleCount(), true,
224 nil,
225 )
226 case dto.MetricType_HISTOGRAM:
227 if metric.Histogram == nil {
228 return written, fmt.Errorf(
229 "expected histogram in metric %s %s", name, metric,
230 )
231 }
232 infSeen := false
233 for _, b := range metric.Histogram.Bucket {
234 n, err = writeOpenMetricsSample(
235 w, name, "_bucket", metric,
236 model.BucketLabel, b.GetUpperBound(),
237 0, b.GetCumulativeCount(), true,
238 b.Exemplar,
239 )
240 written += n
241 if err != nil {
242 return
243 }
244 if math.IsInf(b.GetUpperBound(), +1) {
245 infSeen = true
246 }
247 }
248 if !infSeen {
249 n, err = writeOpenMetricsSample(
250 w, name, "_bucket", metric,
251 model.BucketLabel, math.Inf(+1),
252 0, metric.Histogram.GetSampleCount(), true,
253 nil,
254 )
255 written += n
256 if err != nil {
257 return
258 }
259 }
260 n, err = writeOpenMetricsSample(
261 w, name, "_sum", metric, "", 0,
262 metric.Histogram.GetSampleSum(), 0, false,
263 nil,
264 )
265 written += n
266 if err != nil {
267 return
268 }
269 n, err = writeOpenMetricsSample(
270 w, name, "_count", metric, "", 0,
271 0, metric.Histogram.GetSampleCount(), true,
272 nil,
273 )
274 default:
275 return written, fmt.Errorf(
276 "unexpected type in metric %s %s", name, metric,
277 )
278 }
279 written += n
280 if err != nil {
281 return
282 }
283 }
284 return
285}
286
287// FinalizeOpenMetrics writes the final `# EOF\n` line required by OpenMetrics.
288func FinalizeOpenMetrics(w io.Writer) (written int, err error) {
289 return w.Write([]byte("# EOF\n"))
290}
291
292// writeOpenMetricsSample writes a single sample in OpenMetrics text format to
293// w, given the metric name, the metric proto message itself, optionally an
294// additional label name with a float64 value (use empty string as label name if
295// not required), the value (optionally as float64 or uint64, determined by
296// useIntValue), and optionally an exemplar (use nil if not required). The
297// function returns the number of bytes written and any error encountered.
298func writeOpenMetricsSample(
299 w enhancedWriter,
300 name, suffix string,
301 metric *dto.Metric,
302 additionalLabelName string, additionalLabelValue float64,
303 floatValue float64, intValue uint64, useIntValue bool,
304 exemplar *dto.Exemplar,
305) (int, error) {
306 var written int
307 n, err := w.WriteString(name)
308 written += n
309 if err != nil {
310 return written, err
311 }
312 if suffix != "" {
313 n, err = w.WriteString(suffix)
314 written += n
315 if err != nil {
316 return written, err
317 }
318 }
319 n, err = writeOpenMetricsLabelPairs(
320 w, metric.Label, additionalLabelName, additionalLabelValue,
321 )
322 written += n
323 if err != nil {
324 return written, err
325 }
326 err = w.WriteByte(' ')
327 written++
328 if err != nil {
329 return written, err
330 }
331 if useIntValue {
332 n, err = writeUint(w, intValue)
333 } else {
334 n, err = writeOpenMetricsFloat(w, floatValue)
335 }
336 written += n
337 if err != nil {
338 return written, err
339 }
340 if metric.TimestampMs != nil {
341 err = w.WriteByte(' ')
342 written++
343 if err != nil {
344 return written, err
345 }
346 // TODO(beorn7): Format this directly without converting to a float first.
347 n, err = writeOpenMetricsFloat(w, float64(*metric.TimestampMs)/1000)
348 written += n
349 if err != nil {
350 return written, err
351 }
352 }
353 if exemplar != nil {
354 n, err = writeExemplar(w, exemplar)
355 written += n
356 if err != nil {
357 return written, err
358 }
359 }
360 err = w.WriteByte('\n')
361 written++
362 if err != nil {
363 return written, err
364 }
365 return written, nil
366}
367
368// writeOpenMetricsLabelPairs works like writeOpenMetrics but formats the float
369// in OpenMetrics style.
370func writeOpenMetricsLabelPairs(
371 w enhancedWriter,
372 in []*dto.LabelPair,
373 additionalLabelName string, additionalLabelValue float64,
374) (int, error) {
375 if len(in) == 0 && additionalLabelName == "" {
376 return 0, nil
377 }
378 var (
379 written int
380 separator byte = '{'
381 )
382 for _, lp := range in {
383 err := w.WriteByte(separator)
384 written++
385 if err != nil {
386 return written, err
387 }
388 n, err := w.WriteString(lp.GetName())
389 written += n
390 if err != nil {
391 return written, err
392 }
393 n, err = w.WriteString(`="`)
394 written += n
395 if err != nil {
396 return written, err
397 }
398 n, err = writeEscapedString(w, lp.GetValue(), true)
399 written += n
400 if err != nil {
401 return written, err
402 }
403 err = w.WriteByte('"')
404 written++
405 if err != nil {
406 return written, err
407 }
408 separator = ','
409 }
410 if additionalLabelName != "" {
411 err := w.WriteByte(separator)
412 written++
413 if err != nil {
414 return written, err
415 }
416 n, err := w.WriteString(additionalLabelName)
417 written += n
418 if err != nil {
419 return written, err
420 }
421 n, err = w.WriteString(`="`)
422 written += n
423 if err != nil {
424 return written, err
425 }
426 n, err = writeOpenMetricsFloat(w, additionalLabelValue)
427 written += n
428 if err != nil {
429 return written, err
430 }
431 err = w.WriteByte('"')
432 written++
433 if err != nil {
434 return written, err
435 }
436 }
437 err := w.WriteByte('}')
438 written++
439 if err != nil {
440 return written, err
441 }
442 return written, nil
443}
444
445// writeExemplar writes the provided exemplar in OpenMetrics format to w. The
446// function returns the number of bytes written and any error encountered.
447func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) {
448 written := 0
449 n, err := w.WriteString(" # ")
450 written += n
451 if err != nil {
452 return written, err
453 }
454 n, err = writeOpenMetricsLabelPairs(w, e.Label, "", 0)
455 written += n
456 if err != nil {
457 return written, err
458 }
459 err = w.WriteByte(' ')
460 written++
461 if err != nil {
462 return written, err
463 }
464 n, err = writeOpenMetricsFloat(w, e.GetValue())
465 written += n
466 if err != nil {
467 return written, err
468 }
469 if e.Timestamp != nil {
470 err = w.WriteByte(' ')
471 written++
472 if err != nil {
473 return written, err
474 }
475 err = (*e).Timestamp.CheckValid()
476 if err != nil {
477 return written, err
478 }
479 ts := (*e).Timestamp.AsTime()
480 // TODO(beorn7): Format this directly from components of ts to
481 // avoid overflow/underflow and precision issues of the float
482 // conversion.
483 n, err = writeOpenMetricsFloat(w, float64(ts.UnixNano())/1e9)
484 written += n
485 if err != nil {
486 return written, err
487 }
488 }
489 return written, nil
490}
491
492// writeOpenMetricsFloat works like writeFloat but appends ".0" if the resulting
493// number would otherwise contain neither a "." nor an "e".
494func writeOpenMetricsFloat(w enhancedWriter, f float64) (int, error) {
495 switch {
496 case f == 1:
497 return w.WriteString("1.0")
498 case f == 0:
499 return w.WriteString("0.0")
500 case f == -1:
501 return w.WriteString("-1.0")
502 case math.IsNaN(f):
503 return w.WriteString("NaN")
504 case math.IsInf(f, +1):
505 return w.WriteString("+Inf")
506 case math.IsInf(f, -1):
507 return w.WriteString("-Inf")
508 default:
509 bp := numBufPool.Get().(*[]byte)
510 *bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
511 if !bytes.ContainsAny(*bp, "e.") {
512 *bp = append(*bp, '.', '0')
513 }
514 written, err := w.Write(*bp)
515 numBufPool.Put(bp)
516 return written, err
517 }
518}
519
520// writeUint is like writeInt just for uint64.
521func writeUint(w enhancedWriter, u uint64) (int, error) {
522 bp := numBufPool.Get().(*[]byte)
523 *bp = strconv.AppendUint((*bp)[:0], u, 10)
524 written, err := w.Write(*bp)
525 numBufPool.Put(bp)
526 return written, err
527}
Note: See TracBrowser for help on using the repository browser.