Skip to content

Commit f7fe3be

Browse files
committed
Add async benchmark modes
1 parent 46a0b2c commit f7fe3be

1 file changed

Lines changed: 91 additions & 9 deletions

File tree

Tools/inspection/benchmark_external_inspection.py

Lines changed: 91 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,45 @@ def create_threads(n):
151151
time.sleep(0.05)
152152
'''
153153

154+
ASYNC_CODE = '''\
155+
import asyncio
156+
import contextlib
157+
import math
158+
159+
def compute_slice(seed):
160+
result = 0.0
161+
for i in range(2000):
162+
result += math.sin(seed + i) * math.sqrt(i + 1)
163+
return result
164+
165+
async def leaf_task(seed):
166+
total = 0.0
167+
while True:
168+
total += compute_slice(seed)
169+
await asyncio.sleep(0)
170+
171+
async def parent_task(seed):
172+
child = asyncio.create_task(leaf_task(seed + 1000), name=f"leaf-{seed}")
173+
try:
174+
while True:
175+
compute_slice(seed)
176+
await asyncio.sleep(0.001)
177+
finally:
178+
child.cancel()
179+
with contextlib.suppress(asyncio.CancelledError):
180+
await child
181+
182+
async def main():
183+
tasks = [
184+
asyncio.create_task(parent_task(i), name=f"parent-{i}")
185+
for i in range(8)
186+
]
187+
await asyncio.gather(*tasks)
188+
189+
if __name__ == "__main__":
190+
asyncio.run(main())
191+
'''
192+
154193
CODE_EXAMPLES = {
155194
"basic": {
156195
"code": CODE,
@@ -164,22 +203,44 @@ def create_threads(n):
164203
"code": CODE_WITH_TONS_OF_THREADS,
165204
"description": "Tons of threads doing mixed CPU/IO work",
166205
},
206+
"asyncio": {
207+
"code": ASYNC_CODE,
208+
"description": "Asyncio tasks with active and awaited coroutine chains",
209+
},
210+
}
211+
212+
OPERATIONS = {
213+
"stack_trace": {
214+
"method": "get_stack_trace",
215+
"label": "get_stack_trace()",
216+
},
217+
"async_stack_trace": {
218+
"method": "get_async_stack_trace",
219+
"label": "get_async_stack_trace()",
220+
},
221+
"all_awaited_by": {
222+
"method": "get_all_awaited_by",
223+
"label": "get_all_awaited_by()",
224+
},
167225
}
168226

169227

170-
def benchmark(unwinder, duration_seconds=10, blocking=False):
228+
def benchmark(unwinder, duration_seconds=10, blocking=False, operation="stack_trace"):
171229
"""Benchmark mode - measure raw sampling speed for specified duration"""
172230
sample_count = 0
173231
fail_count = 0
174232
total_work_time = 0.0
175233
start_time = time.perf_counter()
176234
end_time = start_time + duration_seconds
177235
total_attempts = 0
236+
operation_info = OPERATIONS[operation]
237+
operation_method = getattr(unwinder, operation_info["method"])
178238

179239
colors = get_colors(can_colorize())
180240

181241
print(
182-
f"{colors.BOLD_BLUE}Benchmarking sampling speed for {duration_seconds} seconds...{colors.RESET}"
242+
f"{colors.BOLD_BLUE}Benchmarking {operation_info['label']} speed "
243+
f"for {duration_seconds} seconds...{colors.RESET}"
183244
)
184245

185246
try:
@@ -190,8 +251,8 @@ def benchmark(unwinder, duration_seconds=10, blocking=False):
190251
if blocking:
191252
unwinder.pause_threads()
192253
try:
193-
stack_trace = unwinder.get_stack_trace()
194-
if stack_trace:
254+
sample = operation_method()
255+
if sample:
195256
sample_count += 1
196257
finally:
197258
if blocking:
@@ -239,6 +300,7 @@ def benchmark(unwinder, duration_seconds=10, blocking=False):
239300
(sample_count / total_attempts) * 100 if total_attempts > 0 else 0
240301
),
241302
"total_work_time": total_work_time,
303+
"operation": operation_info["label"],
242304
"avg_work_time_us": (
243305
(total_work_time / total_attempts) * 1e6 if total_attempts > 0 else 0
244306
),
@@ -252,7 +314,7 @@ def print_benchmark_results(results):
252314
colors = get_colors(can_colorize())
253315

254316
print(f"\n{colors.BOLD_GREEN}{'='*60}{colors.RESET}")
255-
print(f"{colors.BOLD_GREEN}get_stack_trace() Benchmark Results{colors.RESET}")
317+
print(f"{colors.BOLD_GREEN}{results['operation']} Benchmark Results{colors.RESET}")
256318
print(f"{colors.BOLD_GREEN}{'='*60}{colors.RESET}")
257319

258320
# Basic statistics
@@ -329,6 +391,8 @@ def parse_arguments():
329391
%(prog)s -d 60 # Run basic benchmark for 60 seconds
330392
%(prog)s --code deep_static # Run deep static call stack benchmark
331393
%(prog)s --code deep_static -d 30 # Run deep static benchmark for 30 seconds
394+
%(prog)s --operation async_stack_trace
395+
%(prog)s --operation all_awaited_by
332396
333397
Available code examples:
334398
{examples_desc}
@@ -348,8 +412,15 @@ def parse_arguments():
348412
"--code",
349413
"-c",
350414
choices=list(CODE_EXAMPLES.keys()),
351-
default="basic",
352-
help="Code example to benchmark (default: basic)",
415+
default=None,
416+
help="Code example to benchmark (default: basic, or asyncio for async operations)",
417+
)
418+
419+
parser.add_argument(
420+
"--operation",
421+
choices=list(OPERATIONS.keys()),
422+
default="stack_trace",
423+
help="Remote unwinder operation to benchmark (default: stack_trace)",
353424
)
354425

355426
parser.add_argument(
@@ -365,7 +436,10 @@ def parse_arguments():
365436
help="Stop all threads before sampling for consistent snapshots",
366437
)
367438

368-
return parser.parse_args()
439+
args = parser.parse_args()
440+
if args.code is None:
441+
args.code = "asyncio" if args.operation != "stack_trace" else "basic"
442+
return args
369443

370444

371445
def create_target_process(temp_file, code_example="basic"):
@@ -420,6 +494,9 @@ def main():
420494
print(
421495
f"{colors.CYAN}Benchmark Duration:{colors.RESET} {colors.YELLOW}{args.duration}{colors.RESET} seconds"
422496
)
497+
print(
498+
f"{colors.CYAN}Operation:{colors.RESET} {colors.GREEN}{OPERATIONS[args.operation]['label']}{colors.RESET}"
499+
)
423500
print(
424501
f"{colors.CYAN}Blocking Mode:{colors.RESET} {colors.GREEN if args.blocking else colors.YELLOW}{'enabled' if args.blocking else 'disabled'}{colors.RESET}"
425502
)
@@ -451,7 +528,12 @@ def main():
451528
unwinder = _remote_debugging.RemoteUnwinder(
452529
process.pid, cache_frames=True, **kwargs
453530
)
454-
results = benchmark(unwinder, duration_seconds=args.duration, blocking=args.blocking)
531+
results = benchmark(
532+
unwinder,
533+
duration_seconds=args.duration,
534+
blocking=args.blocking,
535+
operation=args.operation,
536+
)
455537
finally:
456538
cleanup_process(process, temp_file_path)
457539

0 commit comments

Comments
 (0)