From 03140b80678371f7f0bfe7d10da535bf772f4c4b Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Tue, 23 Apr 2024 14:07:16 +0000 Subject: [PATCH] deduplicate target_info and scope_info --- .../googlemanagedprometheus/extra_metrics.go | 75 ++++++++++++++--- .../extra_metrics_test.go | 82 ++++++++++++++++++- 2 files changed, 145 insertions(+), 12 deletions(-) diff --git a/exporter/collector/googlemanagedprometheus/extra_metrics.go b/exporter/collector/googlemanagedprometheus/extra_metrics.go index c68478e72..59435ad5b 100644 --- a/exporter/collector/googlemanagedprometheus/extra_metrics.go +++ b/exporter/collector/googlemanagedprometheus/extra_metrics.go @@ -105,6 +105,12 @@ func addUntypedMetrics(m pmetric.Metrics) { } } +// resourceID identifies a resource. We only send one target info for each +// resourceID. +type resourceID struct { + serviceName, serviceInstanceID, serviceNamespace string +} + // addTargetInfoMetric inserts target_info for each resource. // First, it extracts the target_info metric from each ResourceMetric associated with the input pmetric.Metrics // and inserts it into a new ScopeMetric for that resource, as specified in @@ -113,10 +119,17 @@ func (c Config) addTargetInfoMetric(m pmetric.Metrics) { if !c.ExtraMetricsConfig.EnableTargetInfo { return } + ids := make(map[resourceID]bool) rms := m.ResourceMetrics() // loop over input (original) resource metrics for i := 0; i < rms.Len(); i++ { rm := rms.At(i) + getResourceAttr := func(attr string) string { + if v, ok := rm.Resource().Attributes().Get(attr); ok { + return v.AsString() + } + return "" + } // Keep track of the most recent time in this resource's metrics // Use that time for the timestamp of the new metric @@ -170,6 +183,17 @@ func (c Config) addTargetInfoMetric(m pmetric.Metrics) { } } + id := resourceID{ + serviceName: getResourceAttr(semconv.AttributeServiceName), + serviceNamespace: getResourceAttr(semconv.AttributeServiceNamespace), + serviceInstanceID: getResourceAttr(semconv.AttributeServiceInstanceID), + } + if ids[id] { + // We've already added a resource with the same ID before, so skip this one. + continue + } + ids[id] = true + // create the target_info metric as a Gauge with value 1 targetInfoMetric := rm.ScopeMetrics().AppendEmpty().Metrics().AppendEmpty() targetInfoMetric.SetName("target_info") @@ -201,6 +225,12 @@ func (c Config) addTargetInfoMetric(m pmetric.Metrics) { } } +// scopeID identifies a scope. We only send one scope info for each scopeID within a unique resource. +type scopeID struct { + resource resourceID + name, version string +} + // addScopeInfoMetric adds the otel_scope_info metric to a Metrics slice as specified in // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.16.0/specification/compatibility/prometheus_and_openmetrics.md#instrumentation-scope-1 // It also updates all other metrics with the corresponding scope_name and scope_version attributes, if they are present. @@ -208,9 +238,17 @@ func (c Config) addScopeInfoMetric(m pmetric.Metrics) { if !c.ExtraMetricsConfig.EnableScopeInfo { return } + ids := make(map[scopeID]bool) rms := m.ResourceMetrics() for i := 0; i < rms.Len(); i++ { - sms := rms.At(i).ScopeMetrics() + rm := rms.At(i) + getResourceAttr := func(attr string) string { + if v, ok := rm.Resource().Attributes().Get(attr); ok { + return v.AsString() + } + return "" + } + sms := rm.ScopeMetrics() for j := 0; j < sms.Len(); j++ { sm := sms.At(j) // If not present, skip this scope @@ -218,16 +256,6 @@ func (c Config) addScopeInfoMetric(m pmetric.Metrics) { continue } - // Add otel_scope_info metric - scopeInfoMetric := sm.Metrics().AppendEmpty() - scopeInfoMetric.SetName("otel_scope_info") - dataPoint := scopeInfoMetric.SetEmptyGauge().DataPoints().AppendEmpty() - dataPoint.SetIntValue(1) - sm.Scope().Attributes().Range(func(k string, v pcommon.Value) bool { - dataPoint.Attributes().PutStr(k, v.AsString()) - return true - }) - // Keep track of the most recent time in this scope's metrics // Use that time for the timestamp of the new metric latestTime := time.Time{} @@ -291,7 +319,32 @@ func (c Config) addScopeInfoMetric(m pmetric.Metrics) { } } } + id := scopeID{ + resource: resourceID{ + serviceName: getResourceAttr(semconv.AttributeServiceName), + serviceNamespace: getResourceAttr(semconv.AttributeServiceNamespace), + serviceInstanceID: getResourceAttr(semconv.AttributeServiceInstanceID), + }, + name: sm.Scope().Name(), + version: sm.Scope().Version(), + } + if ids[id] { + // We've already added a scope with the same ID before, so skip this one. + continue + } + ids[id] = true + // Add otel_scope_info metric + scopeInfoMetric := sm.Metrics().AppendEmpty() + scopeInfoMetric.SetName("otel_scope_info") + dataPoint := scopeInfoMetric.SetEmptyGauge().DataPoints().AppendEmpty() + dataPoint.SetIntValue(1) + sm.Scope().Attributes().Range(func(k string, v pcommon.Value) bool { + dataPoint.Attributes().PutStr(k, v.AsString()) + return true + }) + dataPoint.Attributes().PutStr("otel_scope_name", sm.Scope().Name()) + dataPoint.Attributes().PutStr("otel_scope_version", sm.Scope().Version()) dataPoint.SetTimestamp(pcommon.NewTimestampFromTime(latestTime)) } } diff --git a/exporter/collector/googlemanagedprometheus/extra_metrics_test.go b/exporter/collector/googlemanagedprometheus/extra_metrics_test.go index ac4a89786..00c1c3e4e 100644 --- a/exporter/collector/googlemanagedprometheus/extra_metrics_test.go +++ b/exporter/collector/googlemanagedprometheus/extra_metrics_test.go @@ -22,13 +22,18 @@ import ( "go.opentelemetry.io/collector/featuregate" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" + semconv "go.opentelemetry.io/collector/semconv/v1.18.0" ) func testMetric(timestamp time.Time) pmetric.Metrics { - metrics := pmetric.NewMetrics() + return appendMetric(pmetric.NewMetrics(), timestamp) +} + +func appendMetric(metrics pmetric.Metrics, timestamp time.Time) pmetric.Metrics { rm := metrics.ResourceMetrics().AppendEmpty() // foo-label should be copied to target_info, not locationLabel + rm.Resource().Attributes().PutStr(semconv.AttributeServiceName, "my-service") rm.Resource().Attributes().PutStr(locationLabel, "us-east") rm.Resource().Attributes().PutStr("foo-label", "bar") @@ -71,6 +76,30 @@ func TestAddExtraMetrics(t *testing.T) { return metrics }(), }, + { + name: "deduplicate target info from multiple resource metric", + config: Config{ExtraMetricsConfig: ExtraMetricsConfig{EnableTargetInfo: true}}, + input: func() pmetric.Metrics { + metrics := appendMetric(testMetric(timestamp), timestamp) + // Add a non-identifying attribute to the second resource, which should be ignored. + metrics.ResourceMetrics().At(1).Resource().Attributes().PutStr("ignored-label", "ignored-value") + return metrics + }(), + expected: func() pmetric.ResourceMetricsSlice { + metrics := appendMetric(testMetric(timestamp), timestamp).ResourceMetrics() + // Make sure it matches the input + metrics.At(1).Resource().Attributes().PutStr("ignored-label", "ignored-value") + + // Insert a new, empty ScopeMetricsSlice for this resource that will hold target_info + sm := metrics.At(0).ScopeMetrics().AppendEmpty() + metric := sm.Metrics().AppendEmpty() + metric.SetName("target_info") + metric.SetEmptyGauge().DataPoints().AppendEmpty().SetIntValue(1) + metric.Gauge().DataPoints().At(0).Attributes().PutStr("foo-label", "bar") + metric.Gauge().DataPoints().At(0).SetTimestamp(pcommon.NewTimestampFromTime(timestamp)) + return metrics + }(), + }, { name: "add scope info from scope metrics", config: Config{ExtraMetricsConfig: ExtraMetricsConfig{EnableScopeInfo: true}}, @@ -123,6 +152,57 @@ func TestAddExtraMetrics(t *testing.T) { return metrics }(), }, + { + name: "add duplicate scope info with attributes", + config: Config{ExtraMetricsConfig: ExtraMetricsConfig{EnableScopeInfo: true}}, + input: func() pmetric.Metrics { + metrics := appendMetric(testMetric(timestamp), timestamp) + // set different attributes on scopes with the same name + version + metrics.ResourceMetrics().At(0).ScopeMetrics().At(0).Scope().Attributes().PutStr("foo_attribute", "bar") + metrics.ResourceMetrics().At(1).ScopeMetrics().At(0).Scope().Attributes().PutStr("foo_attribute", "not_bar") + + // Add a duplicate scope within the same resource + sm := metrics.ResourceMetrics().At(1).ScopeMetrics().AppendEmpty() + sm.Scope().SetName("myscope") + sm.Scope().SetVersion("v0.0.1") + return metrics + }(), + expected: func() pmetric.ResourceMetricsSlice { + // Make sure it matches the input + metrics := appendMetric(testMetric(timestamp), timestamp).ResourceMetrics() + metrics.At(0).ScopeMetrics().At(0).Scope().Attributes().PutStr("foo_attribute", "bar") + // This scope attribute is on a duplicate scope_info metric, so it does not + metrics.At(1).ScopeMetrics().At(0).Scope().Attributes().PutStr("foo_attribute", "not_bar") + sm := metrics.At(1).ScopeMetrics().AppendEmpty() + sm.Scope().SetName("myscope") + sm.Scope().SetVersion("v0.0.1") + + // Insert the scope_info metric into the existing ScopeMetricsSlice + sm = metrics.At(0).ScopeMetrics().At(0) + scopeInfoMetric := sm.Metrics().AppendEmpty() + scopeInfoMetric.SetName("otel_scope_info") + scopeInfoMetric.SetEmptyGauge().DataPoints().AppendEmpty().SetIntValue(1) + scopeInfoMetric.Gauge().DataPoints().At(0).Attributes().PutStr("foo_attribute", "bar") + + // add otel_scope_* attributes to all metrics in this scope (including otel_scope_info) + for i := 0; i < sm.Metrics().Len(); i++ { + metric := sm.Metrics().At(i) + metric.Gauge().DataPoints().At(0).Attributes().PutStr("otel_scope_name", "myscope") + metric.Gauge().DataPoints().At(0).Attributes().PutStr("otel_scope_version", "v0.0.1") + scopeInfoMetric.Gauge().DataPoints().At(0).SetTimestamp(pcommon.NewTimestampFromTime(timestamp)) + } + + // add otel_scope_* attributes to all metrics in the second scope + sm = metrics.At(1).ScopeMetrics().At(0) + for i := 0; i < sm.Metrics().Len(); i++ { + metric := sm.Metrics().At(i) + metric.Gauge().DataPoints().At(0).Attributes().PutStr("otel_scope_name", "myscope") + metric.Gauge().DataPoints().At(0).Attributes().PutStr("otel_scope_version", "v0.0.1") + scopeInfoMetric.Gauge().DataPoints().At(0).SetTimestamp(pcommon.NewTimestampFromTime(timestamp)) + } + return metrics + }(), + }, { name: "add both scope info and target info", config: Config{ExtraMetricsConfig: ExtraMetricsConfig{