From 9f57d4a978daac7705935b220ac21f747ed208cf Mon Sep 17 00:00:00 2001 From: constanca-m Date: Mon, 25 Sep 2023 13:03:44 +0200 Subject: [PATCH] Add tests for parsing metrics. --- .../helper/prometheus/prometheus_test.go | 482 +++++++++++++++--- metricbeat/helper/prometheus/textparse.go | 41 +- 2 files changed, 442 insertions(+), 81 deletions(-) diff --git a/metricbeat/helper/prometheus/prometheus_test.go b/metricbeat/helper/prometheus/prometheus_test.go index a7bf6a55909..2c23a119ff8 100644 --- a/metricbeat/helper/prometheus/prometheus_test.go +++ b/metricbeat/helper/prometheus/prometheus_test.go @@ -23,6 +23,7 @@ import ( "io" "net/http" "sort" + "strings" "testing" "time" @@ -1006,87 +1007,444 @@ func TestPrometheusKeyLabels(t *testing.T) { } } -func TestPrometheusHistogramWithoutSuffix(t *testing.T) { - promHistogramWithAndWithoutBucket := ` +/* +Test the resulted metrics family when ParseMetricFamilies uses a PromParser. Accepted types: +https://github.com/prometheus/prometheus/blob/b0944590a1c9a6b35dc5a696869f75f422b107a1/pkg/textparse/promparse.go#L291-L304 +Type of parser is determined based on the content used. +*/ +func TestParseMetricsFamilyPromParser(t *testing.T) { + stringp := func(x string) *string { return &x } + float64p := func(x float64) *float64 { return &x } + uint64p := func(x uint64) *uint64 { return &x } + int64p := func(x int64) *int64 { return &x } + + var expected []*MetricFamily + + gauge := ` +# HELP jvm_buffer_count_buffers An estimate of the number of buffers in the pool +# TYPE jvm_buffer_count_buffers gauge +jvm_buffer_count_buffers{id="mapped - 'non-volatile memory'"} 0.0` + gaugeMetricFamily := MetricFamily{ + Name: stringp("jvm_buffer_count_buffers"), + Help: stringp("An estimate of the number of buffers in the pool"), + Type: "gauge", + Unit: nil, + Metric: []*OpenMetric{ + { + Label: []*labels.Label{ + { + Name: "id", + Value: "mapped - 'non-volatile memory'", + }, + }, + Name: stringp("jvm_buffer_count_buffers"), + Gauge: &Gauge{ + Value: float64p(0), + }, + }, + }, + } + expected = append(expected, &gaugeMetricFamily) + + counter := ` +# HELP cc_seconds_total A counter +# TYPE cc_seconds_total counter +cc_seconds_total 1.0` + counterMetricFamily := MetricFamily{ + Name: stringp("cc_seconds_total"), + Help: stringp("A counter"), + Type: "counter", + Unit: nil, + Metric: []*OpenMetric{ + { + Label: []*labels.Label{}, + Name: stringp("cc_seconds_total"), + Counter: &Counter{ + Value: float64p(1.0), + }, + }, + }, + } + expected = append(expected, &counterMetricFamily) + + summary := ` +# HELP rpc_duration_seconds A summary of the RPC duration in seconds. +# TYPE rpc_duration_seconds summary +rpc_duration_seconds{quantile="0.01"} 3102 123 +rpc_duration_seconds{quantile="0.5"} 4773 123 +rpc_duration_seconds{quantile="0.99"} 76656 123 +rpc_duration_seconds_sum 1.7560473e+07 123 +rpc_duration_seconds_count 2693 123` + summaryMetricFamily := MetricFamily{ + Name: stringp("rpc_duration_seconds"), + Help: stringp("A summary of the RPC duration in seconds."), + Type: "summary", + Unit: nil, + Metric: []*OpenMetric{ + { + Label: []*labels.Label{}, + Name: stringp("rpc_duration_seconds"), + Summary: &Summary{ + SampleCount: uint64p(2693), + SampleSum: float64p(1.7560473e+07), + Quantile: []*Quantile{ + { + Quantile: float64p(0.01), + Value: float64p(3102), + }, + { + Quantile: float64p(0.5), + Value: float64p(4773), + }, + { + Quantile: float64p(0.99), + Value: float64p(76656), + }, + }, + }, + TimestampMs: int64p(123), + }, + }, + } + expected = append(expected, &summaryMetricFamily) + + histogram := ` # HELP http_server_requests_seconds Duration of HTTP server request handling # TYPE http_server_requests_seconds histogram -http_server_requests_seconds{exception="None",uri="/actuator/prometheus",quantile="0.002796201",} 0.046137344 -http_server_requests_seconds{exception="None",uri="/actuator/prometheus",quantile="0.003145726",} 0.046137344 -http_server_requests_seconds_bucket{exception="None",uri="/actuator/prometheus",le="0.001",} 0.0 -http_server_requests_seconds_bucket{exception="None",uri="/actuator/prometheus",le="0.001048576",} 0.0 -http_server_requests_seconds_count{exception="None",uri="/actuator/prometheus",} 1.0 -http_server_requests_seconds_sum{exception="None",uri="/actuator/prometheus",} 0.046745444 -http_server_requests_seconds_impossible{exception="None",uri="/actuator/prometheus",} 0.046745444 -http_server_requests_seconds_created{exception="None",uri="/actuator/prometheus",} 0.046745444 -` - - labelsList := []*labels.Label{ - { - Name: "exception", - Value: "None", +http_server_requests_seconds{exception="None",uri="/actuator/prometheus",quantile="0.002796201"} 0.046137344 +http_server_requests_seconds{exception="None",uri="/actuator/prometheus",quantile="0.003145726"} 0.046137344 +http_server_requests_seconds_bucket{exception="None",uri="/actuator/prometheus",le="0.001"} 0.0 +http_server_requests_seconds_bucket{exception="None",uri="/actuator/prometheus",le="0.001048576"} 0.0 +http_server_requests_seconds_count{exception="None",uri="/actuator/prometheus"} 1.0 +http_server_requests_seconds_sum{exception="None",uri="/actuator/prometheus"} 0.046745444 +http_server_requests_seconds_impossible{exception="None",uri="/actuator/prometheus"} 0.046745444 +http_server_requests_seconds_created{exception="None",uri="/actuator/prometheus"} 0.046745444` + histogramMetricFamily := MetricFamily{ + Name: stringp("http_server_requests_seconds"), + Help: stringp("Duration of HTTP server request handling"), + Type: "histogram", + Unit: nil, + Metric: []*OpenMetric{ + { + Label: []*labels.Label{ + { + Name: "exception", + Value: "None", + }, + { + Name: "uri", + Value: "/actuator/prometheus", + }, + }, + Name: stringp("http_server_requests_seconds"), + Histogram: &Histogram{ + IsGaugeHistogram: false, + SampleCount: uint64p(1.0), + SampleSum: float64p(0.046745444), + Bucket: []*Bucket{ + { + CumulativeCount: uint64p(0), + UpperBound: float64p(0.001), + }, + { + CumulativeCount: uint64p(0), + UpperBound: float64p(0.001048576), + }, + }, + }, + }, }, - { - Name: "uri", - Value: "/actuator/prometheus", + } + expected = append(expected, &histogramMetricFamily) + + undefined := ` +# HELP redis_connected_clients Redis connected clients +# TYPE redis_connected_clients undefined +redis_connected_clients{instance="rough-snowflake-web"} 10.0` + + allMetrics := strings.Join([]string{gauge, counter, summary, histogram, undefined}, "\n") + b := []byte(allMetrics) + result, err := ParseMetricFamilies(b, ContentTypeTextFormat, time.Now()) + if err != nil { + t.Fatalf("ParseMetricFamilies for content type %s returned an error.", ContentTypeTextFormat) + } + assert.ElementsMatch(t, expected, result) + +} + +/* +Test the resulted metrics family when ParseMetricFamilies uses an OpenMetricsParser: accepted types +https://github.com/prometheus/prometheus/blob/b0944590a1c9a6b35dc5a696869f75f422b107a1/pkg/textparse/openmetricsparse.go#L261-L280 +Type of parser is determined based on the content used. +*/ +func TestParseMetricsFamilyOpenMetricsParser(t *testing.T) { + stringp := func(x string) *string { return &x } + float64p := func(x float64) *float64 { return &x } + uint64p := func(x uint64) *uint64 { return &x } + int64p := func(x int64) *int64 { return &x } + + var expected []*MetricFamily + + gauge := `# HELP jvm_buffer_count_buffers An estimate of the number of buffers in the pool +# TYPE jvm_buffer_count_buffers gauge +jvm_buffer_count_buffers{id="mapped - 'non-volatile memory'"} 0.0` + gaugeMetricFamily := MetricFamily{ + Name: stringp("jvm_buffer_count_buffers"), + Help: stringp("An estimate of the number of buffers in the pool"), + Type: "gauge", + Unit: nil, + Metric: []*OpenMetric{ + { + Label: []*labels.Label{ + { + Name: "id", + Value: "mapped - 'non-volatile memory'", + }, + }, + Name: stringp("jvm_buffer_count_buffers"), + Gauge: &Gauge{ + Value: float64p(0), + }, + }, }, } + expected = append(expected, &gaugeMetricFamily) - cumulativeCounts := []uint64{ - uint64(0.0), - uint64(0.0), + counter := `# HELP cc_seconds A counter +# TYPE cc_seconds counter +# UNIT cc_seconds seconds +cc_seconds_total 1.0 +cc_seconds_created 123.456` + counterMetricFamily := MetricFamily{ + Name: stringp("cc_seconds"), + Help: stringp("A counter"), + Type: "counter", + Unit: stringp("seconds"), + Metric: []*OpenMetric{ + { + Label: []*labels.Label{}, + Name: stringp("cc_seconds_total"), + Counter: &Counter{ + Value: float64p(1.0), + }, + }, + { + Label: []*labels.Label{}, + Name: stringp("cc_seconds_created"), + Counter: &Counter{ + Value: float64p(123.456), + }, + }, + }, } - upperBounds := []float64{ - 0.001, - 0.001048576, + expected = append(expected, &counterMetricFamily) + + info := + `# TYPE info_metric info +# HELP info_metric help +info_metric{foo="bar"} 1 2` + infoMetricFamily := MetricFamily{ + Name: stringp("info_metric"), + Help: stringp("help"), + Type: "info", + Unit: nil, + Metric: []*OpenMetric{ + { + Label: []*labels.Label{ + { + Name: "foo", + Value: "bar", + }, + }, + Name: stringp("info_metric"), + Info: &Info{ + Value: int64p(1), + }, + TimestampMs: int64p(2000), + }, + }, } - var buckets []*Bucket - for i := range cumulativeCounts { - buckets = append(buckets, &Bucket{ - CumulativeCount: &cumulativeCounts[i], - UpperBound: &upperBounds[i], - }) + expected = append(expected, &infoMetricFamily) + + stateset := + `# TYPE some_name stateset +some_name{a="bar"} 2` + statesetMetricFamily := MetricFamily{ + Name: stringp("some_name"), + Help: nil, + Type: "stateset", + Unit: nil, + Metric: []*OpenMetric{ + { + Label: []*labels.Label{ + { + Name: "a", + Value: "bar", + }, + }, + Name: stringp("some_name"), + Stateset: &Stateset{ + Value: int64p(2), + }, + }, + }, } + expected = append(expected, &statesetMetricFamily) - name := "http_server_requests_seconds" - help := "Duration of HTTP server request handling" - - var expectedMetric OpenMetric - expectedMetric.Name = &name - expectedMetric.Label = labelsList - sampleSum := 0.046745444 - sampleCount := uint64(1.0) - expectedMetric.Histogram = &Histogram{ - IsGaugeHistogram: false, - SampleSum: &sampleSum, - SampleCount: &sampleCount, - Bucket: buckets, + summary := + `# HELP rpc_duration_seconds A summary of the RPC duration in seconds. +# TYPE rpc_duration_seconds summary +rpc_duration_seconds{quantile="0.01"} 3102 1 +rpc_duration_seconds{quantile="0.5"} 4773 1 +rpc_duration_seconds{quantile="0.99"} 76656 1 +rpc_duration_seconds_sum 1.7560473e+07 1 +rpc_duration_seconds_count 2693 1 +rpc_duration_seconds_created 2693 1` + summaryMetricFamily := MetricFamily{ + Name: stringp("rpc_duration_seconds"), + Help: stringp("A summary of the RPC duration in seconds."), + Type: "summary", + Unit: nil, + Metric: []*OpenMetric{ + { + Label: []*labels.Label{}, + Name: stringp("rpc_duration_seconds"), + Summary: &Summary{ + SampleCount: uint64p(2693), + SampleSum: float64p(1.7560473e+07), + Quantile: []*Quantile{ + { + Quantile: float64p(0.01), + Value: float64p(3102), + }, + { + Quantile: float64p(0.5), + Value: float64p(4773), + }, + { + Quantile: float64p(0.99), + Value: float64p(76656), + }, + }, + }, + TimestampMs: int64p(1000), + }, + }, } + expected = append(expected, &summaryMetricFamily) - expectedMetrics := []*OpenMetric{ - &expectedMetric, + histogram := + `# HELP http_server_requests_seconds Duration of HTTP server request handling +# TYPE http_server_requests_seconds histogram +http_server_requests_seconds{exception="None",uri="/actuator/prometheus",quantile="0.002796201"} 0.046137344 +http_server_requests_seconds{exception="None",uri="/actuator/prometheus",quantile="0.003145726"} 0.046137344 +http_server_requests_seconds_bucket{exception="None",uri="/actuator/prometheus",le="0.001"} 0.0 +http_server_requests_seconds_bucket{exception="None",uri="/actuator/prometheus",le="0.001048576"} 0.0 +http_server_requests_seconds_count{exception="None",uri="/actuator/prometheus"} 1.0 +http_server_requests_seconds_sum{exception="None",uri="/actuator/prometheus"} 0.046745444 +http_server_requests_seconds_created{exception="None",uri="/actuator/prometheus"} 0.046745444` + histogramMetricFamily := MetricFamily{ + Name: stringp("http_server_requests_seconds"), + Help: stringp("Duration of HTTP server request handling"), + Type: "histogram", + Unit: nil, + Metric: []*OpenMetric{ + { + Label: []*labels.Label{ + { + Name: "exception", + Value: "None", + }, + { + Name: "uri", + Value: "/actuator/prometheus", + }, + }, + Name: stringp("http_server_requests_seconds"), + Histogram: &Histogram{ + IsGaugeHistogram: false, + SampleCount: uint64p(1.0), + SampleSum: float64p(0.046745444), + Bucket: []*Bucket{ + { + CumulativeCount: uint64p(0), + UpperBound: float64p(0.001), + }, + { + CumulativeCount: uint64p(0), + UpperBound: float64p(0.001048576), + }, + }, + }, + }, + }, } + expected = append(expected, &histogramMetricFamily) - type Histogram struct { - SampleCount *uint64 - SampleSum *float64 - Bucket []*Bucket - IsGaugeHistogram bool + gaugeHistogram := `# TYPE ggh gaugehistogram +ggh_bucket{le=".9"} 2 +ggh_gcount 2 +ggh_gsum 1` + gaugeHistogramMetricFamily := MetricFamily{ + Name: stringp("ggh"), + Help: nil, + Type: "gaugehistogram", + Unit: nil, + Metric: []*OpenMetric{ + { + Label: []*labels.Label{}, + Name: stringp("ggh"), + Histogram: &Histogram{ + IsGaugeHistogram: true, + SampleCount: uint64p(2.0), + SampleSum: float64p(1), + Bucket: []*Bucket{ + { + CumulativeCount: uint64p(2), + UpperBound: float64p(0.9), + }, + }, + }, + }, + }, } + expected = append(expected, &gaugeHistogramMetricFamily) - expected := []*MetricFamily{ - { - Name: &name, - Help: &help, - Type: "histogram", - Unit: nil, - Metric: expectedMetrics, + unknown := `# HELP redis_connected_clients Redis connected clients +# TYPE redis_connected_clients unknown +redis_connected_clients{instance="rough-snowflake-web"} 10.0` + unknownMetricFamily := MetricFamily{ + Name: stringp("redis_connected_clients"), + Help: stringp("Redis connected clients"), + Type: "unknown", + Unit: nil, + Metric: []*OpenMetric{ + { + Label: []*labels.Label{ + { + Name: "instance", + Value: "rough-snowflake-web", + }, + }, + Name: stringp("redis_connected_clients"), + Unknown: &Unknown{ + Value: float64p(10), + }, + }, }, } + expected = append(expected, &unknownMetricFamily) - b := []byte(promHistogramWithAndWithoutBucket) - result, err := ParseMetricFamilies(b, ContentTypeTextFormat, time.Now()) + undefined := `# HELP something Undefined metric. +# TYPE something undefined +something{instance="rough-snowflake-web"} 10.0 +# EOF` + + allMetrics := strings.Join([]string{gauge, counter, info, stateset, summary, histogram, gaugeHistogram, unknown, undefined}, "\n") + b := []byte(allMetrics) + result, err := ParseMetricFamilies(b, OpenMetricsType, time.Now()) if err != nil { - t.Fatal("ParseMetricFamilies returned an error.") + t.Fatalf("ParseMetricFamilies for content type %s returned an error.", ContentTypeTextFormat) } - assert.Equal(t, expected, result) + assert.ElementsMatch(t, expected, result) } diff --git a/metricbeat/helper/prometheus/textparse.go b/metricbeat/helper/prometheus/textparse.go index 8f8fb11d90a..014016df434 100644 --- a/metricbeat/helper/prometheus/textparse.go +++ b/metricbeat/helper/prometheus/textparse.go @@ -318,12 +318,13 @@ func (m *MetricFamily) GetMetric() []*OpenMetric { } const ( - suffixTotal = "_total" - suffixGCount = "_gcount" - suffixGSum = "_gsum" - suffixCount = "_count" - suffixSum = "_sum" - suffixBucket = "_bucket" + suffixTotal = "_total" + suffixGCount = "_gcount" + suffixGSum = "_gsum" + suffixCount = "_count" + suffixSum = "_sum" + suffixBucket = "_bucket" + suffixCreated = "_created" ) // Counters have _total suffix @@ -351,7 +352,7 @@ func isBucket(name string) bool { return strings.HasSuffix(name, suffixBucket) } -func summaryMetricName(name string, s float64, qv string, lbls string, t *int64, summariesByName map[string]map[string]*OpenMetric) (string, *OpenMetric) { +func summaryMetricName(name string, s float64, qv string, lbls string, summariesByName map[string]map[string]*OpenMetric) (string, *OpenMetric) { var summary = &Summary{} var quantile = []*Quantile{} var quant = &Quantile{} @@ -392,7 +393,6 @@ func summaryMetricName(name string, s float64, qv string, lbls string, t *int64, } else if quant.Quantile != nil { metric.Summary.Quantile = append(metric.Summary.Quantile, quant) } - return name, metric } @@ -499,8 +499,6 @@ func ParseMetricFamilies(b []byte, contentType string, ts time.Time) ([]*MetricF fam = &MetricFamily{Name: &s, Type: t} metricFamiliesByName[s] = fam } else { - // In case the metric family already exists, we need to make sure the type is correctly defined - // instead of being `unknown` like it was initialized for the other entry types (help and unit). fam.Type = t } mt = t @@ -511,10 +509,11 @@ func ParseMetricFamilies(b []byte, contentType string, ts time.Time) ([]*MetricF h := string(t) _, ok = metricFamiliesByName[s] if !ok { - fam = &MetricFamily{Name: &s, Help: &h, Type: textparse.MetricTypeUnknown} + fam = &MetricFamily{Name: &s, Help: &h} metricFamiliesByName[s] = fam + } else { + fam.Help = &h } - fam.Help = &h continue case textparse.EntryUnit: buf, t := parser.Unit() @@ -522,10 +521,11 @@ func ParseMetricFamilies(b []byte, contentType string, ts time.Time) ([]*MetricF u := string(t) _, ok = metricFamiliesByName[s] if !ok { - fam = &MetricFamily{Name: &s, Unit: &u, Type: textparse.MetricTypeUnknown} + fam = &MetricFamily{Name: &s, Unit: &u} metricFamiliesByName[string(buf)] = fam + } else { + fam.Unit = &u } - fam.Unit = &u continue case textparse.EntryComment: continue @@ -586,8 +586,13 @@ func ParseMetricFamilies(b []byte, contentType string, ts time.Time) ([]*MetricF var counter = &Counter{Value: &v} mn := lset.Get(labels.MetricName) metric = &OpenMetric{Name: &mn, Counter: counter, Label: labelPairs} - if isTotal(metricName) && contentType == OpenMetricsType { // Remove suffix _total, get lookup metricname - lookupMetricName = strings.TrimSuffix(metricName, suffixTotal) + if contentType == OpenMetricsType { + // Remove the two possible suffixes, _created and _total + if isTotal(metricName) { + lookupMetricName = strings.TrimSuffix(metricName, suffixTotal) + } else { + lookupMetricName = strings.TrimSuffix(metricName, suffixCreated) + } } else { lookupMetricName = metricName } @@ -601,7 +606,7 @@ func ParseMetricFamilies(b []byte, contentType string, ts time.Time) ([]*MetricF metric = &OpenMetric{Name: &metricName, Info: info, Label: labelPairs} lookupMetricName = metricName case textparse.MetricTypeSummary: - lookupMetricName, metric = summaryMetricName(metricName, v, qv, lbls.String(), &t, summariesByName) + lookupMetricName, metric = summaryMetricName(metricName, v, qv, lbls.String(), summariesByName) metric.Label = labelPairs if !isSum(metricName) { continue @@ -653,8 +658,6 @@ func ParseMetricFamilies(b []byte, contentType string, ts time.Time) ([]*MetricF metricFamiliesByName[lookupMetricName] = fam } - fam.Name = &metricName - if hasExemplar := parser.Exemplar(&e); hasExemplar && mt != textparse.MetricTypeHistogram && metric != nil { if !e.HasTs { e.Ts = t