cubefs
465 строк · 10.9 Кб
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
14package expfmt
15
16import (
17"bufio"
18"fmt"
19"io"
20"io/ioutil"
21"math"
22"strconv"
23"strings"
24"sync"
25
26"github.com/prometheus/common/model"
27
28dto "github.com/prometheus/client_model/go"
29)
30
31// enhancedWriter has all the enhanced write functions needed here. bufio.Writer
32// implements it.
33type enhancedWriter interface {
34io.Writer
35WriteRune(r rune) (n int, err error)
36WriteString(s string) (n int, err error)
37WriteByte(c byte) error
38}
39
40const (
41initialNumBufSize = 24
42)
43
44var (
45bufPool = sync.Pool{
46New: func() interface{} {
47return bufio.NewWriter(ioutil.Discard)
48},
49}
50numBufPool = sync.Pool{
51New: func() interface{} {
52b := make([]byte, 0, initialNumBufSize)
53return &b
54},
55}
56)
57
58// MetricFamilyToText converts a MetricFamily proto message into text format and
59// writes the resulting lines to 'out'. It returns the number of bytes written
60// and any error encountered. The output will have the same order as the input,
61// no further sorting is performed. Furthermore, this function assumes the input
62// is already sanitized and does not perform any sanity checks. If the input
63// contains duplicate metrics or invalid metric or label names, the conversion
64// will result in invalid text format output.
65//
66// This method fulfills the type 'prometheus.encoder'.
67func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err error) {
68// Fail-fast checks.
69if len(in.Metric) == 0 {
70return 0, fmt.Errorf("MetricFamily has no metrics: %s", in)
71}
72name := in.GetName()
73if name == "" {
74return 0, fmt.Errorf("MetricFamily has no name: %s", in)
75}
76
77// Try the interface upgrade. If it doesn't work, we'll use a
78// bufio.Writer from the sync.Pool.
79w, ok := out.(enhancedWriter)
80if !ok {
81b := bufPool.Get().(*bufio.Writer)
82b.Reset(out)
83w = b
84defer func() {
85bErr := b.Flush()
86if err == nil {
87err = bErr
88}
89bufPool.Put(b)
90}()
91}
92
93var n int
94
95// Comments, first HELP, then TYPE.
96if in.Help != nil {
97n, err = w.WriteString("# HELP ")
98written += n
99if err != nil {
100return
101}
102n, err = w.WriteString(name)
103written += n
104if err != nil {
105return
106}
107err = w.WriteByte(' ')
108written++
109if err != nil {
110return
111}
112n, err = writeEscapedString(w, *in.Help, false)
113written += n
114if err != nil {
115return
116}
117err = w.WriteByte('\n')
118written++
119if err != nil {
120return
121}
122}
123n, err = w.WriteString("# TYPE ")
124written += n
125if err != nil {
126return
127}
128n, err = w.WriteString(name)
129written += n
130if err != nil {
131return
132}
133metricType := in.GetType()
134switch metricType {
135case dto.MetricType_COUNTER:
136n, err = w.WriteString(" counter\n")
137case dto.MetricType_GAUGE:
138n, err = w.WriteString(" gauge\n")
139case dto.MetricType_SUMMARY:
140n, err = w.WriteString(" summary\n")
141case dto.MetricType_UNTYPED:
142n, err = w.WriteString(" untyped\n")
143case dto.MetricType_HISTOGRAM:
144n, err = w.WriteString(" histogram\n")
145default:
146return written, fmt.Errorf("unknown metric type %s", metricType.String())
147}
148written += n
149if err != nil {
150return
151}
152
153// Finally the samples, one line for each.
154for _, metric := range in.Metric {
155switch metricType {
156case dto.MetricType_COUNTER:
157if metric.Counter == nil {
158return written, fmt.Errorf(
159"expected counter in metric %s %s", name, metric,
160)
161}
162n, err = writeSample(
163w, name, "", metric, "", 0,
164metric.Counter.GetValue(),
165)
166case dto.MetricType_GAUGE:
167if metric.Gauge == nil {
168return written, fmt.Errorf(
169"expected gauge in metric %s %s", name, metric,
170)
171}
172n, err = writeSample(
173w, name, "", metric, "", 0,
174metric.Gauge.GetValue(),
175)
176case dto.MetricType_UNTYPED:
177if metric.Untyped == nil {
178return written, fmt.Errorf(
179"expected untyped in metric %s %s", name, metric,
180)
181}
182n, err = writeSample(
183w, name, "", metric, "", 0,
184metric.Untyped.GetValue(),
185)
186case dto.MetricType_SUMMARY:
187if metric.Summary == nil {
188return written, fmt.Errorf(
189"expected summary in metric %s %s", name, metric,
190)
191}
192for _, q := range metric.Summary.Quantile {
193n, err = writeSample(
194w, name, "", metric,
195model.QuantileLabel, q.GetQuantile(),
196q.GetValue(),
197)
198written += n
199if err != nil {
200return
201}
202}
203n, err = writeSample(
204w, name, "_sum", metric, "", 0,
205metric.Summary.GetSampleSum(),
206)
207written += n
208if err != nil {
209return
210}
211n, err = writeSample(
212w, name, "_count", metric, "", 0,
213float64(metric.Summary.GetSampleCount()),
214)
215case dto.MetricType_HISTOGRAM:
216if metric.Histogram == nil {
217return written, fmt.Errorf(
218"expected histogram in metric %s %s", name, metric,
219)
220}
221infSeen := false
222for _, b := range metric.Histogram.Bucket {
223n, err = writeSample(
224w, name, "_bucket", metric,
225model.BucketLabel, b.GetUpperBound(),
226float64(b.GetCumulativeCount()),
227)
228written += n
229if err != nil {
230return
231}
232if math.IsInf(b.GetUpperBound(), +1) {
233infSeen = true
234}
235}
236if !infSeen {
237n, err = writeSample(
238w, name, "_bucket", metric,
239model.BucketLabel, math.Inf(+1),
240float64(metric.Histogram.GetSampleCount()),
241)
242written += n
243if err != nil {
244return
245}
246}
247n, err = writeSample(
248w, name, "_sum", metric, "", 0,
249metric.Histogram.GetSampleSum(),
250)
251written += n
252if err != nil {
253return
254}
255n, err = writeSample(
256w, name, "_count", metric, "", 0,
257float64(metric.Histogram.GetSampleCount()),
258)
259default:
260return written, fmt.Errorf(
261"unexpected type in metric %s %s", name, metric,
262)
263}
264written += n
265if err != nil {
266return
267}
268}
269return
270}
271
272// writeSample writes a single sample in text format to w, given the metric
273// name, the metric proto message itself, optionally an additional label name
274// with a float64 value (use empty string as label name if not required), and
275// the value. The function returns the number of bytes written and any error
276// encountered.
277func writeSample(
278w enhancedWriter,
279name, suffix string,
280metric *dto.Metric,
281additionalLabelName string, additionalLabelValue float64,
282value float64,
283) (int, error) {
284var written int
285n, err := w.WriteString(name)
286written += n
287if err != nil {
288return written, err
289}
290if suffix != "" {
291n, err = w.WriteString(suffix)
292written += n
293if err != nil {
294return written, err
295}
296}
297n, err = writeLabelPairs(
298w, metric.Label, additionalLabelName, additionalLabelValue,
299)
300written += n
301if err != nil {
302return written, err
303}
304err = w.WriteByte(' ')
305written++
306if err != nil {
307return written, err
308}
309n, err = writeFloat(w, value)
310written += n
311if err != nil {
312return written, err
313}
314if metric.TimestampMs != nil {
315err = w.WriteByte(' ')
316written++
317if err != nil {
318return written, err
319}
320n, err = writeInt(w, *metric.TimestampMs)
321written += n
322if err != nil {
323return written, err
324}
325}
326err = w.WriteByte('\n')
327written++
328if err != nil {
329return written, err
330}
331return written, nil
332}
333
334// writeLabelPairs converts a slice of LabelPair proto messages plus the
335// explicitly given additional label pair into text formatted as required by the
336// text format and writes it to 'w'. An empty slice in combination with an empty
337// string 'additionalLabelName' results in nothing being written. Otherwise, the
338// label pairs are written, escaped as required by the text format, and enclosed
339// in '{...}'. The function returns the number of bytes written and any error
340// encountered.
341func writeLabelPairs(
342w enhancedWriter,
343in []*dto.LabelPair,
344additionalLabelName string, additionalLabelValue float64,
345) (int, error) {
346if len(in) == 0 && additionalLabelName == "" {
347return 0, nil
348}
349var (
350written int
351separator byte = '{'
352)
353for _, lp := range in {
354err := w.WriteByte(separator)
355written++
356if err != nil {
357return written, err
358}
359n, err := w.WriteString(lp.GetName())
360written += n
361if err != nil {
362return written, err
363}
364n, err = w.WriteString(`="`)
365written += n
366if err != nil {
367return written, err
368}
369n, err = writeEscapedString(w, lp.GetValue(), true)
370written += n
371if err != nil {
372return written, err
373}
374err = w.WriteByte('"')
375written++
376if err != nil {
377return written, err
378}
379separator = ','
380}
381if additionalLabelName != "" {
382err := w.WriteByte(separator)
383written++
384if err != nil {
385return written, err
386}
387n, err := w.WriteString(additionalLabelName)
388written += n
389if err != nil {
390return written, err
391}
392n, err = w.WriteString(`="`)
393written += n
394if err != nil {
395return written, err
396}
397n, err = writeFloat(w, additionalLabelValue)
398written += n
399if err != nil {
400return written, err
401}
402err = w.WriteByte('"')
403written++
404if err != nil {
405return written, err
406}
407}
408err := w.WriteByte('}')
409written++
410if err != nil {
411return written, err
412}
413return written, nil
414}
415
416// writeEscapedString replaces '\' by '\\', new line character by '\n', and - if
417// includeDoubleQuote is true - '"' by '\"'.
418var (
419escaper = strings.NewReplacer("\\", `\\`, "\n", `\n`)
420quotedEscaper = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
421)
422
423func writeEscapedString(w enhancedWriter, v string, includeDoubleQuote bool) (int, error) {
424if includeDoubleQuote {
425return quotedEscaper.WriteString(w, v)
426}
427return escaper.WriteString(w, v)
428}
429
430// writeFloat is equivalent to fmt.Fprint with a float64 argument but hardcodes
431// a few common cases for increased efficiency. For non-hardcoded cases, it uses
432// strconv.AppendFloat to avoid allocations, similar to writeInt.
433func writeFloat(w enhancedWriter, f float64) (int, error) {
434switch {
435case f == 1:
436return 1, w.WriteByte('1')
437case f == 0:
438return 1, w.WriteByte('0')
439case f == -1:
440return w.WriteString("-1")
441case math.IsNaN(f):
442return w.WriteString("NaN")
443case math.IsInf(f, +1):
444return w.WriteString("+Inf")
445case math.IsInf(f, -1):
446return w.WriteString("-Inf")
447default:
448bp := numBufPool.Get().(*[]byte)
449*bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
450written, err := w.Write(*bp)
451numBufPool.Put(bp)
452return written, err
453}
454}
455
456// writeInt is equivalent to fmt.Fprint with an int64 argument but uses
457// strconv.AppendInt with a byte slice taken from a sync.Pool to avoid
458// allocations.
459func writeInt(w enhancedWriter, i int64) (int, error) {
460bp := numBufPool.Get().(*[]byte)
461*bp = strconv.AppendInt((*bp)[:0], i, 10)
462written, err := w.Write(*bp)
463numBufPool.Put(bp)
464return written, err
465}
466