Skip to content

Commit f28a8ea

Browse files
committed
perf: optimize hot path for http propagation
This commit optimizes the `HTTPPropagator.extract` method, which is a critical hot path that runs on every request. The changes focus on reducing object allocations and avoiding unnecessary work in common scenarios. * Avoids allocation on sucessful path: We no longer create an empty `Context` object unconditionally. If one is found later, we avoid 1 allocation. * Adds single-context fast path: In multi-style extraction mode, if only one context is found (a common case), we now use it directly instead of calling the more expensive `_resolve_contexts` method. * Optimizes baggage tagging: The loop for applying baggage as tags is optimized by hoisting attribute lookups (`.meta`) and method calls (`.get_all_baggage_items`) out of the loop, reducing overhead per-key. * Cleaner baggage logic: Baggage extraction is now explicitly handled once at the end, simplifying the logic within the "extract-first" and "multi-extract" paths.
1 parent 9c2acd7 commit f28a8ea

File tree

1 file changed

+58
-35
lines changed

1 file changed

+58
-35
lines changed

ddtrace/propagation/http.py

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,68 +1222,91 @@ def my_controller(url, headers):
12221222
:param dict headers: HTTP headers to extract tracing attributes.
12231223
:return: New `Context` with propagated attributes.
12241224
"""
1225-
context = Context()
1225+
# Fast path: empty or no extraction config
12261226
if not headers or not config._propagation_style_extract:
1227-
return context
1227+
return Context()
1228+
12281229
try:
12291230
style = ""
1231+
# Normalize header keys once
12301232
normalized_headers = {name.lower(): v for name, v in headers.items()}
1231-
# tracer configured to extract first only
1233+
prop_styles = config._propagation_style_extract
1234+
1235+
# Short-circuit: first-only extraction mode
12321236
if config._propagation_extract_first:
1233-
# loop through the extract propagation styles specified in order, return whatever context we get first
1234-
for prop_style in config._propagation_style_extract:
1235-
propagator = _PROP_STYLES[prop_style]
1236-
context = propagator._extract(normalized_headers)
1237-
style = prop_style
1237+
for ps in prop_styles:
1238+
# baggage handled later
1239+
if ps == _PROPAGATION_STYLE_BAGGAGE:
1240+
continue
1241+
context = _PROP_STYLES[ps]._extract(normalized_headers)
1242+
style = ps
12381243
if context:
1239-
_record_http_telemetry("context_header_style.extracted", prop_style)
1240-
if config._propagation_http_baggage_enabled is True:
1241-
_attach_baggage_to_context(normalized_headers, context)
1242-
break
1243-
1244-
# loop through all extract propagation styles
1244+
_record_http_telemetry("context_header_style.extracted", ps)
1245+
if config._propagation_http_baggage_enabled:
1246+
_attach_baggage_to_context(normalized_headers, context)
1247+
break
1248+
else:
1249+
context = Context()
12451250
else:
1246-
contexts, styles_w_ctx = HTTPPropagator._extract_configured_contexts_avail(normalized_headers)
1247-
# check that styles_w_ctx is not empty
1251+
# Multi-style extraction: collect all contexts in a single tight loop
1252+
contexts = []
1253+
styles_w_ctx = []
1254+
append = contexts.append
1255+
append_style = styles_w_ctx.append
1256+
for ps in prop_styles:
1257+
if ps == _PROPAGATION_STYLE_BAGGAGE:
1258+
continue
1259+
c = _PROP_STYLES[ps]._extract(normalized_headers)
1260+
if c:
1261+
_record_http_telemetry("context_header_style.extracted", ps)
1262+
append(c)
1263+
append_style(ps)
12481264
if styles_w_ctx:
12491265
style = styles_w_ctx[0]
1250-
1266+
# Context resolution: fast-path single, else resolve
12511267
if contexts:
1252-
context = HTTPPropagator._resolve_contexts(contexts, styles_w_ctx, normalized_headers)
1253-
if config._propagation_http_baggage_enabled is True:
1268+
if len(contexts) == 1:
1269+
context = contexts[0]
1270+
else:
1271+
context = HTTPPropagator._resolve_contexts(contexts, styles_w_ctx, normalized_headers)
1272+
if config._propagation_http_baggage_enabled:
12541273
_attach_baggage_to_context(normalized_headers, context)
1274+
else:
1275+
context = Context()
12551276

1256-
# baggage headers are handled separately from the other propagation styles
1257-
if _PROPAGATION_STYLE_BAGGAGE in config._propagation_style_extract:
1277+
# Baggage extraction, always handled last, only once
1278+
baggage_context = None
1279+
if _PROPAGATION_STYLE_BAGGAGE in prop_styles:
12581280
baggage_context = _BaggageHeader._extract(normalized_headers)
12591281
if baggage_context._baggage != {}:
1260-
# Record telemetry for successful baggage extraction
12611282
_record_http_telemetry("context_header_style.extracted", _PROPAGATION_STYLE_BAGGAGE)
12621283
if context:
12631284
context._baggage = baggage_context.get_all_baggage_items()
12641285
else:
12651286
context = baggage_context
12661287

1288+
# Tagging of baggage keys, single pass
12671289
if config._baggage_tag_keys:
1268-
raw_keys = [k.strip() for k in config._baggage_tag_keys if k.strip()]
1269-
# wildcard: tag all baggage keys
1270-
if "*" in raw_keys:
1290+
keys = [k.strip() for k in config._baggage_tag_keys if k.strip()]
1291+
if "*" in keys:
12711292
tag_keys = baggage_context.get_all_baggage_items().keys()
12721293
else:
1273-
tag_keys = raw_keys
1274-
1275-
for stripped_key in tag_keys:
1276-
if (value := baggage_context.get_baggage_item(stripped_key)) is not None:
1277-
prefixed_key = BAGGAGE_TAG_PREFIX + stripped_key
1278-
if prefixed_key not in context._meta:
1279-
context._meta[prefixed_key] = value
1280-
1294+
tag_keys = keys
1295+
meta = context._meta
1296+
bag_dict = baggage_context.get_all_baggage_items()
1297+
for k in tag_keys:
1298+
v = bag_dict.get(k)
1299+
if v is not None:
1300+
prefixed = BAGGAGE_TAG_PREFIX + k
1301+
if prefixed not in meta:
1302+
meta[prefixed] = v
1303+
1304+
# Propagation behavior: restart, done last for correctness
12811305
if config._propagation_behavior_extract == _PROPAGATION_BEHAVIOR_RESTART:
12821306
link = HTTPPropagator._context_to_span_link(context, style, "propagation_behavior_extract")
12831307
context = Context(baggage=context.get_all_baggage_items(), span_links=[link] if link else [])
12841308

12851309
return context
1286-
12871310
except Exception:
12881311
log.debug("error while extracting context propagation headers", exc_info=True)
1289-
return Context()
1312+
return Context()

0 commit comments

Comments
 (0)