From 280e058b0b4a2a21254ec8526652eef9d21c40e3 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 22 Jun 2026 19:14:03 -0400 Subject: [PATCH 01/10] Wire per-component cardinality limits from Config per Cardinality Limits RFC Co-Authored-By: Claude Sonnet 4.6 --- .../trace/api/config/GeneralConfig.java | 1 + .../trace/common/metrics/AggregateEntry.java | 49 ++++++++---- .../trace/common/metrics/AggregateTable.java | 8 +- .../trace/common/metrics/PeerTagSchema.java | 6 +- .../main/java/datadog/trace/api/Config.java | 11 +++ metadata/supported-configurations.json | 80 +++++++++++++++++++ 6 files changed, 135 insertions(+), 20 deletions(-) diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java index c9963516f1a..79d3861d8ab 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java @@ -79,6 +79,7 @@ public final class GeneralConfig { public static final String TRACER_METRICS_MAX_PENDING = "trace.tracer.metrics.max.pending"; public static final String TRACER_METRICS_IGNORED_RESOURCES = "trace.tracer.metrics.ignored.resources"; + public static final String AZURE_APP_SERVICES = "azure.app.services"; public static final String INTERNAL_EXIT_ON_FAILURE = "trace.internal.exit.on.failure"; diff --git a/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java b/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java index 29ce3d0bea4..7f93fad66a9 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java @@ -1,6 +1,7 @@ package datadog.trace.common.metrics; import datadog.metrics.api.Histogram; +import datadog.trace.api.Config; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.util.Hashtable; import datadog.trace.util.LongHashingUtils; @@ -28,36 +29,54 @@ final class AggregateEntry extends Hashtable.Entry { private static final UTF8BytesString[] EMPTY_TAGS = new UTF8BytesString[0]; - // Sentinel substitution is disabled until per-component config is wired in a follow-up PR. - // Tests that need sentinel mode should pass useBlockedSentinel=true explicitly. - static final boolean LIMITS_ENABLED = false; - - // Per-field cardinality handlers. Limits live on MetricCardinalityLimits -- see that class for - // per-field rationale. + // Per-field cardinality handlers. Limits are tunable via DD_TRACE_STATS_{field}_CARDINALITY_LIMIT + // (e.g. DD_TRACE_STATS_RESOURCE_CARDINALITY_LIMIT). Defaults live on MetricCardinalityLimits. + // Frozen at first class-load from Config. static final PropertyCardinalityHandler RESOURCE_HANDLER = - new PropertyCardinalityHandler("resource", MetricCardinalityLimits.RESOURCE, LIMITS_ENABLED); + new PropertyCardinalityHandler( + "resource", + Config.get().getTraceStatsCardinalityLimit("resource", MetricCardinalityLimits.RESOURCE)); static final PropertyCardinalityHandler SERVICE_HANDLER = - new PropertyCardinalityHandler("service", MetricCardinalityLimits.SERVICE, LIMITS_ENABLED); + new PropertyCardinalityHandler( + "service", + Config.get().getTraceStatsCardinalityLimit("service", MetricCardinalityLimits.SERVICE)); static final PropertyCardinalityHandler OPERATION_HANDLER = new PropertyCardinalityHandler( - "operation", MetricCardinalityLimits.OPERATION, LIMITS_ENABLED); + "operation", + Config.get() + .getTraceStatsCardinalityLimit("operation", MetricCardinalityLimits.OPERATION)); static final PropertyCardinalityHandler SERVICE_SOURCE_HANDLER = new PropertyCardinalityHandler( - "service_source", MetricCardinalityLimits.SERVICE_SOURCE, LIMITS_ENABLED); + "service_source", + Config.get() + .getTraceStatsCardinalityLimit( + "service_source", MetricCardinalityLimits.SERVICE_SOURCE)); static final PropertyCardinalityHandler TYPE_HANDLER = - new PropertyCardinalityHandler("type", MetricCardinalityLimits.TYPE, LIMITS_ENABLED); + new PropertyCardinalityHandler( + "type", + Config.get().getTraceStatsCardinalityLimit("type", MetricCardinalityLimits.TYPE)); static final PropertyCardinalityHandler SPAN_KIND_HANDLER = new PropertyCardinalityHandler( - "span_kind", MetricCardinalityLimits.SPAN_KIND, LIMITS_ENABLED); + "span_kind", + Config.get() + .getTraceStatsCardinalityLimit("span_kind", MetricCardinalityLimits.SPAN_KIND)); static final PropertyCardinalityHandler HTTP_METHOD_HANDLER = new PropertyCardinalityHandler( - "http_method", MetricCardinalityLimits.HTTP_METHOD, LIMITS_ENABLED); + "http_method", + Config.get() + .getTraceStatsCardinalityLimit("http_method", MetricCardinalityLimits.HTTP_METHOD)); static final PropertyCardinalityHandler HTTP_ENDPOINT_HANDLER = new PropertyCardinalityHandler( - "http_endpoint", MetricCardinalityLimits.HTTP_ENDPOINT, LIMITS_ENABLED); + "http_endpoint", + Config.get() + .getTraceStatsCardinalityLimit( + "http_endpoint", MetricCardinalityLimits.HTTP_ENDPOINT)); static final PropertyCardinalityHandler GRPC_STATUS_CODE_HANDLER = new PropertyCardinalityHandler( - "grpc_status_code", MetricCardinalityLimits.GRPC_STATUS_CODE, LIMITS_ENABLED); + "grpc_status_code", + Config.get() + .getTraceStatsCardinalityLimit( + "grpc_status_code", MetricCardinalityLimits.GRPC_STATUS_CODE)); // Single authoritative list used by resetCardinalityHandlers(). populateFrom() and hashOf() keep // named access for readability and to avoid per-span iteration overhead; this array ensures the diff --git a/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateTable.java b/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateTable.java index fcd6c5fcdb4..6010fb18a5c 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateTable.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateTable.java @@ -83,10 +83,10 @@ AggregateEntry findOrInsert(SpanSnapshot snapshot) { * {@code onStatsAggregateDropped}) rather than evicting an established one. Cap is sized to the * steady-state working set, so eviction is rare in the common case. * - *

How often this fires depends on {@link AggregateEntry#LIMITS_ENABLED}. With limits enabled, - * over-cap values for a given field collapse into a shared {@code blocked_by_tracer} bucket, so - * the table itself rarely reaches {@code maxAggregates}. With limits disabled (the default), - * over-cap values flow to distinct buckets and {@code maxAggregates} becomes the load-bearing + *

With per-field cardinality limits enabled, over-cap values for a given field collapse into a + * shared {@code tracer_blocked_value} bucket, so the table itself rarely reaches {@code + * maxAggregates}. Without per-field limits, over-cap values flow to distinct buckets and {@code + * maxAggregates} becomes the load-bearing * backstop -- the cursor-resumed scan was added specifically for this regime. */ private boolean evictOneStale() { diff --git a/dd-trace-core/src/main/java/datadog/trace/common/metrics/PeerTagSchema.java b/dd-trace-core/src/main/java/datadog/trace/common/metrics/PeerTagSchema.java index eca5c6c6a1a..fe8b255c7ca 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/metrics/PeerTagSchema.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/metrics/PeerTagSchema.java @@ -3,6 +3,7 @@ import static datadog.trace.api.DDTags.BASE_SERVICE; import datadog.communication.ddagent.DDAgentFeaturesDiscovery; +import datadog.trace.api.Config; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import java.util.Set; @@ -74,7 +75,10 @@ static PeerTagSchema of(Set names, String state) { for (int i = 0; i < names.length; i++) { this.handlers[i] = new TagCardinalityHandler( - names[i], MetricCardinalityLimits.PEER_TAG_VALUE, AggregateEntry.LIMITS_ENABLED); + names[i], + Config.get() + .getTraceStatsCardinalityLimit( + "peer_tag", MetricCardinalityLimits.PEER_TAG_VALUE)); } } diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index b10bda7c683..74e77dd8e65 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -3863,6 +3863,17 @@ public int getTraceStatsInterval() { return traceStatsInterval; } + /** + * Returns the per-cycle cardinality limit for the named stats field, following the RFC naming + * pattern {@code DD_TRACE_STATS_{tagName}_CARDINALITY_LIMIT} (e.g. {@code + * DD_TRACE_STATS_RESOURCE_CARDINALITY_LIMIT}). The caller supplies the default from {@code + * MetricCardinalityLimits} so per-field rationale stays co-located with the defaults. + */ + public int getTraceStatsCardinalityLimit(String tagName, int defaultLimit) { + return configProvider.getInteger( + "trace.stats." + tagName + ".cardinality.limit", defaultLimit); + } + public boolean isLogsInjectionEnabled() { return logsInjectionEnabled; } diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index ae081138b71..b09c46916f8 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -10641,6 +10641,86 @@ "aliases": [] } ], + "DD_TRACE_STATS_RESOURCE_CARDINALITY_LIMIT": [ + { + "version": "A", + "type": "integer", + "default": "256", + "aliases": [] + } + ], + "DD_TRACE_STATS_SERVICE_CARDINALITY_LIMIT": [ + { + "version": "A", + "type": "integer", + "default": "32", + "aliases": [] + } + ], + "DD_TRACE_STATS_OPERATION_CARDINALITY_LIMIT": [ + { + "version": "A", + "type": "integer", + "default": "64", + "aliases": [] + } + ], + "DD_TRACE_STATS_SERVICE_SOURCE_CARDINALITY_LIMIT": [ + { + "version": "A", + "type": "integer", + "default": "16", + "aliases": [] + } + ], + "DD_TRACE_STATS_TYPE_CARDINALITY_LIMIT": [ + { + "version": "A", + "type": "integer", + "default": "16", + "aliases": [] + } + ], + "DD_TRACE_STATS_SPAN_KIND_CARDINALITY_LIMIT": [ + { + "version": "A", + "type": "integer", + "default": "8", + "aliases": [] + } + ], + "DD_TRACE_STATS_HTTP_METHOD_CARDINALITY_LIMIT": [ + { + "version": "A", + "type": "integer", + "default": "16", + "aliases": [] + } + ], + "DD_TRACE_STATS_HTTP_ENDPOINT_CARDINALITY_LIMIT": [ + { + "version": "A", + "type": "integer", + "default": "64", + "aliases": [] + } + ], + "DD_TRACE_STATS_GRPC_STATUS_CODE_CARDINALITY_LIMIT": [ + { + "version": "A", + "type": "integer", + "default": "24", + "aliases": [] + } + ], + "DD_TRACE_STATS_PEER_TAG_CARDINALITY_LIMIT": [ + { + "version": "A", + "type": "integer", + "default": "512", + "aliases": [] + } + ], "DD_TRACE_STATUS404DECORATOR_ENABLED": [ { "version": "A", From 78a7f0daf88c7b2f81df8fd5d0962cc566a04805 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 24 Jun 2026 13:16:32 -0400 Subject: [PATCH 02/10] Raise RESOURCE cardinality limit to 1024 to avoid premature collapse Co-Authored-By: Claude Sonnet 4.6 --- .../trace/common/metrics/MetricCardinalityLimits.java | 5 +++-- metadata/supported-configurations.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/common/metrics/MetricCardinalityLimits.java b/dd-trace-core/src/main/java/datadog/trace/common/metrics/MetricCardinalityLimits.java index 58dcd3a70a1..1917f6add57 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/metrics/MetricCardinalityLimits.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/metrics/MetricCardinalityLimits.java @@ -14,9 +14,10 @@ private MetricCardinalityLimits() {} /** * Distinct {@code resource.name} values per cycle. Highest-cardinality field by far: DB-query - * obfuscations, HTTP route templates, custom resources. Typical service: 30-200 unique. + * obfuscations, HTTP route templates, custom resources. Typical service: 30-200 unique; 1024 + * leaves headroom for high-cardinality SQL/HTTP workloads without risking premature collapse. */ - static final int RESOURCE = 256; + static final int RESOURCE = 1024; /** * Distinct {@code service.name} values per cycle. Local service plus downstream peer-service diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index b09c46916f8..afe9bf20a78 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -10645,7 +10645,7 @@ { "version": "A", "type": "integer", - "default": "256", + "default": "1024", "aliases": [] } ], From 5b550ddfbbeb4e156774968c02327c885fdaca75 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 30 Jun 2026 20:54:22 -0400 Subject: [PATCH 03/10] Apply spotless formatting Co-Authored-By: Claude Sonnet 4.6 --- .../java/datadog/trace/common/metrics/AggregateEntry.java | 3 +-- .../java/datadog/trace/common/metrics/AggregateTable.java | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java b/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java index 7f93fad66a9..ff1cea55768 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java @@ -53,8 +53,7 @@ final class AggregateEntry extends Hashtable.Entry { "service_source", MetricCardinalityLimits.SERVICE_SOURCE)); static final PropertyCardinalityHandler TYPE_HANDLER = new PropertyCardinalityHandler( - "type", - Config.get().getTraceStatsCardinalityLimit("type", MetricCardinalityLimits.TYPE)); + "type", Config.get().getTraceStatsCardinalityLimit("type", MetricCardinalityLimits.TYPE)); static final PropertyCardinalityHandler SPAN_KIND_HANDLER = new PropertyCardinalityHandler( "span_kind", diff --git a/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateTable.java b/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateTable.java index 6010fb18a5c..6f2072f6495 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateTable.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateTable.java @@ -86,8 +86,8 @@ AggregateEntry findOrInsert(SpanSnapshot snapshot) { *

With per-field cardinality limits enabled, over-cap values for a given field collapse into a * shared {@code tracer_blocked_value} bucket, so the table itself rarely reaches {@code * maxAggregates}. Without per-field limits, over-cap values flow to distinct buckets and {@code - * maxAggregates} becomes the load-bearing - * backstop -- the cursor-resumed scan was added specifically for this regime. + * maxAggregates} becomes the load-bearing backstop -- the cursor-resumed scan was added + * specifically for this regime. */ private boolean evictOneStale() { // Two passes -- [cursor, length) then [0, cursor) -- using the half-open-range iterator. The From c50dd1dbf36c7a6cf81a77420a5b834d19167642 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 1 Jul 2026 13:08:25 -0400 Subject: [PATCH 04/10] Apply spotless formatting to Config.java Co-Authored-By: Claude Sonnet 4.6 --- internal-api/src/main/java/datadog/trace/api/Config.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 74e77dd8e65..f0f73e70dba 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -3870,8 +3870,7 @@ public int getTraceStatsInterval() { * MetricCardinalityLimits} so per-field rationale stays co-located with the defaults. */ public int getTraceStatsCardinalityLimit(String tagName, int defaultLimit) { - return configProvider.getInteger( - "trace.stats." + tagName + ".cardinality.limit", defaultLimit); + return configProvider.getInteger("trace.stats." + tagName + ".cardinality.limit", defaultLimit); } public boolean isLogsInjectionEnabled() { From e558d99cb64d3f4d035207944ecd88ba87cb8d46 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 1 Jul 2026 13:15:10 -0400 Subject: [PATCH 05/10] Fix supported-configurations.json: use int not integer for cardinality limits Co-Authored-By: Claude Sonnet 4.6 --- metadata/supported-configurations.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index afe9bf20a78..c373c484727 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -10644,7 +10644,7 @@ "DD_TRACE_STATS_RESOURCE_CARDINALITY_LIMIT": [ { "version": "A", - "type": "integer", + "type": "int", "default": "1024", "aliases": [] } @@ -10652,7 +10652,7 @@ "DD_TRACE_STATS_SERVICE_CARDINALITY_LIMIT": [ { "version": "A", - "type": "integer", + "type": "int", "default": "32", "aliases": [] } @@ -10660,7 +10660,7 @@ "DD_TRACE_STATS_OPERATION_CARDINALITY_LIMIT": [ { "version": "A", - "type": "integer", + "type": "int", "default": "64", "aliases": [] } @@ -10668,7 +10668,7 @@ "DD_TRACE_STATS_SERVICE_SOURCE_CARDINALITY_LIMIT": [ { "version": "A", - "type": "integer", + "type": "int", "default": "16", "aliases": [] } @@ -10676,7 +10676,7 @@ "DD_TRACE_STATS_TYPE_CARDINALITY_LIMIT": [ { "version": "A", - "type": "integer", + "type": "int", "default": "16", "aliases": [] } @@ -10684,7 +10684,7 @@ "DD_TRACE_STATS_SPAN_KIND_CARDINALITY_LIMIT": [ { "version": "A", - "type": "integer", + "type": "int", "default": "8", "aliases": [] } @@ -10692,7 +10692,7 @@ "DD_TRACE_STATS_HTTP_METHOD_CARDINALITY_LIMIT": [ { "version": "A", - "type": "integer", + "type": "int", "default": "16", "aliases": [] } @@ -10700,7 +10700,7 @@ "DD_TRACE_STATS_HTTP_ENDPOINT_CARDINALITY_LIMIT": [ { "version": "A", - "type": "integer", + "type": "int", "default": "64", "aliases": [] } @@ -10708,7 +10708,7 @@ "DD_TRACE_STATS_GRPC_STATUS_CODE_CARDINALITY_LIMIT": [ { "version": "A", - "type": "integer", + "type": "int", "default": "24", "aliases": [] } @@ -10716,7 +10716,7 @@ "DD_TRACE_STATS_PEER_TAG_CARDINALITY_LIMIT": [ { "version": "A", - "type": "integer", + "type": "int", "default": "512", "aliases": [] } From e3a67ad82c08512f2e865ae40012fa0f871eee7c Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 7 Jul 2026 15:09:55 -0400 Subject: [PATCH 06/10] Align cardinality config with .NET: rename peer_tags, raise http_endpoint, add table alias - Rename DD_TRACE_STATS_PEER_TAG_CARDINALITY_LIMIT -> DD_TRACE_STATS_PEER_TAGS_CARDINALITY_LIMIT to match .NET naming (plural) - Raise DD_TRACE_STATS_HTTP_ENDPOINT_CARDINALITY_LIMIT default 64 -> 512 to match .NET - Add DD_TRACE_STATS_CARDINALITY_LIMIT as the canonical name for the aggregate table cap, aliasing the older DD_TRACE_TRACER_METRICS_MAX_AGGREGATES - Add explicit string constants for all 10 per-field cardinality limit keys in GeneralConfig so the config registry validator can find them Co-Authored-By: Claude Sonnet 4.6 --- .../trace/api/config/GeneralConfig.java | 23 +++++++++++++++++++ .../metrics/MetricCardinalityLimits.java | 2 +- .../trace/common/metrics/PeerTagSchema.java | 2 +- .../main/java/datadog/trace/api/Config.java | 4 +++- metadata/supported-configurations.json | 12 ++++++++-- 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java index 79d3861d8ab..e8d199072d6 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java @@ -70,6 +70,29 @@ public final class GeneralConfig { public static final String TRACE_STATS_COMPUTATION_IGNORE_AGENT_VERSION = "trace.stats.computation.ignore.agent.version"; + public static final String TRACE_STATS_CARDINALITY_LIMIT = "trace.stats.cardinality.limit"; + + public static final String TRACE_STATS_RESOURCE_CARDINALITY_LIMIT = + "trace.stats.resource.cardinality.limit"; + public static final String TRACE_STATS_SERVICE_CARDINALITY_LIMIT = + "trace.stats.service.cardinality.limit"; + public static final String TRACE_STATS_OPERATION_CARDINALITY_LIMIT = + "trace.stats.operation.cardinality.limit"; + public static final String TRACE_STATS_SERVICE_SOURCE_CARDINALITY_LIMIT = + "trace.stats.service_source.cardinality.limit"; + public static final String TRACE_STATS_TYPE_CARDINALITY_LIMIT = + "trace.stats.type.cardinality.limit"; + public static final String TRACE_STATS_SPAN_KIND_CARDINALITY_LIMIT = + "trace.stats.span_kind.cardinality.limit"; + public static final String TRACE_STATS_HTTP_METHOD_CARDINALITY_LIMIT = + "trace.stats.http_method.cardinality.limit"; + public static final String TRACE_STATS_HTTP_ENDPOINT_CARDINALITY_LIMIT = + "trace.stats.http_endpoint.cardinality.limit"; + public static final String TRACE_STATS_GRPC_STATUS_CODE_CARDINALITY_LIMIT = + "trace.stats.grpc_status_code.cardinality.limit"; + public static final String TRACE_STATS_PEER_TAGS_CARDINALITY_LIMIT = + "trace.stats.peer_tags.cardinality.limit"; + public static final String TRACE_OTEL_SEMANTICS_ENABLED = "trace.otel.semantics.enabled"; public static final String TRACER_METRICS_ENABLED = "trace.tracer.metrics.enabled"; diff --git a/dd-trace-core/src/main/java/datadog/trace/common/metrics/MetricCardinalityLimits.java b/dd-trace-core/src/main/java/datadog/trace/common/metrics/MetricCardinalityLimits.java index 1917f6add57..6a679e98133 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/metrics/MetricCardinalityLimits.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/metrics/MetricCardinalityLimits.java @@ -58,7 +58,7 @@ private MetricCardinalityLimits() {} * Distinct {@code http.endpoint} values per cycle. Path templates -- same shape as {@code * RESOURCE} for HTTP-heavy services. Only used when {@code includeEndpointInMetrics} is enabled. */ - static final int HTTP_ENDPOINT = 64; + static final int HTTP_ENDPOINT = 512; /** * Distinct gRPC status code values per cycle. gRPC spec defines exactly 17 codes (0-16); 24 diff --git a/dd-trace-core/src/main/java/datadog/trace/common/metrics/PeerTagSchema.java b/dd-trace-core/src/main/java/datadog/trace/common/metrics/PeerTagSchema.java index fe8b255c7ca..1e0d51e84c8 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/metrics/PeerTagSchema.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/metrics/PeerTagSchema.java @@ -78,7 +78,7 @@ static PeerTagSchema of(Set names, String state) { names[i], Config.get() .getTraceStatsCardinalityLimit( - "peer_tag", MetricCardinalityLimits.PEER_TAG_VALUE)); + "peer_tags", MetricCardinalityLimits.PEER_TAG_VALUE)); } } diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index f0f73e70dba..5e7a33a93c5 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -420,6 +420,7 @@ import static datadog.trace.api.config.GeneralConfig.TRACE_DEBUG; import static datadog.trace.api.config.GeneralConfig.TRACE_LOG_LEVEL; import static datadog.trace.api.config.GeneralConfig.TRACE_OTEL_SEMANTICS_ENABLED; +import static datadog.trace.api.config.GeneralConfig.TRACE_STATS_CARDINALITY_LIMIT; import static datadog.trace.api.config.GeneralConfig.TRACE_STATS_COMPUTATION_ENABLED; import static datadog.trace.api.config.GeneralConfig.TRACE_STATS_COMPUTATION_IGNORE_AGENT_VERSION; import static datadog.trace.api.config.GeneralConfig.TRACE_TAGS; @@ -2250,7 +2251,8 @@ && isMetricsOtelEnabled() final int defaultMaxPending = tightHeap ? 64 : 128; tracerMetricsMaxAggregates = - configProvider.getInteger(TRACER_METRICS_MAX_AGGREGATES, defaultMaxAggregates); + configProvider.getInteger( + TRACE_STATS_CARDINALITY_LIMIT, defaultMaxAggregates, TRACER_METRICS_MAX_AGGREGATES); /* * TRACER_METRICS_MAX_PENDING historically counted conflating Batch slots (~64 spans per batch * via Batch.MAX_BATCH_SIZE). The inbox now holds 1 SpanSnapshot per metrics-eligible span, so diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index c373c484727..7f60c2f4a91 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -10633,6 +10633,14 @@ "aliases": ["DD_TRACE_TRACER_METRICS_ENABLED"] } ], + "DD_TRACE_STATS_CARDINALITY_LIMIT": [ + { + "version": "A", + "type": "int", + "default": "2048", + "aliases": ["DD_TRACE_TRACER_METRICS_MAX_AGGREGATES"] + } + ], "DD_TRACE_STATS_COMPUTATION_IGNORE_AGENT_VERSION": [ { "version": "A", @@ -10701,7 +10709,7 @@ { "version": "A", "type": "int", - "default": "64", + "default": "512", "aliases": [] } ], @@ -10713,7 +10721,7 @@ "aliases": [] } ], - "DD_TRACE_STATS_PEER_TAG_CARDINALITY_LIMIT": [ + "DD_TRACE_STATS_PEER_TAGS_CARDINALITY_LIMIT": [ { "version": "A", "type": "int", From a1937cf72fec8911c6b5cb60190a3f57e44d8daf Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 13 Jul 2026 09:03:05 -0400 Subject: [PATCH 07/10] Drop 7 Java-only per-field cardinality limit config keys SERVICE, OPERATION, SERVICE_SOURCE, TYPE, SPAN_KIND, HTTP_METHOD, and GRPC_STATUS_CODE have naturally low cardinality -- the aggregate table cap (DD_TRACE_STATS_CARDINALITY_LIMIT) is sufficient backstop. Aligns the public config surface with .NET (resource, http_endpoint, peer_tags, table cap). Hardcoded defaults in MetricCardinalityLimits remain; config knobs can be added later if a customer needs to tune them. Co-Authored-By: Claude Sonnet 4.6 --- .../trace/api/config/GeneralConfig.java | 15 ----- metadata/supported-configurations.json | 56 ------------------- 2 files changed, 71 deletions(-) diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java index e8d199072d6..aa87269f964 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java @@ -71,25 +71,10 @@ public final class GeneralConfig { "trace.stats.computation.ignore.agent.version"; public static final String TRACE_STATS_CARDINALITY_LIMIT = "trace.stats.cardinality.limit"; - public static final String TRACE_STATS_RESOURCE_CARDINALITY_LIMIT = "trace.stats.resource.cardinality.limit"; - public static final String TRACE_STATS_SERVICE_CARDINALITY_LIMIT = - "trace.stats.service.cardinality.limit"; - public static final String TRACE_STATS_OPERATION_CARDINALITY_LIMIT = - "trace.stats.operation.cardinality.limit"; - public static final String TRACE_STATS_SERVICE_SOURCE_CARDINALITY_LIMIT = - "trace.stats.service_source.cardinality.limit"; - public static final String TRACE_STATS_TYPE_CARDINALITY_LIMIT = - "trace.stats.type.cardinality.limit"; - public static final String TRACE_STATS_SPAN_KIND_CARDINALITY_LIMIT = - "trace.stats.span_kind.cardinality.limit"; - public static final String TRACE_STATS_HTTP_METHOD_CARDINALITY_LIMIT = - "trace.stats.http_method.cardinality.limit"; public static final String TRACE_STATS_HTTP_ENDPOINT_CARDINALITY_LIMIT = "trace.stats.http_endpoint.cardinality.limit"; - public static final String TRACE_STATS_GRPC_STATUS_CODE_CARDINALITY_LIMIT = - "trace.stats.grpc_status_code.cardinality.limit"; public static final String TRACE_STATS_PEER_TAGS_CARDINALITY_LIMIT = "trace.stats.peer_tags.cardinality.limit"; diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index 7f60c2f4a91..fb913a9ea2f 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -10657,54 +10657,6 @@ "aliases": [] } ], - "DD_TRACE_STATS_SERVICE_CARDINALITY_LIMIT": [ - { - "version": "A", - "type": "int", - "default": "32", - "aliases": [] - } - ], - "DD_TRACE_STATS_OPERATION_CARDINALITY_LIMIT": [ - { - "version": "A", - "type": "int", - "default": "64", - "aliases": [] - } - ], - "DD_TRACE_STATS_SERVICE_SOURCE_CARDINALITY_LIMIT": [ - { - "version": "A", - "type": "int", - "default": "16", - "aliases": [] - } - ], - "DD_TRACE_STATS_TYPE_CARDINALITY_LIMIT": [ - { - "version": "A", - "type": "int", - "default": "16", - "aliases": [] - } - ], - "DD_TRACE_STATS_SPAN_KIND_CARDINALITY_LIMIT": [ - { - "version": "A", - "type": "int", - "default": "8", - "aliases": [] - } - ], - "DD_TRACE_STATS_HTTP_METHOD_CARDINALITY_LIMIT": [ - { - "version": "A", - "type": "int", - "default": "16", - "aliases": [] - } - ], "DD_TRACE_STATS_HTTP_ENDPOINT_CARDINALITY_LIMIT": [ { "version": "A", @@ -10713,14 +10665,6 @@ "aliases": [] } ], - "DD_TRACE_STATS_GRPC_STATUS_CODE_CARDINALITY_LIMIT": [ - { - "version": "A", - "type": "int", - "default": "24", - "aliases": [] - } - ], "DD_TRACE_STATS_PEER_TAGS_CARDINALITY_LIMIT": [ { "version": "A", From ae5e638e7e42f5008994622ee9bf3c40eff38095 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 13 Jul 2026 12:54:59 -0400 Subject: [PATCH 08/10] Fix fixed-limit fields to bypass Config lookup, avoiding unsupported config key detection Fields with hardcoded limits (service, operation, type, span_kind, http_method, grpc_status_code, service_source) now pass their MetricCardinalityLimits constant directly to PropertyCardinalityHandler instead of going through Config.getTraceStatsCardinalityLimit. This prevents the test harness from flagging DD_TRACE_STATS_SERVICE_CARDINALITY_LIMIT etc. as unsupported config keys. Co-Authored-By: Claude Sonnet 4.6 --- .../trace/common/metrics/AggregateEntry.java | 52 ++++++------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java b/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java index 4591e48bec0..dbfd436b60a 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/metrics/AggregateEntry.java @@ -29,53 +29,33 @@ final class AggregateEntry extends Hashtable.Entry { private static final UTF8BytesString[] EMPTY_TAGS = new UTF8BytesString[0]; - // Per-field cardinality handlers. Limits are tunable via DD_TRACE_STATS_{field}_CARDINALITY_LIMIT - // (e.g. DD_TRACE_STATS_RESOURCE_CARDINALITY_LIMIT). Defaults live on MetricCardinalityLimits. - // Frozen at first class-load from Config. + // Configurable per-field cardinality handlers — tunable via env var. static final PropertyCardinalityHandler RESOURCE_HANDLER = new PropertyCardinalityHandler( "resource", Config.get().getTraceStatsCardinalityLimit("resource", MetricCardinalityLimits.RESOURCE)); - static final PropertyCardinalityHandler SERVICE_HANDLER = - new PropertyCardinalityHandler( - "service", - Config.get().getTraceStatsCardinalityLimit("service", MetricCardinalityLimits.SERVICE)); - static final PropertyCardinalityHandler OPERATION_HANDLER = - new PropertyCardinalityHandler( - "operation", - Config.get() - .getTraceStatsCardinalityLimit("operation", MetricCardinalityLimits.OPERATION)); - static final PropertyCardinalityHandler SERVICE_SOURCE_HANDLER = - new PropertyCardinalityHandler( - "service_source", - Config.get() - .getTraceStatsCardinalityLimit( - "service_source", MetricCardinalityLimits.SERVICE_SOURCE)); - static final PropertyCardinalityHandler TYPE_HANDLER = - new PropertyCardinalityHandler( - "type", Config.get().getTraceStatsCardinalityLimit("type", MetricCardinalityLimits.TYPE)); - static final PropertyCardinalityHandler SPAN_KIND_HANDLER = - new PropertyCardinalityHandler( - "span_kind", - Config.get() - .getTraceStatsCardinalityLimit("span_kind", MetricCardinalityLimits.SPAN_KIND)); - static final PropertyCardinalityHandler HTTP_METHOD_HANDLER = - new PropertyCardinalityHandler( - "http_method", - Config.get() - .getTraceStatsCardinalityLimit("http_method", MetricCardinalityLimits.HTTP_METHOD)); static final PropertyCardinalityHandler HTTP_ENDPOINT_HANDLER = new PropertyCardinalityHandler( "http_endpoint", Config.get() .getTraceStatsCardinalityLimit( "http_endpoint", MetricCardinalityLimits.HTTP_ENDPOINT)); + + // Fixed per-field cardinality handlers — hardcoded, not user-configurable. + static final PropertyCardinalityHandler SERVICE_HANDLER = + new PropertyCardinalityHandler("service", MetricCardinalityLimits.SERVICE); + static final PropertyCardinalityHandler OPERATION_HANDLER = + new PropertyCardinalityHandler("operation", MetricCardinalityLimits.OPERATION); + static final PropertyCardinalityHandler SERVICE_SOURCE_HANDLER = + new PropertyCardinalityHandler("service_source", MetricCardinalityLimits.SERVICE_SOURCE); + static final PropertyCardinalityHandler TYPE_HANDLER = + new PropertyCardinalityHandler("type", MetricCardinalityLimits.TYPE); + static final PropertyCardinalityHandler SPAN_KIND_HANDLER = + new PropertyCardinalityHandler("span_kind", MetricCardinalityLimits.SPAN_KIND); + static final PropertyCardinalityHandler HTTP_METHOD_HANDLER = + new PropertyCardinalityHandler("http_method", MetricCardinalityLimits.HTTP_METHOD); static final PropertyCardinalityHandler GRPC_STATUS_CODE_HANDLER = - new PropertyCardinalityHandler( - "grpc_status_code", - Config.get() - .getTraceStatsCardinalityLimit( - "grpc_status_code", MetricCardinalityLimits.GRPC_STATUS_CODE)); + new PropertyCardinalityHandler("grpc_status_code", MetricCardinalityLimits.GRPC_STATUS_CODE); // Single authoritative list used by resetCardinalityHandlers(). populateFrom() and hashOf() keep // named access for readability and to avoid per-span iteration overhead; this array ensures the From 6229ca25b41ec7445dcb2f0e75732a9dba60c806 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 13 Jul 2026 14:16:13 -0400 Subject: [PATCH 09/10] Remove aliases from DD_TRACE_STATS_CARDINALITY_LIMIT registry entry The central registry has no alias for this key; the local entry must match. The code-level alias (Config.java reads DD_TRACE_TRACER_METRICS_MAX_AGGREGATES as a fallback) is unaffected. Co-Authored-By: Claude Sonnet 4.6 --- metadata/supported-configurations.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index c902fd171e9..8149f4f99e9 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -10693,8 +10693,7 @@ { "version": "A", "type": "int", - "default": "2048", - "aliases": ["DD_TRACE_TRACER_METRICS_MAX_AGGREGATES"] + "default": "2048" } ], "DD_TRACE_STATS_COMPUTATION_IGNORE_AGENT_VERSION": [ From da8db72b9e994ebbb041b515164d31729f9201cd Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 13 Jul 2026 14:27:15 -0400 Subject: [PATCH 10/10] Bump DD_TRACE_STATS_CARDINALITY_LIMIT to version B with legacy alias MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Version A must match the central registry exactly (no aliases). The DD_TRACE_TRACER_METRICS_MAX_AGGREGATES alias is Java-specific, so it belongs in version B — matching the same pattern as DD_TRACE_STATS_COMPUTATION_ENABLED aliasing DD_TRACE_TRACER_METRICS_ENABLED. Co-Authored-By: Claude Sonnet 4.6 --- metadata/supported-configurations.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index 8149f4f99e9..20edcb4a513 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -10691,9 +10691,10 @@ ], "DD_TRACE_STATS_CARDINALITY_LIMIT": [ { - "version": "A", + "version": "B", "type": "int", - "default": "2048" + "default": "2048", + "aliases": ["DD_TRACE_TRACER_METRICS_MAX_AGGREGATES"] } ], "DD_TRACE_STATS_COMPUTATION_IGNORE_AGENT_VERSION": [