436 lines
11 KiB
Go
436 lines
11 KiB
Go
// Copyright 2015 The Prometheus Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package expfmt
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
dto "github.com/prometheus/client_model/go"
|
|
|
|
"github.com/prometheus/common/model"
|
|
)
|
|
|
|
func TestTextDecoder(t *testing.T) {
|
|
var (
|
|
ts = model.Now()
|
|
in = `
|
|
# Only a quite simple scenario with two metric families.
|
|
# More complicated tests of the parser itself can be found in the text package.
|
|
# TYPE mf2 counter
|
|
mf2 3
|
|
mf1{label="value1"} -3.14 123456
|
|
mf1{label="value2"} 42
|
|
mf2 4
|
|
`
|
|
out = model.Vector{
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "mf1",
|
|
"label": "value1",
|
|
},
|
|
Value: -3.14,
|
|
Timestamp: 123456,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "mf1",
|
|
"label": "value2",
|
|
},
|
|
Value: 42,
|
|
Timestamp: ts,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "mf2",
|
|
},
|
|
Value: 3,
|
|
Timestamp: ts,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "mf2",
|
|
},
|
|
Value: 4,
|
|
Timestamp: ts,
|
|
},
|
|
}
|
|
)
|
|
|
|
dec := &SampleDecoder{
|
|
Dec: &textDecoder{r: strings.NewReader(in)},
|
|
Opts: &DecodeOptions{
|
|
Timestamp: ts,
|
|
},
|
|
}
|
|
var all model.Vector
|
|
for {
|
|
var smpls model.Vector
|
|
err := dec.Decode(&smpls)
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
all = append(all, smpls...)
|
|
}
|
|
sort.Sort(all)
|
|
sort.Sort(out)
|
|
if !reflect.DeepEqual(all, out) {
|
|
t.Fatalf("output does not match")
|
|
}
|
|
}
|
|
|
|
func TestProtoDecoder(t *testing.T) {
|
|
|
|
var testTime = model.Now()
|
|
|
|
scenarios := []struct {
|
|
in string
|
|
expected model.Vector
|
|
fail bool
|
|
}{
|
|
{
|
|
in: "",
|
|
},
|
|
{
|
|
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@",
|
|
fail: true,
|
|
},
|
|
{
|
|
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@",
|
|
expected: model.Vector{
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_count",
|
|
"some_label_name": "some_label_value",
|
|
},
|
|
Value: -42,
|
|
Timestamp: testTime,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_count",
|
|
"another_label_name": "another_label_value",
|
|
},
|
|
Value: 84,
|
|
Timestamp: testTime,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
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$@",
|
|
expected: model.Vector{
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_count_count",
|
|
"some_label_name": "some_label_value",
|
|
},
|
|
Value: 0,
|
|
Timestamp: testTime,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_count_sum",
|
|
"some_label_name": "some_label_value",
|
|
},
|
|
Value: 0,
|
|
Timestamp: testTime,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_count",
|
|
"some_label_name": "some_label_value",
|
|
"quantile": "0.99",
|
|
},
|
|
Value: -42,
|
|
Timestamp: testTime,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_count",
|
|
"some_label_name": "some_label_value",
|
|
"quantile": "0.999",
|
|
},
|
|
Value: -84,
|
|
Timestamp: testTime,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_count_count",
|
|
"another_label_name": "another_label_value",
|
|
},
|
|
Value: 0,
|
|
Timestamp: testTime,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_count_sum",
|
|
"another_label_name": "another_label_value",
|
|
},
|
|
Value: 0,
|
|
Timestamp: testTime,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_count",
|
|
"another_label_name": "another_label_value",
|
|
"quantile": "0.5",
|
|
},
|
|
Value: 10,
|
|
Timestamp: testTime,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
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",
|
|
expected: model.Vector{
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_duration_microseconds_bucket",
|
|
"le": "100",
|
|
},
|
|
Value: 123,
|
|
Timestamp: testTime,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_duration_microseconds_bucket",
|
|
"le": "120",
|
|
},
|
|
Value: 412,
|
|
Timestamp: testTime,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_duration_microseconds_bucket",
|
|
"le": "144",
|
|
},
|
|
Value: 592,
|
|
Timestamp: testTime,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_duration_microseconds_bucket",
|
|
"le": "172.8",
|
|
},
|
|
Value: 1524,
|
|
Timestamp: testTime,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_duration_microseconds_bucket",
|
|
"le": "+Inf",
|
|
},
|
|
Value: 2693,
|
|
Timestamp: testTime,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_duration_microseconds_sum",
|
|
},
|
|
Value: 1756047.3,
|
|
Timestamp: testTime,
|
|
},
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_duration_microseconds_count",
|
|
},
|
|
Value: 2693,
|
|
Timestamp: testTime,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// The metric type is unset in this protobuf, which needs to be handled
|
|
// correctly by the decoder.
|
|
in: "\x1c\n\rrequest_count\"\v\x1a\t\t\x00\x00\x00\x00\x00\x00\xf0?",
|
|
expected: model.Vector{
|
|
&model.Sample{
|
|
Metric: model.Metric{
|
|
model.MetricNameLabel: "request_count",
|
|
},
|
|
Value: 1,
|
|
Timestamp: testTime,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, scenario := range scenarios {
|
|
dec := &SampleDecoder{
|
|
Dec: &protoDecoder{r: strings.NewReader(scenario.in)},
|
|
Opts: &DecodeOptions{
|
|
Timestamp: testTime,
|
|
},
|
|
}
|
|
|
|
var all model.Vector
|
|
for {
|
|
var smpls model.Vector
|
|
err := dec.Decode(&smpls)
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if scenario.fail {
|
|
if err == nil {
|
|
t.Fatal("Expected error but got none")
|
|
}
|
|
break
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
all = append(all, smpls...)
|
|
}
|
|
sort.Sort(all)
|
|
sort.Sort(scenario.expected)
|
|
if !reflect.DeepEqual(all, scenario.expected) {
|
|
t.Fatalf("%d. output does not match, want: %#v, got %#v", i, scenario.expected, all)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testDiscriminatorHTTPHeader(t testing.TB) {
|
|
var scenarios = []struct {
|
|
input map[string]string
|
|
output Format
|
|
err error
|
|
}{
|
|
{
|
|
input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`},
|
|
output: FmtProtoDelim,
|
|
},
|
|
{
|
|
input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="illegal"; encoding="delimited"`},
|
|
output: FmtUnknown,
|
|
},
|
|
{
|
|
input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="illegal"`},
|
|
output: FmtUnknown,
|
|
},
|
|
{
|
|
input: map[string]string{"Content-Type": `text/plain; version=0.0.4`},
|
|
output: FmtText,
|
|
},
|
|
{
|
|
input: map[string]string{"Content-Type": `text/plain`},
|
|
output: FmtText,
|
|
},
|
|
{
|
|
input: map[string]string{"Content-Type": `text/plain; version=0.0.3`},
|
|
output: FmtUnknown,
|
|
},
|
|
}
|
|
|
|
for i, scenario := range scenarios {
|
|
var header http.Header
|
|
|
|
if len(scenario.input) > 0 {
|
|
header = http.Header{}
|
|
}
|
|
|
|
for key, value := range scenario.input {
|
|
header.Add(key, value)
|
|
}
|
|
|
|
actual := ResponseFormat(header)
|
|
|
|
if scenario.output != actual {
|
|
t.Errorf("%d. expected %s, got %s", i, scenario.output, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDiscriminatorHTTPHeader(t *testing.T) {
|
|
testDiscriminatorHTTPHeader(t)
|
|
}
|
|
|
|
func BenchmarkDiscriminatorHTTPHeader(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
testDiscriminatorHTTPHeader(b)
|
|
}
|
|
}
|
|
|
|
func TestExtractSamples(t *testing.T) {
|
|
var (
|
|
goodMetricFamily1 = &dto.MetricFamily{
|
|
Name: proto.String("foo"),
|
|
Help: proto.String("Help for foo."),
|
|
Type: dto.MetricType_COUNTER.Enum(),
|
|
Metric: []*dto.Metric{
|
|
&dto.Metric{
|
|
Counter: &dto.Counter{
|
|
Value: proto.Float64(4711),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
goodMetricFamily2 = &dto.MetricFamily{
|
|
Name: proto.String("bar"),
|
|
Help: proto.String("Help for bar."),
|
|
Type: dto.MetricType_GAUGE.Enum(),
|
|
Metric: []*dto.Metric{
|
|
&dto.Metric{
|
|
Gauge: &dto.Gauge{
|
|
Value: proto.Float64(3.14),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
badMetricFamily = &dto.MetricFamily{
|
|
Name: proto.String("bad"),
|
|
Help: proto.String("Help for bad."),
|
|
Type: dto.MetricType(42).Enum(),
|
|
Metric: []*dto.Metric{
|
|
&dto.Metric{
|
|
Gauge: &dto.Gauge{
|
|
Value: proto.Float64(2.7),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
opts = &DecodeOptions{
|
|
Timestamp: 42,
|
|
}
|
|
)
|
|
|
|
got, err := ExtractSamples(opts, goodMetricFamily1, goodMetricFamily2)
|
|
if err != nil {
|
|
t.Error("Unexpected error from ExtractSamples:", err)
|
|
}
|
|
want := model.Vector{
|
|
&model.Sample{Metric: model.Metric{model.MetricNameLabel: "foo"}, Value: 4711, Timestamp: 42},
|
|
&model.Sample{Metric: model.Metric{model.MetricNameLabel: "bar"}, Value: 3.14, Timestamp: 42},
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("unexpected samples extracted, got: %v, want: %v", got, want)
|
|
}
|
|
|
|
got, err = ExtractSamples(opts, goodMetricFamily1, badMetricFamily, goodMetricFamily2)
|
|
if err == nil {
|
|
t.Error("Expected error from ExtractSamples")
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("unexpected samples extracted, got: %v, want: %v", got, want)
|
|
}
|
|
}
|