Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(opentelemetry): resolve circular imports #9929

Merged
merged 4 commits into from
Jul 25, 2024

Conversation

mabdinur
Copy link
Contributor

@mabdinur mabdinur commented Jul 24, 2024

Resolves: #9917

Background

ddtrace replaces the default opentelemetry RuntimeContext with a datadog-aware runtime context. This allows the ddtrace library to correlate datadog and opentelemetry spans in the same trace.

The OpenTelemetry API exposes an entrypoint for overriding the default RuntimeContext (ContextVarsRuntimeContext). The ddtrace library implements this entrypoint and overrides ContextVarsRuntimeContext with DDRuntimeContext.

When DD_TRACE_OTEL_ENABLED=True and opentelemetry-api==1.25.0 is used the following occurs:

-> ddtrace imports set_tracer_provider from openelemetry.trace
-> On import openelemetry.trace the opentelemetry.context package is imported.
-> On import opentelemetry.context the _RUNTIME_CONTEXT object is initialized this calls the _load_runtime_context method.
-> On _load_runtime_context the DDRuntimeContext class is imported from ddtrace.opentelemetry._context.
-> On import ddtrace.opentelemetry._context the opentelemetry.trace package is loaded. This packaged is used in DDRuntimeContext to access types and functions used to generate OpenTelemetry compatible spans.
-> On import opentelemetry.trace the opentelemetry.context.__init__ module is indirectly imported.
-> BOOM circular import
-> _load_runtime_context handles the raised exception,DDRuntimeContext is not set, and the default ContextVarsRuntimeContext is used. This breaks span parenting and ddtrace opentelemetry support.

What changed in opentelemetry-api==1.25.0?

Previously, _load_runtime_context was lazily loaded and _RuntimeContext was initialized when the first span was created. This changed in this commit: open-telemetry/opentelemetry-python@52abb61. With this change the _RuntimeContext is ALWAYS initialized as a side effect of importing the opentelemetry.trace package.

Description

Since _load_runtime_context is no longer lazy loaded we must ensure opentelemetry.context is NOT imported as a side effect of importing DDRuntimeContext. We achieve this behavior by moving the ddtrace.opentelemetry._context.py module and inlining problematic imports.

Regression Test

Run the ddtrace opentelemetry testsuite with opentelemetry-api==1.25.0 (currently we use 1.22.0). This should be sufficient to reproduce this bug.

Checklist

  • PR author has checked that all the criteria below are met
  • The PR description includes an overview of the change
  • The PR description articulates the motivation for the change
  • The change includes tests OR the PR description describes a testing strategy
  • The PR description notes risks associated with the change, if any
  • Newly-added code is easy to change
  • The change follows the library release note guidelines
  • The change includes or references documentation updates if necessary
  • Backport labels are set (if applicable)

Reviewer Checklist

  • Reviewer has checked that all the criteria below are met
  • Title is accurate
  • All changes are related to the pull request's stated goal
  • Avoids breaking API changes
  • Testing strategy adequately addresses listed risks
  • Newly-added code is easy to change
  • Release note makes sense to a user of the library
  • If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment
  • Backport labels are set in a manner that is consistent with the release branch maintenance policy

@mabdinur mabdinur force-pushed the munir/otel-fix-circular-imports branch from 26b2540 to 79e6dc8 Compare July 24, 2024 22:48
Copy link
Contributor

github-actions bot commented Jul 24, 2024

CODEOWNERS have been resolved as:

releasenotes/notes/fix-otel-circular-imports-66f6c926d8626751.yaml      @DataDog/apm-python
.github/CODEOWNERS                                                      @DataDog/python-guild @DataDog/apm-core-python
.riot/requirements/1029814.txt                                          @DataDog/apm-python
.riot/requirements/10f03b7.txt                                          @DataDog/apm-python
.riot/requirements/14a0494.txt                                          @DataDog/apm-python
.riot/requirements/1634a62.txt                                          @DataDog/apm-python
.riot/requirements/1d3d6ad.txt                                          @DataDog/apm-python
.riot/requirements/4de07e7.txt                                          @DataDog/apm-python
.riot/requirements/59f93d3.txt                                          @DataDog/apm-python
.riot/requirements/82d4fd1.txt                                          @DataDog/apm-python
.riot/requirements/a16e5c1.txt                                          @DataDog/apm-python
.riot/requirements/a367077.txt                                          @DataDog/apm-python
.riot/requirements/a3e6122.txt                                          @DataDog/apm-python
.riot/requirements/a4a20ae.txt                                          @DataDog/apm-python
.riot/requirements/ca367e6.txt                                          @DataDog/apm-python
.riot/requirements/ea8be54.txt                                          @DataDog/apm-python
.riot/requirements/efe1fdc.txt                                          @DataDog/apm-python
pyproject.toml                                                          @DataDog/python-guild
riotfile.py                                                             @DataDog/apm-python
tests/.suitespec.json                                                   @DataDog/python-guild @DataDog/apm-core-python
tests/opentelemetry/conftest.py                                         @DataDog/apm-sdk-api-python
ddtrace/internal/opentelemetry/context.py                               @DataDog/apm-sdk-api-python

@mabdinur mabdinur force-pushed the munir/otel-fix-circular-imports branch 4 times, most recently from d8e9b91 to 190e4e8 Compare July 24, 2024 23:18
@mabdinur mabdinur marked this pull request as ready for review July 24, 2024 23:20
@mabdinur mabdinur requested review from a team as code owners July 24, 2024 23:20
@pr-commenter
Copy link

pr-commenter bot commented Jul 24, 2024

Benchmarks

Benchmark execution time: 2024-07-25 19:11:01

Comparing candidate commit 59a3247 in PR branch munir/otel-fix-circular-imports with baseline commit 27cd94c in branch main.

Found 1 performance improvements and 3 performance regressions! Performance is the same for 210 metrics, 2 unstable metrics.

scenario:otelspan-start

  • 🟥 execution_time [+6.543ms; +7.867ms] or [+23.317%; +28.033%]
  • 🟩 max_rss_usage [-15.487MB; -15.422MB] or [-34.002%; -33.859%]

scenario:otelspan-start-finish

  • 🟥 execution_time [+8.468ms; +9.563ms] or [+11.570%; +13.066%]

scenario:otelspan-start-finish-telemetry

  • 🟥 execution_time [+8.263ms; +9.306ms] or [+11.028%; +12.420%]

@brettlangdon
Copy link
Member

Do we need to get rid of ddtrace/opentelemetry/_context.py ?

@mabdinur
Copy link
Contributor Author

mabdinur commented Jul 25, 2024

Do we need to get rid of ddtrace/opentelemetry/_context.py ?

I renamed ddtrace/opentelemetry/_context.py to ddtrace/internal/opentelemetry/context.py.

ddtrace/opentelemetry/init.py exposes the ddtrace TracerProvider to users. Users are expected to use this class to enable opentelemetry support. The ddtrace TracerProvider imports from the opentelemetry.trace package and this triggers this circular import. The easiest way to avoid breaking our public api and resolving the circular import is to move the DDRuntimeContext object from the ddtrace.opentelemetry package.

This is definitely a hacky workaround. Ideally we should reach out to opentelemetry-python maintainers and ask them to revisit this change: open-telemetry/opentelemetry-python@52abb61

@mabdinur mabdinur force-pushed the munir/otel-fix-circular-imports branch from 190e4e8 to 459fc76 Compare July 25, 2024 03:15
@datadog-dd-trace-py-rkomorn
Copy link

datadog-dd-trace-py-rkomorn bot commented Jul 25, 2024

Datadog Report

Branch report: munir/otel-fix-circular-imports
Commit report: 6ab64fc
Test service: dd-trace-py

✅ 0 Failed, 118157 Passed, 60122 Skipped, 3h 54m 26.84s Total duration (21m 55.52s time saved)

@mabdinur mabdinur force-pushed the munir/otel-fix-circular-imports branch from 459fc76 to 2b1543a Compare July 25, 2024 03:35
@mabdinur mabdinur marked this pull request as draft July 25, 2024 03:35
@mabdinur mabdinur force-pushed the munir/otel-fix-circular-imports branch from 2b1543a to 05d8b27 Compare July 25, 2024 03:47
@mabdinur mabdinur marked this pull request as ready for review July 25, 2024 04:09
@codecov-commenter
Copy link

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 10.55%. Comparing base (5b5b702) to head (05d8b27).
Report is 2 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main    #9929       +/-   ##
===========================================
- Coverage   27.08%   10.55%   -16.54%     
===========================================
  Files        1385     1368       -17     
  Lines      129571   127970     -1601     
===========================================
- Hits        35093    13503    -21590     
- Misses      94478   114467    +19989     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@brettlangdon
Copy link
Member

I renamed ddtrace/opentelemetry/_context.py to ddtrace/internal/opentelemetry/context.py.

I still see the original file there.

https://github.com/DataDog/dd-trace-py/blob/05d8b274ec7b687bc0ee6221412b8997941c3aa4/ddtrace/opentelemetry/_context.py

@mabdinur mabdinur force-pushed the munir/otel-fix-circular-imports branch from 05d8b27 to 3e03800 Compare July 25, 2024 14:47
@mabdinur
Copy link
Contributor Author

mabdinur commented Jul 25, 2024

I renamed ddtrace/opentelemetry/_context.py to ddtrace/internal/opentelemetry/context.py.

I still see the original file there.

https://github.com/DataDog/dd-trace-py/blob/05d8b274ec7b687bc0ee6221412b8997941c3aa4/ddtrace/opentelemetry/_context.py

Ahh I must've reverted a change during a rebase. The file is removed: https://github.com/DataDog/dd-trace-py/pull/9929/files#diff-62a0b204575d0586747747152ea77d0466cf05d05ca2bd5f4327478ea9583682.

For consistency we can also move ddtrace.opentelemetry._span and ddtrace.opentelemetry._trace to ddtrace/internal/opentelemetry. I can include this refactor in a future PR.

Copy link
Contributor

@ZStriker19 ZStriker19 left a comment

Choose a reason for hiding this comment

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

Thanks for updating codeowners and suitespec too

Copy link
Contributor

@erikayasuda erikayasuda left a comment

Choose a reason for hiding this comment

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

Just some NB nits. Thanks for the great description!

riotfile.py Show resolved Hide resolved
ddtrace/internal/opentelemetry/context.py Show resolved Hide resolved
@mabdinur mabdinur enabled auto-merge (squash) July 25, 2024 21:14
@mabdinur mabdinur merged commit 3237351 into main Jul 25, 2024
175 checks passed
@mabdinur mabdinur deleted the munir/otel-fix-circular-imports branch July 25, 2024 21:38
Copy link
Contributor

The backport to 2.9 failed:

The process '/usr/bin/git' failed with exit code 1

To backport manually, run these commands in your terminal:

# Fetch latest updates from GitHub
git fetch
# Create a new working tree
git worktree add .worktrees/backport-2.9 2.9
# Navigate to the new working tree
cd .worktrees/backport-2.9
# Create a new branch
git switch --create backport-9929-to-2.9
# Cherry-pick the merged commit of this pull request and resolve the conflicts
git cherry-pick -x --mainline 1 3237351b4a35dfd52e3d3d05c81bfe5b51279372
# Push it to GitHub
git push --set-upstream origin backport-9929-to-2.9
# Go back to the original working tree
cd ../..
# Delete the working tree
git worktree remove .worktrees/backport-2.9

Then, create a pull request where the base branch is 2.9 and the compare/head branch is backport-9929-to-2.9.

Copy link
Contributor

The backport to 2.10 failed:

The process '/usr/bin/git' failed with exit code 1

To backport manually, run these commands in your terminal:

# Fetch latest updates from GitHub
git fetch
# Create a new working tree
git worktree add .worktrees/backport-2.10 2.10
# Navigate to the new working tree
cd .worktrees/backport-2.10
# Create a new branch
git switch --create backport-9929-to-2.10
# Cherry-pick the merged commit of this pull request and resolve the conflicts
git cherry-pick -x --mainline 1 3237351b4a35dfd52e3d3d05c81bfe5b51279372
# Push it to GitHub
git push --set-upstream origin backport-9929-to-2.10
# Go back to the original working tree
cd ../..
# Delete the working tree
git worktree remove .worktrees/backport-2.10

Then, create a pull request where the base branch is 2.10 and the compare/head branch is backport-9929-to-2.10.

gnufede pushed a commit that referenced this pull request Jul 26, 2024
Resolves: #9917

## Background

ddtrace replaces the default opentelemetry RuntimeContext with a
datadog-aware runtime context. This allows the ddtrace library to
correlate datadog and opentelemetry spans in the same trace.


The OpenTelemetry API exposes an
[entrypoint](https://github.com/open-telemetry/opentelemetry-python/blob/main/opentelemetry-api/src/opentelemetry/context/__init__.py#L48)
for overriding the default RuntimeContext
([ContextVarsRuntimeContext](https://github.com/open-telemetry/opentelemetry-python/blob/v1.25.0/opentelemetry-api/src/opentelemetry/context/contextvars_context.py#L19)).
The ddtrace library implements this
[entrypoint](https://github.com/DataDog/dd-trace-py/blob/v2.10.0rc3/pyproject.toml#L56)
and overrides `ContextVarsRuntimeContext` with
[DDRuntimeContext](https://github.com/DataDog/dd-trace-py/blob/main/ddtrace/opentelemetry/_context.py#L26).

### When `DD_TRACE_OTEL_ENABLED=True` and `opentelemetry-api==1.25.0` is
used the following occurs:

-> ddtrace imports
[set_tracer_provider](https://github.com/DataDog/dd-trace-py/blob/9cce458b5ffeabe1dfee22687c89b8635856b9b6/ddtrace/bootstrap/preload.py#L107)
from `openelemetry.trace`
-> On import
[openelemetry.trace](https://github.com/open-telemetry/opentelemetry-python/blob/3f95781a64cc6b897e6e34df907cd187c1e4c2f4/opentelemetry-api/src/opentelemetry/trace/__init__.py#L86)
the `opentelemetry.context` package is imported.
-> On [import
opentelemetry.context](https://github.com/open-telemetry/opentelemetry-python/blob/3f95781a64cc6b897e6e34df907cd187c1e4c2f4/opentelemetry-api/src/opentelemetry/context/__init__.py#L69)
the `_RUNTIME_CONTEXT` object is initialized this calls the
`_load_runtime_context` method.
-> On
[_load_runtime_context](https://github.com/open-telemetry/opentelemetry-python/blob/3f95781a64cc6b897e6e34df907cd187c1e4c2f4/opentelemetry-api/src/opentelemetry/context/__init__.py#L47)
the DDRuntimeContext class is imported from
`ddtrace.opentelemetry._context`.
-> On [import
ddtrace.opentelemetry._context](https://github.com/DataDog/dd-trace-py/blob/main/ddtrace/opentelemetry/_context.py#L6)
the `opentelemetry.trace` package is loaded. This packaged is used in
DDRuntimeContext to access [types and
functions](https://github.com/DataDog/dd-trace-py/blob/9cce458b5ffeabe1dfee22687c89b8635856b9b6/ddtrace/opentelemetry/_context.py#L9)
used to generate OpenTelemetry compatible spans.
-> On import `opentelemetry.trace` the `opentelemetry.context.__init__
`module is indirectly imported.
-> BOOM circular import
-> `_load_runtime_context` handles the raised
exception,`DDRuntimeContext` is not set, and the default
`ContextVarsRuntimeContext` is used. This breaks span parenting and
ddtrace opentelemetry support.

### What changed in opentelemetry-api==1.25.0?

Previously, `_load_runtime_context` was lazily loaded and
`_RuntimeContext` was initialized when the first span was created. This
changed in this commit:
open-telemetry/opentelemetry-python@52abb61.
With this change the _RuntimeContext is ALWAYS initialized as a side
effect of importing the `opentelemetry.trace` package.

## Description

Since `_load_runtime_context` is no longer lazy loaded we must ensure
`opentelemetry.context` is NOT imported as a side effect of importing
`DDRuntimeContext`. We achieve this behavior by moving the
`ddtrace.opentelemetry._context.py` module and inlining problematic
imports.

## Regression Test

Run the ddtrace opentelemetry testsuite with `opentelemetry-api==1.25.0`
(currently we use 1.22.0). This should be sufficient to reproduce this
bug.


## Checklist
- [x] PR author has checked that all the criteria below are met
- The PR description includes an overview of the change
- The PR description articulates the motivation for the change
- The change includes tests OR the PR description describes a testing
strategy
- The PR description notes risks associated with the change, if any
- Newly-added code is easy to change
- The change follows the [library release note
guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)
- The change includes or references documentation updates if necessary
- Backport labels are set (if
[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))

## Reviewer Checklist
- [x] Reviewer has checked that all the criteria below are met 
- Title is accurate
- All changes are related to the pull request's stated goal
- Avoids breaking
[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)
changes
- Testing strategy adequately addresses listed risks
- Newly-added code is easy to change
- Release note makes sense to a user of the library
- If necessary, author has acknowledged and discussed the performance
implications of this PR as reported in the benchmarks PR comment
- Backport labels are set in a manner that is consistent with the
[release branch maintenance
policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
mabdinur added a commit that referenced this pull request Aug 5, 2024
Backport #9929 

## Checklist
- [ ] PR author has checked that all the criteria below are met
- The PR description includes an overview of the change
- The PR description articulates the motivation for the change
- The change includes tests OR the PR description describes a testing
strategy
- The PR description notes risks associated with the change, if any
- Newly-added code is easy to change
- The change follows the [library release note
guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)
- The change includes or references documentation updates if necessary
- Backport labels are set (if
[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))

## Reviewer Checklist
- [ ] Reviewer has checked that all the criteria below are met 
- Title is accurate
- All changes are related to the pull request's stated goal
- Avoids breaking
[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)
changes
- Testing strategy adequately addresses listed risks
- Newly-added code is easy to change
- Release note makes sense to a user of the library
- If necessary, author has acknowledged and discussed the performance
implications of this PR as reported in the benchmarks PR comment
- Backport labels are set in a manner that is consistent with the
[release branch maintenance
policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

OpenTelemetry API context propagation is broken starting with opentelemetry-api==1.25.0
6 participants