From 8d4b97f280cff9ccd268506b43c8f278b87d29b1 Mon Sep 17 00:00:00 2001 From: SuperQ Date: Thu, 25 May 2023 20:53:18 +0200 Subject: [PATCH] Refactor tests to work with new protobuf library. Signed-off-by: SuperQ --- prometheus/counter_test.go | 24 +- prometheus/example_metricvec_test.go | 46 +++- prometheus/examples_test.go | 329 ++++++++++++++++++++++++--- prometheus/expvar_collector_test.go | 28 +-- prometheus/gauge_test.go | 6 +- prometheus/histogram_test.go | 51 ++--- prometheus/metric.go | 33 +++ prometheus/testutil/testutil.go | 67 +++++- 8 files changed, 495 insertions(+), 89 deletions(-) diff --git a/prometheus/counter_test.go b/prometheus/counter_test.go index 597a53e..d3481e3 100644 --- a/prometheus/counter_test.go +++ b/prometheus/counter_test.go @@ -61,8 +61,10 @@ func TestCounterAdd(t *testing.T) { m := &dto.Metric{} counter.Write(m) - if expected, got := `label: label: counter: `, m.String(); expected != got { - t.Errorf("expected %q, got %q", expected, got) + want := `label:{name:"a" value:"1"} label:{name:"b" value:"2"} counter:{value:67.42}` + + if err := compareProtoAndMetric(want, m); err != nil { + t.Errorf("Summary didn't match: %s", err) } } @@ -164,8 +166,10 @@ func TestCounterAddInf(t *testing.T) { m := &dto.Metric{} counter.Write(m) - if expected, got := `counter: `, m.String(); expected != got { - t.Errorf("expected %q, got %q", expected, got) + want := `counter:{value:inf}` + + if err := compareProtoAndMetric(want, m); err != nil { + t.Errorf("Summary didn't match: %s", err) } } @@ -188,8 +192,10 @@ func TestCounterAddLarge(t *testing.T) { m := &dto.Metric{} counter.Write(m) - if expected, got := fmt.Sprintf("counter: ", large), m.String(); expected != got { - t.Errorf("expected %q, got %q", expected, got) + want := fmt.Sprintf("counter:{value:%0.16e}", large) + + if err := compareProtoAndMetric(want, m); err != nil { + t.Errorf("Summary didn't match: %s", err) } } @@ -210,8 +216,10 @@ func TestCounterAddSmall(t *testing.T) { m := &dto.Metric{} counter.Write(m) - if expected, got := fmt.Sprintf("counter: ", small), m.String(); expected != got { - t.Errorf("expected %q, got %q", expected, got) + want := fmt.Sprintf("counter:{value:%0.0e}", small) + + if err := compareProtoAndMetric(want, m); err != nil { + t.Errorf("Summary didn't match: %s", err) } } diff --git a/prometheus/example_metricvec_test.go b/prometheus/example_metricvec_test.go index ff4bd1f..d0131f0 100644 --- a/prometheus/example_metricvec_test.go +++ b/prometheus/example_metricvec_test.go @@ -14,13 +14,13 @@ package prometheus_test import ( - "fmt" + "testing" "google.golang.org/protobuf/proto" - dto "github.com/prometheus/client_model/go" - "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" + dto "github.com/prometheus/client_model/go" ) // Info implements an info pseudo-metric, which is modeled as a Gauge that @@ -106,7 +106,7 @@ func (v *InfoVec) MustCurryWith(labels prometheus.Labels) *InfoVec { return vec } -func ExampleMetricVec() { +func TestExampleMetricVec(t *testing.T) { infoVec := NewInfoVec( "library_version_info", "Versions of the libraries used in this binary.", @@ -126,8 +126,40 @@ func ExampleMetricVec() { if err != nil || len(metricFamilies) != 1 { panic("unexpected behavior of custom test registry") } - fmt.Println(metricFamilies[0].String()) - // Output: - // name:"library_version_info" help:"Versions of the libraries used in this binary." type:GAUGE metric: label: gauge: > metric: label: gauge: > + want := ` + name: "library_version_info" + help: "Versions of the libraries used in this binary." + type: GAUGE + metric: { + label: { + name: "library" + value: "k8s.io/client-go" + } + label: { + name: "version" + value: "0.18.8" + } + gauge: { + value: 1 + } + } + metric: { + label: { + name: "library" + value: "prometheus/client_golang" + } + label: { + name: "version" + value: "1.7.1" + } + gauge: { + value: 1 + } + } + ` + + if err := testutil.CompareProtoAndMetricFamily(want, metricFamilies[0]); err != nil { + t.Errorf("Summary didn't match: %s", err) + } } diff --git a/prometheus/examples_test.go b/prometheus/examples_test.go index d52a718..dbb06ab 100644 --- a/prometheus/examples_test.go +++ b/prometheus/examples_test.go @@ -21,13 +21,16 @@ import ( "net/http" "runtime" "strings" + "testing" "time" dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" + "google.golang.org/protobuf/encoding/prototext" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/prometheus/client_golang/prometheus/testutil" ) func ExampleGauge() { @@ -302,7 +305,7 @@ func ExampleRegister() { // taskCounterForWorker2001 registered. } -func ExampleSummary() { +func TestExampleSummary(t *testing.T) { temps := prometheus.NewSummary(prometheus.SummaryOpts{ Name: "pond_temperature_celsius", Help: "The temperature of the frog pond.", @@ -319,13 +322,32 @@ func ExampleSummary() { // internally). metric := &dto.Metric{} temps.Write(metric) - fmt.Println(metric.String()) - // Output: - // summary: quantile: quantile: > + want := ` + summary: { + sample_count: 1000 + sample_sum: 29969.50000000001 + quantile: { + quantile: 0.5 + value: 31.1 + } + quantile: { + quantile: 0.9 + value: 41.3 + } + quantile: { + quantile: 0.99 + value: 41.9 + } + } + ` + + if err := testutil.CompareProtoAndMetric(want, metric); err != nil { + t.Errorf("Summary didn't match: %s", err) + } } -func ExampleSummaryVec() { +func TestExampleSummaryVec(t *testing.T) { temps := prometheus.NewSummaryVec( prometheus.SummaryOpts{ Name: "pond_temperature_celsius", @@ -354,13 +376,86 @@ func ExampleSummaryVec() { if err != nil || len(metricFamilies) != 1 { panic("unexpected behavior of custom test registry") } - fmt.Println(metricFamilies[0].String()) + fmt.Println(prototext.Format(metricFamilies[0])) - // Output: - // name:"pond_temperature_celsius" help:"The temperature of the frog pond." type:SUMMARY metric: summary: quantile: quantile: > > metric: summary: quantile: quantile: > > metric: summary: quantile: quantile: > > + want := ` + name: "pond_temperature_celsius" + help: "The temperature of the frog pond." + type: SUMMARY + metric: { + label: { + name: "species" + value: "leiopelma-hochstetteri" + } + summary: { + sample_count: 0 + sample_sum: 0 + quantile: { + quantile: 0.5 + value: nan + } + quantile: { + quantile: 0.9 + value: nan + } + quantile: { + quantile: 0.99 + value: nan + } + } + } + metric: { + label: { + name: "species" + value: "lithobates-catesbeianus" + } + summary: { + sample_count: 1000 + sample_sum: 31956.100000000017 + quantile: { + quantile: 0.5 + value: 32.4 + } + quantile: { + quantile: 0.9 + value: 41.4 + } + quantile: { + quantile: 0.99 + value: 41.9 + } + } + } + metric: { + label: { + name: "species" + value: "litoria-caerulea" + } + summary: { + sample_count: 1000 + sample_sum: 29969.50000000001 + quantile: { + quantile: 0.5 + value: 31.1 + } + quantile: { + quantile: 0.9 + value: 41.3 + } + quantile: { + quantile: 0.99 + value: 41.9 + } + } + } + ` + + if err := testutil.CompareProtoAndMetricFamily(want, metricFamilies[0]); err != nil { + t.Errorf("Summary didn't match: %s", err) + } } -func ExampleNewConstSummary() { +func TestExampleNewConstSummary(t *testing.T) { desc := prometheus.NewDesc( "http_request_duration_seconds", "A summary of the HTTP request durations.", @@ -381,13 +476,41 @@ func ExampleNewConstSummary() { // internally). metric := &dto.Metric{} s.Write(metric) - fmt.Println(metric.String()) + fmt.Println(prototext.Format(metric)) - // Output: - // label: label: label: summary: quantile: > + want := ` + label: { + name: "code" + value: "200" + } + label: { + name: "method" + value: "get" + } + label: { + name: "owner" + value: "example" + } + summary: { + sample_count: 4711 + sample_sum: 403.34 + quantile: { + quantile: 0.5 + value: 42.3 + } + quantile: { + quantile: 0.9 + value: 323.3 + } + } + ` + + if err := testutil.CompareProtoAndMetric(want, metric); err != nil { + t.Errorf("Summary didn't match: %s", err) + } } -func ExampleHistogram() { +func TestExampleHistogram(t *testing.T) { temps := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "pond_temperature_celsius", Help: "The temperature of the frog pond.", // Sorry, we can't measure how badly it smells. @@ -404,13 +527,41 @@ func ExampleHistogram() { // internally). metric := &dto.Metric{} temps.Write(metric) - fmt.Println(metric.String()) + fmt.Println(prototext.Format(metric)) - // Output: - // histogram: bucket: bucket: bucket: bucket: > + want := ` + histogram: { + sample_count: 1000 + sample_sum: 29969.50000000001 + bucket: { + cumulative_count: 192 + upper_bound: 20 + } + bucket: { + cumulative_count: 366 + upper_bound: 25 + } + bucket: { + cumulative_count: 501 + upper_bound: 30 + } + bucket: { + cumulative_count: 638 + upper_bound: 35 + } + bucket: { + cumulative_count: 816 + upper_bound: 40 + } + } + ` + + if err := testutil.CompareProtoAndMetric(want, metric); err != nil { + t.Errorf("Summary didn't match: %s", err) + } } -func ExampleNewConstHistogram() { +func TestExampleNewConstHistogram(t *testing.T) { desc := prometheus.NewDesc( "http_request_duration_seconds", "A histogram of the HTTP request durations.", @@ -431,13 +582,49 @@ func ExampleNewConstHistogram() { // internally). metric := &dto.Metric{} h.Write(metric) - fmt.Println(metric.String()) + fmt.Println(prototext.Format(metric)) - // Output: - // label: label: label: histogram: bucket: bucket: bucket: > + want := ` + label: { + name: "code" + value: "200" + } + label: { + name: "method" + value: "get" + } + label: { + name: "owner" + value: "example" + } + histogram: { + sample_count: 4711 + sample_sum: 403.34 + bucket: { + cumulative_count: 121 + upper_bound: 25 + } + bucket: { + cumulative_count: 2403 + upper_bound: 50 + } + bucket: { + cumulative_count: 3221 + upper_bound: 100 + } + bucket: { + cumulative_count: 4233 + upper_bound: 200 + } + } + ` + + if err := testutil.CompareProtoAndMetric(want, metric); err != nil { + t.Errorf("Summary didn't match: %s", err) + } } -func ExampleNewConstHistogram_WithExemplar() { +func TestExampleNewConstHistogram_WithExemplar(t *testing.T) { desc := prometheus.NewDesc( "http_request_duration_seconds", "A histogram of the HTTP request durations.", @@ -469,10 +656,86 @@ func ExampleNewConstHistogram_WithExemplar() { // internally). metric := &dto.Metric{} h.Write(metric) - fmt.Println(metric.String()) + fmt.Println(prototext.Format(metric)) - // Output: - // label: label: label: histogram: value:24 timestamp: > > bucket: value:42 timestamp: > > bucket: value:89 timestamp: > > bucket: value:157 timestamp: > > > + want := ` + label: { + name: "code" + value: "200" + } + label: { + name: "method" + value: "get" + } + label: { + name: "owner" + value: "example" + } + histogram: { + sample_count: 4711 + sample_sum: 403.34 + bucket: { + cumulative_count: 121 + upper_bound: 25 + exemplar: { + label: { + name: "testName" + value: "testVal" + } + value: 24 + timestamp: { + seconds: 1136214245 + } + } + } + bucket: { + cumulative_count: 2403 + upper_bound: 50 + exemplar: { + label: { + name: "testName" + value: "testVal" + } + value: 42 + timestamp: { + seconds: 1136214245 + } + } + } + bucket: { + cumulative_count: 3221 + upper_bound: 100 + exemplar: { + label: { + name: "testName" + value: "testVal" + } + value: 89 + timestamp: { + seconds: 1136214245 + } + } + } + bucket: { + cumulative_count: 4233 + upper_bound: 200 + exemplar: { + label: { + name: "testName" + value: "testVal" + } + value: 157 + timestamp: { + seconds: 1136214245 + } + } + } + } + ` + + if err := testutil.CompareProtoAndMetric(want, metric); err != nil { + t.Errorf("Summary didn't match: %s", err) + } } func ExampleAlreadyRegisteredError() { @@ -589,7 +852,7 @@ temperature_kelvin 4.5 // temperature_kelvin{location="outside"} 273.14 // temperature_kelvin{location="somewhere else"} 4.5 // ---------- - // collected metric "temperature_kelvin" { label: gauge: } was collected before with the same name and label values + // collected metric "temperature_kelvin" { label:{name:"location" value:"outside"} gauge:{value:265.3}} was collected before with the same name and label values // # HELP humidity_percent Humidity in %. // # TYPE humidity_percent gauge // humidity_percent{location="inside"} 33.2 @@ -601,7 +864,7 @@ temperature_kelvin 4.5 // temperature_kelvin{location="outside"} 273.14 } -func ExampleNewMetricWithTimestamp() { +func TestExampleNewMetricWithTimestamp(t *testing.T) { desc := prometheus.NewDesc( "temperature_kelvin", "Current temperature in Kelvin.", @@ -625,8 +888,16 @@ func ExampleNewMetricWithTimestamp() { // internally). metric := &dto.Metric{} s.Write(metric) - fmt.Println(metric.String()) + fmt.Println(prototext.Format(metric)) - // Output: - // gauge: timestamp_ms:1257894000012 + want := ` + gauge: { + value: 298.15 + } + timestamp_ms: 1257894000012 + ` + + if err := testutil.CompareProtoAndMetric(want, metric); err != nil { + t.Errorf("Summary didn't match: %s", err) + } } diff --git a/prometheus/expvar_collector_test.go b/prometheus/expvar_collector_test.go index 6bcd9b6..fb9c665 100644 --- a/prometheus/expvar_collector_test.go +++ b/prometheus/expvar_collector_test.go @@ -15,16 +15,16 @@ package prometheus_test import ( "expvar" - "fmt" - "sort" "strings" + "testing" dto "github.com/prometheus/client_model/go" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" ) -func ExampleNewExpvarCollector() { +func TestExampleNewExpvarCollector(t *testing.T) { expvarCollector := prometheus.NewExpvarCollector(map[string]*prometheus.Desc{ "memstats": prometheus.NewDesc( "expvar_memstats", @@ -70,7 +70,6 @@ func ExampleNewExpvarCollector() { // "http-request-count": {"200": {"POST": 11, "GET": 212}, "404": {"POST": 3, "GET": 13}} // Let's see what the scrape would yield, but exclude the memstats metrics. - metricStrings := []string{} metric := dto.Metric{} metricChan := make(chan prometheus.Metric) go func() { @@ -79,19 +78,20 @@ func ExampleNewExpvarCollector() { }() for m := range metricChan { if !strings.Contains(m.Desc().String(), "expvar_memstats") { - metric.Reset() m.Write(&metric) - metricStrings = append(metricStrings, metric.String()) } } - sort.Strings(metricStrings) - for _, s := range metricStrings { - fmt.Println(strings.TrimRight(s, " ")) + + want := `label:{name:"code" value:"200"} label:{name:"method" value:"GET"} untyped:{value:212}` + + if err := testutil.CompareProtoAndMetric(want, &metric); err != nil { + t.Errorf("Summary didn't match: %s", err) } + // Output: - // label: label: untyped: - // label: label: untyped: - // label: label: untyped: - // label: label: untyped: - // untyped: + // label:{name:"code" value:"200"} label:{name:"method" value:"GET"} untyped:{value:212} + // label:{name:"code" value:"200"} label:{name:"method" value:"POST"} untyped:{value:11} + // label:{name:"code" value:"404"} label:{name:"method" value:"GET"} untyped:{value:13} + // label:{name:"code" value:"404"} label:{name:"method" value:"POST"} untyped:{value:3} + // untyped:{value:42} } diff --git a/prometheus/gauge_test.go b/prometheus/gauge_test.go index b70da7d..3e6b6ca 100644 --- a/prometheus/gauge_test.go +++ b/prometheus/gauge_test.go @@ -177,8 +177,10 @@ func TestGaugeFunc(t *testing.T) { m := &dto.Metric{} gf.Write(m) - if expected, got := `label: label: gauge: `, m.String(); expected != got { - t.Errorf("expected %q, got %q", expected, got) + want := `label:{name:"a" value:"1"} label:{name:"b" value:"2"} gauge:{value:3.1415}` + + if err := compareProtoAndMetric(want, m); err != nil { + t.Errorf("Summary didn't match: %s", err) } } diff --git a/prometheus/histogram_test.go b/prometheus/histogram_test.go index 079866b..28819c5 100644 --- a/prometheus/histogram_test.go +++ b/prometheus/histogram_test.go @@ -483,19 +483,19 @@ func TestNativeHistogram(t *testing.T) { name: "no sparse buckets", observations: []float64{1, 2, 3}, factor: 1, - want: `sample_count:3 sample_sum:6 bucket: bucket: bucket: bucket: bucket: bucket: bucket: bucket: bucket: bucket: bucket: `, // Has conventional buckets because there are no sparse buckets. + want: `histogram{sample_count:3 sample_sum:6 bucket:{cumulative_count:0 upper_bound:0.005} bucket:{cumulative_count:0 upper_bound:0.01} bucket:{cumulative_count:0 upper_bound:0.025} bucket:{cumulative_count:0 upper_bound:0.05} bucket:{cumulative_count:0 upper_bound:0.1} bucket:{cumulative_count:0 upper_bound:0.25} bucket:{cumulative_count:0 upper_bound:0.5} bucket:{cumulative_count:1 upper_bound:1} bucket:{cumulative_count:2 upper_bound:2.5} bucket:{cumulative_count:3 upper_bound:5} bucket:{cumulative_count:3 upper_bound:10}}`, // Has conventional buckets because there are no sparse buckets. }, { name: "factor 1.1 results in schema 3", observations: []float64{0, 1, 2, 3}, factor: 1.1, - want: `sample_count:4 sample_sum:6 schema:3 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span: positive_span: positive_span: positive_delta:1 positive_delta:0 positive_delta:0 `, + want: `histogram{sample_count:4 sample_sum:6 schema:3 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span:{offset:0 length:1} positive_span:{offset:7 length:1} positive_span:{offset:4 length:1} positive_delta:1 positive_delta:0 positive_delta:0}`, }, { name: "factor 1.2 results in schema 2", observations: []float64{0, 1, 1.2, 1.4, 1.8, 2}, factor: 1.2, - want: `sample_count:6 sample_sum:7.4 schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span: positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2 `, + want: `histogram{sample_count:6 sample_sum:7.4 schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span:{offset:0 length:5} positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2}`, }, { name: "factor 4 results in schema -1", @@ -506,7 +506,7 @@ func TestNativeHistogram(t *testing.T) { 33.33, // Bucket 3: (16, 64] }, factor: 4, - want: `sample_count:10 sample_sum:62.83 schema:-1 zero_threshold:2.938735877055719e-39 zero_count:0 positive_span: positive_delta:2 positive_delta:2 positive_delta:-1 positive_delta:-2 `, + want: `histogram{sample_count:10 sample_sum:62.83 schema:-1 zero_threshold:2.938735877055719e-39 zero_count:0 positive_span:{offset:0 length:4} positive_delta:2 positive_delta:2 positive_delta:-1 positive_delta:-2}`, }, { name: "factor 17 results in schema -2", @@ -516,58 +516,58 @@ func TestNativeHistogram(t *testing.T) { 33.33, // Bucket 2: (16, 256] }, factor: 17, - want: `sample_count:10 sample_sum:62.83 schema:-2 zero_threshold:2.938735877055719e-39 zero_count:0 positive_span: positive_delta:2 positive_delta:5 positive_delta:-6 `, + want: `histogram{sample_count:10 sample_sum:62.83 schema:-2 zero_threshold:2.938735877055719e-39 zero_count:0 positive_span:{offset:0 length:3} positive_delta:2 positive_delta:5 positive_delta:-6}`, }, { name: "negative buckets", observations: []float64{0, -1, -1.2, -1.4, -1.8, -2}, factor: 1.2, - want: `sample_count:6 sample_sum:-7.4 schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 negative_span: negative_delta:1 negative_delta:-1 negative_delta:2 negative_delta:-2 negative_delta:2 `, + want: `histogram{sample_count:6 sample_sum:-7.4 schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 negative_span:{offset:0 length:5} negative_delta:1 negative_delta:-1 negative_delta:2 negative_delta:-2 negative_delta:2}`, }, { name: "negative and positive buckets", observations: []float64{0, -1, -1.2, -1.4, -1.8, -2, 1, 1.2, 1.4, 1.8, 2}, factor: 1.2, - want: `sample_count:11 sample_sum:0 schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 negative_span: negative_delta:1 negative_delta:-1 negative_delta:2 negative_delta:-2 negative_delta:2 positive_span: positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2 `, + want: `histogram{sample_count:11 sample_sum:0 schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 negative_span:{offset:0 length:5} negative_delta:1 negative_delta:-1 negative_delta:2 negative_delta:-2 negative_delta:2 positive_span:{offset:0 length:5} positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2}`, }, { name: "wide zero bucket", observations: []float64{0, -1, -1.2, -1.4, -1.8, -2, 1, 1.2, 1.4, 1.8, 2}, factor: 1.2, zeroThreshold: 1.4, - want: `sample_count:11 sample_sum:0 schema:2 zero_threshold:1.4 zero_count:7 negative_span: negative_delta:2 positive_span: positive_delta:2 `, + want: `histogram{sample_count:11 sample_sum:0 schema:2 zero_threshold:1.4 zero_count:7 negative_span:{offset:4 length:1} negative_delta:2 positive_span:{offset:4 length:1} positive_delta:2}`, }, { name: "NaN observation", observations: []float64{0, 1, 1.2, 1.4, 1.8, 2, math.NaN()}, factor: 1.2, - want: `sample_count:7 sample_sum:nan schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span: positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2 `, + want: `histogram{sample_count:7 sample_sum:nan schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span:{offset:0 length:5} positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2}`, }, { name: "+Inf observation", observations: []float64{0, 1, 1.2, 1.4, 1.8, 2, math.Inf(+1)}, factor: 1.2, - want: `sample_count:7 sample_sum:inf schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span: positive_span: positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2 positive_delta:-1 `, + want: `histogram{sample_count:7 sample_sum:inf schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span:{offset:0 length:5} positive_span:{offset:4092 length:1} positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2 positive_delta:-1}`, }, { name: "-Inf observation", observations: []float64{0, 1, 1.2, 1.4, 1.8, 2, math.Inf(-1)}, factor: 1.2, - want: `sample_count:7 sample_sum:-inf schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 negative_span: negative_delta:1 positive_span: positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2 `, + want: `histogram{sample_count:7 sample_sum:-inf schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 negative_span:{offset:4097 length:1} negative_delta:1 positive_span:{offset:0 length:5} positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2}`, }, { name: "limited buckets but nothing triggered", observations: []float64{0, 1, 1.2, 1.4, 1.8, 2}, factor: 1.2, maxBuckets: 4, - want: `sample_count:6 sample_sum:7.4 schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span: positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2 `, + want: `histogram{sample_count:6 sample_sum:7.4 schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span:{offset:0 length:5} positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2}`, }, { name: "buckets limited by halving resolution", observations: []float64{0, 1, 1.1, 1.2, 1.4, 1.8, 2, 3}, factor: 1.2, maxBuckets: 4, - want: `sample_count:8 sample_sum:11.5 schema:1 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span: positive_delta:1 positive_delta:2 positive_delta:-1 positive_delta:-2 positive_delta:1 `, + want: `histogram{sample_count:8 sample_sum:11.5 schema:1 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span:{offset:0 length:5} positive_delta:1 positive_delta:2 positive_delta:-1 positive_delta:-2 positive_delta:1}`, }, { name: "buckets limited by widening the zero bucket", @@ -575,7 +575,7 @@ func TestNativeHistogram(t *testing.T) { factor: 1.2, maxBuckets: 4, maxZeroThreshold: 1.2, - want: `sample_count:8 sample_sum:11.5 schema:2 zero_threshold:1 zero_count:2 positive_span: positive_delta:1 positive_delta:1 positive_delta:-2 positive_delta:2 positive_delta:-2 positive_delta:0 positive_delta:1 `, + want: `histogram{sample_count:8 sample_sum:11.5 schema:2 zero_threshold:1 zero_count:2 positive_span:{offset:1 length:7} positive_delta:1 positive_delta:1 positive_delta:-2 positive_delta:2 positive_delta:-2 positive_delta:0 positive_delta:1}`, }, { name: "buckets limited by widening the zero bucket twice", @@ -583,7 +583,7 @@ func TestNativeHistogram(t *testing.T) { factor: 1.2, maxBuckets: 4, maxZeroThreshold: 1.2, - want: `sample_count:9 sample_sum:15.5 schema:2 zero_threshold:1.189207115002721 zero_count:3 positive_span: positive_delta:2 positive_delta:-2 positive_delta:2 positive_delta:-2 positive_delta:0 positive_delta:1 positive_delta:0 `, + want: `histogram{sample_count:9 sample_sum:15.5 schema:2 zero_threshold:1.189207115002721 zero_count:3 positive_span:{offset:2 length:7} positive_delta:2 positive_delta:-2 positive_delta:2 positive_delta:-2 positive_delta:0 positive_delta:1 positive_delta:0}`, }, { name: "buckets limited by reset", @@ -592,21 +592,21 @@ func TestNativeHistogram(t *testing.T) { maxBuckets: 4, maxZeroThreshold: 1.2, minResetDuration: 5 * time.Minute, - want: `sample_count:2 sample_sum:7 schema:2 zero_threshold:2.938735877055719e-39 zero_count:0 positive_span: positive_delta:1 positive_delta:0 `, + want: `histogram{sample_count:2 sample_sum:7 schema:2 zero_threshold:2.938735877055719e-39 zero_count:0 positive_span:{offset:7 length:2} positive_delta:1 positive_delta:0}`, }, { name: "limited buckets but nothing triggered, negative observations", observations: []float64{0, -1, -1.2, -1.4, -1.8, -2}, factor: 1.2, maxBuckets: 4, - want: `sample_count:6 sample_sum:-7.4 schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 negative_span: negative_delta:1 negative_delta:-1 negative_delta:2 negative_delta:-2 negative_delta:2 `, + want: `histogram{sample_count:6 sample_sum:-7.4 schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 negative_span:{offset:0 length:5} negative_delta:1 negative_delta:-1 negative_delta:2 negative_delta:-2 negative_delta:2}`, }, { name: "buckets limited by halving resolution, negative observations", observations: []float64{0, -1, -1.1, -1.2, -1.4, -1.8, -2, -3}, factor: 1.2, maxBuckets: 4, - want: `sample_count:8 sample_sum:-11.5 schema:1 zero_threshold:2.938735877055719e-39 zero_count:1 negative_span: negative_delta:1 negative_delta:2 negative_delta:-1 negative_delta:-2 negative_delta:1 `, + want: `histogram{sample_count:8 sample_sum:-11.5 schema:1 zero_threshold:2.938735877055719e-39 zero_count:1 negative_span:{offset:0 length:5} negative_delta:1 negative_delta:2 negative_delta:-1 negative_delta:-2 negative_delta:1}`, }, { name: "buckets limited by widening the zero bucket, negative observations", @@ -614,7 +614,7 @@ func TestNativeHistogram(t *testing.T) { factor: 1.2, maxBuckets: 4, maxZeroThreshold: 1.2, - want: `sample_count:8 sample_sum:-11.5 schema:2 zero_threshold:1 zero_count:2 negative_span: negative_delta:1 negative_delta:1 negative_delta:-2 negative_delta:2 negative_delta:-2 negative_delta:0 negative_delta:1 `, + want: `histogram{sample_count:8 sample_sum:-11.5 schema:2 zero_threshold:1 zero_count:2 negative_span:{offset:1 length:7} negative_delta:1 negative_delta:1 negative_delta:-2 negative_delta:2 negative_delta:-2 negative_delta:0 negative_delta:1}`, }, { name: "buckets limited by widening the zero bucket twice, negative observations", @@ -622,7 +622,7 @@ func TestNativeHistogram(t *testing.T) { factor: 1.2, maxBuckets: 4, maxZeroThreshold: 1.2, - want: `sample_count:9 sample_sum:-15.5 schema:2 zero_threshold:1.189207115002721 zero_count:3 negative_span: negative_delta:2 negative_delta:-2 negative_delta:2 negative_delta:-2 negative_delta:0 negative_delta:1 negative_delta:0 `, + want: `histogram{sample_count:9 sample_sum:-15.5 schema:2 zero_threshold:1.189207115002721 zero_count:3 negative_span:{offset:2 length:7} negative_delta:2 negative_delta:-2 negative_delta:2 negative_delta:-2 negative_delta:0 negative_delta:1 negative_delta:0}`, }, { name: "buckets limited by reset, negative observations", @@ -631,7 +631,7 @@ func TestNativeHistogram(t *testing.T) { maxBuckets: 4, maxZeroThreshold: 1.2, minResetDuration: 5 * time.Minute, - want: `sample_count:2 sample_sum:-7 schema:2 zero_threshold:2.938735877055719e-39 zero_count:0 negative_span: negative_delta:1 negative_delta:0 `, + want: `histogram{sample_count:2 sample_sum:-7 schema:2 zero_threshold:2.938735877055719e-39 zero_count:0 negative_span:{offset:7 length:2} negative_delta:1 negative_delta:0}`, }, { name: "buckets limited by halving resolution, then reset", @@ -639,7 +639,7 @@ func TestNativeHistogram(t *testing.T) { factor: 1.2, maxBuckets: 4, minResetDuration: 9 * time.Minute, - want: `sample_count:2 sample_sum:7 schema:2 zero_threshold:2.938735877055719e-39 zero_count:0 positive_span: positive_delta:1 positive_delta:0 `, + want: `histogram{sample_count:2 sample_sum:7 schema:2 zero_threshold:2.938735877055719e-39 zero_count:0 positive_span:{offset:7 length:2} positive_delta:1 positive_delta:0}`, }, { name: "buckets limited by widening the zero bucket, then reset", @@ -648,7 +648,7 @@ func TestNativeHistogram(t *testing.T) { maxBuckets: 4, maxZeroThreshold: 1.2, minResetDuration: 9 * time.Minute, - want: `sample_count:2 sample_sum:7 schema:2 zero_threshold:2.938735877055719e-39 zero_count:0 positive_span: positive_delta:1 positive_delta:0 `, + want: `histogram{sample_count:2 sample_sum:7 schema:2 zero_threshold:2.938735877055719e-39 zero_count:0 positive_span:{offset:7 length:2} positive_delta:1 positive_delta:0}`, }, } @@ -676,9 +676,8 @@ func TestNativeHistogram(t *testing.T) { if err := his.Write(m); err != nil { t.Fatal("unexpected error writing metric", err) } - got := m.Histogram.String() - if s.want != got { - t.Errorf("want histogram %q, got %q", s.want, got) + if err := compareProtoAndMetric(s.want, m); err != nil { + t.Errorf("want histogram %q, got %q", s.want, m) } }) } diff --git a/prometheus/metric.go b/prometheus/metric.go index 07bbc9d..48e2e93 100644 --- a/prometheus/metric.go +++ b/prometheus/metric.go @@ -14,7 +14,9 @@ package prometheus import ( + "bytes" "errors" + "fmt" "math" "sort" "strings" @@ -22,6 +24,7 @@ import ( dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/model" + "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" ) @@ -252,3 +255,33 @@ func MustNewMetricWithExemplars(m Metric, exemplars ...Exemplar) Metric { } return ret } + +// compareProtoAndMetric compares a text proto to a Metric. +// +// It jumps through a few hoops because the current protobuf +// implementation is deliberately creating an unstable formatting for the text +// representation. So this takes the text representation of the expected +// Metric and unmarshals it into a proto message object first. Then it +// marshals both the expected and the got proto message into a binary protobuf, +// which it then compares. +func compareProtoAndMetric(wantText string, got *dto.Metric) error { + want := &dto.Metric{} + err := prototext.Unmarshal([]byte(wantText), want) + if err != nil { + return fmt.Errorf("unexpected error unmarshaling Metric text %v", wantText) + } + wantProto, err := proto.Marshal(want) + if err != nil { + return fmt.Errorf("unexpected error marshaling Metric %v", want) + } + + gotProto, err := proto.Marshal(got) + if err != nil { + return fmt.Errorf("unexpected error marshaling Metric %v", got) + } + + if bytes.Compare(wantProto, gotProto) != 0 { + return fmt.Errorf("Wanted Metric %v, got %v.", want, got) + } + return nil +} diff --git a/prometheus/testutil/testutil.go b/prometheus/testutil/testutil.go index 82d4a54..6b887aa 100644 --- a/prometheus/testutil/testutil.go +++ b/prometheus/testutil/testutil.go @@ -45,11 +45,12 @@ import ( "reflect" "github.com/davecgh/go-spew/spew" - dto "github.com/prometheus/client_model/go" - "github.com/prometheus/common/expfmt" - "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/internal" + dto "github.com/prometheus/client_model/go" + "github.com/prometheus/common/expfmt" + "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/proto" ) // ToFloat64 collects all Metrics from the provided Collector. It expects that @@ -341,3 +342,63 @@ func filterMetrics(metrics []*dto.MetricFamily, names []string) []*dto.MetricFam } return filtered } + +// CompareProtoAndMetric compares a text proto to a MetricFamily. +// +// It jumps through a few hoops because the current protobuf +// implementation is deliberately creating an unstable formatting for the text +// representation. So this takes the text representation of the expected +// MetricFamily and unmarshals it into a proto message object first. Then it +// marshals both the expected and the got proto message into a binary protobuf, +// which it then compares. +func CompareProtoAndMetric(wantText string, got *dto.Metric) error { + want := &dto.Metric{} + err := prototext.Unmarshal([]byte(wantText), want) + if err != nil { + return fmt.Errorf("unexpected error unmarshaling Metric text %v", wantText) + } + wantProto, err := proto.Marshal(want) + if err != nil { + return fmt.Errorf("unexpected error marshaling Metric %v", want) + } + + gotProto, err := proto.Marshal(got) + if err != nil { + return fmt.Errorf("unexpected error marshaling Metric %v", got) + } + + if bytes.Compare(wantProto, gotProto) != 0 { + return fmt.Errorf("Wanted Metric %v, got %v.", want, got) + } + return nil +} + +// CompareProtoAndMetricFamily compares a text proto to a MetricFamily. +// +// It jumps through a few hoops because the current protobuf +// implementation is deliberately creating an unstable formatting for the text +// representation. So this takes the text representation of the expected +// MetricFamily and unmarshals it into a proto message object first. Then it +// marshals both the expected and the got proto message into a binary protobuf, +// which it then compares. +func CompareProtoAndMetricFamily(wantText string, got *dto.MetricFamily) error { + want := &dto.MetricFamily{} + err := prototext.Unmarshal([]byte(wantText), want) + if err != nil { + return fmt.Errorf("unexpected error unmarshaling MetricFamily text %v", wantText) + } + wantProto, err := proto.Marshal(want) + if err != nil { + return fmt.Errorf("unexpected error marshaling MetricFamily %v", want) + } + + gotProto, err := proto.Marshal(got) + if err != nil { + return fmt.Errorf("unexpected error marshaling MetricFamily %v", got) + } + + if bytes.Compare(wantProto, gotProto) != 0 { + return fmt.Errorf("Wanted MetricFamily %v, got %v.", want, got) + } + return nil +}