client_golang/prometheus/registry_test.go

634 lines
14 KiB
Go
Raw Normal View History

// Copyright (c) 2013, Prometheus Team
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.
package prometheus
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"io"
"net/http"
"testing"
dto "github.com/prometheus/client_model/go"
"code.google.com/p/goprotobuf/proto"
"github.com/prometheus/client_golang/model"
)
func testRegister(t tester) {
var oldState = struct {
abortOnMisuse bool
debugRegistration bool
useAggressiveSanityChecks bool
}{
abortOnMisuse: *abortOnMisuse,
debugRegistration: *debugRegistration,
useAggressiveSanityChecks: *useAggressiveSanityChecks,
}
defer func() {
abortOnMisuse = &(oldState.abortOnMisuse)
debugRegistration = &(oldState.debugRegistration)
useAggressiveSanityChecks = &(oldState.useAggressiveSanityChecks)
}()
type input struct {
name string
baseLabels map[string]string
}
validLabels := map[string]string{"label": "value"}
var scenarios = []struct {
inputs []input
outputs []bool
}{
{},
{
inputs: []input{
{
name: "my_name_without_labels",
},
},
outputs: []bool{
true,
},
},
{
inputs: []input{
{
name: "my_name_without_labels",
},
{
name: "another_name_without_labels",
},
},
outputs: []bool{
true,
true,
},
},
{
inputs: []input{
{
name: "",
},
},
outputs: []bool{
false,
},
},
{
inputs: []input{
{
name: "valid_name",
baseLabels: map[string]string{model.ReservedLabelPrefix + "internal": "illegal_internal_name"},
},
},
outputs: []bool{
false,
},
},
{
inputs: []input{
{
name: "duplicate_names",
},
{
name: "duplicate_names",
},
},
outputs: []bool{
true,
false,
},
},
{
inputs: []input{
{
name: "duplicate_names_with_identical_labels",
baseLabels: map[string]string{"label": "value"},
},
{
name: "duplicate_names_with_identical_labels",
baseLabels: map[string]string{"label": "value"},
},
},
outputs: []bool{
true,
false,
},
},
{
inputs: []input{
{
name: "metric_1_with_identical_labels",
baseLabels: validLabels,
},
{
name: "metric_2_with_identical_labels",
baseLabels: validLabels,
},
},
outputs: []bool{
true,
true,
},
},
{
inputs: []input{
{
name: "duplicate_names_with_dissimilar_labels",
baseLabels: map[string]string{"label": "foo"},
},
{
name: "duplicate_names_with_dissimilar_labels",
baseLabels: map[string]string{"label": "bar"},
},
},
outputs: []bool{
true,
false,
},
},
}
for i, scenario := range scenarios {
if len(scenario.inputs) != len(scenario.outputs) {
t.Fatalf("%d. expected scenario output length %d, got %d", i, len(scenario.inputs), len(scenario.outputs))
}
abortOnMisuse = proto.Bool(false)
debugRegistration = proto.Bool(false)
useAggressiveSanityChecks = proto.Bool(true)
registry := NewRegistry()
for j, input := range scenario.inputs {
actual := registry.Register(input.name, "", input.baseLabels, nil)
if scenario.outputs[j] != (actual == nil) {
t.Errorf("%d.%d. expected %t, got %t", i, j, scenario.outputs[j], actual == nil)
}
}
}
}
func TestRegister(t *testing.T) {
testRegister(t)
}
func BenchmarkRegister(b *testing.B) {
for i := 0; i < b.N; i++ {
testRegister(b)
}
}
type fakeResponseWriter struct {
header http.Header
body bytes.Buffer
}
func (r *fakeResponseWriter) Header() http.Header {
return r.header
}
func (r *fakeResponseWriter) Write(d []byte) (l int, err error) {
return r.body.Write(d)
}
func (r *fakeResponseWriter) WriteHeader(c int) {
}
func testHandler(t tester) {
metric := NewCounter()
metric.Increment(map[string]string{"labelname": "val1"})
metric.Increment(map[string]string{"labelname": "val2"})
varintBuf := make([]byte, binary.MaxVarintLen32)
externalMetricFamily := []*dto.MetricFamily{
&dto.MetricFamily{
Name: proto.String("externalname"),
Help: proto.String("externaldocstring"),
Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("externallabelname"),
Value: proto.String("externalval1"),
},
&dto.LabelPair{
Name: proto.String("externalbasename"),
Value: proto.String("externalbasevalue"),
},
},
Counter: &dto.Counter{
Value: proto.Float64(1),
},
},
},
},
}
marshaledExternalMetricFamily, err := proto.Marshal(externalMetricFamily[0])
if err != nil {
t.Fatal(err)
}
var externalBuf bytes.Buffer
l := binary.PutUvarint(varintBuf, uint64(len(marshaledExternalMetricFamily)))
_, err = externalBuf.Write(varintBuf[:l])
if err != nil {
t.Fatal(err)
}
_, err = externalBuf.Write(marshaledExternalMetricFamily)
if err != nil {
t.Fatal(err)
}
externalMetricFamilyAsBytes := externalBuf.Bytes()
expectedMetricFamily := &dto.MetricFamily{
Name: proto.String("name"),
Help: proto.String("docstring"),
Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("labelname"),
Value: proto.String("val1"),
},
&dto.LabelPair{
Name: proto.String("basename"),
Value: proto.String("basevalue"),
},
},
Counter: &dto.Counter{
Value: proto.Float64(1),
},
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("labelname"),
Value: proto.String("val2"),
},
&dto.LabelPair{
Name: proto.String("basename"),
Value: proto.String("basevalue"),
},
},
Counter: &dto.Counter{
Value: proto.Float64(1),
},
},
},
}
marshaledExpectedMetricFamily, err := proto.Marshal(expectedMetricFamily)
if err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
l = binary.PutUvarint(varintBuf, uint64(len(marshaledExpectedMetricFamily)))
_, err = buf.Write(varintBuf[:l])
if err != nil {
t.Fatal(err)
}
_, err = buf.Write(marshaledExpectedMetricFamily)
if err != nil {
t.Fatal(err)
}
expectedMetricFamilyAsBytes := buf.Bytes()
type output struct {
headers map[string]string
body []byte
}
var scenarios = []struct {
headers map[string]string
out output
withCounter bool
withExternalMF bool
}{
{
headers: map[string]string{
"Accept": "foo/bar;q=0.2, dings/bums;q=0.8",
},
out: output{
headers: map[string]string{
"Content-Type": `application/json; schema="prometheus/telemetry"; version=0.0.2`,
},
body: []byte("[]\n"),
},
},
{
headers: map[string]string{
"Accept": "foo/bar;q=0.2, application/quark;q=0.8",
},
out: output{
headers: map[string]string{
"Content-Type": `application/json; schema="prometheus/telemetry"; version=0.0.2`,
},
body: []byte("[]\n"),
},
},
{
headers: map[string]string{
"Accept": "foo/bar;q=0.2, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.8",
},
out: output{
headers: map[string]string{
"Content-Type": `application/json; schema="prometheus/telemetry"; version=0.0.2`,
},
body: []byte("[]\n"),
},
},
{
headers: map[string]string{
"Accept": "foo/bar;q=0.2, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.8",
},
out: output{
headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`,
},
body: []byte{},
},
},
{
headers: map[string]string{
"Accept": "application/json",
},
out: output{
headers: map[string]string{
"Content-Type": `application/json; schema="prometheus/telemetry"; version=0.0.2`,
},
body: []byte(`[{"baseLabels":{"__name__":"name","basename":"basevalue"},"docstring":"docstring","metric":{"type":"counter","value":[{"labels":{"labelname":"val1"},"value":1},{"labels":{"labelname":"val2"},"value":1}]}}]
`),
},
withCounter: true,
},
{
headers: map[string]string{
"Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited",
},
out: output{
headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`,
},
body: expectedMetricFamilyAsBytes,
},
withCounter: true,
},
{
headers: map[string]string{
"Accept": "application/json",
},
out: output{
headers: map[string]string{
"Content-Type": `application/json; schema="prometheus/telemetry"; version=0.0.2`,
},
body: []byte("[]\n"),
},
withExternalMF: true,
},
{
headers: map[string]string{
"Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited",
},
out: output{
headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`,
},
body: externalMetricFamilyAsBytes,
},
withExternalMF: true,
},
{
headers: map[string]string{
"Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited",
},
out: output{
headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`,
},
body: bytes.Join(
[][]byte{
expectedMetricFamilyAsBytes,
externalMetricFamilyAsBytes,
},
[]byte{},
),
},
withCounter: true,
withExternalMF: true,
},
}
for i, scenario := range scenarios {
registry := NewRegistry().(*registry)
if scenario.withCounter {
registry.Register(
"name", "docstring",
map[string]string{"basename": "basevalue"},
metric,
)
}
if scenario.withExternalMF {
registry.SetMetricFamilyInjectionHook(
func() []*dto.MetricFamily {
return externalMetricFamily
},
)
}
writer := &fakeResponseWriter{
header: http.Header{},
}
handler := registry.Handler()
request, _ := http.NewRequest("GET", "/", nil)
for key, value := range scenario.headers {
request.Header.Add(key, value)
}
handler(writer, request)
for key, value := range scenario.out.headers {
if writer.Header().Get(key) != value {
t.Errorf(
"%d. expected %q for header %q, got %q",
i, value, key, writer.Header().Get(key),
)
}
}
if !bytes.Equal(scenario.out.body, writer.body.Bytes()) {
t.Errorf(
"%d. expected %q for body, got %q",
i, scenario.out.body, writer.body.Bytes(),
)
}
}
}
func TestHander(t *testing.T) {
testHandler(t)
}
func BenchmarkHandler(b *testing.B) {
for i := 0; i < b.N; i++ {
testHandler(b)
}
}
func testDecorateWriter(t tester) {
type input struct {
headers map[string]string
body []byte
}
type output struct {
headers map[string]string
body []byte
}
var scenarios = []struct {
in input
out output
}{
{},
{
in: input{
headers: map[string]string{
"Accept-Encoding": "gzip,deflate,sdch",
},
body: []byte("Hi, mom!"),
},
out: output{
headers: map[string]string{
"Content-Encoding": "gzip",
},
body: []byte("\x1f\x8b\b\x00\x00\tn\x88\x00\xff\xf2\xc8\xd4Q\xc8\xcd\xcfU\x04\x04\x00\x00\xff\xff9C&&\b\x00\x00\x00"),
},
},
{
in: input{
headers: map[string]string{
"Accept-Encoding": "foo",
},
body: []byte("Hi, mom!"),
},
out: output{
headers: map[string]string{},
body: []byte("Hi, mom!"),
},
},
}
for i, scenario := range scenarios {
request, _ := http.NewRequest("GET", "/", nil)
for key, value := range scenario.in.headers {
request.Header.Add(key, value)
}
baseWriter := &fakeResponseWriter{
header: make(http.Header),
}
writer := decorateWriter(request, baseWriter)
for key, value := range scenario.out.headers {
if baseWriter.Header().Get(key) != value {
t.Errorf("%d. expected %s for header %s, got %s", i, value, key, baseWriter.Header().Get(key))
}
}
writer.Write(scenario.in.body)
if closer, ok := writer.(io.Closer); ok {
closer.Close()
}
if !bytes.Equal(scenario.out.body, baseWriter.body.Bytes()) {
t.Errorf("%d. expected %s for body, got %s", i, scenario.out.body, baseWriter.body.Bytes())
}
}
}
func TestDecorateWriter(t *testing.T) {
testDecorateWriter(t)
}
func BenchmarkDecorateWriter(b *testing.B) {
for i := 0; i < b.N; i++ {
testDecorateWriter(b)
}
}
func testDumpToWriter(t tester) {
type input struct {
metrics map[string]Metric
}
var scenarios = []struct {
in input
out []byte
}{
{
out: []byte("[]"),
},
{
in: input{
metrics: map[string]Metric{
"foo": NewCounter(),
},
},
out: []byte(`[{"baseLabels":{"__name__":"foo","label_foo":"foo"},"docstring":"metric foo","metric":{"type":"counter","value":[]}}]`),
},
{
in: input{
metrics: map[string]Metric{
"foo": NewCounter(),
"bar": NewCounter(),
},
},
out: []byte(`[{"baseLabels":{"__name__":"bar","label_bar":"bar"},"docstring":"metric bar","metric":{"type":"counter","value":[]}},{"baseLabels":{"__name__":"foo","label_foo":"foo"},"docstring":"metric foo","metric":{"type":"counter","value":[]}}]`),
},
}
for i, scenario := range scenarios {
registry := NewRegistry().(*registry)
for name, metric := range scenario.in.metrics {
err := registry.Register(name, fmt.Sprintf("metric %s", name), map[string]string{fmt.Sprintf("label_%s", name): name}, metric)
if err != nil {
t.Errorf("%d. encountered error while registering metric %s", i, err)
}
}
actual, err := json.Marshal(registry)
if err != nil {
t.Errorf("%d. encountered error while dumping %s", i, err)
}
if !bytes.Equal(scenario.out, actual) {
t.Errorf("%d. expected %q for dumping, got %q", i, scenario.out, actual)
}
}
}
func TestDumpToWriter(t *testing.T) {
testDumpToWriter(t)
}
func BenchmarkDumpToWriter(b *testing.B) {
for i := 0; i < b.N; i++ {
testDumpToWriter(b)
}
}