blob: 7b762370e270ef614078a0528f10ff2bf742af77 [file] [log] [blame]
khenaidooab1f7bd2019-11-14 14:00:27 -05001// Copyright 2015 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 (
Abhay Kumara2ae5992025-11-10 14:02:24 +000017 "bufio"
khenaidooab1f7bd2019-11-14 14:00:27 -050018 "fmt"
19 "io"
20 "math"
21 "mime"
22 "net/http"
23
24 dto "github.com/prometheus/client_model/go"
Abhay Kumara2ae5992025-11-10 14:02:24 +000025 "google.golang.org/protobuf/encoding/protodelim"
khenaidooab1f7bd2019-11-14 14:00:27 -050026
khenaidooab1f7bd2019-11-14 14:00:27 -050027 "github.com/prometheus/common/model"
28)
29
30// Decoder types decode an input stream into metric families.
31type Decoder interface {
32 Decode(*dto.MetricFamily) error
33}
34
35// DecodeOptions contains options used by the Decoder and in sample extraction.
36type DecodeOptions struct {
37 // Timestamp is added to each value from the stream that has no explicit timestamp set.
38 Timestamp model.Time
39}
40
41// ResponseFormat extracts the correct format from a HTTP response header.
42// If no matching format can be found FormatUnknown is returned.
43func ResponseFormat(h http.Header) Format {
44 ct := h.Get(hdrContentType)
45
46 mediatype, params, err := mime.ParseMediaType(ct)
47 if err != nil {
48 return FmtUnknown
49 }
50
51 const textType = "text/plain"
52
53 switch mediatype {
54 case ProtoType:
55 if p, ok := params["proto"]; ok && p != ProtoProtocol {
56 return FmtUnknown
57 }
58 if e, ok := params["encoding"]; ok && e != "delimited" {
59 return FmtUnknown
60 }
61 return FmtProtoDelim
62
63 case textType:
64 if v, ok := params["version"]; ok && v != TextVersion {
65 return FmtUnknown
66 }
67 return FmtText
68 }
69
70 return FmtUnknown
71}
72
Abhay Kumara2ae5992025-11-10 14:02:24 +000073// NewDecoder returns a new decoder based on the given input format. Metric
74// names are validated based on the provided Format -- if the format requires
75// escaping, raditional Prometheues validity checking is used. Otherwise, names
76// are checked for UTF-8 validity. Supported formats include delimited protobuf
77// and Prometheus text format. For historical reasons, this decoder fallbacks
78// to classic text decoding for any other format. This decoder does not fully
79// support OpenMetrics although it may often succeed due to the similarities
80// between the formats. This decoder may not support the latest features of
81// Prometheus text format and is not intended for high-performance applications.
82// See: https://github.com/prometheus/common/issues/812
khenaidooab1f7bd2019-11-14 14:00:27 -050083func NewDecoder(r io.Reader, format Format) Decoder {
Abhay Kumara2ae5992025-11-10 14:02:24 +000084 scheme := model.LegacyValidation
85 if format.ToEscapingScheme() == model.NoEscaping {
86 scheme = model.UTF8Validation
khenaidooab1f7bd2019-11-14 14:00:27 -050087 }
Abhay Kumara2ae5992025-11-10 14:02:24 +000088 switch format.FormatType() {
89 case TypeProtoDelim:
90 return &protoDecoder{r: bufio.NewReader(r), s: scheme}
91 case TypeProtoText, TypeProtoCompact:
92 return &errDecoder{err: fmt.Errorf("format %s not supported for decoding", format)}
93 }
94 return &textDecoder{r: r, s: scheme}
khenaidooab1f7bd2019-11-14 14:00:27 -050095}
96
97// protoDecoder implements the Decoder interface for protocol buffers.
98type protoDecoder struct {
Abhay Kumara2ae5992025-11-10 14:02:24 +000099 r protodelim.Reader
100 s model.ValidationScheme
khenaidooab1f7bd2019-11-14 14:00:27 -0500101}
102
103// Decode implements the Decoder interface.
104func (d *protoDecoder) Decode(v *dto.MetricFamily) error {
Abhay Kumara2ae5992025-11-10 14:02:24 +0000105 opts := protodelim.UnmarshalOptions{
106 MaxSize: -1,
107 }
108 if err := opts.UnmarshalFrom(d.r, v); err != nil {
khenaidooab1f7bd2019-11-14 14:00:27 -0500109 return err
110 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000111 if !d.s.IsValidMetricName(v.GetName()) {
khenaidooab1f7bd2019-11-14 14:00:27 -0500112 return fmt.Errorf("invalid metric name %q", v.GetName())
113 }
114 for _, m := range v.GetMetric() {
115 if m == nil {
116 continue
117 }
118 for _, l := range m.GetLabel() {
119 if l == nil {
120 continue
121 }
122 if !model.LabelValue(l.GetValue()).IsValid() {
123 return fmt.Errorf("invalid label value %q", l.GetValue())
124 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000125 if !d.s.IsValidLabelName(l.GetName()) {
khenaidooab1f7bd2019-11-14 14:00:27 -0500126 return fmt.Errorf("invalid label name %q", l.GetName())
127 }
128 }
129 }
130 return nil
131}
132
Abhay Kumara2ae5992025-11-10 14:02:24 +0000133// errDecoder is an error-state decoder that always returns the same error.
134type errDecoder struct {
135 err error
136}
137
138func (d *errDecoder) Decode(*dto.MetricFamily) error {
139 return d.err
140}
141
khenaidooab1f7bd2019-11-14 14:00:27 -0500142// textDecoder implements the Decoder interface for the text protocol.
143type textDecoder struct {
144 r io.Reader
Abhay Kumara2ae5992025-11-10 14:02:24 +0000145 fams map[string]*dto.MetricFamily
146 s model.ValidationScheme
147 err error
khenaidooab1f7bd2019-11-14 14:00:27 -0500148}
149
150// Decode implements the Decoder interface.
151func (d *textDecoder) Decode(v *dto.MetricFamily) error {
Abhay Kumara2ae5992025-11-10 14:02:24 +0000152 if d.err == nil {
153 // Read all metrics in one shot.
154 p := NewTextParser(d.s)
155 d.fams, d.err = p.TextToMetricFamilies(d.r)
156 // If we don't get an error, store io.EOF for the end.
157 if d.err == nil {
158 d.err = io.EOF
khenaidooab1f7bd2019-11-14 14:00:27 -0500159 }
160 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000161 // Pick off one MetricFamily per Decode until there's nothing left.
162 for key, fam := range d.fams {
163 v.Name = fam.Name
164 v.Help = fam.Help
165 v.Type = fam.Type
166 v.Metric = fam.Metric
167 delete(d.fams, key)
168 return nil
169 }
170 return d.err
khenaidooab1f7bd2019-11-14 14:00:27 -0500171}
172
173// SampleDecoder wraps a Decoder to extract samples from the metric families
174// decoded by the wrapped Decoder.
175type SampleDecoder struct {
176 Dec Decoder
177 Opts *DecodeOptions
178
179 f dto.MetricFamily
180}
181
182// Decode calls the Decode method of the wrapped Decoder and then extracts the
183// samples from the decoded MetricFamily into the provided model.Vector.
184func (sd *SampleDecoder) Decode(s *model.Vector) error {
185 err := sd.Dec.Decode(&sd.f)
186 if err != nil {
187 return err
188 }
189 *s, err = extractSamples(&sd.f, sd.Opts)
190 return err
191}
192
193// ExtractSamples builds a slice of samples from the provided metric
khenaidood948f772021-08-11 17:49:24 -0400194// families. If an error occurs during sample extraction, it continues to
khenaidooab1f7bd2019-11-14 14:00:27 -0500195// extract from the remaining metric families. The returned error is the last
196// error that has occurred.
197func ExtractSamples(o *DecodeOptions, fams ...*dto.MetricFamily) (model.Vector, error) {
198 var (
199 all model.Vector
200 lastErr error
201 )
202 for _, f := range fams {
203 some, err := extractSamples(f, o)
204 if err != nil {
205 lastErr = err
206 continue
207 }
208 all = append(all, some...)
209 }
210 return all, lastErr
211}
212
213func extractSamples(f *dto.MetricFamily, o *DecodeOptions) (model.Vector, error) {
214 switch f.GetType() {
215 case dto.MetricType_COUNTER:
216 return extractCounter(o, f), nil
217 case dto.MetricType_GAUGE:
218 return extractGauge(o, f), nil
219 case dto.MetricType_SUMMARY:
220 return extractSummary(o, f), nil
221 case dto.MetricType_UNTYPED:
222 return extractUntyped(o, f), nil
223 case dto.MetricType_HISTOGRAM:
224 return extractHistogram(o, f), nil
225 }
226 return nil, fmt.Errorf("expfmt.extractSamples: unknown metric family type %v", f.GetType())
227}
228
229func extractCounter(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
230 samples := make(model.Vector, 0, len(f.Metric))
231
232 for _, m := range f.Metric {
233 if m.Counter == nil {
234 continue
235 }
236
237 lset := make(model.LabelSet, len(m.Label)+1)
238 for _, p := range m.Label {
239 lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
240 }
241 lset[model.MetricNameLabel] = model.LabelValue(f.GetName())
242
243 smpl := &model.Sample{
244 Metric: model.Metric(lset),
245 Value: model.SampleValue(m.Counter.GetValue()),
246 }
247
248 if m.TimestampMs != nil {
249 smpl.Timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000)
250 } else {
251 smpl.Timestamp = o.Timestamp
252 }
253
254 samples = append(samples, smpl)
255 }
256
257 return samples
258}
259
260func extractGauge(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
261 samples := make(model.Vector, 0, len(f.Metric))
262
263 for _, m := range f.Metric {
264 if m.Gauge == nil {
265 continue
266 }
267
268 lset := make(model.LabelSet, len(m.Label)+1)
269 for _, p := range m.Label {
270 lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
271 }
272 lset[model.MetricNameLabel] = model.LabelValue(f.GetName())
273
274 smpl := &model.Sample{
275 Metric: model.Metric(lset),
276 Value: model.SampleValue(m.Gauge.GetValue()),
277 }
278
279 if m.TimestampMs != nil {
280 smpl.Timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000)
281 } else {
282 smpl.Timestamp = o.Timestamp
283 }
284
285 samples = append(samples, smpl)
286 }
287
288 return samples
289}
290
291func extractUntyped(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
292 samples := make(model.Vector, 0, len(f.Metric))
293
294 for _, m := range f.Metric {
295 if m.Untyped == nil {
296 continue
297 }
298
299 lset := make(model.LabelSet, len(m.Label)+1)
300 for _, p := range m.Label {
301 lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
302 }
303 lset[model.MetricNameLabel] = model.LabelValue(f.GetName())
304
305 smpl := &model.Sample{
306 Metric: model.Metric(lset),
307 Value: model.SampleValue(m.Untyped.GetValue()),
308 }
309
310 if m.TimestampMs != nil {
311 smpl.Timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000)
312 } else {
313 smpl.Timestamp = o.Timestamp
314 }
315
316 samples = append(samples, smpl)
317 }
318
319 return samples
320}
321
322func extractSummary(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
323 samples := make(model.Vector, 0, len(f.Metric))
324
325 for _, m := range f.Metric {
326 if m.Summary == nil {
327 continue
328 }
329
330 timestamp := o.Timestamp
331 if m.TimestampMs != nil {
332 timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000)
333 }
334
335 for _, q := range m.Summary.Quantile {
336 lset := make(model.LabelSet, len(m.Label)+2)
337 for _, p := range m.Label {
338 lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
339 }
340 // BUG(matt): Update other names to "quantile".
341 lset[model.LabelName(model.QuantileLabel)] = model.LabelValue(fmt.Sprint(q.GetQuantile()))
342 lset[model.MetricNameLabel] = model.LabelValue(f.GetName())
343
344 samples = append(samples, &model.Sample{
345 Metric: model.Metric(lset),
346 Value: model.SampleValue(q.GetValue()),
347 Timestamp: timestamp,
348 })
349 }
350
351 lset := make(model.LabelSet, len(m.Label)+1)
352 for _, p := range m.Label {
353 lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
354 }
355 lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum")
356
357 samples = append(samples, &model.Sample{
358 Metric: model.Metric(lset),
359 Value: model.SampleValue(m.Summary.GetSampleSum()),
360 Timestamp: timestamp,
361 })
362
363 lset = make(model.LabelSet, len(m.Label)+1)
364 for _, p := range m.Label {
365 lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
366 }
367 lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count")
368
369 samples = append(samples, &model.Sample{
370 Metric: model.Metric(lset),
371 Value: model.SampleValue(m.Summary.GetSampleCount()),
372 Timestamp: timestamp,
373 })
374 }
375
376 return samples
377}
378
379func extractHistogram(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
380 samples := make(model.Vector, 0, len(f.Metric))
381
382 for _, m := range f.Metric {
383 if m.Histogram == nil {
384 continue
385 }
386
387 timestamp := o.Timestamp
388 if m.TimestampMs != nil {
389 timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000)
390 }
391
392 infSeen := false
393
394 for _, q := range m.Histogram.Bucket {
395 lset := make(model.LabelSet, len(m.Label)+2)
396 for _, p := range m.Label {
397 lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
398 }
399 lset[model.LabelName(model.BucketLabel)] = model.LabelValue(fmt.Sprint(q.GetUpperBound()))
400 lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket")
401
402 if math.IsInf(q.GetUpperBound(), +1) {
403 infSeen = true
404 }
405
406 samples = append(samples, &model.Sample{
407 Metric: model.Metric(lset),
408 Value: model.SampleValue(q.GetCumulativeCount()),
409 Timestamp: timestamp,
410 })
411 }
412
413 lset := make(model.LabelSet, len(m.Label)+1)
414 for _, p := range m.Label {
415 lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
416 }
417 lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum")
418
419 samples = append(samples, &model.Sample{
420 Metric: model.Metric(lset),
421 Value: model.SampleValue(m.Histogram.GetSampleSum()),
422 Timestamp: timestamp,
423 })
424
425 lset = make(model.LabelSet, len(m.Label)+1)
426 for _, p := range m.Label {
427 lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
428 }
429 lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count")
430
431 count := &model.Sample{
432 Metric: model.Metric(lset),
433 Value: model.SampleValue(m.Histogram.GetSampleCount()),
434 Timestamp: timestamp,
435 }
436 samples = append(samples, count)
437
438 if !infSeen {
439 // Append an infinity bucket sample.
440 lset := make(model.LabelSet, len(m.Label)+2)
441 for _, p := range m.Label {
442 lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
443 }
444 lset[model.LabelName(model.BucketLabel)] = model.LabelValue("+Inf")
445 lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket")
446
447 samples = append(samples, &model.Sample{
448 Metric: model.Metric(lset),
449 Value: count.Value,
450 Timestamp: timestamp,
451 })
452 }
453 }
454
455 return samples
456}