1// 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 (
17	"io"
18	"net/http"
19	"reflect"
20	"sort"
21	"strings"
22	"testing"
23
24	"github.com/golang/protobuf/proto"
25	dto "github.com/prometheus/client_model/go"
26
27	"github.com/prometheus/common/model"
28)
29
30func TestTextDecoder(t *testing.T) {
31	var (
32		ts = model.Now()
33		in = `
34# Only a quite simple scenario with two metric families.
35# More complicated tests of the parser itself can be found in the text package.
36# TYPE mf2 counter
37mf2 3
38mf1{label="value1"} -3.14 123456
39mf1{label="value2"} 42
40mf2 4
41`
42		out = model.Vector{
43			&model.Sample{
44				Metric: model.Metric{
45					model.MetricNameLabel: "mf1",
46					"label":               "value1",
47				},
48				Value:     -3.14,
49				Timestamp: 123456,
50			},
51			&model.Sample{
52				Metric: model.Metric{
53					model.MetricNameLabel: "mf1",
54					"label":               "value2",
55				},
56				Value:     42,
57				Timestamp: ts,
58			},
59			&model.Sample{
60				Metric: model.Metric{
61					model.MetricNameLabel: "mf2",
62				},
63				Value:     3,
64				Timestamp: ts,
65			},
66			&model.Sample{
67				Metric: model.Metric{
68					model.MetricNameLabel: "mf2",
69				},
70				Value:     4,
71				Timestamp: ts,
72			},
73		}
74	)
75
76	dec := &SampleDecoder{
77		Dec: &textDecoder{r: strings.NewReader(in)},
78		Opts: &DecodeOptions{
79			Timestamp: ts,
80		},
81	}
82	var all model.Vector
83	for {
84		var smpls model.Vector
85		err := dec.Decode(&smpls)
86		if err == io.EOF {
87			break
88		}
89		if err != nil {
90			t.Fatal(err)
91		}
92		all = append(all, smpls...)
93	}
94	sort.Sort(all)
95	sort.Sort(out)
96	if !reflect.DeepEqual(all, out) {
97		t.Fatalf("output does not match")
98	}
99}
100
101func TestProtoDecoder(t *testing.T) {
102
103	var testTime = model.Now()
104
105	scenarios := []struct {
106		in       string
107		expected model.Vector
108		fail     bool
109	}{
110		{
111			in: "",
112		},
113		{
114			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@",
115			fail: true,
116		},
117		{
118			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@",
119			expected: model.Vector{
120				&model.Sample{
121					Metric: model.Metric{
122						model.MetricNameLabel: "request_count",
123						"some_label_name":     "some_label_value",
124					},
125					Value:     -42,
126					Timestamp: testTime,
127				},
128				&model.Sample{
129					Metric: model.Metric{
130						model.MetricNameLabel: "request_count",
131						"another_label_name":  "another_label_value",
132					},
133					Value:     84,
134					Timestamp: testTime,
135				},
136			},
137		},
138		{
139			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$@",
140			expected: model.Vector{
141				&model.Sample{
142					Metric: model.Metric{
143						model.MetricNameLabel: "request_count_count",
144						"some_label_name":     "some_label_value",
145					},
146					Value:     0,
147					Timestamp: testTime,
148				},
149				&model.Sample{
150					Metric: model.Metric{
151						model.MetricNameLabel: "request_count_sum",
152						"some_label_name":     "some_label_value",
153					},
154					Value:     0,
155					Timestamp: testTime,
156				},
157				&model.Sample{
158					Metric: model.Metric{
159						model.MetricNameLabel: "request_count",
160						"some_label_name":     "some_label_value",
161						"quantile":            "0.99",
162					},
163					Value:     -42,
164					Timestamp: testTime,
165				},
166				&model.Sample{
167					Metric: model.Metric{
168						model.MetricNameLabel: "request_count",
169						"some_label_name":     "some_label_value",
170						"quantile":            "0.999",
171					},
172					Value:     -84,
173					Timestamp: testTime,
174				},
175				&model.Sample{
176					Metric: model.Metric{
177						model.MetricNameLabel: "request_count_count",
178						"another_label_name":  "another_label_value",
179					},
180					Value:     0,
181					Timestamp: testTime,
182				},
183				&model.Sample{
184					Metric: model.Metric{
185						model.MetricNameLabel: "request_count_sum",
186						"another_label_name":  "another_label_value",
187					},
188					Value:     0,
189					Timestamp: testTime,
190				},
191				&model.Sample{
192					Metric: model.Metric{
193						model.MetricNameLabel: "request_count",
194						"another_label_name":  "another_label_value",
195						"quantile":            "0.5",
196					},
197					Value:     10,
198					Timestamp: testTime,
199				},
200			},
201		},
202		{
203			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",
204			expected: model.Vector{
205				&model.Sample{
206					Metric: model.Metric{
207						model.MetricNameLabel: "request_duration_microseconds_bucket",
208						"le": "100",
209					},
210					Value:     123,
211					Timestamp: testTime,
212				},
213				&model.Sample{
214					Metric: model.Metric{
215						model.MetricNameLabel: "request_duration_microseconds_bucket",
216						"le": "120",
217					},
218					Value:     412,
219					Timestamp: testTime,
220				},
221				&model.Sample{
222					Metric: model.Metric{
223						model.MetricNameLabel: "request_duration_microseconds_bucket",
224						"le": "144",
225					},
226					Value:     592,
227					Timestamp: testTime,
228				},
229				&model.Sample{
230					Metric: model.Metric{
231						model.MetricNameLabel: "request_duration_microseconds_bucket",
232						"le": "172.8",
233					},
234					Value:     1524,
235					Timestamp: testTime,
236				},
237				&model.Sample{
238					Metric: model.Metric{
239						model.MetricNameLabel: "request_duration_microseconds_bucket",
240						"le": "+Inf",
241					},
242					Value:     2693,
243					Timestamp: testTime,
244				},
245				&model.Sample{
246					Metric: model.Metric{
247						model.MetricNameLabel: "request_duration_microseconds_sum",
248					},
249					Value:     1756047.3,
250					Timestamp: testTime,
251				},
252				&model.Sample{
253					Metric: model.Metric{
254						model.MetricNameLabel: "request_duration_microseconds_count",
255					},
256					Value:     2693,
257					Timestamp: testTime,
258				},
259			},
260		},
261		{
262			// The metric type is unset in this protobuf, which needs to be handled
263			// correctly by the decoder.
264			in: "\x1c\n\rrequest_count\"\v\x1a\t\t\x00\x00\x00\x00\x00\x00\xf0?",
265			expected: model.Vector{
266				&model.Sample{
267					Metric: model.Metric{
268						model.MetricNameLabel: "request_count",
269					},
270					Value:     1,
271					Timestamp: testTime,
272				},
273			},
274		},
275	}
276
277	for i, scenario := range scenarios {
278		dec := &SampleDecoder{
279			Dec: &protoDecoder{r: strings.NewReader(scenario.in)},
280			Opts: &DecodeOptions{
281				Timestamp: testTime,
282			},
283		}
284
285		var all model.Vector
286		for {
287			var smpls model.Vector
288			err := dec.Decode(&smpls)
289			if err == io.EOF {
290				break
291			}
292			if scenario.fail {
293				if err == nil {
294					t.Fatal("Expected error but got none")
295				}
296				break
297			}
298			if err != nil {
299				t.Fatal(err)
300			}
301			all = append(all, smpls...)
302		}
303		sort.Sort(all)
304		sort.Sort(scenario.expected)
305		if !reflect.DeepEqual(all, scenario.expected) {
306			t.Fatalf("%d. output does not match, want: %#v, got %#v", i, scenario.expected, all)
307		}
308	}
309}
310
311func testDiscriminatorHTTPHeader(t testing.TB) {
312	var scenarios = []struct {
313		input  map[string]string
314		output Format
315		err    error
316	}{
317		{
318			input:  map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`},
319			output: FmtProtoDelim,
320		},
321		{
322			input:  map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="illegal"; encoding="delimited"`},
323			output: FmtUnknown,
324		},
325		{
326			input:  map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="illegal"`},
327			output: FmtUnknown,
328		},
329		{
330			input:  map[string]string{"Content-Type": `text/plain; version=0.0.4`},
331			output: FmtText,
332		},
333		{
334			input:  map[string]string{"Content-Type": `text/plain`},
335			output: FmtText,
336		},
337		{
338			input:  map[string]string{"Content-Type": `text/plain; version=0.0.3`},
339			output: FmtUnknown,
340		},
341	}
342
343	for i, scenario := range scenarios {
344		var header http.Header
345
346		if len(scenario.input) > 0 {
347			header = http.Header{}
348		}
349
350		for key, value := range scenario.input {
351			header.Add(key, value)
352		}
353
354		actual := ResponseFormat(header)
355
356		if scenario.output != actual {
357			t.Errorf("%d. expected %s, got %s", i, scenario.output, actual)
358		}
359	}
360}
361
362func TestDiscriminatorHTTPHeader(t *testing.T) {
363	testDiscriminatorHTTPHeader(t)
364}
365
366func BenchmarkDiscriminatorHTTPHeader(b *testing.B) {
367	for i := 0; i < b.N; i++ {
368		testDiscriminatorHTTPHeader(b)
369	}
370}
371
372func TestExtractSamples(t *testing.T) {
373	var (
374		goodMetricFamily1 = &dto.MetricFamily{
375			Name: proto.String("foo"),
376			Help: proto.String("Help for foo."),
377			Type: dto.MetricType_COUNTER.Enum(),
378			Metric: []*dto.Metric{
379				&dto.Metric{
380					Counter: &dto.Counter{
381						Value: proto.Float64(4711),
382					},
383				},
384			},
385		}
386		goodMetricFamily2 = &dto.MetricFamily{
387			Name: proto.String("bar"),
388			Help: proto.String("Help for bar."),
389			Type: dto.MetricType_GAUGE.Enum(),
390			Metric: []*dto.Metric{
391				&dto.Metric{
392					Gauge: &dto.Gauge{
393						Value: proto.Float64(3.14),
394					},
395				},
396			},
397		}
398		badMetricFamily = &dto.MetricFamily{
399			Name: proto.String("bad"),
400			Help: proto.String("Help for bad."),
401			Type: dto.MetricType(42).Enum(),
402			Metric: []*dto.Metric{
403				&dto.Metric{
404					Gauge: &dto.Gauge{
405						Value: proto.Float64(2.7),
406					},
407				},
408			},
409		}
410
411		opts = &DecodeOptions{
412			Timestamp: 42,
413		}
414	)
415
416	got, err := ExtractSamples(opts, goodMetricFamily1, goodMetricFamily2)
417	if err != nil {
418		t.Error("Unexpected error from ExtractSamples:", err)
419	}
420	want := model.Vector{
421		&model.Sample{Metric: model.Metric{model.MetricNameLabel: "foo"}, Value: 4711, Timestamp: 42},
422		&model.Sample{Metric: model.Metric{model.MetricNameLabel: "bar"}, Value: 3.14, Timestamp: 42},
423	}
424	if !reflect.DeepEqual(got, want) {
425		t.Errorf("unexpected samples extracted, got: %v, want: %v", got, want)
426	}
427
428	got, err = ExtractSamples(opts, goodMetricFamily1, badMetricFamily, goodMetricFamily2)
429	if err == nil {
430		t.Error("Expected error from ExtractSamples")
431	}
432	if !reflect.DeepEqual(got, want) {
433		t.Errorf("unexpected samples extracted, got: %v, want: %v", got, want)
434	}
435}
436