From 1b65cba406722323abe2dfbcc79ad9aebe23355d Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Thu, 8 Aug 2024 09:37:21 -0700 Subject: [PATCH 1/2] [pmetrictest] Add comparison options for histograms --- .chloggen/add_compare_for_histograms.yaml | 27 +++ pkg/pdatatest/pmetrictest/options.go | 212 ++++++++++++++++++---- 2 files changed, 201 insertions(+), 38 deletions(-) create mode 100644 .chloggen/add_compare_for_histograms.yaml diff --git a/.chloggen/add_compare_for_histograms.yaml b/.chloggen/add_compare_for_histograms.yaml new file mode 100644 index 000000000000..55285ebbf153 --- /dev/null +++ b/.chloggen/add_compare_for_histograms.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: pmetrictest + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add support for histogram comparison options + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [34521] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [] diff --git a/pkg/pdatatest/pmetrictest/options.go b/pkg/pdatatest/pmetrictest/options.go index 9887cbaed916..da983e585833 100644 --- a/pkg/pdatatest/pmetrictest/options.go +++ b/pkg/pdatatest/pmetrictest/options.go @@ -54,7 +54,15 @@ func maskMetricSliceValues(metrics pmetric.MetricSlice, metricNames ...string) { } for i := 0; i < metrics.Len(); i++ { if len(metricNames) == 0 || metricNameSet[metrics.At(i).Name()] { - maskDataPointSliceValues(getDataPointSlice(metrics.At(i))) + switch metrics.At(i).Type() { + case pmetric.MetricTypeEmpty, pmetric.MetricTypeSum, pmetric.MetricTypeGauge: + maskDataPointSliceValues(getDataPointSlice(metrics.At(i))) + case pmetric.MetricTypeHistogram: + maskHistogramDataPointSliceValues(metrics.At(i).Histogram().DataPoints()) + default: + panic(fmt.Sprintf("data type not supported: %s", metrics.At(i).Type())) + } + } } } @@ -67,7 +75,9 @@ func getDataPointSlice(metric pmetric.Metric) pmetric.NumberDataPointSlice { dataPointSlice = metric.Gauge().DataPoints() case pmetric.MetricTypeSum: dataPointSlice = metric.Sum().DataPoints() - case pmetric.MetricTypeEmpty, pmetric.MetricTypeHistogram, pmetric.MetricTypeExponentialHistogram, pmetric.MetricTypeSummary: + case pmetric.MetricTypeEmpty: + dataPointSlice = pmetric.NewNumberDataPointSlice() + case pmetric.MetricTypeHistogram, pmetric.MetricTypeExponentialHistogram, pmetric.MetricTypeSummary: fallthrough default: panic(fmt.Sprintf("data type not supported: %s", metric.Type())) @@ -84,6 +94,22 @@ func maskDataPointSliceValues(dataPoints pmetric.NumberDataPointSlice) { } } +// maskHistogramDataPointSliceValues sets all data point values to zero. +func maskHistogramDataPointSliceValues(dataPoints pmetric.HistogramDataPointSlice) { + for i := 0; i < dataPoints.Len(); i++ { + dataPoint := dataPoints.At(i) + dataPoint.SetCount(0) + dataPoint.SetSum(0) + dataPoint.SetMin(0) + dataPoint.SetMax(0) + dataPoint.BucketCounts().FromRaw([]uint64{}) + dataPoint.Exemplars().RemoveIf(func(pmetric.Exemplar) bool { + return true + }) + dataPoint.ExplicitBounds().FromRaw([]float64{}) + } +} + // IgnoreTimestamp is a CompareMetricsOption that clears Timestamp fields on all the data points. func IgnoreTimestamp() CompareMetricsOption { return compareMetricsOptionFunc(func(expected, actual pmetric.Metrics) { @@ -270,21 +296,52 @@ func maskMetricSliceAttributeValues(metrics pmetric.MetricSlice, attributeName s for i := 0; i < metrics.Len(); i++ { if len(metricNames) == 0 || metricNameSet[metrics.At(i).Name()] { - dps := getDataPointSlice(metrics.At(i)) - maskDataPointSliceAttributeValues(dps, attributeName) - - // If attribute values are ignored, some data points may become - // indistinguishable from each other, but sorting by value allows - // for a reasonably thorough comparison and a deterministic outcome. - dps.Sort(func(a, b pmetric.NumberDataPoint) bool { - if a.IntValue() < b.IntValue() { - return true - } - if a.DoubleValue() < b.DoubleValue() { - return true - } - return false - }) + switch metrics.At(i).Type() { + case pmetric.MetricTypeHistogram: + dps := metrics.At(i).Histogram().DataPoints() + maskHistogramSliceAttributeValues(dps, attributeName) + + // If attribute values are ignored, some data points may become + // indistinguishable from each other, but sorting by value allows + // for a reasonably thorough comparison and a deterministic outcome. + dps.Sort(func(a, b pmetric.HistogramDataPoint) bool { + if a.Sum() < b.Sum() { + return true + } + if a.Min() < b.Min() { + return true + } + if a.Max() < b.Max() { + return true + } + if a.Count() < b.Count() { + return true + } + if a.BucketCounts().Len() < b.BucketCounts().Len() { + return true + } + if a.ExplicitBounds().Len() < b.ExplicitBounds().Len() { + return true + } + return false + }) + default: + dps := getDataPointSlice(metrics.At(i)) + maskDataPointSliceAttributeValues(dps, attributeName) + + // If attribute values are ignored, some data points may become + // indistinguishable from each other, but sorting by value allows + // for a reasonably thorough comparison and a deterministic outcome. + dps.Sort(func(a, b pmetric.NumberDataPoint) bool { + if a.IntValue() < b.IntValue() { + return true + } + if a.DoubleValue() < b.DoubleValue() { + return true + } + return false + }) + } } } } @@ -312,6 +369,29 @@ func maskDataPointSliceAttributeValues(dataPoints pmetric.NumberDataPointSlice, } } +// maskHistogramSliceAttributeValues sets the value of the specified attribute to +// the zero value associated with the attribute data type. +func maskHistogramSliceAttributeValues(dataPoints pmetric.HistogramDataPointSlice, attributeName string) { + for i := 0; i < dataPoints.Len(); i++ { + attributes := dataPoints.At(i).Attributes() + attribute, ok := attributes.Get(attributeName) + if ok { + switch attribute.Type() { + case pcommon.ValueTypeStr: + attribute.SetStr("") + case pcommon.ValueTypeBool: + attribute.SetBool(false) + case pcommon.ValueTypeInt: + attribute.SetInt(0) + case pcommon.ValueTypeEmpty, pcommon.ValueTypeDouble, pcommon.ValueTypeMap, pcommon.ValueTypeSlice, pcommon.ValueTypeBytes: + fallthrough + default: + panic(fmt.Sprintf("data type not supported: %s", attribute.Type())) + } + } + } +} + // MatchMetricAttributeValue is a CompareMetricsOption that transforms a metric attribute value based on a regular expression. func MatchMetricAttributeValue(attributeName string, pattern string, metricNames ...string) CompareMetricsOption { re := regexp.MustCompile(pattern) @@ -339,21 +419,53 @@ func matchMetricSliceAttributeValues(metrics pmetric.MetricSlice, attributeName for i := 0; i < metrics.Len(); i++ { if len(metricNames) == 0 || metricNameSet[metrics.At(i).Name()] { - dps := getDataPointSlice(metrics.At(i)) - matchDataPointSliceAttributeValues(dps, attributeName, re) - - // If attribute values are ignored, some data points may become - // indistinguishable from each other, but sorting by value allows - // for a reasonably thorough comparison and a deterministic outcome. - dps.Sort(func(a, b pmetric.NumberDataPoint) bool { - if a.IntValue() < b.IntValue() { - return true - } - if a.DoubleValue() < b.DoubleValue() { - return true - } - return false - }) + switch metrics.At(i).Type() { + case pmetric.MetricTypeHistogram: + dps := metrics.At(i).Histogram().DataPoints() + matchHistogramDataPointSliceAttributeValues(dps, attributeName, re) + + // If attribute values are ignored, some data points may become + // indistinguishable from each other, but sorting by value allows + // for a reasonably thorough comparison and a deterministic outcome. + dps.Sort(func(a, b pmetric.HistogramDataPoint) bool { + if a.Sum() < b.Sum() { + return true + } + if a.Min() < b.Min() { + return true + } + if a.Max() < b.Max() { + return true + } + if a.Count() < b.Count() { + return true + } + if a.BucketCounts().Len() < b.BucketCounts().Len() { + return true + } + if a.ExplicitBounds().Len() < b.ExplicitBounds().Len() { + return true + } + return false + }) + default: + dps := getDataPointSlice(metrics.At(i)) + matchDataPointSliceAttributeValues(dps, attributeName, re) + + // If attribute values are ignored, some data points may become + // indistinguishable from each other, but sorting by value allows + // for a reasonably thorough comparison and a deterministic outcome. + dps.Sort(func(a, b pmetric.NumberDataPoint) bool { + if a.IntValue() < b.IntValue() { + return true + } + if a.DoubleValue() < b.DoubleValue() { + return true + } + return false + }) + } + } } } @@ -371,6 +483,19 @@ func matchDataPointSliceAttributeValues(dataPoints pmetric.NumberDataPointSlice, } } +func matchHistogramDataPointSliceAttributeValues(dataPoints pmetric.HistogramDataPointSlice, attributeName string, re *regexp.Regexp) { + for i := 0; i < dataPoints.Len(); i++ { + attributes := dataPoints.At(i).Attributes() + attribute, ok := attributes.Get(attributeName) + if ok { + results := re.FindStringSubmatch(attribute.Str()) + if len(results) > 0 { + attribute.SetStr(results[0]) + } + } + } +} + // MatchResourceAttributeValue is a CompareMetricsOption that transforms a resource attribute value based on a regular expression. func MatchResourceAttributeValue(attributeName string, pattern string) CompareMetricsOption { re := regexp.MustCompile(pattern) @@ -438,12 +563,23 @@ func maskSubsequentDataPoints(metrics pmetric.Metrics, metricNames []string) { ms := sms.At(j).Metrics() for k := 0; k < ms.Len(); k++ { if len(metricNames) == 0 || metricNameSet[ms.At(k).Name()] { - dps := getDataPointSlice(ms.At(k)) - n := 0 - dps.RemoveIf(func(pmetric.NumberDataPoint) bool { - n++ - return n > 1 - }) + switch ms.At(k).Type() { + case pmetric.MetricTypeHistogram: + dps := ms.At(k).Histogram().DataPoints() + n := 0 + dps.RemoveIf(func(pmetric.HistogramDataPoint) bool { + n++ + return n > 1 + }) + default: + dps := getDataPointSlice(ms.At(k)) + n := 0 + dps.RemoveIf(func(pmetric.NumberDataPoint) bool { + n++ + return n > 1 + }) + } + } } } From 8ff8639b8a6f263cc2c23d9af4580e1ade1faf0c Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Thu, 8 Aug 2024 12:00:22 -0700 Subject: [PATCH 2/2] Update .chloggen/add_compare_for_histograms.yaml Co-authored-by: Dmitrii Anoshin --- .chloggen/add_compare_for_histograms.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.chloggen/add_compare_for_histograms.yaml b/.chloggen/add_compare_for_histograms.yaml index 55285ebbf153..5e429c193419 100644 --- a/.chloggen/add_compare_for_histograms.yaml +++ b/.chloggen/add_compare_for_histograms.yaml @@ -24,4 +24,4 @@ subtext: # Include 'user' if the change is relevant to end users. # Include 'api' if there is a change to a library API. # Default: '[user]' -change_logs: [] +change_logs: [api]