Skip to content

[APMSVLS-485] feat(traces): span-derived primary tags for stats - #1336

Draft
lucaspimentel wants to merge 4 commits into
lpimentel/bound-stats-cardinalityfrom
lpimentel/span-derived-primary-tags
Draft

[APMSVLS-485] feat(traces): span-derived primary tags for stats#1336
lucaspimentel wants to merge 4 commits into
lpimentel/bound-stats-cardinalityfrom
lpimentel/span-derived-primary-tags

Conversation

@lucaspimentel

@lucaspimentel lucaspimentel commented Aug 21, 2026

Copy link
Copy Markdown
Member

Stacked PRs:

Overview

Adds bottlecap-side config for span-derived primary tags (APMSVLS-485), matching the Serverless Compatibility Layer (datadog-trace-agent):

Variable Default Notes
DD_TRACE_EXPERIMENTAL_FEATURES_ENABLED false Gates the two settings below.
DD_TRACE_STATS_ADDITIONAL_TAGS empty Comma-separated span meta keys to use as additional stats aggregation dimensions.
DD_TRACE_STATS_ADDITIONAL_TAGS_CARDINALITY_LIMIT 100 Per-bucket cap on distinct tag-value combinations.

These feed StatsConcentratorServiceSpanConcentrator::new's additional_metric_tag_keys, which libdd-trace-stats (via #1332's bump to 72fa8685) already implements end-to-end: extraction from span meta, inclusion in the aggregation key hash, cardinality limiting, and export as ClientGroupedStats.additional_metric_tags. So this PR is pure wiring, exactly as #1332 predicted.

Both gated settings reset to their defaults when the experimental gate is off, so a stale env var can't leak through.

No libdatadog change is needed. The ticket points at span_derived_primary_tags (field 22) being stubbed as vec![], but that field is deprecated in favor of additional_metric_tags (field 23) — see the comment in stats.proto. Field 22 intentionally stays empty; this PR uses the modern replacement.

Misconfiguration handling

libdatadog warns about bad input but still applies it, so both settings are validated here and the warnings restated in bottlecap's terms.

DD_TRACE_STATS_ADDITIONAL_TAGS_CARDINALITY_LIMIT:

  • 0 would collapse every additional metric tag into the tracer_blocked_value sentinel — silent, total data loss for the dimension → falls back to the default of 100. Worth noting the Go trace agent reads 0 as no cap, so a user carrying that setting over would otherwise lose exactly what they meant to keep unbounded. "Unbounded" is deliberately not offered: chore(deps): update libdatadog to 72fa8685 and serverless-components to 9daae40 #1332 bounded this precisely to cap concentrator memory in a memory-capped Lambda.
  • Values at or above the whole-key limit (7000) → clamped to 6999, mainly to silence libdatadog's misconfiguration warning. Per-field limits are applied before the whole-key limit, so such a value is effectively unbounded rather than strictly inert; either way, reaching it needs ~7k distinct tag combinations inside a single 10s bucket, which will not happen in a Lambda invocation.

DD_TRACE_STATS_ADDITIONAL_TAGS: libdatadog aggregates on at most 4 keys, sorting alphabetically and dropping the rest. So zone,tenant_id,region,shard,customer silently drops zone — by alphabetical accident, not by anything the user expressed. This is arguably the nastier failure of the two, since the config appears to work and only the dimensions are wrong. libdatadog's warning names the dropped keys but not the kept ones, the selection rule, or the env var, so bottlecap now logs all three. Truncation itself stays in libdatadog.

SCL parity divergence

serverless-components@9daae40 applies this env var raw (.parse::<usize>().ok(), no range check), so after this PR DD_TRACE_STATS_ADDITIONAL_TAGS_CARDINALITY_LIMIT=0 means "default 100" in Lambda and "collapse everything" in Azure Functions. Given the SCL-parity framing, resolve_cardinality_limits should be ported upstream to datadog-trace-agent as a follow-up.

On per-bucket cardinality resets

The cardinality limit is tracked per 10s StatsBucket and resets per bucket. Unlike the rate limiter in #1320, this is not broken by Lambda freeze/thaw, so it is deliberately left alone:

  • A rate limiter's budget is a rate over wall-clock read at decision time, so a freeze inflates elapsed time without any work happening and the budget refills for free.
  • This limit is a count of distinct keys inside a bucket that gets flushed and freed, and buckets are keyed by span timestamps (span.start + span.duration), not processing time. A freeze just routes the next invocation's spans into a different bucket, which is correct.

Bucketing stays load-bearing for the concentrator memory and /v0.6/stats payload bounds.

Testing

Config (src/config/mod.rs) — the experimental gate on/off for both settings, comma-separated parsing with surrounding whitespace, and an unparseable cardinality limit falling back to None.

Concentrator (src/traces/stats_concentrator_service.rs):

  • test_additional_metric_tags_populated_when_configured — a configured key present in span meta surfaces as datacenter:us-east-1 on the exported ClientGroupedStats.
  • test_additional_metric_tags_empty_by_default — unset config exports no additional tags even when the span carries a matching meta key.
  • test_resolve_cardinality_limits — unset/0/in-range/at-or-above-whole-key validation, and that overriding additional_tags_limit leaves the other limits at their defaults.
  • test_kept_and_dropped_additional_metric_tag_keys — the alphabetical keep/drop split reported for an over-cap key list, derived from the concentrator's own additional_metric_tag_keys() getter (so the kept set can't drift from libdatadog's real cap), and that duplicates collapse before the cap applies.

cargo test --lib, cargo clippy --lib --all-targets -- -D warnings, and cargo fmt --check all pass.

@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented Aug 21, 2026

Copy link
Copy Markdown

Pipelines

Unblock PR with BitsAI

⚠️ Warnings

🚦 2 Pipeline jobs failed

DataDog/datadog-lambda-extension | e2e-test-status (amd64) — 🔧 Needs a code fix, caused by this PR

View in Datadog · View in GitLab

DataDog/datadog-lambda-extension | e2e-test-status (amd64, fips)

View in Datadog · View in GitLab

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 7ff07f2 | Docs | View more details | Give us feedback!

Wire DD_TRACE_EXPERIMENTAL_FEATURES_ENABLED, DD_TRACE_STATS_ADDITIONAL_TAGS,
and DD_TRACE_STATS_ADDITIONAL_TAGS_CARDINALITY_LIMIT into
StatsConcentratorService, matching the Serverless Compatibility Layer
(datadog-trace-agent). Lets users configure span meta keys as additional
stats aggregation dimensions (ClientGroupedStats.additional_metric_tags),
gated behind the experimental features flag.

libdd-trace-stats (pinned via #1332) already implements
additional_metric_tag_keys end-to-end; this only adds the bottlecap-side
config plumbing. The deprecated span_derived_primary_tags proto field
(superseded by additional_metric_tags) intentionally stays empty.

Depends on #1332 (libdatadog/serverless-components rev bump); base this
branch on lpimentel/bump-libdatadog-72fa8685 until that merges.
libdatadog warns about out-of-range cardinality limits but still applies
them. Validate DD_TRACE_STATS_ADDITIONAL_TAGS_CARDINALITY_LIMIT before
passing it through:

- 0 collapsed every additional metric tag into tracer_blocked_value;
  now falls back to libdatadog's default of 100.
- Values at or above the whole-key limit (7000) are clamped to 6999,
  mainly to silence libdatadog's misconfiguration warning. Per-field
  limits are applied before the whole-key limit, so such a value is
  effectively unbounded rather than inert; either way, reaching it needs
  ~7k distinct tag combinations in a single 10s bucket, which will not
  happen in a Lambda invocation.

Both log a warning naming the effective value.
libdatadog aggregates on at most 4 additional metric tag keys, sorting
alphabetically and dropping the rest, so excess keys are chosen by
alphabetical accident rather than by anything the user expressed. Its
warning names the dropped keys but not the kept ones, the selection rule,
or the env var; restate all three in bottlecap's voice. Truncation itself
stays in libdatadog.

Also clarify the cardinality limit warnings. The Go trace agent reads
0 as "no cap", so spell out that 0 does not mean unlimited here and point
at unsetting DD_TRACE_STATS_ADDITIONAL_TAGS to actually disable the
dimension. Per-field limits apply before the whole-key limit, so a limit
at or above the whole-key limit is effectively unbounded rather than
inert; the clamp mainly silences libdatadog's misconfiguration warning.
The excess-key warning predicted libdatadog's normalization instead of
reading it: a hand-copied `MAX_ADDITIONAL_METRIC_TAG_KEYS = 4` mirroring
a private upstream constant, plus a local re-implementation of its
sort/dedup/truncate. Both could drift silently, and the copy would then
name the wrong keys as dropped.

libdatadog already exposes the survivors via
`SpanConcentrator::additional_metric_tag_keys()`, so ask for them
instead: diff the requested list against the kept list, move the warning
to after `SpanConcentrator::new`, and delete the constant and the mirror.
The effective cap is now `kept.len()` rather than a number we assert on
faith.

The reworked test builds a real concentrator and checks which keys
survive, so it exercises upstream's actual rule -- and confirms the cap
is in fact 4, which nothing previously verified.

Also drop the #1332 reference from the `resolve_cardinality_limits` doc
comment; it is stale once that PR merges, and the rationale reads better
stated directly.

🤖
@lucaspimentel
lucaspimentel force-pushed the lpimentel/span-derived-primary-tags branch from f99baf9 to 7ff07f2 Compare August 21, 2026 23:31
@lucaspimentel
lucaspimentel changed the base branch from lpimentel/bump-libdatadog-72fa8685 to lpimentel/bound-stats-cardinality August 21, 2026 23:31
@lucaspimentel
lucaspimentel requested a balanced review from Copilot August 22, 2026 00:49
@lucaspimentel

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ff07f23b8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +269 to +272
if self.trace_experimental_features_enabled {
if !source.trace_stats_additional_tags.is_empty() {
self.additional_metric_tags
.clone_from(&source.trace_stats_additional_tags);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply the experimental gate after merging all sources

When the gate is enabled via environment but the tag keys or cardinality limit come from datadog.yaml, this drops the YAML values: merge_from processes the YAML source first while self.trace_experimental_features_enabled is still false and clears both settings, then the environment source enables the gate without restoring them. This makes valid mixed-source configuration silently ineffective; preserve the supplied settings until all sources have been merged or perform the gating as a final resolution step.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds experimental span-derived dimensions to Lambda trace-stat aggregation.

Changes:

  • Parses and gates additional tag configuration.
  • Wires tag keys and cardinality limits into the stats concentrator.
  • Adds validation, warnings, and tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
bottlecap/src/config/mod.rs Adds gated configuration and tests.
bottlecap/src/traces/stats_concentrator_service.rs Applies tags and cardinality limits.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +269 to +272
if self.trace_experimental_features_enabled {
if !source.trace_stats_additional_tags.is_empty() {
self.additional_metric_tags
.clone_from(&source.trace_stats_additional_tags);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants