diff --git a/connector/servicegraphconnector/connector_test.go b/connector/servicegraphconnector/connector_test.go index e57c3781836c..2eb9749207c9 100644 --- a/connector/servicegraphconnector/connector_test.go +++ b/connector/servicegraphconnector/connector_test.go @@ -125,6 +125,7 @@ func TestConnectorConsume(t *testing.T) { pmetrictest.IgnoreMetricDataPointsOrder(), pmetrictest.IgnoreStartTimestamp(), pmetrictest.IgnoreTimestamp(), + pmetrictest.IgnoreDatapointAttributesOrder(), ) require.NoError(t, err) }) diff --git a/pkg/pdatatest/pmetrictest/options.go b/pkg/pdatatest/pmetrictest/options.go index 14a3326a28a5..89b7af8fc888 100644 --- a/pkg/pdatatest/pmetrictest/options.go +++ b/pkg/pdatatest/pmetrictest/options.go @@ -7,6 +7,7 @@ import ( "bytes" "fmt" "regexp" + "sort" "time" "go.opentelemetry.io/collector/pdata/pcommon" @@ -179,6 +180,74 @@ func IgnoreMetricAttributeValue(attributeName string, metricNames ...string) Com }) } +// IgnoreMetricAttributeValue is a CompareMetricsOption that clears value of the metric attribute. +func IgnoreDatapointAttributesOrder() CompareMetricsOption { + return compareMetricsOptionFunc(func(expected, actual pmetric.Metrics) { + orderDatapointAttributes(expected) + orderDatapointAttributes(actual) + }) +} + +func orderDatapointAttributes(metrics pmetric.Metrics) { + rms := metrics.ResourceMetrics() + for i := 0; i < rms.Len(); i++ { + ilms := rms.At(i).ScopeMetrics() + for j := 0; j < ilms.Len(); j++ { + msl := ilms.At(j).Metrics() + for g := 0; g < msl.Len(); g++ { + msl.At(g) + switch msl.At(g).Type() { + case pmetric.MetricTypeGauge: + for k := 0; k < msl.At(g).Gauge().DataPoints().Len(); k++ { + rawOrdered := orderMapByKey(msl.At(g).Gauge().DataPoints().At(k).Attributes().AsRaw()) + _ = msl.At(g).Gauge().DataPoints().At(k).Attributes().FromRaw(rawOrdered) + } + case pmetric.MetricTypeSum: + for k := 0; k < msl.At(g).Sum().DataPoints().Len(); k++ { + rawOrdered := orderMapByKey(msl.At(g).Sum().DataPoints().At(k).Attributes().AsRaw()) + _ = msl.At(g).Sum().DataPoints().At(k).Attributes().FromRaw(rawOrdered) + } + case pmetric.MetricTypeHistogram: + for k := 0; k < msl.At(g).Histogram().DataPoints().Len(); k++ { + rawOrdered := orderMapByKey(msl.At(g).Histogram().DataPoints().At(k).Attributes().AsRaw()) + _ = msl.At(g).Histogram().DataPoints().At(k).Attributes().FromRaw(rawOrdered) + } + case pmetric.MetricTypeExponentialHistogram: + for k := 0; k < msl.At(g).ExponentialHistogram().DataPoints().Len(); k++ { + rawOrdered := orderMapByKey(msl.At(g).ExponentialHistogram().DataPoints().At(k).Attributes().AsRaw()) + _ = msl.At(g).ExponentialHistogram().DataPoints().At(k).Attributes().FromRaw(rawOrdered) + } + case pmetric.MetricTypeSummary: + for k := 0; k < msl.At(g).Summary().DataPoints().Len(); k++ { + rawOrdered := orderMapByKey(msl.At(g).Summary().DataPoints().At(k).Attributes().AsRaw()) + _ = msl.At(g).Summary().DataPoints().At(k).Attributes().FromRaw(rawOrdered) + } + case pmetric.MetricTypeEmpty: + } + } + } + } +} + +func orderMapByKey(input map[string]any) map[string]any { + // Create a slice to hold the keys + keys := make([]string, 0, len(input)) + for k := range input { + keys = append(keys, k) + } + + // Sort the keys + sort.Strings(keys) + + // Create a new map to hold the sorted key-value pairs + orderedMap := make(map[string]any, len(input)) + for _, k := range keys { + orderedMap[k] = input[k] + } + + return orderedMap +} + func maskMetricAttributeValue(metrics pmetric.Metrics, attributeName string, metricNames []string) { rms := metrics.ResourceMetrics() for i := 0; i < rms.Len(); i++ {