cubefs

Форк
0
527 строк · 13.1 Кб
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

14
package expfmt
15

16
import (
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.)
63
func 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.
288
func 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.
298
func 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.
370
func 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.
447
func 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".
494
func 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.
521
func 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
}
528

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.