Skip to content

Commit 0b81599

Browse files
tests: v2
1 parent 0117c98 commit 0b81599

File tree

2 files changed

+152
-418
lines changed

2 files changed

+152
-418
lines changed

ddtrace/profiling/collector/_lock.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class _ProfiledLock:
5252
"init_location",
5353
"acquired_time",
5454
"name",
55+
"is_internal",
5556
)
5657

5758
def __init__(
@@ -60,6 +61,7 @@ def __init__(
6061
tracer: Optional[Tracer],
6162
max_nframes: int,
6263
capture_sampler: collector.CaptureSampler,
64+
is_internal: bool = False,
6365
) -> None:
6466
self.__wrapped__: Any = wrapped
6567
self.tracer: Optional[Tracer] = tracer
@@ -71,6 +73,9 @@ def __init__(
7173
self.init_location: str = f"{os.path.basename(code.co_filename)}:{frame.f_lineno}"
7274
self.acquired_time: int = 0
7375
self.name: Optional[str] = None
76+
# If True, this lock is internal to another sync primitive (e.g., Lock inside Semaphore)
77+
# and should not generate profile samples to avoid double-counting
78+
self.is_internal: bool = is_internal
7479

7580
### DUNDER methods ###
7681

@@ -161,6 +166,11 @@ def _flush_sample(self, start: int, end: int, is_acquire: bool) -> None:
161166
end: End timestamp in nanoseconds
162167
is_acquire: True for acquire operations, False for release operations
163168
"""
169+
# Skip profiling for internal locks (e.g., Lock inside Semaphore/Condition)
170+
# to avoid double-counting when multiple collectors are active
171+
if self.is_internal:
172+
return
173+
164174
handle: ddup.SampleHandle = ddup.SampleHandle()
165175

166176
handle.push_monotonic_ns(end)
@@ -297,12 +307,30 @@ def patch(self) -> None:
297307
original_lock: Any = self._original_lock # Capture non-None value
298308

299309
def _profiled_allocate_lock(*args: Any, **kwargs: Any) -> _ProfiledLock:
300-
"""Simple wrapper that returns profiled locks."""
310+
"""Simple wrapper that returns profiled locks.
311+
312+
Detects if the lock is being created from within threading.py stdlib
313+
(i.e., internal to Semaphore/Condition) to avoid double-counting.
314+
"""
315+
import threading as threading_module
316+
317+
# Check if caller is from threading.py (internal lock)
318+
is_internal: bool = False
319+
try:
320+
# Frame 0: _profiled_allocate_lock
321+
# Frame 1: _LockAllocatorWrapper.__call__
322+
# Frame 2: actual caller (threading.Lock() call site)
323+
caller_filename = sys._getframe(2).f_code.co_filename
324+
is_internal = bool(threading_module.__file__) and caller_filename == threading_module.__file__
325+
except (ValueError, AttributeError):
326+
pass
327+
301328
return self.PROFILED_LOCK_CLASS(
302329
wrapped=original_lock(*args, **kwargs),
303330
tracer=self.tracer,
304331
max_nframes=self.nframes,
305332
capture_sampler=self._capture_sampler,
333+
is_internal=is_internal,
306334
)
307335

308336
self._set_patch_target(_LockAllocatorWrapper(_profiled_allocate_lock))

0 commit comments

Comments
 (0)