| // 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 ( |
| "bytes" |
| "math" |
| "strings" |
| "testing" |
| |
| "github.com/golang/protobuf/proto" |
| |
| dto "github.com/prometheus/client_model/go" |
| ) |
| |
| func TestCreate(t *testing.T) { |
| var scenarios = []struct { |
| in *dto.MetricFamily |
| out string |
| }{ |
| // 0: Counter, NaN as value, timestamp given. |
| { |
| in: &dto.MetricFamily{ |
| Name: proto.String("name"), |
| Help: proto.String("two-line\n doc str\\ing"), |
| 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(math.NaN()), |
| }, |
| }, |
| &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(.23), |
| }, |
| TimestampMs: proto.Int64(1234567890), |
| }, |
| }, |
| }, |
| out: `# HELP name two-line\n doc str\\ing |
| # TYPE name counter |
| name{labelname="val1",basename="basevalue"} NaN |
| name{labelname="val2",basename="basevalue"} 0.23 1234567890 |
| `, |
| }, |
| // 1: Gauge, some escaping required, +Inf as value, multi-byte characters in label values. |
| { |
| in: &dto.MetricFamily{ |
| Name: proto.String("gauge_name"), |
| Help: proto.String("gauge\ndoc\nstr\"ing"), |
| Type: dto.MetricType_GAUGE.Enum(), |
| Metric: []*dto.Metric{ |
| &dto.Metric{ |
| Label: []*dto.LabelPair{ |
| &dto.LabelPair{ |
| Name: proto.String("name_1"), |
| Value: proto.String("val with\nnew line"), |
| }, |
| &dto.LabelPair{ |
| Name: proto.String("name_2"), |
| Value: proto.String("val with \\backslash and \"quotes\""), |
| }, |
| }, |
| Gauge: &dto.Gauge{ |
| Value: proto.Float64(math.Inf(+1)), |
| }, |
| }, |
| &dto.Metric{ |
| Label: []*dto.LabelPair{ |
| &dto.LabelPair{ |
| Name: proto.String("name_1"), |
| Value: proto.String("Björn"), |
| }, |
| &dto.LabelPair{ |
| Name: proto.String("name_2"), |
| Value: proto.String("佖佥"), |
| }, |
| }, |
| Gauge: &dto.Gauge{ |
| Value: proto.Float64(3.14E42), |
| }, |
| }, |
| }, |
| }, |
| out: `# 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 |
| `, |
| }, |
| // 2: Untyped, no help, one sample with no labels and -Inf as value, another sample with one label. |
| { |
| in: &dto.MetricFamily{ |
| Name: proto.String("untyped_name"), |
| Type: dto.MetricType_UNTYPED.Enum(), |
| Metric: []*dto.Metric{ |
| &dto.Metric{ |
| Untyped: &dto.Untyped{ |
| Value: proto.Float64(math.Inf(-1)), |
| }, |
| }, |
| &dto.Metric{ |
| Label: []*dto.LabelPair{ |
| &dto.LabelPair{ |
| Name: proto.String("name_1"), |
| Value: proto.String("value 1"), |
| }, |
| }, |
| Untyped: &dto.Untyped{ |
| Value: proto.Float64(-1.23e-45), |
| }, |
| }, |
| }, |
| }, |
| out: `# TYPE untyped_name untyped |
| untyped_name -Inf |
| untyped_name{name_1="value 1"} -1.23e-45 |
| `, |
| }, |
| // 3: Summary. |
| { |
| in: &dto.MetricFamily{ |
| Name: proto.String("summary_name"), |
| Help: proto.String("summary docstring"), |
| Type: dto.MetricType_SUMMARY.Enum(), |
| Metric: []*dto.Metric{ |
| &dto.Metric{ |
| Summary: &dto.Summary{ |
| SampleCount: proto.Uint64(42), |
| SampleSum: proto.Float64(-3.4567), |
| Quantile: []*dto.Quantile{ |
| &dto.Quantile{ |
| Quantile: proto.Float64(0.5), |
| Value: proto.Float64(-1.23), |
| }, |
| &dto.Quantile{ |
| Quantile: proto.Float64(0.9), |
| Value: proto.Float64(.2342354), |
| }, |
| &dto.Quantile{ |
| Quantile: proto.Float64(0.99), |
| Value: proto.Float64(0), |
| }, |
| }, |
| }, |
| }, |
| &dto.Metric{ |
| Label: []*dto.LabelPair{ |
| &dto.LabelPair{ |
| Name: proto.String("name_1"), |
| Value: proto.String("value 1"), |
| }, |
| &dto.LabelPair{ |
| Name: proto.String("name_2"), |
| Value: proto.String("value 2"), |
| }, |
| }, |
| Summary: &dto.Summary{ |
| SampleCount: proto.Uint64(4711), |
| SampleSum: proto.Float64(2010.1971), |
| Quantile: []*dto.Quantile{ |
| &dto.Quantile{ |
| Quantile: proto.Float64(0.5), |
| Value: proto.Float64(1), |
| }, |
| &dto.Quantile{ |
| Quantile: proto.Float64(0.9), |
| Value: proto.Float64(2), |
| }, |
| &dto.Quantile{ |
| Quantile: proto.Float64(0.99), |
| Value: proto.Float64(3), |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| out: `# HELP summary_name summary docstring |
| # TYPE summary_name summary |
| summary_name{quantile="0.5"} -1.23 |
| summary_name{quantile="0.9"} 0.2342354 |
| summary_name{quantile="0.99"} 0 |
| summary_name_sum -3.4567 |
| summary_name_count 42 |
| summary_name{name_1="value 1",name_2="value 2",quantile="0.5"} 1 |
| summary_name{name_1="value 1",name_2="value 2",quantile="0.9"} 2 |
| summary_name{name_1="value 1",name_2="value 2",quantile="0.99"} 3 |
| summary_name_sum{name_1="value 1",name_2="value 2"} 2010.1971 |
| summary_name_count{name_1="value 1",name_2="value 2"} 4711 |
| `, |
| }, |
| // 4: Histogram |
| { |
| in: &dto.MetricFamily{ |
| Name: proto.String("request_duration_microseconds"), |
| Help: proto.String("The response latency."), |
| Type: dto.MetricType_HISTOGRAM.Enum(), |
| Metric: []*dto.Metric{ |
| &dto.Metric{ |
| Histogram: &dto.Histogram{ |
| SampleCount: proto.Uint64(2693), |
| SampleSum: proto.Float64(1756047.3), |
| Bucket: []*dto.Bucket{ |
| &dto.Bucket{ |
| UpperBound: proto.Float64(100), |
| CumulativeCount: proto.Uint64(123), |
| }, |
| &dto.Bucket{ |
| UpperBound: proto.Float64(120), |
| CumulativeCount: proto.Uint64(412), |
| }, |
| &dto.Bucket{ |
| UpperBound: proto.Float64(144), |
| CumulativeCount: proto.Uint64(592), |
| }, |
| &dto.Bucket{ |
| UpperBound: proto.Float64(172.8), |
| CumulativeCount: proto.Uint64(1524), |
| }, |
| &dto.Bucket{ |
| UpperBound: proto.Float64(math.Inf(+1)), |
| CumulativeCount: proto.Uint64(2693), |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| out: `# 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 |
| `, |
| }, |
| // 5: Histogram with missing +Inf bucket. |
| { |
| in: &dto.MetricFamily{ |
| Name: proto.String("request_duration_microseconds"), |
| Help: proto.String("The response latency."), |
| Type: dto.MetricType_HISTOGRAM.Enum(), |
| Metric: []*dto.Metric{ |
| &dto.Metric{ |
| Histogram: &dto.Histogram{ |
| SampleCount: proto.Uint64(2693), |
| SampleSum: proto.Float64(1756047.3), |
| Bucket: []*dto.Bucket{ |
| &dto.Bucket{ |
| UpperBound: proto.Float64(100), |
| CumulativeCount: proto.Uint64(123), |
| }, |
| &dto.Bucket{ |
| UpperBound: proto.Float64(120), |
| CumulativeCount: proto.Uint64(412), |
| }, |
| &dto.Bucket{ |
| UpperBound: proto.Float64(144), |
| CumulativeCount: proto.Uint64(592), |
| }, |
| &dto.Bucket{ |
| UpperBound: proto.Float64(172.8), |
| CumulativeCount: proto.Uint64(1524), |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| out: `# 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 |
| `, |
| }, |
| // 6: No metric type, should result in default type Counter. |
| { |
| in: &dto.MetricFamily{ |
| Name: proto.String("name"), |
| Help: proto.String("doc string"), |
| Metric: []*dto.Metric{ |
| &dto.Metric{ |
| Counter: &dto.Counter{ |
| Value: proto.Float64(math.Inf(-1)), |
| }, |
| }, |
| }, |
| }, |
| out: `# HELP name doc string |
| # TYPE name counter |
| name -Inf |
| `, |
| }, |
| } |
| |
| for i, scenario := range scenarios { |
| out := bytes.NewBuffer(make([]byte, 0, len(scenario.out))) |
| n, err := MetricFamilyToText(out, scenario.in) |
| if err != nil { |
| t.Errorf("%d. error: %s", i, err) |
| continue |
| } |
| if expected, got := len(scenario.out), n; expected != got { |
| t.Errorf( |
| "%d. expected %d bytes written, got %d", |
| i, expected, got, |
| ) |
| } |
| if expected, got := scenario.out, out.String(); expected != got { |
| t.Errorf( |
| "%d. expected out=%q, got %q", |
| i, expected, got, |
| ) |
| } |
| } |
| |
| } |
| |
| func BenchmarkCreate(b *testing.B) { |
| mf := &dto.MetricFamily{ |
| Name: proto.String("request_duration_microseconds"), |
| Help: proto.String("The response latency."), |
| Type: dto.MetricType_HISTOGRAM.Enum(), |
| Metric: []*dto.Metric{ |
| &dto.Metric{ |
| Label: []*dto.LabelPair{ |
| &dto.LabelPair{ |
| Name: proto.String("name_1"), |
| Value: proto.String("val with\nnew line"), |
| }, |
| &dto.LabelPair{ |
| Name: proto.String("name_2"), |
| Value: proto.String("val with \\backslash and \"quotes\""), |
| }, |
| &dto.LabelPair{ |
| Name: proto.String("name_3"), |
| Value: proto.String("Just a quite long label value to test performance."), |
| }, |
| }, |
| Histogram: &dto.Histogram{ |
| SampleCount: proto.Uint64(2693), |
| SampleSum: proto.Float64(1756047.3), |
| Bucket: []*dto.Bucket{ |
| &dto.Bucket{ |
| UpperBound: proto.Float64(100), |
| CumulativeCount: proto.Uint64(123), |
| }, |
| &dto.Bucket{ |
| UpperBound: proto.Float64(120), |
| CumulativeCount: proto.Uint64(412), |
| }, |
| &dto.Bucket{ |
| UpperBound: proto.Float64(144), |
| CumulativeCount: proto.Uint64(592), |
| }, |
| &dto.Bucket{ |
| UpperBound: proto.Float64(172.8), |
| CumulativeCount: proto.Uint64(1524), |
| }, |
| &dto.Bucket{ |
| UpperBound: proto.Float64(math.Inf(+1)), |
| CumulativeCount: proto.Uint64(2693), |
| }, |
| }, |
| }, |
| }, |
| &dto.Metric{ |
| Label: []*dto.LabelPair{ |
| &dto.LabelPair{ |
| Name: proto.String("name_1"), |
| Value: proto.String("Björn"), |
| }, |
| &dto.LabelPair{ |
| Name: proto.String("name_2"), |
| Value: proto.String("佖佥"), |
| }, |
| &dto.LabelPair{ |
| Name: proto.String("name_3"), |
| Value: proto.String("Just a quite long label value to test performance."), |
| }, |
| }, |
| Histogram: &dto.Histogram{ |
| SampleCount: proto.Uint64(5699), |
| SampleSum: proto.Float64(49484343543.4343), |
| Bucket: []*dto.Bucket{ |
| &dto.Bucket{ |
| UpperBound: proto.Float64(100), |
| CumulativeCount: proto.Uint64(120), |
| }, |
| &dto.Bucket{ |
| UpperBound: proto.Float64(120), |
| CumulativeCount: proto.Uint64(412), |
| }, |
| &dto.Bucket{ |
| UpperBound: proto.Float64(144), |
| CumulativeCount: proto.Uint64(596), |
| }, |
| &dto.Bucket{ |
| UpperBound: proto.Float64(172.8), |
| CumulativeCount: proto.Uint64(1535), |
| }, |
| }, |
| }, |
| TimestampMs: proto.Int64(1234567890), |
| }, |
| }, |
| } |
| out := bytes.NewBuffer(make([]byte, 0, 1024)) |
| |
| for i := 0; i < b.N; i++ { |
| _, err := MetricFamilyToText(out, mf) |
| if err != nil { |
| b.Fatal(err) |
| } |
| out.Reset() |
| } |
| } |
| |
| func BenchmarkCreateBuildInfo(b *testing.B) { |
| mf := &dto.MetricFamily{ |
| Name: proto.String("benchmark_build_info"), |
| Help: proto.String("Test the creation of constant 1-value build_info metric."), |
| Type: dto.MetricType_GAUGE.Enum(), |
| Metric: []*dto.Metric{ |
| &dto.Metric{ |
| Label: []*dto.LabelPair{ |
| &dto.LabelPair{ |
| Name: proto.String("version"), |
| Value: proto.String("1.2.3"), |
| }, |
| &dto.LabelPair{ |
| Name: proto.String("revision"), |
| Value: proto.String("2e84f5e4eacdffb574035810305191ff390360fe"), |
| }, |
| &dto.LabelPair{ |
| Name: proto.String("go_version"), |
| Value: proto.String("1.11.1"), |
| }, |
| }, |
| Gauge: &dto.Gauge{ |
| Value: proto.Float64(1), |
| }, |
| }, |
| }, |
| } |
| out := bytes.NewBuffer(make([]byte, 0, 1024)) |
| |
| for i := 0; i < b.N; i++ { |
| _, err := MetricFamilyToText(out, mf) |
| if err != nil { |
| b.Fatal(err) |
| } |
| out.Reset() |
| } |
| } |
| |
| func TestCreateError(t *testing.T) { |
| var scenarios = []struct { |
| in *dto.MetricFamily |
| err string |
| }{ |
| // 0: No metric. |
| { |
| in: &dto.MetricFamily{ |
| Name: proto.String("name"), |
| Help: proto.String("doc string"), |
| Type: dto.MetricType_COUNTER.Enum(), |
| Metric: []*dto.Metric{}, |
| }, |
| err: "MetricFamily has no metrics", |
| }, |
| // 1: No metric name. |
| { |
| in: &dto.MetricFamily{ |
| Help: proto.String("doc string"), |
| Type: dto.MetricType_UNTYPED.Enum(), |
| Metric: []*dto.Metric{ |
| &dto.Metric{ |
| Untyped: &dto.Untyped{ |
| Value: proto.Float64(math.Inf(-1)), |
| }, |
| }, |
| }, |
| }, |
| err: "MetricFamily has no name", |
| }, |
| // 2: Wrong type. |
| { |
| in: &dto.MetricFamily{ |
| Name: proto.String("name"), |
| Help: proto.String("doc string"), |
| Type: dto.MetricType_COUNTER.Enum(), |
| Metric: []*dto.Metric{ |
| &dto.Metric{ |
| Untyped: &dto.Untyped{ |
| Value: proto.Float64(math.Inf(-1)), |
| }, |
| }, |
| }, |
| }, |
| err: "expected counter in metric", |
| }, |
| } |
| |
| for i, scenario := range scenarios { |
| var out bytes.Buffer |
| _, err := MetricFamilyToText(&out, scenario.in) |
| if err == nil { |
| t.Errorf("%d. expected error, got nil", i) |
| continue |
| } |
| if expected, got := scenario.err, err.Error(); strings.Index(got, expected) != 0 { |
| t.Errorf( |
| "%d. expected error starting with %q, got %q", |
| i, expected, got, |
| ) |
| } |
| } |
| |
| } |