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	"bytes"
18	"compress/gzip"
19	"io"
20	"io/ioutil"
21	"testing"
22
23	"github.com/matttproud/golang_protobuf_extensions/pbutil"
24
25	dto "github.com/prometheus/client_model/go"
26)
27
28var parser TextParser
29
30// Benchmarks to show how much penalty text format parsing actually inflicts.
31//
32// Example results on Linux 3.13.0, Intel(R) Core(TM) i7-4700MQ CPU @ 2.40GHz, go1.4.
33//
34// BenchmarkParseText          1000           1188535 ns/op          205085 B/op       6135 allocs/op
35// BenchmarkParseTextGzip      1000           1376567 ns/op          246224 B/op       6151 allocs/op
36// BenchmarkParseProto        10000            172790 ns/op           52258 B/op       1160 allocs/op
37// BenchmarkParseProtoGzip     5000            324021 ns/op           94931 B/op       1211 allocs/op
38// BenchmarkParseProtoMap     10000            187946 ns/op           58714 B/op       1203 allocs/op
39//
40// CONCLUSION: The overhead for the map is negligible. Text format needs ~5x more allocations.
41// Without compression, it needs ~7x longer, but with compression (the more relevant scenario),
42// the difference becomes less relevant, only ~4x.
43//
44// The test data contains 248 samples.
45
46// BenchmarkParseText benchmarks the parsing of a text-format scrape into metric
47// family DTOs.
48func BenchmarkParseText(b *testing.B) {
49	b.StopTimer()
50	data, err := ioutil.ReadFile("testdata/text")
51	if err != nil {
52		b.Fatal(err)
53	}
54	b.StartTimer()
55
56	for i := 0; i < b.N; i++ {
57		if _, err := parser.TextToMetricFamilies(bytes.NewReader(data)); err != nil {
58			b.Fatal(err)
59		}
60	}
61}
62
63// BenchmarkParseTextGzip benchmarks the parsing of a gzipped text-format scrape
64// into metric family DTOs.
65func BenchmarkParseTextGzip(b *testing.B) {
66	b.StopTimer()
67	data, err := ioutil.ReadFile("testdata/text.gz")
68	if err != nil {
69		b.Fatal(err)
70	}
71	b.StartTimer()
72
73	for i := 0; i < b.N; i++ {
74		in, err := gzip.NewReader(bytes.NewReader(data))
75		if err != nil {
76			b.Fatal(err)
77		}
78		if _, err := parser.TextToMetricFamilies(in); err != nil {
79			b.Fatal(err)
80		}
81	}
82}
83
84// BenchmarkParseProto benchmarks the parsing of a protobuf-format scrape into
85// metric family DTOs. Note that this does not build a map of metric families
86// (as the text version does), because it is not required for Prometheus
87// ingestion either. (However, it is required for the text-format parsing, as
88// the metric family might be sprinkled all over the text, while the
89// protobuf-format guarantees bundling at one place.)
90func BenchmarkParseProto(b *testing.B) {
91	b.StopTimer()
92	data, err := ioutil.ReadFile("testdata/protobuf")
93	if err != nil {
94		b.Fatal(err)
95	}
96	b.StartTimer()
97
98	for i := 0; i < b.N; i++ {
99		family := &dto.MetricFamily{}
100		in := bytes.NewReader(data)
101		for {
102			family.Reset()
103			if _, err := pbutil.ReadDelimited(in, family); err != nil {
104				if err == io.EOF {
105					break
106				}
107				b.Fatal(err)
108			}
109		}
110	}
111}
112
113// BenchmarkParseProtoGzip is like BenchmarkParseProto above, but parses gzipped
114// protobuf format.
115func BenchmarkParseProtoGzip(b *testing.B) {
116	b.StopTimer()
117	data, err := ioutil.ReadFile("testdata/protobuf.gz")
118	if err != nil {
119		b.Fatal(err)
120	}
121	b.StartTimer()
122
123	for i := 0; i < b.N; i++ {
124		family := &dto.MetricFamily{}
125		in, err := gzip.NewReader(bytes.NewReader(data))
126		if err != nil {
127			b.Fatal(err)
128		}
129		for {
130			family.Reset()
131			if _, err := pbutil.ReadDelimited(in, family); err != nil {
132				if err == io.EOF {
133					break
134				}
135				b.Fatal(err)
136			}
137		}
138	}
139}
140
141// BenchmarkParseProtoMap is like BenchmarkParseProto but DOES put the parsed
142// metric family DTOs into a map. This is not happening during Prometheus
143// ingestion. It is just here to measure the overhead of that map creation and
144// separate it from the overhead of the text format parsing.
145func BenchmarkParseProtoMap(b *testing.B) {
146	b.StopTimer()
147	data, err := ioutil.ReadFile("testdata/protobuf")
148	if err != nil {
149		b.Fatal(err)
150	}
151	b.StartTimer()
152
153	for i := 0; i < b.N; i++ {
154		families := map[string]*dto.MetricFamily{}
155		in := bytes.NewReader(data)
156		for {
157			family := &dto.MetricFamily{}
158			if _, err := pbutil.ReadDelimited(in, family); err != nil {
159				if err == io.EOF {
160					break
161				}
162				b.Fatal(err)
163			}
164			families[family.GetName()] = family
165		}
166	}
167}
168