@@ -827,6 +827,36 @@ async def main():
827827 asyncio.run(main())
828828'''
829829
830+ def _collect_async_samples (self , async_aware_mode ):
831+ """Helper to collect samples and count function occurrences.
832+
833+ Returns a dict mapping function names to their sample counts.
834+ """
835+ with test_subprocess (self .async_script ) as subproc :
836+ try :
837+ collector = CollapsedStackCollector (1000 , skip_idle = False )
838+ profiling .sampling .sample .sample (
839+ subproc .process .pid ,
840+ collector ,
841+ duration_sec = SHORT_TIMEOUT ,
842+ async_aware = async_aware_mode ,
843+ )
844+ except PermissionError :
845+ self .skipTest ("Insufficient permissions for remote profiling" )
846+
847+ # Count samples per function from collapsed stacks
848+ # stack_counter keys are (call_tree, thread_id) where call_tree
849+ # is a tuple of (file, line, func) tuples
850+ func_samples = {}
851+ total = 0
852+ for (call_tree , _thread_id ), count in collector .stack_counter .items ():
853+ total += count
854+ for _file , _line , func in call_tree :
855+ func_samples [func ] = func_samples .get (func , 0 ) + count
856+
857+ func_samples ["_total" ] = total
858+ return func_samples
859+
830860 def test_async_aware_all_sees_sleeping_and_running_tasks (self ):
831861 """Test that async_aware='all' captures both sleeping and CPU-running tasks.
832862
@@ -840,35 +870,12 @@ def test_async_aware_all_sees_sleeping_and_running_tasks(self):
840870
841871 async_aware='all' should see ALL 4 leaf tasks in the output.
842872 """
843- with (
844- test_subprocess (self .async_script ) as subproc ,
845- io .StringIO () as captured_output ,
846- mock .patch ("sys.stdout" , captured_output ),
847- ):
848- try :
849- collector = PstatsCollector (sample_interval_usec = 1000 , skip_idle = False )
850- profiling .sampling .sample .sample (
851- subproc .process .pid ,
852- collector ,
853- duration_sec = SHORT_TIMEOUT ,
854- async_aware = "all" ,
855- )
856- collector .print_stats (show_summary = False )
857- except PermissionError :
858- self .skipTest ("Insufficient permissions for remote profiling" )
859-
860- output = captured_output .getvalue ()
873+ samples = self ._collect_async_samples ("all" )
861874
862- # async_aware="all" should see ALL leaf tasks
863- self .assertIn ("sleeping_leaf" , output )
864- self .assertIn ("cpu_leaf" , output )
865- # Should see the tree structure via task markers
866- self .assertIn ("<task>" , output )
867- # Should see task names in output (leaf tasks)
868- self .assertIn ("Sleeper" , output )
869- self .assertIn ("Worker" , output )
870- # Should see the parent task in the tree (supervisor function)
871- self .assertIn ("supervisor" , output )
875+ self .assertGreater (samples ["_total" ], 0 , "Should have collected samples" )
876+ self .assertIn ("sleeping_leaf" , samples )
877+ self .assertIn ("cpu_leaf" , samples )
878+ self .assertIn ("supervisor" , samples )
872879
873880 def test_async_aware_running_sees_only_cpu_task (self ):
874881 """Test that async_aware='running' only captures the actively running task.
@@ -883,32 +890,18 @@ def test_async_aware_running_sees_only_cpu_task(self):
883890
884891 async_aware='running' should only see the Worker task doing CPU work.
885892 """
886- with (
887- test_subprocess (self .async_script ) as subproc ,
888- io .StringIO () as captured_output ,
889- mock .patch ("sys.stdout" , captured_output ),
890- ):
891- try :
892- collector = PstatsCollector (sample_interval_usec = 1000 , skip_idle = False )
893- profiling .sampling .sample .sample (
894- subproc .process .pid ,
895- collector ,
896- duration_sec = SHORT_TIMEOUT ,
897- async_aware = "running" ,
898- )
899- collector .print_stats (show_summary = False )
900- except PermissionError :
901- self .skipTest ("Insufficient permissions for remote profiling" )
893+ samples = self ._collect_async_samples ("running" )
902894
903- output = captured_output .getvalue ()
895+ total = samples ["_total" ]
896+ cpu_leaf_samples = samples .get ("cpu_leaf" , 0 )
897+
898+ self .assertGreater (total , 0 , "Should have collected some samples" )
899+ self .assertGreater (cpu_leaf_samples , 0 , "cpu_leaf should appear in samples" )
904900
905- # async_aware="running" should see the CPU task prominently
906- self .assertIn ("cpu_leaf" , output )
907- # Should see Worker task marker
908- self .assertIn ("Worker" , output )
909- # Should see the tree structure (supervisor in the parent chain)
910- self .assertIn ("supervisor" , output )
911- # Should see task boundary markers
912- self .assertIn ("<task>" , output )
913- # async_aware="running" should NOT see sleeping tasks
914- self .assertNotIn ("sleeping_leaf" , output )
901+ # cpu_leaf should have at least 90% of samples (typically 99%+)
902+ # sleeping_leaf may occasionally appear with very few samples (< 1%)
903+ # when tasks briefly wake up to check sleep timers
904+ cpu_percentage = (cpu_leaf_samples / total ) * 100
905+ self .assertGreater (cpu_percentage , 90.0 ,
906+ f"cpu_leaf should dominate samples in 'running' mode, "
907+ f"got { cpu_percentage :.1f} % ({ cpu_leaf_samples } /{ total } )" )
0 commit comments