diff --git a/.chloggen/drop-resource-attribute-from-span.yaml b/.chloggen/drop-resource-attribute-from-span.yaml new file mode 100644 index 0000000000000..fb792ffedbc81 --- /dev/null +++ b/.chloggen/drop-resource-attribute-from-span.yaml @@ -0,0 +1,34 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: breaking + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: spanmetricsconnector + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Exclude all resource attributes in spanmetrics + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [42103] + +# (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: | + This change aligns with the ServiceGraph implementation and may introduce a breaking change: + + - Users utilizing Prometheus remote write will not experience a breaking change. + - Users using OTLP/HTTP may encounter a breaking change. + + The change is currently guarded by the feature gate `connector.spanmetrics.excludeResourceMetrics` and is disabled by default. + It will be enabled by default in the next release. + +# 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: [user] diff --git a/connector/spanmetricsconnector/README.md b/connector/spanmetricsconnector/README.md index 2da51fcbf12e8..e215599f6ba4f 100644 --- a/connector/spanmetricsconnector/README.md +++ b/connector/spanmetricsconnector/README.md @@ -136,7 +136,8 @@ The following settings can be optionally configured: - `events`: Use to configure the events metric. - `enabled`: (default: `false`): enabling will add the events metric. - `dimensions`: (mandatory if `enabled`) the list of the span's event attributes to add as dimensions to the `traces.span.metrics.events` metric, which will be included _on top of_ the common and configured `dimensions` for span attributes and resource attributes. -- `resource_metrics_key_attributes`: Filter the resource attributes used to produce the resource metrics key map hash. Use this in case changing resource attributes (e.g. process id) are breaking counter metrics. +- `resource_metrics_key_attributes`: Filter the resource attributes used to produce the resource metrics key map hash(It's only used to build the hash key, not copy the attributes to metrics resource attributes). + Use this in case changing resource attributes (e.g. process id) are breaking counter metrics. - `aggregation_cardinality_limit` (default: `0`): Defines the maximum number of unique combinations of dimensions that will be tracked for metrics aggregation. When the limit is reached, additional unique combinations will be dropped but registered under a new entry with `otel.metric.overflow="true"`. A value of `0` means no limit is applied. The feature gate `connector.spanmetrics.legacyMetricNames` (disabled by default) controls the connector to use legacy metric names. @@ -289,7 +290,7 @@ More context is available in [GitHub issue #21101](https://github.com/open-telem ### About `resource_metrics_key_attributes` -The `resource_metrics_key_attributes` setting controls which resource attributes are included in the metric stream identity. These attributes are used to build the key map that determines how metrics are grouped. +The `resource_metrics_key_attributes` setting are used to build the key map that determines how metrics are grouped. If this field is left empty, the connector will use **all** available attributes to compute the resource metric hash. diff --git a/connector/spanmetricsconnector/connector.go b/connector/spanmetricsconnector/connector.go index 0f6fa6ef564ab..90d03a6cad5ae 100644 --- a/connector/spanmetricsconnector/connector.go +++ b/connector/spanmetricsconnector/connector.go @@ -278,7 +278,9 @@ func (p *connectorImp) buildMetrics() pmetric.Metrics { p.resourceMetrics.ForEach(func(_ resourceKey, rawMetrics *resourceMetrics) { rm := m.ResourceMetrics().AppendEmpty() - rawMetrics.attributes.CopyTo(rm.Resource().Attributes()) + if !excludeResourceMetrics.IsEnabled() { + rawMetrics.attributes.CopyTo(rm.Resource().Attributes()) + } sm := rm.ScopeMetrics().AppendEmpty() sm.Scope().SetName("spanmetricsconnector") diff --git a/connector/spanmetricsconnector/connector_test.go b/connector/spanmetricsconnector/connector_test.go index 9aff73a5bf698..6b703460bb08a 100644 --- a/connector/spanmetricsconnector/connector_test.go +++ b/connector/spanmetricsconnector/connector_test.go @@ -162,16 +162,8 @@ func verifyConsumeMetricsInput(tb testing.TB, input pmetric.Metrics, expectedTem for i := 0; i < input.ResourceMetrics().Len(); i++ { rm := input.ResourceMetrics().At(i) - var numDataPoints int - val, ok := rm.Resource().Attributes().Get(serviceNameKey) - require.True(tb, ok) - serviceName := val.AsString() - switch serviceName { - case "service-a": - numDataPoints = 2 - case "service-b": - numDataPoints = 1 - } + // validate no Resource + assert.Empty(tb, rm.Resource().Attributes().Len()) ilm := rm.ScopeMetrics() require.Equal(tb, 1, ilm.Len()) @@ -188,6 +180,16 @@ func verifyConsumeMetricsInput(tb testing.TB, input pmetric.Metrics, expectedTem seenMetricIDs := make(map[metricID]bool) callsDps := metric.Sum().DataPoints() + var numDataPoints int + val, _ := callsDps.At(0).Attributes().Get(serviceNameKey) + serviceName := val.AsString() + switch serviceName { + case "service-a": + numDataPoints = 2 + case "service-b": + numDataPoints = 1 + } + require.Equal(tb, numDataPoints, callsDps.Len()) for dpi := 0; dpi < numDataPoints; dpi++ { dp := callsDps.At(dpi) @@ -765,6 +767,12 @@ func TestConsumeMetricsErrors(t *testing.T) { } func TestConsumeTraces(t *testing.T) { + // enable it + require.NoError(t, featuregate.GlobalRegistry().Set(excludeResourceMetrics.ID(), true)) + defer func() { + require.NoError(t, featuregate.GlobalRegistry().Set(legacyMetricNamesFeatureGate.ID(), false)) + }() + t.Parallel() testcases := []struct { @@ -1680,6 +1688,12 @@ func assertDataPointsHaveExactlyOneExemplarForTrace(t *testing.T, metrics pmetri } func TestTimestampsForUninterruptedStream(t *testing.T) { + // enable it + require.NoError(t, featuregate.GlobalRegistry().Set(excludeResourceMetrics.ID(), true)) + defer func() { + require.NoError(t, featuregate.GlobalRegistry().Set(legacyMetricNamesFeatureGate.ID(), false)) + }() + tests := []struct { temporality string verifyTimestamps func(startTime1, timestamp1, startTime2, timestamp2 pcommon.Timestamp) @@ -1757,7 +1771,7 @@ func verifyAndCollectCommonTimestamps(t *testing.T, m pmetric.Metrics) (start, t for i := 0; i < m.ResourceMetrics().Len(); i++ { rm := m.ResourceMetrics().At(i) - serviceName, _ := rm.Resource().Attributes().Get("service.name") + serviceName, _ := rm.ScopeMetrics().At(0).Metrics().At(0).Sum().DataPoints().At(0).Attributes().Get("service.name") if serviceName.Str() == "unrelated-service" { continue } diff --git a/connector/spanmetricsconnector/factory.go b/connector/spanmetricsconnector/factory.go index f46d68d88e6de..b5d9252039506 100644 --- a/connector/spanmetricsconnector/factory.go +++ b/connector/spanmetricsconnector/factory.go @@ -26,12 +26,14 @@ const ( legacyMetricNamesFeatureGateID = "connector.spanmetrics.legacyMetricNames" includeCollectorInstanceIDFeatureGateID = "connector.spanmetrics.includeCollectorInstanceID" useSecondAsDefaultMetricsUnitFeatureGateID = "connector.spanmetrics.useSecondAsDefaultMetricsUnit" + excludeResourceMetricsFeatureGate = "connector.spanmetrics.excludeResourceMetrics" ) var ( legacyMetricNamesFeatureGate *featuregate.Gate includeCollectorInstanceID *featuregate.Gate useSecondAsDefaultMetricsUnit *featuregate.Gate + excludeResourceMetrics *featuregate.Gate ) func init() { @@ -52,6 +54,12 @@ func init() { useSecondAsDefaultMetricsUnitFeatureGateID, featuregate.StageAlpha, featuregate.WithRegisterDescription("When enabled, connector use second as default unit for duration metrics."), + featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/42462"), + ) + excludeResourceMetrics = featuregate.GlobalRegistry().MustRegister( + excludeResourceMetricsFeatureGate, + featuregate.StageAlpha, + featuregate.WithRegisterDescription("When enabled, connector will exclude all resource attributes."), featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/42103"), ) }