Skip to content

fix(tracing): preserve dd.internal.entity_id tag on runtime metrics flush - #19602

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 8 commits into
mainfrom
vparfait/fix-runtime-metrics-entity-id-tag
Aug 18, 2026
Merged

fix(tracing): preserve dd.internal.entity_id tag on runtime metrics flush#19602
gh-worker-dd-mergequeue-cf854d[bot] merged 8 commits into
mainfrom
vparfait/fix-runtime-metrics-entity-id-tag

Conversation

@vparfait

Copy link
Copy Markdown
Collaborator

Description

RuntimeWorker.flush() reassigned the dogstatsd client's constant_tags wholesale on every flush, using only service/env/version/platform/process tags. This discarded whatever the client derived at construction — most notably dd.internal.entity_id (from DD_ENTITY_ID), which the Datadog Agent uses for Kubernetes origin detection (pod_name/kube_namespace attribution). The tag was gone starting from the very first flush, for the life of the process.

The fix snapshots the client's original constant_tags at construction (RuntimeWorker.__init__) and merges that snapshot back into the tag list on every flush(), deduping exact-string repeats — this matters for customers who applied the documented DD_TAGS=dd.internal.entity_id:... workaround, since that string can now arrive from two sources.

Fixes #19526.

Testing

Added two regression tests to tests/tracer/runtime/test_runtime_metrics.py:

  • test_runtime_worker_flush_preserves_entity_id_tag: asserts dd.internal.entity_id appears exactly once on every metric line actually sent (via a mocked socket), across multiple flush() calls.
  • test_runtime_worker_flush_dedupes_entity_id_tag: asserts no duplicate tag when both DD_ENTITY_ID and the DD_TAGS workaround are set.

Both tests were verified to fail against the pre-fix code (missing tag after the first flush) and pass against the fix. Full tracer suite run locally (all three venv variants: tracer, tracer-python-optimize, tracer-uwsgi) with no regressions.

Risks

Low. The change only affects tag computation in RuntimeWorker.flush(); no public API changes. Verified no fork-safety impact (the worker instance persists across fork; the snapshot survives via normal copy-on-fork semantics) and confirmed via grep that this is the only place in the non-vendored codebase that mutates constant_tags.

Additional Notes

None.

…lush

RuntimeWorker.flush() reassigned the dogstatsd client's constant_tags on
every flush, wholesale replacing whatever the client derived at
construction (e.g. dd.internal.entity_id from DD_ENTITY_ID). This
permanently dropped Kubernetes origin detection tags for runtime.python.*
metrics starting from the very first flush.

Snapshot the client's original constant_tags at construction and merge
them back in on every flush, deduping exact-string repeats for customers
who applied the documented DD_TAGS workaround.

Fixes GH-19526.
@vparfait
vparfait requested review from a team as code owners August 10, 2026 01:06
@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codeowners resolved as

Resolved from the full PR diff against main using the target branch CODEOWNERS file.
CODEOWNERS team requests not listed below are not required by the current file set.

No remaining files require a CODEOWNERS review.

@datadog-prod-us1-6

datadog-prod-us1-6 Bot commented Aug 10, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 76c3180 | Docs | View more details | Give us feedback!

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Aug 10, 2026

Copy link
Copy Markdown

Circular import analysis

⚠️ Existing circular imports

There are 5 circular imports that already exist on the base branch and have not been changed by this PR.

ddtrace.contrib.internal.pytorch._distributed -> ddtrace.contrib.internal.pytorch._rank_root -> ddtrace.contrib.internal.pytorch._distributed
ddtrace.contrib.internal.django.patch -> ddtrace.contrib.internal.django.response -> ddtrace.contrib.internal.django.patch
ddtrace.errortracking._handled_exceptions.bytecode_injector -> ddtrace.errortracking._handled_exceptions.callbacks -> ddtrace.errortracking._handled_exceptions.collector -> ddtrace.errortracking._handled_exceptions.bytecode_reporting -> ddtrace.errortracking._handled_exceptions.bytecode_injector
ddtrace.llmobs -> ddtrace.llmobs._evaluators -> ddtrace.llmobs._evaluators.format -> ddtrace.llmobs._experiment -> ddtrace.llmobs
ddtrace.appsec._asm_request_context -> ddtrace.appsec._iast._iast_request_context_base -> ddtrace.appsec._iast._iast_env -> ddtrace.appsec._iast.reporter -> ddtrace.appsec._exploit_prevention.stack_traces -> ddtrace.appsec._asm_request_context

@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: 0880de8863

ℹ️ 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 ddtrace/internal/runtime/runtime_metrics.py
@pr-commenter

pr-commenter Bot commented Aug 10, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-08-18 11:07:52

Comparing candidate commit 76c3180 in PR branch vparfait/fix-runtime-metrics-entity-id-tag with baseline commit 3b40799 in branch main.

📊 Benchmarking dashboard

Found 0 performance improvements and 4 performance regressions! Performance is the same for 383 metrics, 9 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:httppropagationinject-ids_only

  • 🟥 execution_time [+2.318µs; +2.451µs] or [+10.681%; +11.294%]

scenario:span-start

  • 🟥 execution_time [+1.218ms; +1.363ms] or [+8.032%; +8.986%]

scenario:telemetryaddmetric-1-count-metric-1-times

  • 🟥 execution_time [+443.087ns; +487.013ns] or [+16.642%; +18.292%]

scenario:tracer-small

  • 🟥 execution_time [+31.025µs; +33.289µs] or [+9.140%; +9.807%]

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:coreapiscenario-context_with_data_listeners

  • unstable execution_time [-752.440ns; +722.782ns] or [-6.866%; +6.595%]

scenario:coreapiscenario-core_dispatch_1_listener

  • unstable execution_time [-27.343ns; +38.038ns] or [-4.516%; +6.283%]

scenario:coreapiscenario-core_dispatch_50_listeners

  • unstable execution_time [-1646.165ns; +1629.587ns] or [-9.714%; +9.616%]

scenario:coreapiscenario-core_dispatch_exception_listeners

  • unstable execution_time [-1131.219ns; +1323.011ns] or [-8.818%; +10.313%]

scenario:coreapiscenario-core_dispatch_listeners

  • unstable execution_time [-327.336ns; +321.362ns] or [-8.931%; +8.768%]

scenario:coreapiscenario-core_dispatch_no_args_listeners

  • unstable execution_time [-251.532ns; +259.511ns] or [-8.606%; +8.879%]

scenario:coreapiscenario-core_dispatch_with_results_1_listener

  • unstable execution_time [-88.413ns; +62.165ns] or [-7.613%; +5.353%]

scenario:coreapiscenario-core_dispatch_with_results_50_listeners

  • unstable execution_time [-4138.514ns; +3989.340ns] or [-10.069%; +9.706%]

scenario:coreapiscenario-core_dispatch_with_results_listeners

  • unstable execution_time [-755.354ns; +791.398ns] or [-9.306%; +9.751%]

@emmettbutler emmettbutler 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.

nice catch!

vparfait and others added 2 commits August 12, 2026 11:14
… metrics

RuntimeWorker snapshotted the dogstatsd client's entire constant_tags at
construction to preserve dd.internal.entity_id across flushes. But that
client also derives service/env/version tags from DD_SERVICE/DD_ENV/
DD_VERSION at construction time, and those are already recomputed fresh
from ddtrace.config on every flush via TracerTags(). Re-merging the whole
snapshot meant a stale env-derived value (e.g. service:env-service) could
be sent alongside the current one (e.g. service:override-service) whenever
config.service/env/version changed after the worker started, since the
dedup only caught exact-string repeats, not same-key conflicts.

Narrow the snapshot to only dd.internal.entity_id, the one tag with no
other fresh source in flush().

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Aug 12, 2026

Copy link
Copy Markdown

Dependency direction analysis

⚠️ Existing dependency direction violations

There are 254 dependency direction violations that already exist on the base branch and have not been changed by this PR.

Show existing violations (showing 5 of 254 highest severity)
ddtrace.internal.tracemethods -×-> ddtrace.trace  (internal-core -> product:tracing, score=134)
ddtrace.llmobs._integrations.bedrock_agents -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=132)
ddtrace.debugging._debugger -×-> ddtrace.trace  (product:debugging -> product:tracing, score=132)
ddtrace.llmobs._integrations.langgraph -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=132)
ddtrace.appsec._contrib.django -×-> ddtrace.trace  (product:appsec -> product:tracing, score=132)

To see all violations, download the layers-base.json and layers-pr.json artifacts from this CI job and run:

uv run --script scripts/import-analysis/layers.py compare layers-base.json layers-pr.json

@vparfait

Copy link
Copy Markdown
Collaborator Author

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Aug 12, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-08-12 04:56:45 UTC ℹ️ Start processing command /merge


2026-08-12 04:57:01 UTC ℹ️ MergeQueue: Pull request is not mergeable yet

It will be processed automatically as soon as GitHub reports it as mergeable. View in MergeQueue UI.

  • Run /code blockers to see what is blocking it.
  • Run /remove to cancel it.

2026-08-12 09:00:06 UTC ⚠️ MergeQueue: This merge request was unqueued

devflow unqueued this merge request: It did not become mergeable within the expected time

Copilot AI left a comment

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.

Pull request overview

Fixes runtime-metrics tag handling so the DogStatsD dd.internal.entity_id (and related constant tags) are not lost when RuntimeWorker.flush() refreshes tags, restoring Kubernetes origin-detection attribution for runtime.python.* metrics.

Changes:

  • Preserve client-derived constant tags across RuntimeWorker.flush() calls while keeping service/env/version up-to-date.
  • Add regression tests asserting entity-id tag preservation and deduplication behavior in emitted packets.
  • Add a release note documenting the fix for Kubernetes origin-detection tagging.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
ddtrace/internal/runtime/runtime_metrics.py Adjusts runtime-metrics tag computation to preserve important DogStatsD constant tags across flushes.
tests/tracer/runtime/test_runtime_metrics.py Adds regression tests validating entity-id tag preservation/deduplication and tag freshness behavior.
releasenotes/notes/fix-runtime-metrics-entity-id-tag-f401edcebf35c495.yaml Documents the runtime-metrics entity-id tagging fix in release notes.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ddtrace/internal/runtime/runtime_metrics.py Outdated
Comment thread tests/tracer/runtime/test_runtime_metrics.py Outdated
Comment thread ddtrace/internal/runtime/runtime_metrics.py Outdated
vparfait and others added 2 commits August 17, 2026 10:41
Address review feedback from @mabdinur to trim the explanatory comment
in RuntimeWorker.__init__ down to the essential rationale.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vparfait

Copy link
Copy Markdown
Collaborator Author

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Aug 17, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-08-17 07:53:03 UTC ℹ️ Start processing command /merge


2026-08-17 07:53:08 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in main is approximately 47m (p90).


2026-08-17 08:18:02 UTCMergeQueue: The build pipeline contains failing jobs for this merge request

Build pipeline has failing jobs for 1341082:

⚠️ Do NOT retry failed jobs directly (why?).

What to do next?

  • Investigate the failures and when ready, re-add your pull request to the queue!
  • If your PR checks are green, try to rebase/merge. It might be because the CI run is a bit old.
  • Any question, go check the FAQ.
Details

Since those jobs are not marked as being allowed to fail, the pipeline will most likely fail.
Therefore, and to allow other builds to be processed, this merge request has been rejected and the pipeline got canceled.

test_runtime_worker_flush_does_not_leak_stale_service_tag hard-set
ddtrace.config.service back to None in its finally block instead of
restoring the value it actually had before the test ran. Since
config.service is a plain attribute computed once at process start
(not a lazily-recomputed property), this permanently pinned it to
None for the rest of the pytest session, breaking
test_start_span_service_default (and any other test relying on the
default-inferred service name) whenever it ran afterward in the same
process -- which is what broke dd-gitlab/core/tracer 8/19 on this PR.

Use the repo's override_global_config() helper instead, which snapshots
and restores the real prior value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vparfait

Copy link
Copy Markdown
Collaborator Author

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Aug 18, 2026

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-08-18 10:54:46 UTC ℹ️ Start processing command /merge


2026-08-18 10:55:00 UTC ℹ️ MergeQueue: Pull request is not mergeable yet

It will be processed automatically as soon as GitHub reports it as mergeable. View in MergeQueue UI.

  • Run /code blockers to see what is blocking it.
  • Run /remove to cancel it.

2026-08-18 11:24:13 UTC ℹ️ MergeQueue: merge request added to the queue

The expected merge time in main is approximately 47m (p90).


2026-08-18 12:05:36 UTC ℹ️ MergeQueue: This merge request was merged

@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot merged commit 4c22703 into main Aug 18, 2026
1209 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot deleted the vparfait/fix-runtime-metrics-entity-id-tag branch August 18, 2026 12:05
vlad-scherbich pushed a commit that referenced this pull request Aug 18, 2026
…lush (#19602)

## Description

`RuntimeWorker.flush()` reassigned the dogstatsd client's `constant_tags` wholesale on every flush, using only `service`/`env`/`version`/platform/process tags. This discarded whatever the client derived at construction — most notably `dd.internal.entity_id` (from `DD_ENTITY_ID`), which the Datadog Agent uses for Kubernetes origin detection (`pod_name`/`kube_namespace` attribution). The tag was gone starting from the very first flush, for the life of the process.

The fix snapshots the client's original `constant_tags` at construction (`RuntimeWorker.__init__`) and merges that snapshot back into the tag list on every `flush()`, deduping exact-string repeats — this matters for customers who applied the documented `DD_TAGS=dd.internal.entity_id:...` workaround, since that string can now arrive from two sources.

Fixes #19526.

## Testing

Added two regression tests to `tests/tracer/runtime/test_runtime_metrics.py`:
- `test_runtime_worker_flush_preserves_entity_id_tag`: asserts `dd.internal.entity_id` appears exactly once on every metric line actually sent (via a mocked socket), across multiple `flush()` calls.
- `test_runtime_worker_flush_dedupes_entity_id_tag`: asserts no duplicate tag when both `DD_ENTITY_ID` and the `DD_TAGS` workaround are set.

Both tests were verified to fail against the pre-fix code (missing tag after the first flush) and pass against the fix. Full `tracer` suite run locally (all three venv variants: `tracer`, `tracer-python-optimize`, `tracer-uwsgi`) with no regressions.

## Risks

Low. The change only affects tag computation in `RuntimeWorker.flush()`; no public API changes. Verified no fork-safety impact (the worker instance persists across fork; the snapshot survives via normal copy-on-fork semantics) and confirmed via grep that this is the only place in the non-vendored codebase that mutates `constant_tags`.

## Additional Notes

None.

Co-authored-by: venus.parfait <venus.parfait@datadoghq.com>
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.

[BUG]: Runtime metrics undercount gunicorn RSS after pre-fork enable; entity_id tags dropped

7 participants