Skip to content

feat: publish Linux OTel contexts - #4077

Open
morrisonlevi wants to merge 39 commits into
masterfrom
feat/otel-context
Open

feat: publish Linux OTel contexts#4077
morrisonlevi wants to merge 39 commits into
masterfrom
feat/otel-context

Conversation

@morrisonlevi

@morrisonlevi morrisonlevi commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

PROF-15487

Description

Publish standard OTel process and thread contexts from the tracer and make the profiler consume them for runtime identity and effective service metadata. Handle span, stack, Fiber, configuration, and fork lifecycle changes while retaining the legacy non-Linux path.

WIP for appsec.

Reviewer checklist

  • Test coverage seems ok.
  • Appropriate labels assigned.

@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented Jul 29, 2026

Copy link
Copy Markdown

Pipelines  Tests

Unblock PR with BitsAI

⚠️ Warnings

🚦 9 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-php | System Tests: [parametric]   View in Datadog   GitLab

🧪 2 Tests failed

tests.parametric.test_otel_api_interoperability.Test_Otel_API_Interoperability.test_concurrent_traces_in_order[library_env0, parametric-php] from system_tests_suite   View in Datadog
requests.exceptions.HTTPError: 500 Server Error: Internal Server Error for url: http://localhost:4505/trace/otel/start_span

self = <tests.parametric.test_otel_api_interoperability.Test_Otel_API_Interoperability object at 0x7f9c870d58e0>
test_agent = <utils.docker_fixtures._test_agent.TestAgentAPI object at 0x7f9c861617c0>
test_library = <utils.docker_fixtures._test_clients._test_client_parametric.ParametricTestClientApi object at 0x7f9c864fa180>

    def test_concurrent_traces_in_order(self, test_agent: TestAgentAPI, test_library: APMLibrary):
        """- Basic concurrent traces and spans"""
        with (
            test_library,
...
tests.parametric.test_otel_api_interoperability.Test_Otel_API_Interoperability.test_otel_start_after_datadog_span[library_env0, parametric-php] from system_tests_suite   View in Datadog
requests.exceptions.HTTPError: 500 Server Error: Internal Server Error for url: http://localhost:4501/trace/otel/start_span

self = <tests.parametric.test_otel_api_interoperability.Test_Otel_API_Interoperability object at 0x7f91697c5a60>
test_agent = <utils.docker_fixtures._test_agent.TestAgentAPI object at 0x7f9168691970>
test_library = <utils.docker_fixtures._test_clients._test_client_parametric.ParametricTestClientApi object at 0x7f9168b75670>

    def test_otel_start_after_datadog_span(self, test_agent: TestAgentAPI, test_library: APMLibrary):
        """- Start a span using the OTel API while a span created using the Datadog API already exists"""
        with test_library:
            with (
...

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.1]   View in Datadog   GitLab

View all 9 failed jobs.

ℹ️ Info

No other issues found (see more)

❄️ No new flaky tests detected

🔄 Datadog auto-retried 2 jobs - 0 passed on retry View in Datadog

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 60.63% (-0.00%)

Useful? React with 👍 / 👎

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

@pr-commenter

pr-commenter Bot commented Jul 30, 2026

Copy link
Copy Markdown

Benchmarks [ tracer ]

Benchmark execution time: 2026-08-06 04:13:57

Comparing candidate commit 995a137 in PR branch feat/otel-context with baseline commit 89b12c4 in branch master.

Found 8 performance improvements and 5 performance regressions! Performance is the same for 180 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-opcache

  • 🟥 execution_time [+3.013µs; +3.807µs] or [+2.787%; +3.522%]

scenario:PDOBench/benchPDOOverheadWithDBM-opcache

  • 🟥 execution_time [+6.195µs; +7.963µs] or [+2.048%; +2.632%]

scenario:SpanBench/benchDatadogAPI-opcache

  • 🟥 execution_time [+2.362µs; +5.163µs] or [+3.008%; +6.575%]

scenario:SpanBench/benchOpenTelemetryAPI

  • 🟩 execution_time [-423.752µs; -408.535µs] or [-53.401%; -51.483%]
  • 🟩 mem_peak [-6.932MB; -6.932MB] or [-14.510%; -14.510%]

scenario:SpanBench/benchOpenTelemetryAPI-opcache

  • 🟩 execution_time [-396.042µs; -388.626µs] or [-53.477%; -52.476%]
  • 🟩 mem_peak [-6.829MB; -6.829MB] or [-15.270%; -15.270%]

scenario:SpanBench/benchOpenTelemetryInteroperability

  • 🟥 mem_peak [+643.605KB; +643.613KB] or [+2.232%; +2.232%]

scenario:SpanBench/benchOpenTelemetryInteroperability-opcache

  • 🟥 mem_peak [+641.568KB; +641.576KB] or [+2.478%; +2.478%]

scenario:TraceFlushBench/benchFlushTrace

  • 🟩 execution_time [-1.169ms; -1.152ms] or [-77.391%; -76.296%]

scenario:TraceFlushBench/benchFlushTrace-opcache

  • 🟩 execution_time [-1.167ms; -1.155ms] or [-77.130%; -76.366%]

scenario:TraceSerializationBench/benchSerializeTrace

  • 🟩 execution_time [-64.679µs; -52.121µs] or [-12.634%; -10.181%]

scenario:TraceSerializationBench/benchSerializeTrace-opcache

  • 🟩 execution_time [-58.647µs; -48.353µs] or [-13.722%; -11.313%]

@pr-commenter

pr-commenter Bot commented Jul 30, 2026

Copy link
Copy Markdown

Benchmarks [ appsec ]

Benchmark execution time: 2026-07-30 13:31:53

Comparing candidate commit 6b3710c in PR branch feat/otel-context with baseline commit 397bd9e in branch master.

Found 0 performance improvements and 0 performance regressions! Performance is the same for 12 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 ----------------------------------'

Publish standard OTel process and thread contexts from the tracer and make the profiler consume them for runtime identity and effective service metadata. Handle span, stack, Fiber, configuration, and fork lifecycle changes while retaining the legacy non-Linux path.
@github-actions github-actions Bot added the profiling Relates to the Continuous Profiler label Jul 30, 2026
@pr-commenter

pr-commenter Bot commented Jul 30, 2026

Copy link
Copy Markdown

Benchmarks [ profiler ]

Benchmark execution time: 2026-08-06 03:08:00

Comparing candidate commit 995a137 in PR branch feat/otel-context with baseline commit 89b12c4 in branch master.

Found 2 performance improvements and 4 performance regressions! Performance is the same for 23 metrics, 7 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:php-profiler-exceptions-control

  • 🟥 cpu_user_time [+2.855ms; +5.844ms] or [+5.499%; +11.256%]
  • 🟩 cpu_system_time [-6.400ms; -3.883ms] or [-22.181%; -13.457%]

scenario:php-profiler-timeline-memory-with-profiler

  • 🟥 execution_time [+46.831ms; +69.261ms] or [+4.242%; +6.274%]
  • 🟩 cpu_usage_percentage [-3.488%; -2.615%]

scenario:php-profiler-timeline-memory-with-profiler-and-timeline

  • 🟥 execution_time [+47.504ms; +62.144ms] or [+3.646%; +4.770%]

scenario:walk_stack/1

  • 🟥 wall_time [+329.591ns; +334.168ns] or [+2.673%; +2.710%]

@morrisonlevi
morrisonlevi marked this pull request as ready for review July 30, 2026 18:59
@morrisonlevi
morrisonlevi requested review from a team as code owners July 30, 2026 18:59
@morrisonlevi
morrisonlevi requested review from pavlokhrebto and typotter and removed request for a team July 30, 2026 18:59

@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: 8d01de4429

ℹ️ 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 profiling/src/process_context/linux.rs
Comment on lines +1850 to +1855
ThreadContextRead::Inactive => (
unsafe { libc::pthread_self() as i64 },
0,
0,
ProcessIdentity::default(),
),

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 Fall back to the legacy context when TLS is unavailable

On Linux, when the profiler is loaded with a ddtrace build that still exports ddtrace_get_profiling_context but does not export the new otel_thread_ctx_v1 symbol—for example, during an independent or rolling upgrade of these separately loadable extensions—this inactive branch emits zero span IDs instead of using the legacy API that startup still locates. All profiler samples then lose trace correlation even though a compatible correlation API is available; use the legacy context when the TLS symbol or record is unavailable.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The tracer and profiler are meant to be upgraded in tandem. They are not designed to be upgraded piecemeal and in the future this will be impossible when the extensions are merged.

Comment thread profiling/src/profiling/mod.rs Outdated
Service, environment, version, and configured hostname can vary by request. Omit them from the process-wide context while retaining their thread-context key mapping and active values.
@morrisonlevi morrisonlevi changed the title feat(profiling): publish Linux OTel contexts feat: publish Linux OTel contexts Aug 3, 2026
Comment thread tracer/otel_context.c Outdated
Comment thread tracer/ddtrace.c Outdated
Comment thread tracer/distributed_tracing_headers.c Outdated
Comment thread tracer/functions.c
Comment on lines +431 to +435
#ifdef __linux__
if (object->ce == ddtrace_ce_root_span_data) {
ddtrace_detach_otel_thread_context_for_root(ROOTSPANDATA(object));
}
#endif

@bwoebi bwoebi Aug 5, 2026

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.

I need to read more code first, but this definitely is not the correct place for this. This code is called for any freed RootSpanData, regardless of whether it's active (in fact when it's reaching this code, it's should not be active anymore).

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.

I think this can just be outright removed. Existing code handles this already.

Comment thread tracer/functions.c Outdated
Comment thread tracer/otel_context.c
Comment on lines +98 to +100
if (UNEXPECTED(root->otel_context_attributes_generation != DDTRACE_G(otel_context_attributes_generation))) {
ddtrace_otel_refresh_attribute_values(root);
}

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.

This is the sole place otel_context_attributes_generation gets consumed.
But why? Why do we need to call ddtrace_otel_refresh_attribute_values() in this case?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The generation check is necessary. It is a lazy invalidation mechanism for the OTel records embedded in multiple root spans.

Each span stack/root owns a separate cached Thread Context record. Nested/manual stacks and suspended fibers can therefore hold records that are not currently attached. Service, environment, or version changes
can affect multiple records because non-entrypoint roots inherit metadata from the entrypoint root.

On a metadata change:

  1. The active record is refreshed.
  2. The global generation is incremented.
  3. Other records remain stale while inactive.
  4. When one is attached again, the generation mismatch causes it to refresh.

Removing the refresh from ddtrace_otel_attach_stack() would expose stale metadata after switching stacks. This is covered by tests/ext/otel_thread_context_stack_switch.phpt, particularly switching back to
mainRoot after updating its metadata while otherRoot is active.


Probably should leave a code comment here then.

@bwoebi bwoebi Aug 5, 2026

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.

But then it should only increment then when the actual entrypoint root span is updated, and not any root span in ddtrace_otel_update_attribute_values.

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.

Also, should we attach simply the actual entrypoint root span to root_span_data as pointer? And compare the local generation vs the generation on the entrypoint root span? Instead of having a thread global generation counter, which invalidates much more than it needs to.

Comment thread tracer/span.h
Comment on lines +132 to +134
// Address of otel_thread_ctx_v1 for this root's thread, resolved at root creation to avoid a dynamic TLS lookup
// on every stack switch.
void **otel_context_slot;

@bwoebi bwoebi Aug 5, 2026

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.

Is this actually that expensive? Any DDTRACE_G() lookup will basically do the same, I hope the __thread dispatch table is in CPU cache at all times.
I would just remove this.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I actually benchmarked this before making the commit. I had the AI regenerate assembly to show the difference:


The optimization was introduced by e864a7b. I reproduced the relevant assembly using a default-visible global-dynamic TLS symbol in an LLD-linked shared library.

AMD64

Direct TLS assignment:

  attach_direct:
      push   %rbx
      mov    %rdi, %rbx
      lea    otel_thread_ctx_v1@TLSGD(%rip), %rdi
      call   __tls_get_addr@plt
      mov    %rbx, (%rax)
      pop    %rbx
      ret

Cached slot assignment:

  attach_cached:
      mov    %rsi, (%rdi)
      ret
ARM64

Direct TLS assignment:

  attach_direct:
      stp     x29, x30, [sp, #-16]!
      mov     x29, sp
      mov     x8, x0
      adrp    x0, <TLS descriptor page>
      ldr     x1, [x0, <resolver offset>]
      add     x0, x0, <descriptor offset>
      blr     x1
      mrs     x9, tpidr_el0
      str     x8, [x9, x0]
      ldp     x29, x30, [sp], #16
      ret

Cached slot assignment:

  attach_cached:
      str     x1, [x0]
      ret

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

On "regular" code this will not matter at all, but it might for fiber heavy code.

Comment thread tracer/otel_context.c Outdated

zend_string *service = NULL, *env = NULL, *version = NULL;
ddtrace_span_data *source = ddtrace_otel_attr_source_span(root);
datadog_populate_target_data_with_defaults(source, &service, &env, &version, get_DD_SERVICE(), get_DD_ENV(), get_DD_VERSION());

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.

Why do we call this here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It's needed but it can be simplified to use this helper datadog_populate_target_data which already exists:

static inline void datadog_populate_target_data(ddtrace_span_data *span, zend_string **service, zend_string **env, zend_string **version) {
    datadog_populate_target_data_with_defaults(span, service, env, version, get_DD_SERVICE(), get_DD_ENV(), get_DD_VERSION());
}

AI explanation below.


This call resolves the effective Universal Service Monitoring identity written into the fixed-size OTel Thread Context record:

  datadog_populate_target_data_with_defaults(...)

It is needed because:

  • service.name, deployment environment, and service version may come from span properties or tracer configuration.
  • A nested local root should inherit these attributes from the entrypoint root rather than publishing its own integration service. This is why ddtrace_otel_attr_source_span() is used.
  • There may be no applicable entrypoint span, in which case configured/default values are required.
  • The record must contain copied bytes because an external profiler cannot safely dereference Zend strings.

This behavior is visible in otel_thread_context_stack_switch.phpt: otherRoot has other-service, but its Thread Context intentionally publishes main-service.

Comment thread tracer/otel_context.c
offset = ddtrace_otel_record_write_attr_zstr(record, offset, DDTRACE_OTEL_ATTR_SERVICE_VERSION, ddtrace_otel_attr_zstr(version));

char thread_id[32];
int thread_id_len = snprintf(thread_id, sizeof(thread_id), "%llu", (unsigned long long)syscall(SYS_gettid));

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.

should we cache the tid vs doing a syscall on the write?

Comment thread tracer/span.c Outdated
Comment on lines +1128 to +1134
#ifdef __linux__
if (stack->active) {
ddtrace_update_otel_thread_context_span_id(SPANDATA(stack->active));
} else {
ddtrace_otel_detach();
}
#endif

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.

This code should not be executed when a ddtrace_switch_span_stack happens later, otherwise it will be transiently detached before it switches to a parent stack.

    if (&stack->root_span->span == span) {
        ddtrace_switch_span_stack(stack->parent_stack);
        stack->root_span = NULL;
    } else if (!stack->active || SPANDATA(stack->active)->stack != stack) {
        dd_close_entry_span_of_stack(stack);
    } else {
#ifdef __linux__
        if (stack->active) {
            ddtrace_update_otel_thread_context_span_id(SPANDATA(stack->active));
        } else {
            ddtrace_otel_detach();
        }
#endif
    }

like this. Closing suffers from the same issue. dd_close_entry_span_of_stack also needs a detach if it's not switching spans.

Comment thread components-rs/lib.rs
Comment on lines +129 to +140
// TracerMetadata emits empty resource attributes for absent optional fields to advertise
// support. These values can vary independently on every PHP request, so their values and
// resource keys must be omitted from the process-wide context. The keys remain discoverable
// through threadlocal.attribute_key_map.
if let Some(resource) = context.resource.as_mut() {
resource.attributes.retain(|attribute| {
!matches!(
attribute.key.as_str(),
"service.name" | "service.version" | "deployment.environment.name"
)
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

would be better to change libdatadog not to add these when it's not provided

DEPENDS ${CMAKE_SOURCE_DIR}/../datadog-linux.sym
VERBATIM
)
elseif(APPLE)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think this will build on mac os as is, as it appears to include linux only sources.

And the linux part doesn't add -mtls-dialect=gnu2.

But not critical as I promised the cmake ddtrace build would not be a maintenance burden :p

Comment thread ext/sidecar.c
Comment on lines +472 to +476
if (datadog_sidecar_instance_id) {
ddog_sidecar_instanceId_drop(datadog_sidecar_instance_id);
datadog_sidecar_instance_id = NULL;
}
dd_set_resettable_sidecar_globals();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

After this, datadog_force_new_instance_id becomes dead code (only called from a test function). Why not just make datadog_force_new_instance_id unconditional and call it in the beginning of datadog_internal_handle_fork ?

}

fn refresh(&mut self) -> std::io::Result<()> {
let result = ProcessContextSelfReader::new().and_then(|reader| reader.read());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It should not be necessary to create a new ProcessContextSelfReader unless there was a fork or unpublish + publish. To do so, incurs the penalty of having to do discovery again, and calling pipe2 again.

@morrisonlevi morrisonlevi Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It's not particularly performance sensitive. There are two call sites:

  1. In RINIT, it's called from initialize if there isn't a cached process context already. You can think of this as once per thread.
  2. If there is an unknown thread attribute key, then we refresh the process context. In practice this should not happen, all of our keys are static at the moment. The exception is after a fork, as it gets invalidated during the pre-fork (this means that the parent also is invalidated).

Doing it this way means we don't hold open 2 file descriptors per thread. The thing cannot be shared across threads either.

In other words, aside from bugs, it's already almost optimal. If customers have fork-heavy code, we may want to revise this pre-fork part though.

};
if cache.refresh().is_err() {
return decoded;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If this starts failsing we presumably keep getting unknown_index == true, and we try again, with discovery and everything. I don't think it should retry indefinitely.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I addressed this in 2c1a93d, give it a look!

let Ok(cache) = cell.try_borrow() else {
return ThreadContext::default();
};
let (decoded, unknown_index) = cache.decode_thread_attributes(attributes, defaults);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Have you considering caching also the decoded ThreadContext. Presumably the cache would be hit most of the time.

Comment on lines +194 to +200
fn effective_profile_tags(base: Arc<Vec<Tag>>, identity: &ProcessIdentity) -> Arc<Vec<Tag>> {
let mut updated = None;
apply_profile_tag_override(&base, &mut updated, "service", identity.service.as_deref());
apply_profile_tag_override(&base, &mut updated, "env", identity.environment.as_deref());
apply_profile_tag_override(&base, &mut updated, "version", identity.version.as_deref());
updated.map(Arc::new).unwrap_or(base)
}

@morrisonlevi morrisonlevi Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

TODO: I think the performance of this function is going to be a bit poor. Should probably not emit the service/env/version tags and "merge" things at a higher level, rather than updating tags.

Comment thread ext/otel_context.c
void datadog_otel_process_context_publish(void) {
char detected_hostname[HOST_NAME_MAX + 1] = {0};
ddog_CharSlice hostname = {0};
if (gethostname(detected_hostname, HOST_NAME_MAX) == 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The gethostname() function returns the standard host name for the current processor, as previously set by sethostname(). The namelen argument specifies the size of the name array. The returned name is null-terminated, unless insufficient space is provided.
Host names are limited in length to {sysconf(_SC_HOST_NAME_MAX)} characters, not including the trailing null, currently 255.

So you should pass sizeof(detected_hostname) / HOST_NAME_MAX + 1 to gethostname.

Comment thread profiling/src/php_ffi.c
const zend_extension *maybe_ddtrace = (zend_extension *)item->data;
if (maybe_ddtrace != extension && is_ddtrace_extension(maybe_ddtrace)) {
#ifdef __linux__
datadog_php_profiling_ddtrace_handle = maybe_ddtrace->handle;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think the ssi loader sets this, which would cause datadog_php_profiling_get_otel_thread_context to return early.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

profiling Relates to the Continuous Profiler tracing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants