Skip to content

Commit 9a5fd14

Browse files
authored
fix(tracing): activate distributed headers once per request (#12250)
Currently core will call _start_span for each django, flask, and certain other integrations span's generated. Basically an integration using `core.context_with_data` to generate spans. Each `_start_span` call calls `activate_distribute_headers`, which in turn call `HTTP_PROPAGATOR.extract`. This leads to around 7-10 unneeded calls on average of that method per request. The logic in [activate_distributed_headers](https://github.com/DataDog/dd-trace-py/blob/main/ddtrace/contrib/internal/trace_utils.py#L586-L594) is what's currently saving us from not re-activating the same context over and over again when each new span is generated. By adding an `activate_distributed_headers` param we can make sure to only activate the headers when necessary, reducing the calling of code that runs for no reason and improving performance. Something I learned while making this PR is that when `core.context_with_data()` is called, if we do `ctx.get_item` in say ` _start_span` and the item doesn't exist on the local context we look at the parent context, essentially going all the way up the tree of contexts till we either find a value or return `None`. With that being the case, we needed to use `core.get_local_item("activate_distributed_headers")` instead of the usual `ctx.get_item`, since the parent context will almost always have `activate_distribute_headers` set to `True`. ## 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)
1 parent 534fa86 commit 9a5fd14

File tree

14 files changed

+39
-20
lines changed

14 files changed

+39
-20
lines changed

ddtrace/_trace/trace_handlers.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,15 @@ def _get_parameters_for_new_span_directly_from_context(ctx: core.ExecutionContex
106106

107107

108108
def _start_span(ctx: core.ExecutionContext, call_trace: bool = True, **kwargs) -> "Span":
109+
activate_distributed_headers = ctx.get_local_item("activate_distributed_headers")
109110
span_kwargs = _get_parameters_for_new_span_directly_from_context(ctx)
110111
call_trace = ctx.get_item("call_trace", call_trace)
111112
tracer = ctx.get_item("tracer") or (ctx.get_item("middleware") or ctx["pin"]).tracer
112-
distributed_headers_config = ctx.get_item("distributed_headers_config")
113-
if distributed_headers_config:
113+
integration_config = ctx.get_item("integration_config")
114+
if integration_config and activate_distributed_headers:
114115
trace_utils.activate_distributed_headers(
115116
tracer,
116-
int_config=distributed_headers_config,
117+
int_config=integration_config,
117118
request_headers=ctx["distributed_headers"],
118119
override=ctx.get_item("distributed_headers_config_override"),
119120
)
@@ -123,7 +124,7 @@ def _start_span(ctx: core.ExecutionContext, call_trace: bool = True, **kwargs) -
123124

124125
if config._inferred_proxy_services_enabled:
125126
# dispatch event for checking headers and possibly making an inferred proxy span
126-
core.dispatch("inferred_proxy.start", (ctx, tracer, span_kwargs, call_trace, distributed_headers_config))
127+
core.dispatch("inferred_proxy.start", (ctx, tracer, span_kwargs, call_trace, integration_config))
127128
# re-get span_kwargs in case an inferred span was created and we have a new span_kwargs.child_of field
128129
span_kwargs = ctx.get_item("span_kwargs", span_kwargs)
129130

@@ -197,7 +198,7 @@ def _set_inferred_proxy_tags(span, status_code):
197198
inferred_span.set_tag(ERROR_STACK, span.get_tag(ERROR_STACK))
198199

199200

200-
def _on_inferred_proxy_start(ctx, tracer, span_kwargs, call_trace, distributed_headers_config):
201+
def _on_inferred_proxy_start(ctx, tracer, span_kwargs, call_trace, integration_config):
201202
# Skip creating another inferred span if one has already been created for this request
202203
if ctx.get_item("inferred_proxy_span"):
203204
return
@@ -207,7 +208,7 @@ def _on_inferred_proxy_start(ctx, tracer, span_kwargs, call_trace, distributed_h
207208
headers = ctx.get_item("headers", ctx.get_item("distributed_headers", None))
208209

209210
# Inferred Proxy Spans
210-
if distributed_headers_config and headers is not None:
211+
if integration_config and headers is not None:
211212
create_inferred_proxy_span_if_headers_exist(
212213
ctx,
213214
headers=headers,
@@ -499,7 +500,7 @@ def _on_django_finalize_response_pre(ctx, after_request_tags, request, response)
499500
span = ctx.span
500501
after_request_tags(ctx["pin"], span, request, response)
501502

502-
trace_utils.set_http_meta(span, ctx["distributed_headers_config"], route=span.get_tag("http.route"))
503+
trace_utils.set_http_meta(span, ctx["integration_config"], route=span.get_tag("http.route"))
503504
_set_inferred_proxy_tags(span, None)
504505

505506

@@ -512,7 +513,7 @@ def _on_django_start_response(
512513

513514
trace_utils.set_http_meta(
514515
ctx.span,
515-
ctx["distributed_headers_config"],
516+
ctx["integration_config"],
516517
method=request.method,
517518
query=query,
518519
raw_uri=uri,

ddtrace/contrib/internal/aiohttp/middlewares.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ async def attach_context(request):
4141
tags={},
4242
tracer=tracer,
4343
distributed_headers=request.headers,
44-
distributed_headers_config=config.aiohttp,
44+
integration_config=config.aiohttp,
45+
activate_distributed_headers=True,
4546
distributed_headers_config_override=app[CONFIG_KEY]["distributed_tracing_enabled"],
4647
headers_case_sensitive=True,
4748
analytics_enabled=analytics_enabled,

ddtrace/contrib/internal/asgi/middleware.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,9 @@ async def __call__(self, scope, receive, send):
164164
resource=resource,
165165
span_type=SpanTypes.WEB,
166166
service=trace_utils.int_service(None, self.integration_config),
167-
distributed_headers_config=config.asgi,
168167
distributed_headers=headers,
168+
integration_config=config.asgi,
169+
activate_distributed_headers=True,
169170
pin=pin,
170171
) as ctx, ctx.span as span:
171172
span.set_tag_str(COMPONENT, self.integration_config.integration_name)

ddtrace/contrib/internal/bottle/trace.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ def wrapped(*args, **kwargs):
4646
tags={},
4747
tracer=self.tracer,
4848
distributed_headers=request.headers,
49-
distributed_headers_config=config.bottle,
49+
integration_config=config.bottle,
5050
headers_case_sensitive=True,
5151
analytics_sample_rate=config.bottle.get_analytics_sample_rate(use_global_config=True),
52+
activate_distributed_headers=True,
5253
) as ctx, ctx.span as req_span:
5354
ctx.set_item("req_span", req_span)
5455
core.dispatch("web.request.start", (ctx, config.bottle))

ddtrace/contrib/internal/cherrypy/patch.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ def _on_start_resource(self):
8383
tags={},
8484
tracer=self._tracer,
8585
distributed_headers=cherrypy.request.headers,
86-
distributed_headers_config=config.cherrypy,
86+
integration_config=config.cherrypy,
87+
activate_distributed_headers=True,
8788
headers_case_sensitive=True,
8889
) as ctx:
8990
req_span = ctx.span

ddtrace/contrib/internal/django/patch.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,9 @@ def traced_get_response(django, pin, func, instance, args, kwargs):
482482
service=trace_utils.int_service(pin, config.django),
483483
span_type=SpanTypes.WEB,
484484
tags={COMPONENT: config.django.integration_name, SPAN_KIND: SpanKind.SERVER},
485-
distributed_headers_config=config.django,
485+
integration_config=config.django,
486486
distributed_headers=request_headers,
487+
activate_distributed_headers=True,
487488
pin=pin,
488489
) as ctx, ctx.span:
489490
core.dispatch(

ddtrace/contrib/internal/falcon/middleware.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ def process_request(self, req, resp):
3030
tags={},
3131
tracer=self.tracer,
3232
distributed_headers=headers,
33-
distributed_headers_config=config.falcon,
33+
integration_config=config.falcon,
34+
activate_distributed_headers=True,
3435
headers_case_sensitive=True,
3536
analytics_sample_rate=config.falcon.get_analytics_sample_rate(use_global_config=True),
3637
) as ctx:

ddtrace/contrib/internal/molten/patch.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ def patch_app_call(wrapped, instance, args, kwargs):
9494
tags={},
9595
tracer=pin.tracer,
9696
distributed_headers=dict(request.headers), # request.headers is type Iterable[Tuple[str, str]]
97-
distributed_headers_config=config.molten,
97+
integration_config=config.molten,
98+
activate_distributed_headers=True,
9899
headers_case_sensitive=True,
99100
analytics_sample_rate=config.molten.get_analytics_sample_rate(use_global_config=True),
100101
) as ctx, ctx.span as req_span:

ddtrace/contrib/internal/pyramid/trace.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ def trace_tween(request):
7979
tags={},
8080
tracer=tracer,
8181
distributed_headers=request.headers,
82-
distributed_headers_config=config.pyramid,
82+
integration_config=config.pyramid,
83+
activate_distributed_headers=True,
8384
headers_case_sensitive=True,
8485
# DEV: pyramid is special case maintains separate configuration from config api
8586
analytics_enabled=settings.get(SETTINGS_ANALYTICS_ENABLED),

ddtrace/contrib/internal/rq/patch.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def traced_perform_job(rq, pin, func, instance, args, kwargs):
110110
pin=pin,
111111
span_type=SpanTypes.WORKER,
112112
resource=job.func_name,
113-
distributed_headers_config=config.rq_worker,
113+
integration_config=config.rq_worker,
114114
distributed_headers=job.meta,
115115
tags={COMPONENT: config.rq.integration_name, SPAN_KIND: SpanKind.CONSUMER, JOB_ID: job.get_id()},
116116
) as ctx, ctx.span:

ddtrace/contrib/internal/sanic/patch.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@ def _create_sanic_request_span(request):
208208
tags={},
209209
pin=pin,
210210
distributed_headers=headers,
211-
distributed_headers_config=config.sanic,
211+
integration_config=config.sanic,
212+
activate_distributed_headers=True,
212213
headers_case_sensitive=True,
213214
analytics_sample_rate=config.sanic.get_analytics_sample_rate(use_global_config=True),
214215
) as ctx:

ddtrace/contrib/internal/tornado/handlers.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ def execute(func, handler, args, kwargs):
3636
tags={},
3737
tracer=tracer,
3838
distributed_headers=handler.request.headers,
39-
distributed_headers_config=config.tornado,
39+
integration_config=config.tornado,
40+
activate_distributed_headers=True,
4041
distributed_headers_config_override=distributed_tracing,
4142
headers_case_sensitive=True,
4243
# DEV: tornado is special case maintains separate configuration from config api

ddtrace/contrib/internal/wsgi/wsgi.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,12 @@ def __call__(self, environ: Iterable, start_response: Callable) -> wrapt.ObjectP
104104
span_type=SpanTypes.WEB,
105105
span_name=(self._request_call_name if hasattr(self, "_request_call_name") else self._request_span_name),
106106
middleware_config=self._config,
107-
distributed_headers_config=self._config,
107+
integration_config=self._config,
108108
distributed_headers=environ,
109109
environ=environ,
110110
middleware=self,
111111
span_key="req_span",
112+
activate_distributed_headers=True,
112113
) as ctx:
113114
ctx.set_item("wsgi.construct_url", construct_url)
114115

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
fixes:
3+
- |
4+
tracing: This performance fix resolves an issue where Django, Flask, Cherrypy, wsgi,
5+
asgi, pyramid, molten, falcon, tornado, aiohttp, bottle, rq, and sanic integrations
6+
were unnecessarily running code to activate distributed tracing headers multiple times per request.
7+
This is fixed by only activating distributed tracing headers once per request.

0 commit comments

Comments
 (0)