mirror of
https://github.com/hanzoai/common.git
synced 2026-10-30 18:44:05 +00:00
Module path renamed github.com/prometheus/common → github.com/hanzoai/common. The only functional patch is config/http_config.go: strip conntrack-based dial-context instrumentation, replace with stdlib net.Dialer. This breaks the transitive chain prometheus/common/config → mwitkow/go-conntrack → prometheus/client_golang/prometheus that was leaking into every consumer of prometheus/common/config (alertmanager notifiers, etc.). Functional impact: HTTP clients built via NewClientFromConfig no longer emit go-conntrack connection-tracking metrics (active/closed counters). The connection count is observable via the underlying http.Transport metrics that luxfi/metric ships, so this is loss-of-observability-zero for callers on the Hanzo/Lux stack.
1416 lines
37 KiB
Go
1416 lines
37 KiB
Go
// Copyright 2014 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 (
|
|
"errors"
|
|
"math"
|
|
"strings"
|
|
"testing"
|
|
|
|
dto "github.com/prometheus/client_model/go"
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"github.com/prometheus/common/model"
|
|
)
|
|
|
|
func TestNewTextParser(t *testing.T) {
|
|
p := NewTextParser(model.UTF8Validation)
|
|
if p.scheme != model.UTF8Validation {
|
|
t.Errorf("expected NewTextParser to return a TextParser with scheme %s - got %s", model.UTF8Validation, p.scheme)
|
|
}
|
|
|
|
p = NewTextParser(model.LegacyValidation)
|
|
if p.scheme != model.LegacyValidation {
|
|
t.Errorf("expected NewTextParser to return a TextParser with scheme %s - got %s", model.LegacyValidation, p.scheme)
|
|
}
|
|
|
|
p = NewTextParser(model.UnsetValidation)
|
|
if p.scheme != model.UnsetValidation {
|
|
t.Errorf("expected NewTextParser to return a TextParser with scheme %s - got %s", model.UnsetValidation, p.scheme)
|
|
}
|
|
}
|
|
|
|
func testTextParse(t testing.TB) {
|
|
scenarios := []struct {
|
|
in string
|
|
out []*dto.MetricFamily
|
|
}{
|
|
// 0: Empty lines as input.
|
|
{
|
|
in: `
|
|
|
|
`,
|
|
out: []*dto.MetricFamily{},
|
|
},
|
|
// 1: Minimal case.
|
|
{
|
|
in: `
|
|
minimal_metric 1.234
|
|
another_metric -3e3 103948
|
|
# Even that:
|
|
no_labels{} 3
|
|
# HELP line for non-existing metric will be ignored.
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("minimal_metric"),
|
|
Type: dto.MetricType_UNTYPED.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Untyped: &dto.Untyped{
|
|
Value: proto.Float64(1.234),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: proto.String("another_metric"),
|
|
Type: dto.MetricType_UNTYPED.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Untyped: &dto.Untyped{
|
|
Value: proto.Float64(-3e3),
|
|
},
|
|
TimestampMs: proto.Int64(103948),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: proto.String("no_labels"),
|
|
Type: dto.MetricType_UNTYPED.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Untyped: &dto.Untyped{
|
|
Value: proto.Float64(3),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 2: Counters & gauges, docstrings, various whitespace, escape sequences.
|
|
{
|
|
in: `
|
|
# A normal comment.
|
|
#
|
|
# TYPE name counter
|
|
name{labelname="val1",basename="basevalue"} NaN
|
|
name {labelname="val2",basename="base\"v\\al\nue"} 0.23 1234567890
|
|
# HELP name two-line\n doc str\\ing
|
|
|
|
# HELP name2 doc str"ing 2
|
|
# TYPE name2 gauge
|
|
name2{labelname="val2" ,basename = "basevalue2" } +Inf 54321
|
|
name2{ labelname = "val1" , }-Inf
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("name"),
|
|
Help: proto.String("two-line\n doc str\\ing"),
|
|
Type: dto.MetricType_COUNTER.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("labelname"),
|
|
Value: proto.String("val1"),
|
|
},
|
|
{
|
|
Name: proto.String("basename"),
|
|
Value: proto.String("basevalue"),
|
|
},
|
|
},
|
|
Counter: &dto.Counter{
|
|
Value: proto.Float64(math.NaN()),
|
|
},
|
|
},
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("labelname"),
|
|
Value: proto.String("val2"),
|
|
},
|
|
{
|
|
Name: proto.String("basename"),
|
|
Value: proto.String("base\"v\\al\nue"),
|
|
},
|
|
},
|
|
Counter: &dto.Counter{
|
|
Value: proto.Float64(.23),
|
|
},
|
|
TimestampMs: proto.Int64(1234567890),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: proto.String("name2"),
|
|
Help: proto.String("doc str\"ing 2"),
|
|
Type: dto.MetricType_GAUGE.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("labelname"),
|
|
Value: proto.String("val2"),
|
|
},
|
|
{
|
|
Name: proto.String("basename"),
|
|
Value: proto.String("basevalue2"),
|
|
},
|
|
},
|
|
Gauge: &dto.Gauge{
|
|
Value: proto.Float64(math.Inf(+1)),
|
|
},
|
|
TimestampMs: proto.Int64(54321),
|
|
},
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("labelname"),
|
|
Value: proto.String("val1"),
|
|
},
|
|
},
|
|
Gauge: &dto.Gauge{
|
|
Value: proto.Float64(math.Inf(-1)),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 3: The evil summary, mixed with other types and funny comments.
|
|
{
|
|
in: `
|
|
# TYPE my_summary summary
|
|
my_summary{n1="val1",quantile="0.5"} 110
|
|
decoy -1 -2
|
|
my_summary{n1="val1",quantile="0.9"} 140 1
|
|
my_summary_count{n1="val1"} 42
|
|
# Latest timestamp wins in case of a summary.
|
|
my_summary_sum{n1="val1"} 4711 2
|
|
fake_sum{n1="val1"} 2001
|
|
# TYPE another_summary summary
|
|
another_summary_count{n2="val2",n1="val1"} 20
|
|
my_summary_count{n2="val2",n1="val1"} 5 5
|
|
another_summary{n1="val1",n2="val2",quantile=".3"} -1.2
|
|
my_summary_sum{n1="val2"} 08 15
|
|
my_summary{n1="val3", quantile="0.2"} 4711
|
|
my_summary{n1="val1",n2="val2",quantile="-12.34",} NaN
|
|
# some
|
|
# funny comments
|
|
# HELP
|
|
# HELP
|
|
# HELP my_summary
|
|
# HELP my_summary
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("fake_sum"),
|
|
Type: dto.MetricType_UNTYPED.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("n1"),
|
|
Value: proto.String("val1"),
|
|
},
|
|
},
|
|
Untyped: &dto.Untyped{
|
|
Value: proto.Float64(2001),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: proto.String("decoy"),
|
|
Type: dto.MetricType_UNTYPED.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Untyped: &dto.Untyped{
|
|
Value: proto.Float64(-1),
|
|
},
|
|
TimestampMs: proto.Int64(-2),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: proto.String("my_summary"),
|
|
Type: dto.MetricType_SUMMARY.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("n1"),
|
|
Value: proto.String("val1"),
|
|
},
|
|
},
|
|
Summary: &dto.Summary{
|
|
SampleCount: proto.Uint64(42),
|
|
SampleSum: proto.Float64(4711),
|
|
Quantile: []*dto.Quantile{
|
|
{
|
|
Quantile: proto.Float64(0.5),
|
|
Value: proto.Float64(110),
|
|
},
|
|
{
|
|
Quantile: proto.Float64(0.9),
|
|
Value: proto.Float64(140),
|
|
},
|
|
},
|
|
},
|
|
TimestampMs: proto.Int64(2),
|
|
},
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("n2"),
|
|
Value: proto.String("val2"),
|
|
},
|
|
{
|
|
Name: proto.String("n1"),
|
|
Value: proto.String("val1"),
|
|
},
|
|
},
|
|
Summary: &dto.Summary{
|
|
SampleCount: proto.Uint64(5),
|
|
Quantile: []*dto.Quantile{
|
|
{
|
|
Quantile: proto.Float64(-12.34),
|
|
Value: proto.Float64(math.NaN()),
|
|
},
|
|
},
|
|
},
|
|
TimestampMs: proto.Int64(5),
|
|
},
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("n1"),
|
|
Value: proto.String("val2"),
|
|
},
|
|
},
|
|
Summary: &dto.Summary{
|
|
SampleSum: proto.Float64(8),
|
|
},
|
|
TimestampMs: proto.Int64(15),
|
|
},
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("n1"),
|
|
Value: proto.String("val3"),
|
|
},
|
|
},
|
|
Summary: &dto.Summary{
|
|
Quantile: []*dto.Quantile{
|
|
{
|
|
Quantile: proto.Float64(0.2),
|
|
Value: proto.Float64(4711),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: proto.String("another_summary"),
|
|
Type: dto.MetricType_SUMMARY.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("n2"),
|
|
Value: proto.String("val2"),
|
|
},
|
|
{
|
|
Name: proto.String("n1"),
|
|
Value: proto.String("val1"),
|
|
},
|
|
},
|
|
Summary: &dto.Summary{
|
|
SampleCount: proto.Uint64(20),
|
|
Quantile: []*dto.Quantile{
|
|
{
|
|
Quantile: proto.Float64(0.3),
|
|
Value: proto.Float64(-1.2),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 4: The histogram.
|
|
{
|
|
in: `
|
|
# HELP request_duration_microseconds The response latency.
|
|
# TYPE request_duration_microseconds histogram
|
|
request_duration_microseconds_bucket{le="100"} 123
|
|
request_duration_microseconds_bucket{le="120"} 412
|
|
request_duration_microseconds_bucket{le="144"} 592
|
|
request_duration_microseconds_bucket{le="172.8"} 1524
|
|
request_duration_microseconds_bucket{le="+Inf"} 2693
|
|
request_duration_microseconds_sum 1.7560473e+06
|
|
request_duration_microseconds_count 2693
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("request_duration_microseconds"),
|
|
Help: proto.String("The response latency."),
|
|
Type: dto.MetricType_HISTOGRAM.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Histogram: &dto.Histogram{
|
|
SampleCount: proto.Uint64(2693),
|
|
SampleSum: proto.Float64(1756047.3),
|
|
Bucket: []*dto.Bucket{
|
|
{
|
|
UpperBound: proto.Float64(100),
|
|
CumulativeCount: proto.Uint64(123),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(120),
|
|
CumulativeCount: proto.Uint64(412),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(144),
|
|
CumulativeCount: proto.Uint64(592),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(172.8),
|
|
CumulativeCount: proto.Uint64(1524),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(math.Inf(+1)),
|
|
CumulativeCount: proto.Uint64(2693),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 5: Quoted metric name and quoted label name with dots.
|
|
{
|
|
in: `
|
|
# HELP "my.noncompliant.metric" help text
|
|
# TYPE "my.noncompliant.metric" counter
|
|
{"my.noncompliant.metric","label.name"="value"} 1
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("my.noncompliant.metric"),
|
|
Help: proto.String("help text"),
|
|
Type: dto.MetricType_COUNTER.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("label.name"),
|
|
Value: proto.String("value"),
|
|
},
|
|
},
|
|
Counter: &dto.Counter{
|
|
Value: proto.Float64(1),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 6: Metric family with dots in name.
|
|
{
|
|
in: `
|
|
# HELP "name.with.dots" boring help
|
|
# TYPE "name.with.dots" counter
|
|
{"name.with.dots",labelname="val1",basename="basevalue"} 42.0
|
|
{"name.with.dots",labelname="val2",basename="basevalue"} 0.23 1234567890
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("name.with.dots"),
|
|
Help: proto.String("boring help"),
|
|
Type: dto.MetricType_COUNTER.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("labelname"),
|
|
Value: proto.String("val1"),
|
|
},
|
|
{
|
|
Name: proto.String("basename"),
|
|
Value: proto.String("basevalue"),
|
|
},
|
|
},
|
|
Counter: &dto.Counter{
|
|
Value: proto.Float64(42),
|
|
},
|
|
},
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("labelname"),
|
|
Value: proto.String("val2"),
|
|
},
|
|
{
|
|
Name: proto.String("basename"),
|
|
Value: proto.String("basevalue"),
|
|
},
|
|
},
|
|
Counter: &dto.Counter{
|
|
Value: proto.Float64(.23),
|
|
},
|
|
TimestampMs: proto.Int64(1234567890),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 7: Metric family with dots in name, no labels.
|
|
{
|
|
in: `
|
|
# HELP "name.with.dots" boring help
|
|
# TYPE "name.with.dots" counter
|
|
{"name.with.dots"} 42.0
|
|
{"name.with.dots"} 0.23 1234567890
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("name.with.dots"),
|
|
Help: proto.String("boring help"),
|
|
Type: dto.MetricType_COUNTER.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Counter: &dto.Counter{
|
|
Value: proto.Float64(42),
|
|
},
|
|
},
|
|
{
|
|
Counter: &dto.Counter{
|
|
Value: proto.Float64(.23),
|
|
},
|
|
TimestampMs: proto.Int64(1234567890),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 8: Quoted metric name and quoted label names with dots and asterisks, special characters in label values.
|
|
{
|
|
in: `# HELP "gauge.name" gauge\ndoc\nstr\"ing
|
|
# TYPE "gauge.name" gauge
|
|
{"gauge.name","name.1"="val with\nnew line","name*2"="val with \\backslash and \"quotes\""} +Inf
|
|
{"gauge.name","name.1"="Björn","name*2"="佖佥"} 3.14e+42
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("gauge.name"),
|
|
Help: proto.String("gauge\ndoc\nstr\"ing"),
|
|
Type: dto.MetricType_GAUGE.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("name.1"),
|
|
Value: proto.String("val with\nnew line"),
|
|
},
|
|
{
|
|
Name: proto.String("name*2"),
|
|
Value: proto.String("val with \\backslash and \"quotes\""),
|
|
},
|
|
},
|
|
Gauge: &dto.Gauge{
|
|
Value: proto.Float64(math.Inf(+1)),
|
|
},
|
|
},
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("name.1"),
|
|
Value: proto.String("Björn"),
|
|
},
|
|
{
|
|
Name: proto.String("name*2"),
|
|
Value: proto.String("佖佥"),
|
|
},
|
|
},
|
|
Gauge: &dto.Gauge{
|
|
Value: proto.Float64(3.14e42),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 9: Various escaped special characters in metric and label names.
|
|
{
|
|
in: `
|
|
# HELP "my\"noncompliant\nmetric\\" help text
|
|
# TYPE "my\"noncompliant\nmetric\\" counter
|
|
{"my\"noncompliant\nmetric\\","label\"name\n"="value"} 1
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("my\"noncompliant\nmetric\\"),
|
|
Help: proto.String("help text"),
|
|
Type: dto.MetricType_COUNTER.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("label\"name\n"),
|
|
Value: proto.String("value"),
|
|
},
|
|
},
|
|
Counter: &dto.Counter{
|
|
Value: proto.Float64(1),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 10: Quoted metric name, not the first element in the label set.
|
|
{
|
|
in: `
|
|
# HELP "my.noncompliant.metric" help text
|
|
# TYPE "my.noncompliant.metric" counter
|
|
{labelname="value", "my.noncompliant.metric"} 1
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("my.noncompliant.metric"),
|
|
Help: proto.String("help text"),
|
|
Type: dto.MetricType_COUNTER.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Label: []*dto.LabelPair{
|
|
{
|
|
Name: proto.String("labelname"),
|
|
Value: proto.String("value"),
|
|
},
|
|
},
|
|
Counter: &dto.Counter{
|
|
Value: proto.Float64(1),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 11: Multiple minimal metrics with quoted metric names.
|
|
{
|
|
in: `
|
|
{"name.1"} 1
|
|
{"name.2"} 1
|
|
{"name.3"} 1
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("name.1"),
|
|
Type: dto.MetricType_UNTYPED.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Untyped: &dto.Untyped{
|
|
Value: proto.Float64(1),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: proto.String("name.2"),
|
|
Type: dto.MetricType_UNTYPED.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Untyped: &dto.Untyped{
|
|
Value: proto.Float64(1),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: proto.String("name.3"),
|
|
Type: dto.MetricType_UNTYPED.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Untyped: &dto.Untyped{
|
|
Value: proto.Float64(1),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 12: A full float histogram.
|
|
{
|
|
in: `
|
|
# HELP request_duration_microseconds The response latency.
|
|
# TYPE request_duration_microseconds histogram
|
|
request_duration_microseconds_bucket{le="100"} 123.5
|
|
request_duration_microseconds_bucket{le="120"} 412.6
|
|
request_duration_microseconds_bucket{le="144"} 592.7
|
|
request_duration_microseconds_bucket{le="172.8"} 1524.8
|
|
request_duration_microseconds_bucket{le="+Inf"} 2693.9
|
|
request_duration_microseconds_sum 1.7560473e+06
|
|
request_duration_microseconds_count 2693.9
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("request_duration_microseconds"),
|
|
Help: proto.String("The response latency."),
|
|
Type: dto.MetricType_HISTOGRAM.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Histogram: &dto.Histogram{
|
|
SampleCountFloat: proto.Float64(2693.9),
|
|
SampleSum: proto.Float64(1756047.3),
|
|
Bucket: []*dto.Bucket{
|
|
{
|
|
UpperBound: proto.Float64(100),
|
|
CumulativeCountFloat: proto.Float64(123.5),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(120),
|
|
CumulativeCountFloat: proto.Float64(412.6),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(144),
|
|
CumulativeCountFloat: proto.Float64(592.7),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(172.8),
|
|
CumulativeCountFloat: proto.Float64(1524.8),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(math.Inf(+1)),
|
|
CumulativeCountFloat: proto.Float64(2693.9),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 13: A float histogram where only the count is really a float.
|
|
{
|
|
in: `
|
|
# HELP request_duration_microseconds The response latency.
|
|
# TYPE request_duration_microseconds histogram
|
|
request_duration_microseconds_bucket{le="100"} 123
|
|
request_duration_microseconds_bucket{le="120"} 412
|
|
request_duration_microseconds_bucket{le="144"} 592
|
|
request_duration_microseconds_bucket{le="172.8"} 1524
|
|
request_duration_microseconds_sum 1.7560473e+06
|
|
request_duration_microseconds_count 2693.9
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("request_duration_microseconds"),
|
|
Help: proto.String("The response latency."),
|
|
Type: dto.MetricType_HISTOGRAM.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Histogram: &dto.Histogram{
|
|
SampleCountFloat: proto.Float64(2693.9),
|
|
SampleSum: proto.Float64(1756047.3),
|
|
Bucket: []*dto.Bucket{
|
|
{
|
|
UpperBound: proto.Float64(100),
|
|
CumulativeCountFloat: proto.Float64(123),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(120),
|
|
CumulativeCountFloat: proto.Float64(412),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(144),
|
|
CumulativeCountFloat: proto.Float64(592),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(172.8),
|
|
CumulativeCountFloat: proto.Float64(1524),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 14: A float histogram where only one of the buckets is really a float.
|
|
{
|
|
in: `
|
|
# HELP request_duration_microseconds The response latency.
|
|
# TYPE request_duration_microseconds histogram
|
|
request_duration_microseconds_bucket{le="100"} 123
|
|
request_duration_microseconds_bucket{le="120"} 412
|
|
request_duration_microseconds_bucket{le="144"} 592
|
|
request_duration_microseconds_bucket{le="172.8"} 1524.8
|
|
request_duration_microseconds_bucket{le="+Inf"} 2693
|
|
request_duration_microseconds_sum 1.7560473e+06
|
|
request_duration_microseconds_count 2693
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("request_duration_microseconds"),
|
|
Help: proto.String("The response latency."),
|
|
Type: dto.MetricType_HISTOGRAM.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Histogram: &dto.Histogram{
|
|
SampleCountFloat: proto.Float64(2693),
|
|
SampleSum: proto.Float64(1756047.3),
|
|
Bucket: []*dto.Bucket{
|
|
{
|
|
UpperBound: proto.Float64(100),
|
|
CumulativeCountFloat: proto.Float64(123),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(120),
|
|
CumulativeCountFloat: proto.Float64(412),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(144),
|
|
CumulativeCountFloat: proto.Float64(592),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(172.8),
|
|
CumulativeCountFloat: proto.Float64(1524.8),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(math.Inf(+1)),
|
|
CumulativeCountFloat: proto.Float64(2693),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 15: A gauge histogram.
|
|
{
|
|
in: `
|
|
# HELP request_duration_microseconds The response latency.
|
|
# TYPE request_duration_microseconds gaugehistogram
|
|
request_duration_microseconds_bucket{le="100"} 123
|
|
request_duration_microseconds_bucket{le="120"} 412
|
|
request_duration_microseconds_bucket{le="144"} 592
|
|
request_duration_microseconds_bucket{le="172.8"} 1524
|
|
request_duration_microseconds_bucket{le="+Inf"} 2693
|
|
request_duration_microseconds_sum 1.7560473e+06
|
|
request_duration_microseconds_count 2693
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("request_duration_microseconds"),
|
|
Help: proto.String("The response latency."),
|
|
Type: dto.MetricType_GAUGE_HISTOGRAM.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Histogram: &dto.Histogram{
|
|
SampleCount: proto.Uint64(2693),
|
|
SampleSum: proto.Float64(1756047.3),
|
|
Bucket: []*dto.Bucket{
|
|
{
|
|
UpperBound: proto.Float64(100),
|
|
CumulativeCount: proto.Uint64(123),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(120),
|
|
CumulativeCount: proto.Uint64(412),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(144),
|
|
CumulativeCount: proto.Uint64(592),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(172.8),
|
|
CumulativeCount: proto.Uint64(1524),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(math.Inf(+1)),
|
|
CumulativeCount: proto.Uint64(2693),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 16: A gauge histogram with alternative spelling.
|
|
{
|
|
in: `
|
|
# HELP request_duration_microseconds The response latency.
|
|
# TYPE request_duration_microseconds gauge_histogram
|
|
request_duration_microseconds_bucket{le="100"} 123
|
|
request_duration_microseconds_bucket{le="120"} 412
|
|
request_duration_microseconds_bucket{le="144"} 592
|
|
request_duration_microseconds_bucket{le="172.8"} 1524
|
|
request_duration_microseconds_bucket{le="+Inf"} 2693
|
|
request_duration_microseconds_sum 1.7560473e+06
|
|
request_duration_microseconds_count 2693
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("request_duration_microseconds"),
|
|
Help: proto.String("The response latency."),
|
|
Type: dto.MetricType_GAUGE_HISTOGRAM.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Histogram: &dto.Histogram{
|
|
SampleCount: proto.Uint64(2693),
|
|
SampleSum: proto.Float64(1756047.3),
|
|
Bucket: []*dto.Bucket{
|
|
{
|
|
UpperBound: proto.Float64(100),
|
|
CumulativeCount: proto.Uint64(123),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(120),
|
|
CumulativeCount: proto.Uint64(412),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(144),
|
|
CumulativeCount: proto.Uint64(592),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(172.8),
|
|
CumulativeCount: proto.Uint64(1524),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(math.Inf(+1)),
|
|
CumulativeCount: proto.Uint64(2693),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// 17: A float gauge histogram where only one of the buckets is really a float and with alternative spelling.
|
|
{
|
|
in: `
|
|
# HELP request_duration_microseconds The response latency.
|
|
# TYPE request_duration_microseconds gauge_histogram
|
|
request_duration_microseconds_bucket{le="100"} 123
|
|
request_duration_microseconds_bucket{le="120"} 412
|
|
request_duration_microseconds_bucket{le="144"} 592
|
|
request_duration_microseconds_bucket{le="172.8"} 1524.8
|
|
request_duration_microseconds_bucket{le="+Inf"} 2693
|
|
request_duration_microseconds_sum 1.7560473e+06
|
|
request_duration_microseconds_count 2693
|
|
`,
|
|
out: []*dto.MetricFamily{
|
|
{
|
|
Name: proto.String("request_duration_microseconds"),
|
|
Help: proto.String("The response latency."),
|
|
Type: dto.MetricType_GAUGE_HISTOGRAM.Enum(),
|
|
Metric: []*dto.Metric{
|
|
{
|
|
Histogram: &dto.Histogram{
|
|
SampleCountFloat: proto.Float64(2693),
|
|
SampleSum: proto.Float64(1756047.3),
|
|
Bucket: []*dto.Bucket{
|
|
{
|
|
UpperBound: proto.Float64(100),
|
|
CumulativeCountFloat: proto.Float64(123),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(120),
|
|
CumulativeCountFloat: proto.Float64(412),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(144),
|
|
CumulativeCountFloat: proto.Float64(592),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(172.8),
|
|
CumulativeCountFloat: proto.Float64(1524.8),
|
|
},
|
|
{
|
|
UpperBound: proto.Float64(math.Inf(+1)),
|
|
CumulativeCountFloat: proto.Float64(2693),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, scenario := range scenarios {
|
|
out, err := parser.TextToMetricFamilies(strings.NewReader(scenario.in))
|
|
if err != nil {
|
|
t.Errorf("%d. error: %s", i, err)
|
|
continue
|
|
}
|
|
if expected, got := len(scenario.out), len(out); expected != got {
|
|
t.Errorf(
|
|
"%d. expected %d MetricFamilies, got %d",
|
|
i, expected, got,
|
|
)
|
|
}
|
|
for _, expected := range scenario.out {
|
|
got, ok := out[expected.GetName()]
|
|
if !ok {
|
|
t.Errorf(
|
|
"%d. expected MetricFamily %q, found none",
|
|
i, expected.GetName(),
|
|
)
|
|
continue
|
|
}
|
|
if expected.String() != got.String() {
|
|
t.Errorf(
|
|
"%d. expected MetricFamily %s, got %s",
|
|
i, expected, got,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTextParse(t *testing.T) {
|
|
testTextParse(t)
|
|
}
|
|
|
|
func BenchmarkTextParse(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
testTextParse(b)
|
|
}
|
|
}
|
|
|
|
func testTextParseError(t testing.TB) {
|
|
scenarios := []struct {
|
|
in string
|
|
errUTF8 string
|
|
// if errLegacy is blank, it is assumed to be the same as errUTF8
|
|
errLegacy string
|
|
}{
|
|
// 0: No new-line at end of input.
|
|
{
|
|
in: `
|
|
bla 3.14
|
|
blubber 42`,
|
|
errUTF8: "text format parsing error in line 3: unexpected end of input stream",
|
|
},
|
|
// 1: Invalid escape sequence in label value.
|
|
{
|
|
in: `metric{label="\t"} 3.14`,
|
|
errUTF8: "text format parsing error in line 1: invalid escape sequence",
|
|
},
|
|
// 2: Newline in label value.
|
|
{
|
|
in: `
|
|
metric{label="new
|
|
line"} 3.14
|
|
`,
|
|
errUTF8: `text format parsing error in line 2: label value "new" contains unescaped new-line`,
|
|
},
|
|
// 3:
|
|
{
|
|
in: `metric{@="bla"} 3.14`,
|
|
errUTF8: "text format parsing error in line 1: invalid label name for metric",
|
|
},
|
|
// 4:
|
|
{
|
|
in: `metric{__name__="bla"} 3.14`,
|
|
errUTF8: `text format parsing error in line 1: label name "__name__" is reserved`,
|
|
},
|
|
// 5:
|
|
{
|
|
in: `metric{label+="bla"} 3.14`,
|
|
errUTF8: "text format parsing error in line 1: expected '=' after label name",
|
|
},
|
|
// 6:
|
|
{
|
|
in: `metric{label=bla} 3.14`,
|
|
errUTF8: "text format parsing error in line 1: expected '\"' at start of label value",
|
|
},
|
|
// 7:
|
|
{
|
|
in: `
|
|
# TYPE metric summary
|
|
metric{quantile="bla"} 3.14
|
|
`,
|
|
errUTF8: "text format parsing error in line 3: expected float as value for 'quantile' label",
|
|
},
|
|
// 8:
|
|
{
|
|
in: `metric{label="bla"+} 3.14`,
|
|
errUTF8: "text format parsing error in line 1: unexpected end of label value",
|
|
},
|
|
// 9:
|
|
{
|
|
in: `metric{label="bla"} 3.14 2.72
|
|
`,
|
|
errUTF8: "text format parsing error in line 1: expected integer as timestamp",
|
|
},
|
|
// 10:
|
|
{
|
|
in: `metric{label="bla"} 3.14 2 3
|
|
`,
|
|
errUTF8: "text format parsing error in line 1: spurious string after timestamp",
|
|
},
|
|
// 11:
|
|
{
|
|
in: `metric{label="bla"} blubb
|
|
`,
|
|
errUTF8: "text format parsing error in line 1: expected float as value",
|
|
},
|
|
// 12:
|
|
{
|
|
in: `
|
|
# HELP metric one
|
|
# HELP metric two
|
|
`,
|
|
errUTF8: "text format parsing error in line 3: second HELP line for metric name",
|
|
},
|
|
// 13:
|
|
{
|
|
in: `
|
|
# TYPE metric counter
|
|
# TYPE metric untyped
|
|
`,
|
|
errUTF8: `text format parsing error in line 3: second TYPE line for metric name "metric", or TYPE reported after samples`,
|
|
},
|
|
// 14:
|
|
{
|
|
in: `
|
|
metric 4.12
|
|
# TYPE metric counter
|
|
`,
|
|
errUTF8: `text format parsing error in line 3: second TYPE line for metric name "metric", or TYPE reported after samples`,
|
|
},
|
|
// 15:
|
|
{
|
|
in: `
|
|
# TYPE metric bla
|
|
`,
|
|
errUTF8: "text format parsing error in line 2: unknown metric type",
|
|
},
|
|
// 16:
|
|
{
|
|
in: `
|
|
# TYPE met-ric
|
|
`,
|
|
errUTF8: "text format parsing error in line 2: invalid metric name in comment",
|
|
},
|
|
// 17:
|
|
{
|
|
in: `@invalidmetric{label="bla"} 3.14 2`,
|
|
errUTF8: "text format parsing error in line 1: invalid metric name",
|
|
},
|
|
// 18:
|
|
{
|
|
in: `{label="bla"} 3.14 2`,
|
|
errUTF8: "text format parsing error in line 1: invalid metric name",
|
|
},
|
|
// 19:
|
|
{
|
|
in: `
|
|
# TYPE metric histogram
|
|
metric_bucket{le="bla"} 3.14
|
|
`,
|
|
errUTF8: "text format parsing error in line 3: expected float as value for 'le' label",
|
|
},
|
|
// 20: Invalid UTF-8 in label value.
|
|
{
|
|
in: "metric{l=\"\xbd\"} 3.14\n",
|
|
errUTF8: "text format parsing error in line 1: invalid label value \"\\xbd\"",
|
|
},
|
|
// 21: Go 1.13 sometimes allows underscores in numbers.
|
|
{
|
|
in: "foo 1_2\n",
|
|
errUTF8: "text format parsing error in line 1: expected float as value",
|
|
},
|
|
// 22: Go 1.13 supports hex floating point.
|
|
{
|
|
in: "foo 0x1p-3\n",
|
|
errUTF8: "text format parsing error in line 1: expected float as value",
|
|
},
|
|
// 23: Check for various other literals variants, just in case.
|
|
{
|
|
in: "foo 0x1P-3\n",
|
|
errUTF8: "text format parsing error in line 1: expected float as value",
|
|
},
|
|
// 24:
|
|
{
|
|
in: "foo 0B1\n",
|
|
errUTF8: "text format parsing error in line 1: expected float as value",
|
|
},
|
|
// 25:
|
|
{
|
|
in: "foo 0O1\n",
|
|
errUTF8: "text format parsing error in line 1: expected float as value",
|
|
},
|
|
// 26:
|
|
{
|
|
in: "foo 0X1\n",
|
|
errUTF8: "text format parsing error in line 1: expected float as value",
|
|
},
|
|
// 27:
|
|
{
|
|
in: "foo 0x1\n",
|
|
errUTF8: "text format parsing error in line 1: expected float as value",
|
|
},
|
|
// 28:
|
|
{
|
|
in: "foo 0b1\n",
|
|
errUTF8: "text format parsing error in line 1: expected float as value",
|
|
},
|
|
// 29:
|
|
{
|
|
in: "foo 0o1\n",
|
|
errUTF8: "text format parsing error in line 1: expected float as value",
|
|
},
|
|
// 30:
|
|
{
|
|
in: "foo 0x1\n",
|
|
errUTF8: "text format parsing error in line 1: expected float as value",
|
|
},
|
|
// 31:
|
|
{
|
|
in: "foo 0x1\n",
|
|
errUTF8: "text format parsing error in line 1: expected float as value",
|
|
},
|
|
// 32: Check histogram label.
|
|
{
|
|
in: `
|
|
# TYPE metric histogram
|
|
metric_bucket{le="0x1p-3"} 3.14
|
|
`,
|
|
errUTF8: "text format parsing error in line 3: expected float as value for 'le' label",
|
|
},
|
|
// 33: Check quantile label.
|
|
{
|
|
in: `
|
|
# TYPE metric summary
|
|
metric{quantile="0x1p-3"} 3.14
|
|
`,
|
|
errUTF8: "text format parsing error in line 3: expected float as value for 'quantile' label",
|
|
},
|
|
// 34: Check duplicate label.
|
|
{
|
|
in: `metric{label="bla",label="bla"} 3.14`,
|
|
errUTF8: "text format parsing error in line 1: duplicate label names for metric",
|
|
},
|
|
// 35: Multiple quoted metric names.
|
|
{
|
|
in: `{"one.name","another.name"} 3.14`,
|
|
errUTF8: "text format parsing error in line 1: multiple metric names",
|
|
errLegacy: `text format parsing error in line 1: invalid metric name "one.name"`,
|
|
},
|
|
// 36: Invalid escape sequence in quoted metric name.
|
|
{
|
|
in: `{"a\xc5z",label="bla"} 3.14`,
|
|
errUTF8: "text format parsing error in line 1: invalid escape sequence",
|
|
},
|
|
// 37: Unexpected end of quoted metric name.
|
|
{
|
|
in: `{"metric.name".label="bla"} 3.14`,
|
|
errUTF8: "text format parsing error in line 1: unexpected end of metric name",
|
|
},
|
|
// 38: Invalid escape sequence in quoted metric name.
|
|
{
|
|
in: `
|
|
# TYPE "metric.name\t" counter
|
|
{"metric.name\t",label="bla"} 3.14
|
|
`,
|
|
errUTF8: "text format parsing error in line 2: invalid escape sequence",
|
|
},
|
|
// 39: Newline in quoted metric name.
|
|
{
|
|
in: `
|
|
# TYPE "metric
|
|
name" counter
|
|
{"metric
|
|
name",label="bla"} 3.14
|
|
`,
|
|
errUTF8: `text format parsing error in line 2: metric name "metric" contains unescaped new-line`,
|
|
},
|
|
// 40: Newline in quoted label name.
|
|
{
|
|
in: `
|
|
{"metric.name","new
|
|
line"="bla"} 3.14
|
|
`,
|
|
errUTF8: `text format parsing error in line 2: label name "new" contains unescaped new-line`,
|
|
errLegacy: `text format parsing error in line 2: invalid metric name "metric.name"`,
|
|
},
|
|
// 41: Dotted metric name fails legacy validation.
|
|
{
|
|
in: `{"metric.name",foo="bla"} 3.14
|
|
`,
|
|
errUTF8: ``,
|
|
errLegacy: `text format parsing error in line 1: invalid metric name "metric.name"`,
|
|
},
|
|
// 42: Dotted label name fails legacy validation.
|
|
{
|
|
in: `metric_name{"foo"="bar", "dotted.label"="bla"} 3.14
|
|
`,
|
|
errUTF8: ``,
|
|
errLegacy: `text format parsing error in line 1: invalid label name "dotted.label"`,
|
|
},
|
|
// 43: Histogram with negative count.
|
|
{
|
|
in: `
|
|
# HELP request_duration_microseconds The response latency.
|
|
# TYPE request_duration_microseconds histogram
|
|
request_duration_microseconds_bucket{le="100"} 123
|
|
request_duration_microseconds_bucket{le="120"} 412
|
|
request_duration_microseconds_bucket{le="144"} 592
|
|
request_duration_microseconds_bucket{le="172.8"} 1524
|
|
request_duration_microseconds_bucket{le="+Inf"} 2693
|
|
request_duration_microseconds_sum 1.7560473e+06
|
|
request_duration_microseconds_count -2693
|
|
`,
|
|
errUTF8: `text format parsing error in line 10: negative count for histogram "request_duration_microseconds"`,
|
|
},
|
|
// 44: Histogram with negative bucket.
|
|
{
|
|
in: `
|
|
# HELP request_duration_microseconds The response latency.
|
|
# TYPE request_duration_microseconds histogram
|
|
request_duration_microseconds_bucket{le="100"} 123
|
|
request_duration_microseconds_bucket{le="120"} -412
|
|
request_duration_microseconds_bucket{le="144"} 592
|
|
request_duration_microseconds_bucket{le="172.8"} 1524
|
|
request_duration_microseconds_bucket{le="+Inf"} 2693
|
|
request_duration_microseconds_sum 1.7560473e+06
|
|
request_duration_microseconds_count 2693
|
|
`,
|
|
errUTF8: `text format parsing error in line 5: negative bucket population for histogram "request_duration_microseconds"`,
|
|
},
|
|
// 45: Histogram with negative float count.
|
|
{
|
|
in: `
|
|
# HELP request_duration_microseconds The response latency.
|
|
# TYPE request_duration_microseconds histogram
|
|
request_duration_microseconds_bucket{le="100"} 123
|
|
request_duration_microseconds_bucket{le="120"} 412
|
|
request_duration_microseconds_bucket{le="144"} 592
|
|
request_duration_microseconds_bucket{le="172.8"} 1524
|
|
request_duration_microseconds_bucket{le="+Inf"} 2693
|
|
request_duration_microseconds_sum 1.7560473e+06
|
|
request_duration_microseconds_count -2693.123
|
|
`,
|
|
errUTF8: `text format parsing error in line 10: negative count for histogram "request_duration_microseconds"`,
|
|
},
|
|
// 46: Histogram with negative float bucket.
|
|
{
|
|
in: `
|
|
# HELP request_duration_microseconds The response latency.
|
|
# TYPE request_duration_microseconds histogram
|
|
request_duration_microseconds_bucket{le="100"} 123
|
|
request_duration_microseconds_bucket{le="120"} -412.456
|
|
request_duration_microseconds_bucket{le="144"} 592
|
|
request_duration_microseconds_bucket{le="172.8"} 1524
|
|
request_duration_microseconds_bucket{le="+Inf"} 2693
|
|
request_duration_microseconds_sum 1.7560473e+06
|
|
request_duration_microseconds_count 2693
|
|
`,
|
|
errUTF8: `text format parsing error in line 5: negative bucket population for histogram "request_duration_microseconds"`,
|
|
},
|
|
}
|
|
for i, scenario := range scenarios {
|
|
parser.scheme = model.UTF8Validation
|
|
_, err := parser.TextToMetricFamilies(strings.NewReader(scenario.in))
|
|
if err == nil {
|
|
if scenario.errUTF8 != "" {
|
|
t.Errorf("%d. expected error, got nil", i)
|
|
}
|
|
} else if expected, got := scenario.errUTF8, err.Error(); strings.Index(got, expected) != 0 {
|
|
t.Errorf(
|
|
"%d. expected error starting with %q, got %q",
|
|
i, expected, got,
|
|
)
|
|
}
|
|
|
|
parser.scheme = model.LegacyValidation
|
|
_, err = parser.TextToMetricFamilies(strings.NewReader(scenario.in))
|
|
if err == nil {
|
|
if scenario.errLegacy != "" {
|
|
t.Errorf("%d. expected error, got nil", i)
|
|
}
|
|
} else {
|
|
expected := scenario.errUTF8
|
|
if scenario.errLegacy != "" {
|
|
expected = scenario.errLegacy
|
|
}
|
|
if got := err.Error(); strings.Index(got, expected) != 0 {
|
|
t.Errorf(
|
|
"%d. expected error starting with %q, got %q",
|
|
i, expected, got,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTextParseError(t *testing.T) {
|
|
testTextParseError(t)
|
|
}
|
|
|
|
func BenchmarkParseError(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
testTextParseError(b)
|
|
}
|
|
}
|
|
|
|
func TestTextParserStartOfLine(t *testing.T) {
|
|
t.Run("EOF", func(t *testing.T) {
|
|
p := NewTextParser(model.UTF8Validation)
|
|
in := strings.NewReader("")
|
|
p.reset(in)
|
|
fn := p.startOfLine()
|
|
if fn != nil {
|
|
t.Errorf("Unexpected non-nil function: %v", fn)
|
|
}
|
|
if p.err != nil {
|
|
t.Errorf("Unexpected error: %v", p.err)
|
|
}
|
|
})
|
|
|
|
t.Run("OtherError", func(t *testing.T) {
|
|
p := NewTextParser(model.UTF8Validation)
|
|
in := &errReader{err: errors.New("unexpected error")}
|
|
p.reset(in)
|
|
fn := p.startOfLine()
|
|
if fn != nil {
|
|
t.Errorf("Unexpected non-nil function: %v", fn)
|
|
}
|
|
if p.err != nil && !errors.Is(p.err, in.err) {
|
|
t.Errorf("Unexpected error: %v, expected %v", p.err, in.err)
|
|
}
|
|
})
|
|
}
|
|
|
|
type errReader struct {
|
|
err error
|
|
}
|
|
|
|
func (r *errReader) Read([]byte) (int, error) {
|
|
return 0, r.err
|
|
}
|