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