Skip to content

feat(process-tags): signal service name source via svc.user/svc.auto#3921

Merged
Leiyks merged 3 commits into
masterfrom
leiyks/svc-source-process-tags
Jun 25, 2026
Merged

feat(process-tags): signal service name source via svc.user/svc.auto#3921
Leiyks merged 3 commits into
masterfrom
leiyks/svc-source-process-tags

Conversation

@Leiyks

@Leiyks Leiyks commented May 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the PHP-tracer side of RFC Signal Service Name Source via Process Tags.

Surfaces one of two mutually-exclusive process tags so the Datadog backend can distinguish user-set vs tracer-auto-resolved service names:

  • svc.user:true — when DD_SERVICE is non-empty (env var, INI, OTEL fallback, remote config)
  • svc.auto:<default_service_name> — when DD_SERVICE is empty and the tracer auto-resolved the name (e.g. web.request, script basename, cli.command)

Per the RFC caveats, no conclusions are drawn from the absence of both tags.

Architecture

Process tags are per-process; the active service name in PHP is per-request (mutable via ini_set, OTEL fallbacks, remote-config-driven INI updates). The earlier approach of baking svc.user/svc.auto into the static process_tags string leaked the latest request's override into subsequent FPM requests served by the same worker — addressed by the senior review.

Two cooperating paths now:

1. Per-span emission (traces) — ext/serializer.c

In ddtrace_serialize_span_to_rust_span, after attaching the static _dd.tags.process string, the per-span svc.user:true / svc.auto:<normalized-default> tag is appended based on the request-active state (get_DD_SERVICE() at serialization time, normalized via ddog_normalize_process_tag_value). Each span sees its own request's state — cross-request leak is impossible by construction.

2. Sidecar (telemetry / remote config / runtime info) — ext/sidecar.c

ddtrace_sidecar_update_process_tags now also calls the new libdatadog FFI ddog_sidecar_session_set_default_service_name(transport, default_service_name):

  • Empty default_service_name → sidecar treats it as user-defined and injects svc.user:true
  • Non-empty default_service_name (pre-normalized) → sidecar injects svc.auto:<default>

The sidecar performs the injection at payload emission time (via the new SessionInfo::process_tags_with_svc_source() helper), so all of telemetry / remote config / runtime_info / stats payloads see consistent svc.* tagging without the tracer having to bake it into the static string.

The libdatadog half lives in DataDog/libdatadog#2053; the submodule pointer in this PR is bumped to that branch tip and must be moved to the merged SHA before this PR merges.

Behavior

Source of service name Span _dd.tags.process Sidecar-emitted process_tags
DD_SERVICE=... env var svc.user:true svc.user:true
datadog.service=... system INI svc.user:true svc.user:true
OTEL_SERVICE_NAME=... (fallback) svc.user:true svc.user:true
OTEL_RESOURCE_ATTRIBUTES=service.name=... (fallback) svc.user:true svc.user:true
ini_set('datadog.service', ...) (per-request) svc.user:true (this span only) unchanged (process-level)
No DD_SERVICE set svc.auto:<basename>.php (CLI) / svc.auto:web.request (web) same

Companion PR

DataDog/libdatadog#2053 — exposes ddog_sidecar_session_set_default_service_name and the sidecar-side injection logic. This PR's CI will be red on the submodule pointer until #2053 lands.

@datadog-prod-us1-3

datadog-prod-us1-3 Bot commented May 27, 2026

Copy link
Copy Markdown

Pipelines  Tests

Fix all issues with BitsAI

⚠️ Warnings

🚦 7 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-php | ASAN test_c: [8.3, arm64]   View in Datadog   GitLab

DataDog/apm-reliability/dd-trace-php | php-app.arm64.DOC: [public.ecr.aws/lts/ubuntu:22.04, linux/arm64, 7.0]   View in Datadog   GitLab

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

View all 7 failed jobs.

❄️ 1 New flaky test detected

testSvcTagDoesNotLeakBetweenRequests from custom-framework-autoloading-test.DDTrace\Tests\Integrations\Custom\Autoloaded\ProcessTagsWebTest   View in Datadog
DDTrace\Tests\Integrations\Custom\Autoloaded\ProcessTagsWebTest::testSvcTagDoesNotLeakBetweenRequests
Error: Call to undefined method DDTrace\Tests\Integrations\Custom\Autoloaded\ProcessTagsWebTest::assertStringContainsString()

tests/Integrations/Custom/Autoloaded/ProcessTagsWebTest.php:82
tests/Common/RetryTraitVersionSpecific70.php:28
phpvfscomposer://tests/vendor/phpunit/phpunit/phpunit:52

New test introduced in this PR is flaky.

View in Flaky Test Management

ℹ️ Info

No other issues found (see more)

🧪 All tests passed

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 54.08% (-0.03%)

Useful? React with 👍 / 👎

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

@pr-commenter

pr-commenter Bot commented May 27, 2026

Copy link
Copy Markdown

Benchmarks [ tracer ]

Benchmark execution time: 2026-06-25 21:22:19

Comparing candidate commit d322c40 in PR branch leiyks/svc-source-process-tags with baseline commit 9236cdf in branch master.

Found 0 performance improvements and 3 performance regressions! Performance is the same for 190 metrics, 1 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:MessagePackSerializationBench/benchMessagePackSerialization

  • 🟥 execution_time [+4.224µs; +4.856µs] or [+4.068%; +4.676%]

scenario:MessagePackSerializationBench/benchMessagePackSerialization-opcache

  • 🟥 execution_time [+3.277µs; +4.883µs] or [+3.151%; +4.694%]

scenario:TraceFlushBench/benchFlushTrace-opcache

  • 🟥 execution_time [+50.318µs; +80.882µs] or [+2.101%; +3.377%]

Comment thread ext/serializer.c Outdated
Comment thread ext/sidecar.c Outdated
@Leiyks Leiyks force-pushed the leiyks/svc-source-process-tags branch 5 times, most recently from f9c7288 to 3d7cb6d Compare June 18, 2026 12:06
Comment thread tracer/serializer.c Outdated
Comment thread tracer/serializer.c Outdated
Comment thread tracer/serializer.c Outdated
if (!zend_hash_str_exists(root_meta, ZEND_STRL("_dd.svc_src"))) {
zval root_svc;
ZVAL_UNDEF(&root_svc);
datadog_convert_to_string(&root_svc, &span->root->property_service);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

By the way, why to a zval, datadog_convert_to_str would do it too. You don't need the zval actually.

Comment thread ext/sidecar.c
Comment thread ext/sidecar.c Outdated

@bwoebi bwoebi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks correct from logic, just a few code nits

@bwoebi

bwoebi commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

And there's one phpt test with wrong expectations, the code is correct.

@Leiyks Leiyks marked this pull request as ready for review June 25, 2026 15:17
@Leiyks Leiyks requested review from a team as code owners June 25, 2026 15:17
@Leiyks Leiyks requested review from leoromanovsky and typotter and removed request for a team June 25, 2026 15:17

@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: 2c8a3f1e2c

ℹ️ 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 thread ext/sidecar.c
Comment on lines +158 to +164
zend_string *default_svc = datadog_default_service_name();
if (default_svc) {
const char *normalized = ddog_normalize_process_tag_value(dd_zend_string_to_CharSlice(default_svc));
if (normalized) {
datadog_ffi_try("Failed updating sidecar default service name",
ddog_sidecar_session_set_default_service_name(&DATADOG_G(sidecar),
(ddog_CharSlice){ .ptr = normalized, .len = strlen(normalized) }));

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 Reapply svc.auto state on reconnect

This only sends the new default_service_name from datadog_sidecar_update_process_tags(), which is called from the one-time first-RINIT path and the explicit reload path. Fresh sidecar sessions created later by dd_sidecar_on_reconnect()/datadog_sidecar_ensure_active() run dd_sidecar_post_connect() and resend UST, but never call this setter, so after a sidecar restart or a late worker-thread connection the sidecar has no default service name and its telemetry/runtime-info process tags lose svc.auto:<default> until process tags are manually reloaded.

Useful? React with 👍 / 👎.

Comment thread ext/sidecar.c
Comment on lines +828 to +830
if (get_global_DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED()) {
datadog_sidecar_refresh_user_service_defined();
}

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 Refresh svc source before sidecar emissions

Refreshing the user_service_defined flag after ddtrace_sidecar_submit_span_data_direct_defaults() is too late for the sidecar payloads that function can emit: it sends universal service tags and, when !root, may immediately flush buffered telemetry. In FPM/vhost or remote-config cases where the resolved DD_SERVICE source changes between requests, those first payloads for the new request are tagged using the previous request's svc.user/svc.auto state.

Useful? React with 👍 / 👎.

gh-worker-dd-mergequeue-cf854d Bot pushed a commit to DataDog/libdatadog that referenced this pull request Jun 25, 2026
)

## Summary

Adds `ddog_sidecar_session_set_default_service_name(transport, default_service_name)` so tracers can communicate whether the application's service name was user-set or tracer-auto-resolved.

The sidecar stores this per-session and **injects `svc.user:true`** or **`svc.auto:<default>`** into outgoing payloads (telemetry, remote config, runtime info) at emission time — eliminating the need for tracers to bake svc.* into their static process_tags string (which would conflict with request-local service mutations in languages like PHP).

Implements the sidecar half of the RFC ["Signal Service Name Source via Process Tags"](https://docs.google.com/document/d/1c47iSTWxIOHMHfZTF2nT9xfyQaIBP9KJvI9sRn5SvpM).

## FFI

```c
ddog_MaybeError ddog_sidecar_session_set_default_service_name(
    struct ddog_SidecarTransport **transport,
    struct ddog_CharSlice default_service_name);
```

- Empty `default_service_name` → `ServiceNameSource::UserDefined` → sidecar emits `svc.user:true`
- Non-empty `default_service_name` (pre-normalized via `ddog_normalize_process_tag_value`) → `ServiceNameSource::AutoResolved(name)` → sidecar emits `svc.auto:<name>`
- Never called → no svc.* tag emitted (matches the RFC: *"no conclusions should be drawn from the absence of both"*)

## Internals

- New `ServiceNameSource` enum in `service/mod.rs`
- New `Arc<Mutex<Option<ServiceNameSource>>>` field on `SessionInfo`
- New `SessionInfo::process_tags_with_svc_source()` helper — single source of truth used by all consumers of session process_tags (telemetry, RC, runtime_info, sidecar_server)
- RPC method + outbox slot + sender method + blocking helper, mirroring `set_session_process_tags`

## Companion PR

[DataDog/dd-trace-php#3921](DataDog/dd-trace-php#3921) — PHP tracer wires `ddog_sidecar_session_set_default_service_name` in `ext/sidecar.c` and bumps the submodule to a commit including this change.

Co-authored-by: alexandre.rulleau <alexandre.rulleau@datadoghq.com>
@Leiyks Leiyks force-pushed the leiyks/svc-source-process-tags branch from a11d2ce to 7694af0 Compare June 25, 2026 19:58
Leiyks and others added 3 commits June 25, 2026 22:03
Implements the PHP-tracer side of RFC "Signal Service Name Source via
Process Tags". Surfaces one of two mutually-exclusive process tags so
the backend can distinguish user-set vs tracer-auto-resolved service
names:

- svc.user:true  — DD_SERVICE non-empty (env, INI, OTEL fallback, RC)
- svc.auto:<name> — DD_SERVICE empty; tracer auto-resolved the default

Per the RFC caveats, no conclusions are drawn from the absence of both.

In ddtrace_serialize_span_to_rust_span's is_first_span block, the
auto-resolved default name is read directly from the root span's
property_service when its _dd.svc_src is absent (Service Override
Source Attribution RFC: cleared svc_src ↔ service is the global
default), avoiding a second pass through datadog_default_service_name().
Each span sees its own request's state — no FPM cross-request leak.

datadog_sidecar_update_process_tags now also calls
ddog_sidecar_session_set_default_service_name(transport, …):

- Empty CharSlice → sidecar injects svc.user:true
- Normalized default → sidecar injects svc.auto:<default>

Injection happens at payload emission time in libdatadog (companion PR
DataDog/libdatadog#2053), so telemetry / remote-config / runtime-info /
stats payloads all see consistent svc.* tagging without baking it into
the static process_tags string.

- New CLI .phpt tests covering svc.user, svc.auto, OTEL fallback as
  user-defined, and ini_set-driven runtime mutation.
- New web-SAPI test ProcessTagsWebTest::testSvcTagDoesNotLeakBetweenRequests
  proving the per-span design holds across FPM workers.
- Existing process_tags.phpt / telemetry_process_tags.phpt updated to
  expect the appended svc.auto tag.
Co-authored-by: Bob Weinand <bobwei9@hotmail.com>
…expectation

- serializer.c: drop the temp zval and use datadog_convert_to_str directly
  (zend_string out, released by caller) per review.
- process_tags.phpt: the test sets \$parent_span->service = 'test_service'
  manually, so the root span carries _dd.svc_src='m' (Service Override
  Source Attribution RFC); the per-span emission correctly skips svc.auto
  because the service isn't the global default. Remove the spurious
  svc.auto:process_tags.php from the expected output.
@Leiyks Leiyks force-pushed the leiyks/svc-source-process-tags branch from 7694af0 to d322c40 Compare June 25, 2026 20:03
@Leiyks Leiyks merged commit 18ca946 into master Jun 25, 2026
2126 of 2154 checks passed
@Leiyks Leiyks deleted the leiyks/svc-source-process-tags branch June 25, 2026 20:37
@github-actions github-actions Bot added this to the 1.23.0 milestone Jun 25, 2026
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