Skip to content

Commit 99bfcfe

Browse files
committed
fix(traces): keep backend as safety net for trace stats when tracer claims client-computed-stats
The Datadog-Client-Computed-Stats header should only suppress extension-side stats generation, not backend stats. When the extension is not computing stats itself, always set _dd.compute_stats:1 so the backend remains a fallback.
1 parent b67655d commit 99bfcfe

File tree

2 files changed

+25
-17
lines changed

2 files changed

+25
-17
lines changed

bottlecap/src/lifecycle/invocation/processor.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2255,8 +2255,9 @@ mod tests {
22552255
"client_computed_stats must be propagated to the aws.lambda span payload"
22562256
);
22572257

2258-
// Verify _dd.compute_stats is "0" in the built payload tags: client_computed_stats=true
2259-
// means the tracer has already computed stats, so neither extension nor backend should.
2258+
// Verify _dd.compute_stats is "1" in the built payload tags: the backend is always
2259+
// the safety net when compute_trace_stats_on_extension is false, regardless of
2260+
// client_computed_stats.
22602261
let send_data = payload.builder.build();
22612262
let libdd_trace_utils::tracer_payload::TracerPayloadCollection::V07(payloads) =
22622263
send_data.get_payloads()
@@ -2266,8 +2267,8 @@ mod tests {
22662267
for p in payloads {
22672268
assert_eq!(
22682269
p.tags.get(crate::tags::lambda::tags::COMPUTE_STATS_KEY),
2269-
Some(&"0".to_string()),
2270-
"_dd.compute_stats must be 0 when client_computed_stats is true"
2270+
Some(&"1".to_string()),
2271+
"_dd.compute_stats must be 1 (backend safety net) when compute_trace_stats_on_extension is false"
22712272
);
22722273
}
22732274
}

bottlecap/src/traces/trace_processor.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -351,14 +351,19 @@ impl TraceProcessor for ServerlessTraceProcessor {
351351
for tracer_payload in collection.iter_mut() {
352352
tracer_payload.tags.extend(tags.clone());
353353
// Tell the backend whether to compute stats:
354-
// - "1" (compute on backend) if neither the tracer nor the extension is computing them
355-
// - "0" (skip on backend) if the extension or the tracer has already computed them
356-
let compute_stats = if !config.compute_trace_stats_on_extension
357-
&& !header_tags.client_computed_stats
358-
{
359-
"1"
360-
} else {
354+
// - "1" (compute on backend) if the extension is NOT computing stats
355+
// - "0" (skip on backend) if the extension is computing stats
356+
//
357+
// We intentionally ignore client_computed_stats here to keep
358+
// the backend as a safety net. If the tracer claims it computed
359+
// stats but they don't actually reach the backend, setting this
360+
// to "0" would cause stats to disappear entirely.
361+
// This may lead to double-counting when the tracer also sends
362+
// stats, but that is preferable to zero stats.
363+
let compute_stats = if config.compute_trace_stats_on_extension {
361364
"0"
365+
} else {
366+
"1"
362367
};
363368
tracer_payload
364369
.tags
@@ -1400,7 +1405,7 @@ mod tests {
14001405
/// | Input: `compute_trace_stats_on_extension` | Input: `client_computed_stats` | Expected: `_dd.compute_stats` | Expected: Extension generates stats? |
14011406
/// |-------------------------------------------|--------------------------------|-------------------------------|--------------------------------------|
14021407
/// | `false` | `false` | `"1"` | No |
1403-
/// | `false` | `true` | `"0"` | No |
1408+
/// | `false` | `true` | `"1"` | No |
14041409
/// | `true` | `false` | `"0"` | Yes |
14051410
/// | `true` | `true` | `"0"` | No |
14061411
#[allow(clippy::unwrap_used)]
@@ -1414,11 +1419,12 @@ mod tests {
14141419
use libdd_trace_obfuscation::obfuscation_config::ObfuscationConfig;
14151420
use tokio::sync::mpsc;
14161421

1417-
// "_dd.compute_stats" is "1" only when neither side computes stats (backend must do it).
1418-
let expected_tag = if !compute_trace_stats_on_extension && !client_computed_stats {
1419-
"1"
1420-
} else {
1422+
// "_dd.compute_stats" is "1" when the extension is NOT computing stats (backend must do it).
1423+
// client_computed_stats does NOT affect this tag — the backend is the safety net.
1424+
let expected_tag = if compute_trace_stats_on_extension {
14211425
"0"
1426+
} else {
1427+
"1"
14221428
};
14231429
// The extension generates stats only when it is configured to do so and the tracer hasn't.
14241430
let expect_stats = compute_trace_stats_on_extension && !client_computed_stats;
@@ -1529,7 +1535,8 @@ mod tests {
15291535
#[tokio::test]
15301536
#[allow(clippy::unwrap_used)]
15311537
async fn test_compute_stats_tag_tracer_computes() {
1532-
// Tracer computed stats (Datadog-Client-Computed-Stats header set) → tag "0".
1538+
// Tracer computed stats (Datadog-Client-Computed-Stats header set) → tag "1".
1539+
// Backend still computes as a safety net; extension skips its own stats generation.
15331540
check_compute_stats_behavior(false, true).await;
15341541
}
15351542

0 commit comments

Comments
 (0)