Skip to content

Commit

Permalink
[pmetrictest] Add comparison options for histograms (open-telemetry#3…
Browse files Browse the repository at this point in the history
…4521)

**Description:**
pmetrictest panics on some histogram data points when comparing them.
This was intended as explicit lack of support.

This PR adds additional code to handle histogram use cases.

---------

Co-authored-by: Dmitrii Anoshin <[email protected]>
  • Loading branch information
2 people authored and f7o committed Sep 12, 2024
1 parent 1cacc04 commit 696c088
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 38 deletions.
27 changes: 27 additions & 0 deletions .chloggen/add_compare_for_histograms.yaml
Original file line number Diff line number Diff line change
@@ -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: [api]
212 changes: 174 additions & 38 deletions pkg/pdatatest/pmetrictest/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
}

}
}
}
Expand All @@ -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()))
Expand All @@ -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) {
Expand Down Expand Up @@ -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
})
}
}
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
})
}

}
}
}
Expand All @@ -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)
Expand Down Expand Up @@ -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
})
}

}
}
}
Expand Down

0 comments on commit 696c088

Please sign in to comment.