Skip to content

feat(tracer): align C span code with v1-compatible stub API (v2 groundwork)#4046

Draft
Leiyks wants to merge 9 commits into
masterfrom
leiyks/dd-trace-php-v2
Draft

feat(tracer): align C span code with v1-compatible stub API (v2 groundwork)#4046
Leiyks wants to merge 9 commits into
masterfrom
leiyks/dd-trace-php-v2

Conversation

@Leiyks

@Leiyks Leiyks commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What & why

Part of APMLP-1197 (dd-trace-php v1/v2 roadmap). The v1-compatible span
stub API (tracer/ddtrace.stub.php) landed earlier on this branch; this
brings the tracer C code into consistency with it. The v04 wire
format is intentionally kept byte-identical — libdatadog's v1 span
FFI is not yet available, so no wire/protocol switch happens here.

Changes

  • arginfo + struct offsets: regenerated ddtrace_arginfo.h; reordered
    ddtrace_span_properties / ddtrace_root_span_data / ddtrace_span_stack in
    span.h to match the new stub property order (offset parity is
    ABI-critical — a mismatch is silent data corruption).
  • SpanEvent/SpanLink: dropped JsonSerializable + jsonSerialize();
    relocated the serialization into serializer.c → _dd.span_links / events
    meta blobs are byte-identical.
  • component/spanKind: now sourced from the new SpanData properties and
    translated back to meta (component / span.kind) at serialize time, so
    the wire is unchanged; new SpanKind class registered.
  • Dead code: removed stub-removed functions + DD_TRACE_WARN_LEGACY_DD_TRACE;
    finished the userland integration-analytics removal (Integration.php +
    call sites); deleted orphaned tests and rewrote dd_trace_reset helper
    tests without weakening coverage.

Deliberate scope decisions

  • Wire stays v04 (no native ddog_span_new_link/event switch) — deferred
    until libdatadog v1 FFI is ready.
  • New properties attributes / samplingMechanism / hostname get struct
    slots (to keep offsets correct) but are left unwired for now.
  • ddtrace_arginfo.h keeps deprecations doc-only (hand-stripped the
    ZEND_ACC_DEPRECATED flags current gen_stub now emits) to match the
    committed convention and avoid runtime E_DEPRECATED on hot paths;
    reconciling gen_stub output is a separate follow-up.

Testing

Container build clean; 22 span-shape + 26 curl integration + 30
ConfigurationTest tests passing; links/events wire proven byte-identical.

Draft: pending full CI.


Relationship to the V1 Efficient Trace Payload protocol

This change aligns the userland span API and tracer C with the approved Efficient Trace Payload Protocol RFC and the WIP Tracer Implementation Guide for V1 Trace Payloads, without changing the wire format. The v04 payload remains byte-for-byte the sender's output because the vendored libdatadog exposes the v1 types (libdd-trace-utils/src/span/v1/mod.rs, pb.idx.rs) only internally — the PHP FFI (components-rs/bytes.rs) still binds ddog_SpanBytes = v04::SpanBytes, so there is no v1 serializer to drive yet.

Implemented now (v1-shaped API, v04 wire):

  • Promoted span fields env, version, component, kind exist as SpanData properties (RFC idx.Span fields 13–16). Internal C producers set the property; the serializer translates component/spanKind back into meta["component"]/meta["span.kind"] so v04 is unchanged.
  • DDTrace\SpanKind constants match the OTEL SpanKind enum exactly (RFC field 16: kind = otel.SpanKind): UNSPECIFIED=0, INTERNAL=1, SERVER=2, CLIENT=3, PRODUCER=4, CONSUMER=5.

Deferred until the v1 FFI lands (groundwork present, unwired):

  • Config gate & negotiation (guide "Enablement"): enable only when DD_TRACE_AGENT_PROTOCOL_VERSION ∈ {"1.0","1"} and the agent /info advertises /v1.0/traces, with mandatory fallback to v04 when absent. Intentionally not added yet to avoid a no-op config key with no serializer behind it.
  • AnyValue attributes (RFC idx.AnyValue): SpanData::$attributes / SpanStack::$attributes reserved; the initial implementation will use String/Float/Bytes and fold meta/metrics/meta_struct into a single typed map (guide L14–16).
  • Chunk-level promotions: 128-bit big-endian traceID bytes (RFC idx.TraceChunk field 6 / guide L33) and samplingMechanism as a bare uint32 replacing _dd.p.dm:-N (RFC field 7 / guide L36) — RootSpanData::$samplingMechanism reserved.
  • hostname promoted to the TracerPayload (RFC field 8) — RootSpanData::$hostname reserved; v04 keeps emitting _dd.hostname.
  • error as bool (RFC idx.Span field 8) and 429 / Datadog-Send-Real-Http-Status back-off (guide L21–24) — trace sender untouched.

Note for the v1 serializer work: the promoted fields have a split source of truth (property for C producers, meta for userland integrations such as OTel SpanBuilder and Kafka). The v1 serializer must read each promoted value from property-or-meta and remove it from the attributes map, otherwise a v1 payload would carry the value both in the promoted field and in attributes (guide L19).

Leiyks added 3 commits July 16, 2026 14:08
Signed-off-by: Alexandre Rulleau <alexandre.rulleau@datadoghq.com>
Signed-off-by: Alexandre Rulleau <alexandre.rulleau@datadoghq.com>
Bring the tracer C code into consistency with the already-rewritten
v1-compatible span stub (tracer/ddtrace.stub.php), keeping the v04 wire
format byte-identical (libdatadog v1 FFI is not yet available).

- Regenerate ddtrace_arginfo.h from the stub; reorder the
  ddtrace_span_properties / ddtrace_root_span_data / ddtrace_span_stack
  structs to match the new property declaration order (PHP property
  offsets are bound to these C struct fields).
- Drop SpanEvent/SpanLink JsonSerializable + jsonSerialize(); relocate
  that logic into serializer.c so the _dd.span_links / events meta blobs
  are produced with identical bytes.
- Make component/spanKind sourced from the new SpanData properties,
  translated back into meta (span.kind/component) at serialize time so
  the v04 wire is unchanged. Register the new SpanKind class.
- Remove dead code for stub-removed functions and the
  DD_TRACE_WARN_LEGACY_DD_TRACE config key; complete the userland
  integration-analytics removal (Integration.php + call sites); delete
  tests orphaned by removed functions and rewrite the dd_trace_reset
  helper tests without gutting their coverage.

Refs APMLP-1197.
@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Jul 17, 2026

Copy link
Copy Markdown

Pipelines  Tests

Unblock PR with BitsAI

⚠️ Warnings

🚦 4 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-php | check-big-regressions   View in Datadog   GitLab

DataDog/apm-reliability/dd-trace-php | min install tests   View in Datadog   GitLab

DataDog/apm-reliability/dd-trace-php | appsec integration tests: [test8.0-release]   View in Datadog   GitLab

View all 4 failed jobs.

ℹ️ Info

No other issues found (see more)

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 54.11% (-0.01%)

Useful? React with 👍 / 👎

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

@pr-commenter

pr-commenter Bot commented Jul 17, 2026

Copy link
Copy Markdown

Benchmarks [ tracer ]

Benchmark execution time: 2026-07-22 15:28:37

Comparing candidate commit 55c6254 in PR branch leiyks/dd-trace-php-v2 with baseline commit a70313e in branch master.

Some scenarios are present only in baseline or only in candidate runs. If you didn't create or remove some scenarios in your branch, this maybe a sign of crashed benchmarks 💥💥💥
Check Gitlab CI job log to find if any benchmark has crashed.

Scenarios present only in baseline:

  • MessagePackSerializationBench/benchMessagePackSerialization-opcache
  • MessagePackSerializationBench/benchMessagePackSerialization

Found 5 performance improvements and 11 performance regressions! Performance is the same for 174 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:ContextPropagationBench/benchExtractHeaders64Bit-opcache

  • 🟩 execution_time [-766.284ns; -685.716ns] or [-39.438%; -35.292%]

scenario:ContextPropagationBench/benchExtractTraceContext128Bit-opcache

  • 🟩 execution_time [-848.467ns; -727.533ns] or [-32.224%; -27.631%]

scenario:ContextPropagationBench/benchExtractTraceContext64Bit-opcache

  • 🟥 execution_time [+747.793ns; +804.207ns] or [+85.560%; +92.014%]

scenario:ContextPropagationBench/benchInject128Bit-opcache

  • 🟥 execution_time [+661.358ns; +718.642ns] or [+39.296%; +42.700%]

scenario:ContextPropagationBench/benchInject64Bit-opcache

  • 🟥 execution_time [+724.538ns; +1065.462ns] or [+5.501%; +8.089%]

scenario:EmptyFileBench/benchEmptyFileBaseline-opcache

  • 🟥 execution_time [+90.167µs; +273.473µs] or [+2.785%; +8.446%]

scenario:HookBench/benchHookOverheadTraceMethod-opcache

  • 🟥 mem_peak [+64.184KB; +64.184KB] or [+3.013%; +3.013%]

scenario:PDOBench/benchPDOOverhead

  • 🟥 execution_time [+7.334µs; +10.784µs] or [+2.857%; +4.200%]

scenario:PDOBench/benchPDOOverheadWithDBM

  • 🟥 execution_time [+5.953µs; +9.174µs] or [+2.314%; +3.565%]

scenario:SamplingRuleMatchingBench/benchRegexMatching1-opcache

  • 🟩 execution_time [-157.316ns; -84.684ns] or [-9.029%; -4.860%]

scenario:SamplingRuleMatchingBench/benchRegexMatching2-opcache

  • 🟩 execution_time [-124.140ns; -54.860ns] or [-7.211%; -3.187%]

scenario:SamplingRuleMatchingBench/benchRegexMatching3-opcache

  • 🟩 execution_time [-115.652ns; -56.148ns] or [-6.696%; -3.251%]

scenario:SpanBench/benchOpenTelemetryAPI

  • 🟥 mem_peak [+978.753KB; +978.762KB] or [+2.052%; +2.052%]

scenario:SpanBench/benchOpenTelemetryAPI-opcache

  • 🟥 mem_peak [+897.186KB; +897.201KB] or [+2.006%; +2.006%]

scenario:SpanBench/benchOpenTelemetryInteroperability-opcache

  • 🟥 execution_time [+4.727µs; +8.160µs] or [+2.654%; +4.581%]

scenario:TraceAnnotationsBench/benchTraceAnnotationOverhead-opcache

  • 🟥 mem_peak [+64.184KB; +64.184KB] or [+2.992%; +2.992%]

Leiyks added 6 commits July 20, 2026 17:43
…DD_TRACE_WARN_LEGACY_DD_TRACE

The config key DD_TRACE_WARN_LEGACY_DD_TRACE was removed from
tracer/configuration.h, but metadata/supported-configurations.json was not
regenerated, causing the 'Configuration Consistency' CI job to fail. Ran
tooling/generate-supported-configurations.sh to sync.
RootSpanData extends SpanData, which already declares $env and $version.
The v1-stub alignment accidentally re-declared them on RootSpanData (they
were never on RootSpanData on master, and the ddtrace_root_span_data C struct
has no separate env/version slots -- they are inherited SpanData slots). On
PHP < 8.1 this redundant child redeclaration corrupts the RootSpanData
property table: env/version are var_dump'd twice and propagatedTags loses its
default, breaking tests/ext/active_span.phpt and span_clone.phpt on 7.0-8.0
(they passed on 8.1+). Removing the redeclaration from the stub and arginfo
aligns stub=arginfo=C struct and restores consistent output across all
versions; env/version remain available on RootSpanData via inheritance.
…on PHP 7

The new `attributes` property (stub default `= []`) was not materialized at
span/stack creation. On PHP < 8.0 array-typed property defaults become null
(see the ZVAL_EMPTY_ARRAY shim in functions.c) and are only lazily turned into
arrays when touched; since `attributes` is never touched during a plain span
lifecycle it var_dump'd as NULL on 7.0-7.4 (array(0){} on 8.0+), breaking
tests/ext/active_span.phpt and span_clone.phpt. Force-materialize it in
ddtrace_init_span and dd_alloc_span_stack (guarded to PHP < 8.0) so it is a
consistent empty array on every supported version, matching its stub default.
The chore that dropped integration (App Analytics) auto-tagging removed
Integration::addTraceAnalyticsIfEnabled and the per-integration
DD_TRACE_<integration>_ANALYTICS_* config, so the _dd1.sr.eausr metric is no
longer auto-added to integration spans. Align the tests:

- Delete the 25 TraceSearchConfigTest.php files (Laravel, Lumen, Symfony,
  ZendFramework, Custom) whose sole subject was the removed per-integration
  trace-analytics config.
- Drop the per-integration analytics setup + _dd1.sr.eausr metric assertions
  from the mixed PDO and SQLSRV integration tests, preserving all other span
  coverage.
- Remove Curl's dedicated testTraceAnalytics method + its data provider
  (per-integration analytics config matrix) and the stale DD_CURL_ANALYTICS_ENABLED
  teardown-cleanup entries in Curl/Guzzle.

The user-facing App Analytics API (Span::setMetric(Tag::ANALYTICS_KEY) ->
TraceAnalyticsProcessor, Tag::ANALYTICS_KEY constant) is intentionally kept by
this branch, so its tests (SpanTest, TraceAnalyticsProcessorTest,
UserAvailableConstantsTest, OpenTelemetry, ext/test_special_attributes) are
left intact.
Comment-only cleanup of the C span-consistency changes: trim the
multi-line explanatory blocks in serializer.c, span.c,
handlers_httpstreams.c and tracer_telemetry.c down to concise 1-2 line
comments. No behavior change.
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.

1 participant