1
2
3
4
5
6
7
8
9
10
11
12
13
14 package expfmt
15
16 import (
17 "bufio"
18 "bytes"
19 "errors"
20 "io"
21 "math"
22 "net/http"
23 "os"
24 "reflect"
25 "sort"
26 "strings"
27 "testing"
28
29 dto "github.com/prometheus/client_model/go"
30 "google.golang.org/protobuf/proto"
31
32 "github.com/prometheus/common/model"
33 )
34
35 func TestTextDecoder(t *testing.T) {
36 var (
37 ts = model.Now()
38 in = `
39 # Only a quite simple scenario with two metric families.
40 # More complicated tests of the parser itself can be found in the text package.
41 # TYPE mf2 counter
42 mf2 3
43 mf1{label="value1"} -3.14 123456
44 mf1{label="value2"} 42
45 mf2 4
46 `
47 out = model.Vector{
48 &model.Sample{
49 Metric: model.Metric{
50 model.MetricNameLabel: "mf1",
51 "label": "value1",
52 },
53 Value: -3.14,
54 Timestamp: 123456,
55 },
56 &model.Sample{
57 Metric: model.Metric{
58 model.MetricNameLabel: "mf1",
59 "label": "value2",
60 },
61 Value: 42,
62 Timestamp: ts,
63 },
64 &model.Sample{
65 Metric: model.Metric{
66 model.MetricNameLabel: "mf2",
67 },
68 Value: 3,
69 Timestamp: ts,
70 },
71 &model.Sample{
72 Metric: model.Metric{
73 model.MetricNameLabel: "mf2",
74 },
75 Value: 4,
76 Timestamp: ts,
77 },
78 }
79 )
80
81 dec := &SampleDecoder{
82 Dec: &textDecoder{r: strings.NewReader(in)},
83 Opts: &DecodeOptions{
84 Timestamp: ts,
85 },
86 }
87 var all model.Vector
88 for {
89 var smpls model.Vector
90 err := dec.Decode(&smpls)
91 if err != nil && errors.Is(err, io.EOF) {
92 break
93 }
94 if err != nil {
95 t.Fatal(err)
96 }
97 all = append(all, smpls...)
98 }
99 sort.Sort(all)
100 sort.Sort(out)
101 if !reflect.DeepEqual(all, out) {
102 t.Fatalf("output does not match")
103 }
104 }
105
106 func TestProtoDecoder(t *testing.T) {
107 testTime := model.Now()
108
109 scenarios := []struct {
110 in string
111 expected model.Vector
112 legacyNameFail bool
113 fail bool
114 }{
115 {
116 in: "",
117 },
118 {
119 in: "\x8f\x01\n\rrequest_count\x12\x12Number of requests\x18\x00\"0\n#\n\x0fsome_!abel_name\x12\x10some_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00E\xc0\"6\n)\n\x12another_label_name\x12\x13another_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00U@",
120 fail: true,
121 },
122 {
123 in: "\x8f\x01\n\rrequest_count\x12\x12Number of requests\x18\x00\"0\n#\n\x0fsome_label_name\x12\x10some_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00E\xc0\"6\n)\n\x12another_label_name\x12\x13another_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00U@",
124 expected: model.Vector{
125 &model.Sample{
126 Metric: model.Metric{
127 model.MetricNameLabel: "request_count",
128 "some_label_name": "some_label_value",
129 },
130 Value: -42,
131 Timestamp: testTime,
132 },
133 &model.Sample{
134 Metric: model.Metric{
135 model.MetricNameLabel: "request_count",
136 "another_label_name": "another_label_value",
137 },
138 Value: 84,
139 Timestamp: testTime,
140 },
141 },
142 },
143 {
144 in: "\xb9\x01\n\rrequest_count\x12\x12Number of requests\x18\x02\"O\n#\n\x0fsome_label_name\x12\x10some_label_value\"(\x1a\x12\t\xaeG\xe1z\x14\xae\xef?\x11\x00\x00\x00\x00\x00\x00E\xc0\x1a\x12\t+\x87\x16\xd9\xce\xf7\xef?\x11\x00\x00\x00\x00\x00\x00U\xc0\"A\n)\n\x12another_label_name\x12\x13another_label_value\"\x14\x1a\x12\t\x00\x00\x00\x00\x00\x00\xe0?\x11\x00\x00\x00\x00\x00\x00$@",
145 expected: model.Vector{
146 &model.Sample{
147 Metric: model.Metric{
148 model.MetricNameLabel: "request_count_count",
149 "some_label_name": "some_label_value",
150 },
151 Value: 0,
152 Timestamp: testTime,
153 },
154 &model.Sample{
155 Metric: model.Metric{
156 model.MetricNameLabel: "request_count_sum",
157 "some_label_name": "some_label_value",
158 },
159 Value: 0,
160 Timestamp: testTime,
161 },
162 &model.Sample{
163 Metric: model.Metric{
164 model.MetricNameLabel: "request_count",
165 "some_label_name": "some_label_value",
166 "quantile": "0.99",
167 },
168 Value: -42,
169 Timestamp: testTime,
170 },
171 &model.Sample{
172 Metric: model.Metric{
173 model.MetricNameLabel: "request_count",
174 "some_label_name": "some_label_value",
175 "quantile": "0.999",
176 },
177 Value: -84,
178 Timestamp: testTime,
179 },
180 &model.Sample{
181 Metric: model.Metric{
182 model.MetricNameLabel: "request_count_count",
183 "another_label_name": "another_label_value",
184 },
185 Value: 0,
186 Timestamp: testTime,
187 },
188 &model.Sample{
189 Metric: model.Metric{
190 model.MetricNameLabel: "request_count_sum",
191 "another_label_name": "another_label_value",
192 },
193 Value: 0,
194 Timestamp: testTime,
195 },
196 &model.Sample{
197 Metric: model.Metric{
198 model.MetricNameLabel: "request_count",
199 "another_label_name": "another_label_value",
200 "quantile": "0.5",
201 },
202 Value: 10,
203 Timestamp: testTime,
204 },
205 },
206 },
207 {
208 in: "\x8d\x01\n\x1drequest_duration_microseconds\x12\x15The response latency.\x18\x04\"S:Q\b\x85\x15\x11\xcd\xcc\xccL\x8f\xcb:A\x1a\v\b{\x11\x00\x00\x00\x00\x00\x00Y@\x1a\f\b\x9c\x03\x11\x00\x00\x00\x00\x00\x00^@\x1a\f\b\xd0\x04\x11\x00\x00\x00\x00\x00\x00b@\x1a\f\b\xf4\v\x11\x9a\x99\x99\x99\x99\x99e@\x1a\f\b\x85\x15\x11\x00\x00\x00\x00\x00\x00\xf0\u007f",
209 expected: model.Vector{
210 &model.Sample{
211 Metric: model.Metric{
212 model.MetricNameLabel: "request_duration_microseconds_bucket",
213 "le": "100",
214 },
215 Value: 123,
216 Timestamp: testTime,
217 },
218 &model.Sample{
219 Metric: model.Metric{
220 model.MetricNameLabel: "request_duration_microseconds_bucket",
221 "le": "120",
222 },
223 Value: 412,
224 Timestamp: testTime,
225 },
226 &model.Sample{
227 Metric: model.Metric{
228 model.MetricNameLabel: "request_duration_microseconds_bucket",
229 "le": "144",
230 },
231 Value: 592,
232 Timestamp: testTime,
233 },
234 &model.Sample{
235 Metric: model.Metric{
236 model.MetricNameLabel: "request_duration_microseconds_bucket",
237 "le": "172.8",
238 },
239 Value: 1524,
240 Timestamp: testTime,
241 },
242 &model.Sample{
243 Metric: model.Metric{
244 model.MetricNameLabel: "request_duration_microseconds_bucket",
245 "le": "+Inf",
246 },
247 Value: 2693,
248 Timestamp: testTime,
249 },
250 &model.Sample{
251 Metric: model.Metric{
252 model.MetricNameLabel: "request_duration_microseconds_sum",
253 },
254 Value: 1756047.3,
255 Timestamp: testTime,
256 },
257 &model.Sample{
258 Metric: model.Metric{
259 model.MetricNameLabel: "request_duration_microseconds_count",
260 },
261 Value: 2693,
262 Timestamp: testTime,
263 },
264 },
265 },
266 {
267 in: "\u007f\n\x1drequest_duration_microseconds\x12\x15The response latency.\x18\x04\"E:C\b\x85\x15\x11\xcd\xcc\xccL\x8f\xcb:A\x1a\v\b{\x11\x00\x00\x00\x00\x00\x00Y@\x1a\f\b\x9c\x03\x11\x00\x00\x00\x00\x00\x00^@\x1a\f\b\xd0\x04\x11\x00\x00\x00\x00\x00\x00b@\x1a\f\b\xf4\v\x11\x9a\x99\x99\x99\x99\x99e@",
268 expected: model.Vector{
269 &model.Sample{
270 Metric: model.Metric{
271 model.MetricNameLabel: "request_duration_microseconds_count",
272 },
273 Value: 2693,
274 Timestamp: testTime,
275 },
276 &model.Sample{
277 Metric: model.Metric{
278 "le": "+Inf",
279 model.MetricNameLabel: "request_duration_microseconds_bucket",
280 },
281 Value: 2693,
282 Timestamp: testTime,
283 },
284 &model.Sample{
285 Metric: model.Metric{
286 model.MetricNameLabel: "request_duration_microseconds_sum",
287 },
288 Value: 1756047.3,
289 Timestamp: testTime,
290 },
291 &model.Sample{
292 Metric: model.Metric{
293 "le": "172.8",
294 model.MetricNameLabel: "request_duration_microseconds_bucket",
295 },
296 Value: 1524,
297 Timestamp: testTime,
298 },
299 &model.Sample{
300 Metric: model.Metric{
301 "le": "144",
302 model.MetricNameLabel: "request_duration_microseconds_bucket",
303 },
304 Value: 592,
305 Timestamp: testTime,
306 },
307 &model.Sample{
308 Metric: model.Metric{
309 "le": "120",
310 model.MetricNameLabel: "request_duration_microseconds_bucket",
311 },
312 Value: 412,
313 Timestamp: testTime,
314 },
315 &model.Sample{
316 Metric: model.Metric{
317 "le": "100",
318 model.MetricNameLabel: "request_duration_microseconds_bucket",
319 },
320 Value: 123,
321 Timestamp: testTime,
322 },
323 },
324 },
325 {
326
327
328 in: "\x1c\n\rrequest_count\"\v\x1a\t\t\x00\x00\x00\x00\x00\x00\xf0?",
329 expected: model.Vector{
330 &model.Sample{
331 Metric: model.Metric{
332 model.MetricNameLabel: "request_count",
333 },
334 Value: 1,
335 Timestamp: testTime,
336 },
337 },
338 },
339 {
340 in: "\xa8\x01\n\ngauge.name\x12\x11gauge\ndoc\nstr\"ing\x18\x01\"T\n\x1b\n\x06name.1\x12\x11val with\nnew line\n*\n\x06name*2\x12 val with \\backslash and \"quotes\"\x12\t\t\x00\x00\x00\x00\x00\x00\xf0\x7f\"/\n\x10\n\x06name.1\x12\x06Björn\n\x10\n\x06name*2\x12\x06佖佥\x12\t\t\xd1\xcfD\xb9\xd0\x05\xc2H",
341 legacyNameFail: true,
342 expected: model.Vector{
343 &model.Sample{
344 Metric: model.Metric{
345 model.MetricNameLabel: "gauge.name",
346 "name.1": "val with\nnew line",
347 "name*2": "val with \\backslash and \"quotes\"",
348 },
349 Value: model.SampleValue(math.Inf(+1)),
350 Timestamp: testTime,
351 },
352 &model.Sample{
353 Metric: model.Metric{
354 model.MetricNameLabel: "gauge.name",
355 "name.1": "Björn",
356 "name*2": "佖佥",
357 },
358 Value: 3.14e42,
359 Timestamp: testTime,
360 },
361 },
362 },
363 }
364
365 for i, scenario := range scenarios {
366 dec := &SampleDecoder{
367 Dec: &protoDecoder{r: strings.NewReader(scenario.in)},
368 Opts: &DecodeOptions{
369 Timestamp: testTime,
370 },
371 }
372
373 var all model.Vector
374 for {
375 model.NameValidationScheme = model.LegacyValidation
376 var smpls model.Vector
377 err := dec.Decode(&smpls)
378 if err != nil && errors.Is(err, io.EOF) {
379 break
380 }
381 if scenario.legacyNameFail {
382 if err == nil {
383 t.Fatal("Expected error when decoding without UTF-8 support enabled but got none")
384 }
385 model.NameValidationScheme = model.UTF8Validation
386 dec = &SampleDecoder{
387 Dec: &protoDecoder{r: strings.NewReader(scenario.in)},
388 Opts: &DecodeOptions{
389 Timestamp: testTime,
390 },
391 }
392 err = dec.Decode(&smpls)
393 if errors.Is(err, io.EOF) {
394 break
395 }
396 if err != nil {
397 t.Fatalf("Unexpected error when decoding with UTF-8 support: %v", err)
398 }
399 }
400 if scenario.fail {
401 if err == nil {
402 t.Fatal("Expected error but got none")
403 }
404 break
405 }
406 if err != nil {
407 t.Fatal(err)
408 }
409 all = append(all, smpls...)
410 }
411 sort.Sort(all)
412 sort.Sort(scenario.expected)
413 if !reflect.DeepEqual(all, scenario.expected) {
414 t.Fatalf("%d. output does not match, want: %#v, got %#v", i, scenario.expected, all)
415 }
416 }
417 }
418
419 func TestProtoMultiMessageDecoder(t *testing.T) {
420 data, err := os.ReadFile("testdata/protobuf-multimessage")
421 if err != nil {
422 t.Fatalf("Reading file failed: %v", err)
423 }
424
425 buf := bytes.NewReader(data)
426 decoder := NewDecoder(buf, fmtProtoDelim)
427 var metrics []*dto.MetricFamily
428 for {
429 var mf dto.MetricFamily
430 if err := decoder.Decode(&mf); err != nil {
431 if errors.Is(err, io.EOF) {
432 break
433 }
434 t.Fatalf("Unmarshalling failed: %v", err)
435 }
436 metrics = append(metrics, &mf)
437 }
438
439 if len(metrics) != 6 {
440 t.Fatalf("Expected %d metrics but got %d!", 6, len(metrics))
441 }
442 }
443
444 func testDiscriminatorHTTPHeader(t testing.TB) {
445 scenarios := []struct {
446 input map[string]string
447 output Format
448 }{
449 {
450 input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`},
451 output: fmtProtoDelim,
452 },
453 {
454 input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="illegal"; encoding="delimited"`},
455 output: fmtUnknown,
456 },
457 {
458 input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="illegal"`},
459 output: fmtUnknown,
460 },
461 {
462 input: map[string]string{"Content-Type": `text/plain; version=0.0.4`},
463 output: fmtText,
464 },
465 {
466 input: map[string]string{"Content-Type": `text/plain`},
467 output: fmtText,
468 },
469 {
470 input: map[string]string{"Content-Type": `text/plain; version=0.0.3`},
471 output: fmtUnknown,
472 },
473 }
474
475 for i, scenario := range scenarios {
476 var header http.Header
477
478 if len(scenario.input) > 0 {
479 header = http.Header{}
480 }
481
482 for key, value := range scenario.input {
483 header.Add(key, value)
484 }
485
486 actual := ResponseFormat(header)
487
488 if scenario.output != actual {
489 t.Errorf("%d. expected %s, got %s", i, scenario.output, actual)
490 }
491 }
492 }
493
494 func TestDiscriminatorHTTPHeader(t *testing.T) {
495 testDiscriminatorHTTPHeader(t)
496 }
497
498 func BenchmarkDiscriminatorHTTPHeader(b *testing.B) {
499 for i := 0; i < b.N; i++ {
500 testDiscriminatorHTTPHeader(b)
501 }
502 }
503
504 func TestExtractSamples(t *testing.T) {
505 var (
506 goodMetricFamily1 = &dto.MetricFamily{
507 Name: proto.String("foo"),
508 Help: proto.String("Help for foo."),
509 Type: dto.MetricType_COUNTER.Enum(),
510 Metric: []*dto.Metric{
511 {
512 Counter: &dto.Counter{
513 Value: proto.Float64(4711),
514 },
515 },
516 },
517 }
518 goodMetricFamily2 = &dto.MetricFamily{
519 Name: proto.String("bar"),
520 Help: proto.String("Help for bar."),
521 Type: dto.MetricType_GAUGE.Enum(),
522 Metric: []*dto.Metric{
523 {
524 Gauge: &dto.Gauge{
525 Value: proto.Float64(3.14),
526 },
527 },
528 },
529 }
530 badMetricFamily = &dto.MetricFamily{
531 Name: proto.String("bad"),
532 Help: proto.String("Help for bad."),
533 Type: dto.MetricType(42).Enum(),
534 Metric: []*dto.Metric{
535 {
536 Gauge: &dto.Gauge{
537 Value: proto.Float64(2.7),
538 },
539 },
540 },
541 }
542
543 opts = &DecodeOptions{
544 Timestamp: 42,
545 }
546 )
547
548 got, err := ExtractSamples(opts, goodMetricFamily1, goodMetricFamily2)
549 if err != nil {
550 t.Error("Unexpected error from ExtractSamples:", err)
551 }
552 want := model.Vector{
553 &model.Sample{Metric: model.Metric{model.MetricNameLabel: "foo"}, Value: 4711, Timestamp: 42},
554 &model.Sample{Metric: model.Metric{model.MetricNameLabel: "bar"}, Value: 3.14, Timestamp: 42},
555 }
556 if !reflect.DeepEqual(got, want) {
557 t.Errorf("unexpected samples extracted, got: %v, want: %v", got, want)
558 }
559
560 got, err = ExtractSamples(opts, goodMetricFamily1, badMetricFamily, goodMetricFamily2)
561 if err == nil {
562 t.Error("Expected error from ExtractSamples")
563 }
564 if !reflect.DeepEqual(got, want) {
565 t.Errorf("unexpected samples extracted, got: %v, want: %v", got, want)
566 }
567 }
568
569 func TestTextDecoderWithBufioReader(t *testing.T) {
570 example := `
571 # TYPE foo gauge
572 foo 0
573 `
574
575 var decoded bool
576 r := bufio.NewReader(strings.NewReader(example))
577 dec := NewDecoder(r, fmtText)
578 for {
579 var mf dto.MetricFamily
580 if err := dec.Decode(&mf); err != nil {
581 if errors.Is(err, io.EOF) {
582 break
583 }
584 t.Fatalf("Unexpected error: %v", err)
585 }
586 if mf.GetName() != "foo" {
587 t.Errorf("Unexpected metric name: got %v, expected %v", mf.GetName(), "foo")
588 }
589 if len(mf.Metric) != 1 {
590 t.Errorf("Unexpected number of metrics: got %v, expected %v", len(mf.Metric), 1)
591 }
592 decoded = true
593 }
594 if !decoded {
595 t.Fatal("Metric foo not decoded")
596 }
597 }
598
View as plain text