[APMSVLS-485] feat(traces): span-derived primary tags - #1336
Conversation
|
c87989a to
f99baf9
Compare
5110d8d to
7996ec2
Compare
f99baf9 to
7ff07f2
Compare
|
@codex review |
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
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.
4a88378 to
1ca9352
Compare
a201e5c to
80b1f16
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a446908753
ℹ️ 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".
a446908 to
2544bdb
Compare
eeef196 to
f76c2e4
Compare
2544bdb to
b27a5a3
Compare
f76c2e4 to
82733f2
Compare
b27a5a3 to
ad80013
Compare
litianningdatadog
left a comment
There was a problem hiding this comment.
Since this PR is the tip of PR stacks, besides unit tests it would be great to provide test manifest to prove the feature works as expected.
c0b1c82 to
26a4add
Compare
e989b1f to
1707668
Compare
Thanks, @litianningdatadog! I added additional tests to this PR using the fake intake to assert that the trace stats in the generated payload look as expected. I'm also adding e2e tests in serverless-e2e-test#330. |
26a4add to
2180427
Compare
e862a67 to
a0b2491
Compare
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. 🤖
….yaml Additional trace stats tags configured in datadog.yaml were silently dropped whenever the experimental-features gate was enabled through the environment instead. Config sources merge one at a time (datadog.yaml first, then env vars), and the gate was checked during each merge, so the yaml values were cleared before the env-var pass could turn the gate on. The gate now runs once, after every source has merged, so the gate and the values it gates can come from different sources in either order. 🤖
…tats field When too many distinct additional metric tag sets collapsed, the warning advised removing request ids and path parameters from resource names, which has no effect on additional-tag cardinality. That case now points at the knobs that do help: listing fewer keys in DD_TRACE_STATS_ADDITIONAL_TAGS, choosing keys with fewer distinct values, or raising DD_TRACE_STATS_ADDITIONAL_TAGS_CARDINALITY_LIMIT. The other fields keep the reduce-cardinality advice. 🤖
Add fake-intake tests proving DD_TRACE_STATS_ADDITIONAL_TAGS survives concentration and serialization: configured span meta keys split stats groups and carry encoded tag values, the experimental gate merges groups with empty additional_metric_tags, and the cardinality limit collapses excess values into the tracer_blocked_value overflow group without dropping hits.
a0b2491 to
4fcc27b
Compare
Stacked PRs:
datadog-agent-trace-samplercrate serverless-components#141libdatadogto72fa8685andserverless-componentsto9daae40#1332Overview
Adds bottlecap-side config for span-derived primary tags (APMSVLS-485), matching the Serverless Compatibility Layer (
datadog-trace-agent):DD_TRACE_EXPERIMENTAL_FEATURES_ENABLEDfalseDD_TRACE_STATS_ADDITIONAL_TAGSmetakeys to use as additional stats aggregation dimensions.DD_TRACE_STATS_ADDITIONAL_TAGS_CARDINALITY_LIMIT100These feed
StatsConcentratorService→SpanConcentrator::new'sadditional_metric_tag_keys, whichlibdd-trace-stats(via #1332's bump to72fa8685) already implements end-to-end: extraction from spanmeta, inclusion in the aggregation key hash, cardinality limiting, and export asClientGroupedStats.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 asvec![], but that field is deprecated in favor ofadditional_metric_tags(field 23) — see the comment instats.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:0would collapse every additional metric tag into thetracer_blocked_valuesentinel — silent, total data loss for the dimension → falls back to the default of100. Worth noting the Go trace agent reads0as 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): updatelibdatadogto72fa8685andserverless-componentsto9daae40#1332 bounded this precisely to cap concentrator memory in a memory-capped Lambda.7000) → clamped to6999, 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. Sozone,tenant_id,region,shard,customersilently dropszone— 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@9daae40applies this env var raw (.parse::<usize>().ok(), no range check), so after this PRDD_TRACE_STATS_ADDITIONAL_TAGS_CARDINALITY_LIMIT=0means "default100" in Lambda and "collapse everything" in Azure Functions. Given the SCL-parity framing,resolve_cardinality_limitsshould be ported upstream todatadog-trace-agentas a follow-up.On per-bucket cardinality resets
The cardinality limit is tracked per 10s
StatsBucketand resets per bucket. Unlike the rate limiter in #1320, this is not broken by Lambda freeze/thaw, so it is deliberately left alone: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/statspayload 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 toNone.Concentrator (
src/traces/stats_concentrator_service.rs):test_additional_metric_tags_populated_when_configured— a configured key present in spanmetasurfaces asdatacenter:us-east-1on the exportedClientGroupedStats.test_additional_metric_tags_empty_by_default— unset config exports no additional tags even when the span carries a matchingmetakey.test_resolve_cardinality_limits— unset/0/in-range/at-or-above-whole-key validation, and that overridingadditional_tags_limitleaves 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 ownadditional_metric_tag_keys()getter (so the kept set can't drift from libdatadog's real cap), and that duplicates collapse before the cap applies.test_observe_collapsed_fields— per-field collapse detection scans each field independently, the whole-key overflow entry (every field set to the sentinel) is skipped rather than misreported as all four collapsing, and the two sentinel encodings (bare key forpeer_tags, trailing colon foradditional_metric_tags) are both recognized.test_resource_collapse_observed_without_whole_key_overflow— exceedingresource_limit(1,024) while staying underwhole_key_limit(7,000) is observable via the payload scan with no whole-key overflow entry, covering the observability trap wherecollapsed_spansalone stays at 0.test_collapse_warns_once_per_signalandtest_collapse_warns_once_per_signal_with_additional_tags— whole-key overflow and per-field collapse are reported independently, each at most once per sandbox, and repeated flushes leave state untouched; the additional-tags variant covers the second possible-fields shape whereADDITIONAL_TAGSjoins the saturation mask, including the fabricated payload carrying the sentinel only when additional tags are configured.cargo test --lib,cargo clippy --lib --all-targets -- -D warnings, andcargo fmt --checkall pass.Fake intake (
tests/apm_integration_test.rs) — payload-level coverage through concentrator →StatsFlusher→ msgpack/gzip → in-process fake intake, with the config built through the real parsing path (get_config+figment::Jail, so the env vars and gate are exercised, not just struct fields):stats_additional_metric_tags_through_fake_intake— spans carryingmeta["region"]arrive withadditional_metric_tagspopulated: distinct region values split into separate groups with encodedregion:<value>tags, repeated values aggregate into the same group (hit count 2), and an unconfigured meta key (tenant_id) is not exported.stats_additional_metric_tags_gated_off_through_fake_intake—DD_TRACE_STATS_ADDITIONAL_TAGSset without the gate: spans differing only in region merge into a single group with emptyadditional_metric_tags, proving the gate affects the emitted payload, not only the parsed config.stats_additional_metric_tags_cardinality_limit_through_fake_intake—DD_TRACE_STATS_ADDITIONAL_TAGS_CARDINALITY_LIMIT=1with two distinct values in one bucket: the first value is admitted, the second collapses into thetracer_blocked_value:overflow group, and the total hit count is preserved.stats_additional_metric_tags_multiple_keys_through_fake_intake— two configured keys (region,tenant_id) group on both values together: one group per distinct combination, repeats of the same combination aggregating, and a change in either key producing a new group.cargo nextest run --workspace(667 passed) andRUSTFLAGS="-D warnings" cargo clippy --workspace --all-targets --features defaultpass.