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	"bytes"
18	"math"
19	"strings"
20	"testing"
21
22	"github.com/golang/protobuf/proto"
23
24	dto "github.com/prometheus/client_model/go"
25)
26
27func testCreate(t testing.TB) {
28	var scenarios = []struct {
29		in  *dto.MetricFamily
30		out string
31	}{
32		// 0: Counter, NaN as value, timestamp given.
33		{
34			in: &dto.MetricFamily{
35				Name: proto.String("name"),
36				Help: proto.String("two-line\n doc  str\\ing"),
37				Type: dto.MetricType_COUNTER.Enum(),
38				Metric: []*dto.Metric{
39					&dto.Metric{
40						Label: []*dto.LabelPair{
41							&dto.LabelPair{
42								Name:  proto.String("labelname"),
43								Value: proto.String("val1"),
44							},
45							&dto.LabelPair{
46								Name:  proto.String("basename"),
47								Value: proto.String("basevalue"),
48							},
49						},
50						Counter: &dto.Counter{
51							Value: proto.Float64(math.NaN()),
52						},
53					},
54					&dto.Metric{
55						Label: []*dto.LabelPair{
56							&dto.LabelPair{
57								Name:  proto.String("labelname"),
58								Value: proto.String("val2"),
59							},
60							&dto.LabelPair{
61								Name:  proto.String("basename"),
62								Value: proto.String("basevalue"),
63							},
64						},
65						Counter: &dto.Counter{
66							Value: proto.Float64(.23),
67						},
68						TimestampMs: proto.Int64(1234567890),
69					},
70				},
71			},
72			out: `# HELP name two-line\n doc  str\\ing
73# TYPE name counter
74name{labelname="val1",basename="basevalue"} NaN
75name{labelname="val2",basename="basevalue"} 0.23 1234567890
76`,
77		},
78		// 1: Gauge, some escaping required, +Inf as value, multi-byte characters in label values.
79		{
80			in: &dto.MetricFamily{
81				Name: proto.String("gauge_name"),
82				Help: proto.String("gauge\ndoc\nstr\"ing"),
83				Type: dto.MetricType_GAUGE.Enum(),
84				Metric: []*dto.Metric{
85					&dto.Metric{
86						Label: []*dto.LabelPair{
87							&dto.LabelPair{
88								Name:  proto.String("name_1"),
89								Value: proto.String("val with\nnew line"),
90							},
91							&dto.LabelPair{
92								Name:  proto.String("name_2"),
93								Value: proto.String("val with \\backslash and \"quotes\""),
94							},
95						},
96						Gauge: &dto.Gauge{
97							Value: proto.Float64(math.Inf(+1)),
98						},
99					},
100					&dto.Metric{
101						Label: []*dto.LabelPair{
102							&dto.LabelPair{
103								Name:  proto.String("name_1"),
104								Value: proto.String("Björn"),
105							},
106							&dto.LabelPair{
107								Name:  proto.String("name_2"),
108								Value: proto.String("佖佥"),
109							},
110						},
111						Gauge: &dto.Gauge{
112							Value: proto.Float64(3.14E42),
113						},
114					},
115				},
116			},
117			out: `# HELP gauge_name gauge\ndoc\nstr"ing
118# TYPE gauge_name gauge
119gauge_name{name_1="val with\nnew line",name_2="val with \\backslash and \"quotes\""} +Inf
120gauge_name{name_1="Björn",name_2="佖佥"} 3.14e+42
121`,
122		},
123		// 2: Untyped, no help, one sample with no labels and -Inf as value, another sample with one label.
124		{
125			in: &dto.MetricFamily{
126				Name: proto.String("untyped_name"),
127				Type: dto.MetricType_UNTYPED.Enum(),
128				Metric: []*dto.Metric{
129					&dto.Metric{
130						Untyped: &dto.Untyped{
131							Value: proto.Float64(math.Inf(-1)),
132						},
133					},
134					&dto.Metric{
135						Label: []*dto.LabelPair{
136							&dto.LabelPair{
137								Name:  proto.String("name_1"),
138								Value: proto.String("value 1"),
139							},
140						},
141						Untyped: &dto.Untyped{
142							Value: proto.Float64(-1.23e-45),
143						},
144					},
145				},
146			},
147			out: `# TYPE untyped_name untyped
148untyped_name -Inf
149untyped_name{name_1="value 1"} -1.23e-45
150`,
151		},
152		// 3: Summary.
153		{
154			in: &dto.MetricFamily{
155				Name: proto.String("summary_name"),
156				Help: proto.String("summary docstring"),
157				Type: dto.MetricType_SUMMARY.Enum(),
158				Metric: []*dto.Metric{
159					&dto.Metric{
160						Summary: &dto.Summary{
161							SampleCount: proto.Uint64(42),
162							SampleSum:   proto.Float64(-3.4567),
163							Quantile: []*dto.Quantile{
164								&dto.Quantile{
165									Quantile: proto.Float64(0.5),
166									Value:    proto.Float64(-1.23),
167								},
168								&dto.Quantile{
169									Quantile: proto.Float64(0.9),
170									Value:    proto.Float64(.2342354),
171								},
172								&dto.Quantile{
173									Quantile: proto.Float64(0.99),
174									Value:    proto.Float64(0),
175								},
176							},
177						},
178					},
179					&dto.Metric{
180						Label: []*dto.LabelPair{
181							&dto.LabelPair{
182								Name:  proto.String("name_1"),
183								Value: proto.String("value 1"),
184							},
185							&dto.LabelPair{
186								Name:  proto.String("name_2"),
187								Value: proto.String("value 2"),
188							},
189						},
190						Summary: &dto.Summary{
191							SampleCount: proto.Uint64(4711),
192							SampleSum:   proto.Float64(2010.1971),
193							Quantile: []*dto.Quantile{
194								&dto.Quantile{
195									Quantile: proto.Float64(0.5),
196									Value:    proto.Float64(1),
197								},
198								&dto.Quantile{
199									Quantile: proto.Float64(0.9),
200									Value:    proto.Float64(2),
201								},
202								&dto.Quantile{
203									Quantile: proto.Float64(0.99),
204									Value:    proto.Float64(3),
205								},
206							},
207						},
208					},
209				},
210			},
211			out: `# HELP summary_name summary docstring
212# TYPE summary_name summary
213summary_name{quantile="0.5"} -1.23
214summary_name{quantile="0.9"} 0.2342354
215summary_name{quantile="0.99"} 0
216summary_name_sum -3.4567
217summary_name_count 42
218summary_name{name_1="value 1",name_2="value 2",quantile="0.5"} 1
219summary_name{name_1="value 1",name_2="value 2",quantile="0.9"} 2
220summary_name{name_1="value 1",name_2="value 2",quantile="0.99"} 3
221summary_name_sum{name_1="value 1",name_2="value 2"} 2010.1971
222summary_name_count{name_1="value 1",name_2="value 2"} 4711
223`,
224		},
225		// 4: Histogram
226		{
227			in: &dto.MetricFamily{
228				Name: proto.String("request_duration_microseconds"),
229				Help: proto.String("The response latency."),
230				Type: dto.MetricType_HISTOGRAM.Enum(),
231				Metric: []*dto.Metric{
232					&dto.Metric{
233						Histogram: &dto.Histogram{
234							SampleCount: proto.Uint64(2693),
235							SampleSum:   proto.Float64(1756047.3),
236							Bucket: []*dto.Bucket{
237								&dto.Bucket{
238									UpperBound:      proto.Float64(100),
239									CumulativeCount: proto.Uint64(123),
240								},
241								&dto.Bucket{
242									UpperBound:      proto.Float64(120),
243									CumulativeCount: proto.Uint64(412),
244								},
245								&dto.Bucket{
246									UpperBound:      proto.Float64(144),
247									CumulativeCount: proto.Uint64(592),
248								},
249								&dto.Bucket{
250									UpperBound:      proto.Float64(172.8),
251									CumulativeCount: proto.Uint64(1524),
252								},
253								&dto.Bucket{
254									UpperBound:      proto.Float64(math.Inf(+1)),
255									CumulativeCount: proto.Uint64(2693),
256								},
257							},
258						},
259					},
260				},
261			},
262			out: `# HELP request_duration_microseconds The response latency.
263# TYPE request_duration_microseconds histogram
264request_duration_microseconds_bucket{le="100"} 123
265request_duration_microseconds_bucket{le="120"} 412
266request_duration_microseconds_bucket{le="144"} 592
267request_duration_microseconds_bucket{le="172.8"} 1524
268request_duration_microseconds_bucket{le="+Inf"} 2693
269request_duration_microseconds_sum 1.7560473e+06
270request_duration_microseconds_count 2693
271`,
272		},
273		// 5: Histogram with missing +Inf bucket.
274		{
275			in: &dto.MetricFamily{
276				Name: proto.String("request_duration_microseconds"),
277				Help: proto.String("The response latency."),
278				Type: dto.MetricType_HISTOGRAM.Enum(),
279				Metric: []*dto.Metric{
280					&dto.Metric{
281						Histogram: &dto.Histogram{
282							SampleCount: proto.Uint64(2693),
283							SampleSum:   proto.Float64(1756047.3),
284							Bucket: []*dto.Bucket{
285								&dto.Bucket{
286									UpperBound:      proto.Float64(100),
287									CumulativeCount: proto.Uint64(123),
288								},
289								&dto.Bucket{
290									UpperBound:      proto.Float64(120),
291									CumulativeCount: proto.Uint64(412),
292								},
293								&dto.Bucket{
294									UpperBound:      proto.Float64(144),
295									CumulativeCount: proto.Uint64(592),
296								},
297								&dto.Bucket{
298									UpperBound:      proto.Float64(172.8),
299									CumulativeCount: proto.Uint64(1524),
300								},
301							},
302						},
303					},
304				},
305			},
306			out: `# HELP request_duration_microseconds The response latency.
307# TYPE request_duration_microseconds histogram
308request_duration_microseconds_bucket{le="100"} 123
309request_duration_microseconds_bucket{le="120"} 412
310request_duration_microseconds_bucket{le="144"} 592
311request_duration_microseconds_bucket{le="172.8"} 1524
312request_duration_microseconds_bucket{le="+Inf"} 2693
313request_duration_microseconds_sum 1.7560473e+06
314request_duration_microseconds_count 2693
315`,
316		},
317		// 6: No metric type, should result in default type Counter.
318		{
319			in: &dto.MetricFamily{
320				Name: proto.String("name"),
321				Help: proto.String("doc string"),
322				Metric: []*dto.Metric{
323					&dto.Metric{
324						Counter: &dto.Counter{
325							Value: proto.Float64(math.Inf(-1)),
326						},
327					},
328				},
329			},
330			out: `# HELP name doc string
331# TYPE name counter
332name -Inf
333`,
334		},
335	}
336
337	for i, scenario := range scenarios {
338		out := bytes.NewBuffer(make([]byte, 0, len(scenario.out)))
339		n, err := MetricFamilyToText(out, scenario.in)
340		if err != nil {
341			t.Errorf("%d. error: %s", i, err)
342			continue
343		}
344		if expected, got := len(scenario.out), n; expected != got {
345			t.Errorf(
346				"%d. expected %d bytes written, got %d",
347				i, expected, got,
348			)
349		}
350		if expected, got := scenario.out, out.String(); expected != got {
351			t.Errorf(
352				"%d. expected out=%q, got %q",
353				i, expected, got,
354			)
355		}
356	}
357
358}
359
360func TestCreate(t *testing.T) {
361	testCreate(t)
362}
363
364func BenchmarkCreate(b *testing.B) {
365	for i := 0; i < b.N; i++ {
366		testCreate(b)
367	}
368}
369
370func testCreateError(t testing.TB) {
371	var scenarios = []struct {
372		in  *dto.MetricFamily
373		err string
374	}{
375		// 0: No metric.
376		{
377			in: &dto.MetricFamily{
378				Name:   proto.String("name"),
379				Help:   proto.String("doc string"),
380				Type:   dto.MetricType_COUNTER.Enum(),
381				Metric: []*dto.Metric{},
382			},
383			err: "MetricFamily has no metrics",
384		},
385		// 1: No metric name.
386		{
387			in: &dto.MetricFamily{
388				Help: proto.String("doc string"),
389				Type: dto.MetricType_UNTYPED.Enum(),
390				Metric: []*dto.Metric{
391					&dto.Metric{
392						Untyped: &dto.Untyped{
393							Value: proto.Float64(math.Inf(-1)),
394						},
395					},
396				},
397			},
398			err: "MetricFamily has no name",
399		},
400		// 2: Wrong type.
401		{
402			in: &dto.MetricFamily{
403				Name: proto.String("name"),
404				Help: proto.String("doc string"),
405				Type: dto.MetricType_COUNTER.Enum(),
406				Metric: []*dto.Metric{
407					&dto.Metric{
408						Untyped: &dto.Untyped{
409							Value: proto.Float64(math.Inf(-1)),
410						},
411					},
412				},
413			},
414			err: "expected counter in metric",
415		},
416	}
417
418	for i, scenario := range scenarios {
419		var out bytes.Buffer
420		_, err := MetricFamilyToText(&out, scenario.in)
421		if err == nil {
422			t.Errorf("%d. expected error, got nil", i)
423			continue
424		}
425		if expected, got := scenario.err, err.Error(); strings.Index(got, expected) != 0 {
426			t.Errorf(
427				"%d. expected error starting with %q, got %q",
428				i, expected, got,
429			)
430		}
431	}
432
433}
434
435func TestCreateError(t *testing.T) {
436	testCreateError(t)
437}
438
439func BenchmarkCreateError(b *testing.B) {
440	for i := 0; i < b.N; i++ {
441		testCreateError(b)
442	}
443}
444