Skip to content

feat(tracing): add DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT#3997

Open
MilanGarnier wants to merge 29 commits into
masterfrom
MilanGarnier/propagation-behavior-extract
Open

feat(tracing): add DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT#3997
MilanGarnier wants to merge 29 commits into
masterfrom
MilanGarnier/propagation-behavior-extract

Conversation

@MilanGarnier

Copy link
Copy Markdown
Contributor

Summary

Implements DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT (APMAPI-1941, feature-parity #353) for the PHP tracer, matching behavior already present in .NET, Python, Node, Java, Ruby, and Rust.

Three values:

  • continue (default): inherit upstream trace context unchanged — no behavior change
  • restart: start a fresh trace; the upstream context is captured as a span link with reason=propagation_behavior_extract
  • ignore: drop all extracted context including baggage and sampling priority

Design:

  • Single chokepoint: all extraction funnels through ddtrace_apply_distributed_tracing_result in distributed_tracing_headers.c, covering both request-init and consume_distributed_tracing_headers userland calls
  • restart path: when the root span doesn't yet exist at request-init time, the span link is queued in DDTRACE_G(pending_upstream_span_link) and consumed in ddtrace_open_span when the root span is created
  • Extracted ddtrace_build_span_link_from_result() from SpanLink::fromHeaders for reuse

Test plan

  • tests/ext/distributed_tracing/propagation_behavior_extract_continue.phpt — verifies trace_id inherited, no span link, baggage preserved
  • tests/ext/distributed_tracing/propagation_behavior_extract_restart.phpt — verifies fresh trace_id, span link with correct upstream ids and reason, baggage preserved, _dd.p.* not in link attributes
  • tests/ext/distributed_tracing/propagation_behavior_extract_ignore.phpt — verifies fresh trace_id, no span link, baggage dropped, sampling priority dropped
  • tests/ext/distributed_tracing/propagation_behavior_extract_config.phpt — verifies case-insensitive parsing, invalid value falls back to continue
  • CI phpt suite

Related

  • APMAPI-1941
  • Ruby: dd-trace-rb#5844
  • Rust: dd-trace-rs#248

🤖 Generated with Claude Code

@MilanGarnier MilanGarnier added the AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos label Jun 17, 2026
@datadog-official

datadog-official Bot commented Jun 17, 2026

Copy link
Copy Markdown

Pipelines  Tests

Unblock PR with BitsAI

⚠️ Warnings

🚦 45 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-php | test_web_custom: [8.2, fpm-fcgi]   View in Datadog   GitLab

🧪 1 Test failed

All test failures are known flaky.

❄️ Known flaky: testInstrumentation from custom-framework-autoloading-test.DDTrace\Tests\Integrations\Custom\Autoloaded\InstrumentationTest   View in Datadog
DDTrace\Tests\Integrations\Custom\Autoloaded\InstrumentationTest::testInstrumentation
Failed asserting that an array has the key 'logs_created'.

tests/Integrations/Custom/Autoloaded/InstrumentationTest.php:80
tests/Common/RetryTraitVersionGeneric.php:28

Not introduced in this PR.

DataDog/apm-reliability/dd-trace-php | test_extension_ci: [8.0]   View in Datadog   GitLab

DataDog/apm-reliability/dd-trace-php | test_extension_ci: [8.5]   View in Datadog   GitLab

View all 45 failed jobs.

ℹ️ Info

No other issues found (see more)

❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 54.08% (+0.00%)

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 0cd47dd | Docs | Datadog PR Page | Give us feedback!

MilanGarnier and others added 5 commits June 17, 2026 17:11
Adds the new `DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT` config key (values:
continue, restart, ignore) with a CUSTOM(INT) parser, the corresponding
C enum, and the supported-configurations.json entry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds three behaviors at the single extraction chokepoint
(ddtrace_apply_distributed_tracing_result):

- continue (default): inherit upstream trace context unchanged
- restart: start a fresh trace; upstream captured as a span link with
  reason=propagation_behavior_extract. When the root span doesn't exist
  yet at request-init time, the link is queued in
  DDTRACE_G(pending_upstream_span_link) and attached in ddtrace_open_span.
- ignore: drop all extracted context including baggage and sampling priority

Also extracts ddtrace_build_span_link_from_result() from the SpanLink
fromHeaders method so it can be reused by the restart path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers the three behaviors (continue, restart, ignore) and config
parsing (case-insensitive values, invalid value falls back to default).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@MilanGarnier
MilanGarnier force-pushed the MilanGarnier/propagation-behavior-extract branch from 6b7e259 to 3ca8428 Compare June 17, 2026 15:33
Comment thread tracer/distributed_tracing_headers.c Outdated
Comment thread tracer/distributed_tracing_headers.c Outdated
Comment thread tracer/distributed_tracing_headers.c Outdated
Comment thread tracer/functions.c
@MilanGarnier
MilanGarnier marked this pull request as ready for review June 23, 2026 12:15
@MilanGarnier
MilanGarnier requested a review from a team as a code owner June 23, 2026 12:15

@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: 19c7dafd9f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tracer/distributed_tracing_headers.c Outdated
Comment on lines +646 to +647
zend_hash_clean(&result->meta_tags);
zend_hash_clean(&result->propagated_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 Drop upstream tracestate on restart

When DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=restart receives headers that include x-datadog-origin or W3C tracestate, this path only clears trace id/parent/sampling and _dd.p.*; the code below still moves result->origin, result->tracestate, and tracestate_unknown_dd_keys onto the new root/global context, and outbound injection later re-emits them. In that scenario the restarted trace continues propagating upstream trace context instead of preserving only baggage plus the span link.

Useful? React with 👍 / 👎.

@MilanGarnier
MilanGarnier marked this pull request as draft June 29, 2026 11:41
@pr-commenter

pr-commenter Bot commented Jun 29, 2026

Copy link
Copy Markdown

Benchmarks [ tracer ]

Benchmark execution time: 2026-07-15 12:46:55

Comparing candidate commit ecd8519 in PR branch MilanGarnier/propagation-behavior-extract with baseline commit f99efe3 in branch master.

Found 0 performance improvements and 6 performance regressions! Performance is the same for 188 metrics, 0 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:EmptyFileBench/benchEmptyFileOverhead-opcache

  • 🟥 execution_time [+186.731µs; +339.109µs] or [+5.387%; +9.784%]

scenario:PDOBench/benchPDOOverhead

  • 🟥 execution_time [+10.708µs; +12.528µs] or [+4.127%; +4.829%]

scenario:PDOBench/benchPDOOverheadWithDBM

  • 🟥 execution_time [+10.434µs; +13.007µs] or [+4.017%; +5.007%]

scenario:PHPRedisBench/benchRedisOverhead

  • 🟥 execution_time [+40.761µs; +50.589µs] or [+3.951%; +4.904%]

scenario:SamplingRuleMatchingBench/benchRegexMatching1

  • 🟥 execution_time [+43.472ns; +99.128ns] or [+2.852%; +6.504%]

scenario:SamplingRuleMatchingBench/benchRegexMatching2-opcache

  • 🟥 execution_time [+267.775ns; +626.025ns] or [+2.111%; +4.935%]

@MilanGarnier MilanGarnier removed the AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos label Jul 6, 2026
@MilanGarnier
MilanGarnier marked this pull request as ready for review July 9, 2026 13:29
@MilanGarnier
MilanGarnier requested a review from a team as a code owner July 9, 2026 13:29
@MilanGarnier
MilanGarnier requested review from LobeTia and removed request for a team July 9, 2026 13:29

@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


P2 Badge Do not sample restarted traces from the upstream parent

When restart or ignore is used with a remote OTel parent, the code starts a fresh trace but still passes the extracted $parentContext to shouldSample(). Parent-based samplers will continue to honor the upstream sampled/drop flags, so these modes do not actually discard the incoming sampling context even though the C extraction path clears propagated priority to UNKNOWN; use a root/empty parent context for sampling in these modes.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tracer/distributed_tracing_headers.c Outdated
Comment thread src/DDTrace/OpenTelemetry/SpanBuilder.php
Comment on lines +226 to +229
$traceParent = "00-{$parentSpanContext->getTraceId()}-$parentId-01";
\DDTrace\consume_distributed_tracing_headers([
'traceparent' => $traceParent,
'tracestate' => (string) $samplingResultTraceState,

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 Preserve upstream OTel tracestate in restart links

When restart is triggered from an extracted OTel W3C context that carries tracestate or an unsampled trace flag, this synthetic header hard-codes sampled (-01) and uses the local sampler's tracestate instead of the parent span context's trace flags/tracestate. The C restart path builds the span link from these headers, so the link loses upstream vendor tracestate and records local sampler state rather than the linked upstream context.

Useful? React with 👍 / 👎.

Comment thread tracer/span.c Outdated

ddtrace_set_root_span_properties(root);

if (primary_stack && Z_TYPE(DDTRACE_G(pending_upstream_span_link)) == IS_OBJECT) {

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 Attach queued restart links to explicit trace roots

When request-init queues pending_upstream_span_link and the application creates its first root with DDTrace\start_trace_span() under DD_TRACE_GENERATE_ROOT_SPAN=0, start_trace_span() switches to a child root stack before ddtrace_open_span(), so primary_stack is false and this condition skips the queued link. In that flow restart starts a fresh trace but loses the upstream span link (or leaves it pending for a later unrelated primary-stack span), so explicit trace roots do not get the restart link promised by the new behavior.

Useful? React with 👍 / 👎.

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