Skip to content

Commit e6eaa2c

Browse files
committed
CLI update
1 parent 2203021 commit e6eaa2c

2 files changed

Lines changed: 55 additions & 31 deletions

File tree

Lib/profiling/sampling/cli.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,8 @@ def _add_sampling_options(parser):
197197
)
198198
sampling_group.add_argument(
199199
"--async-aware",
200-
choices=["running", "all"],
201-
default=None,
202-
metavar="MODE",
203-
help='Enable async-aware profiling: "running" (only running task) '
204-
'or "all" (all tasks including waiting)',
200+
action="store_true",
201+
help="Enable async-aware profiling (uses task-based stack reconstruction)",
205202
)
206203

207204

@@ -213,7 +210,14 @@ def _add_mode_options(parser):
213210
choices=["wall", "cpu", "gil"],
214211
default="wall",
215212
help="Sampling mode: wall (all samples), cpu (only samples when thread is on CPU), "
216-
"gil (only samples when thread holds the GIL)",
213+
"gil (only samples when thread holds the GIL). Incompatible with --async-aware",
214+
)
215+
mode_group.add_argument(
216+
"--async-mode",
217+
choices=["running", "all"],
218+
default="running",
219+
help='Async profiling mode: "running" (only running task) '
220+
'or "all" (all tasks including waiting). Requires --async-aware',
217221
)
218222

219223

@@ -391,7 +395,7 @@ def _validate_args(args, parser):
391395
)
392396

393397
# Async-aware mode is incompatible with --native, --no-gc, --mode, and --all-threads
394-
if args.async_aware is not None:
398+
if args.async_aware:
395399
issues = []
396400
if args.native:
397401
issues.append("--native")
@@ -407,6 +411,10 @@ def _validate_args(args, parser):
407411
"Async-aware profiling uses task-based stack reconstruction."
408412
)
409413

414+
# --async-mode requires --async-aware
415+
if hasattr(args, 'async_mode') and args.async_mode != "running" and not args.async_aware:
416+
parser.error("--async-mode requires --async-aware to be enabled.")
417+
410418
# Live mode is incompatible with format options
411419
if hasattr(args, 'live') and args.live:
412420
if args.format != "pstats":
@@ -595,7 +603,7 @@ def _handle_attach(args):
595603
all_threads=args.all_threads,
596604
realtime_stats=args.realtime_stats,
597605
mode=mode,
598-
async_aware=args.async_aware,
606+
async_aware=args.async_mode if args.async_aware else None,
599607
native=args.native,
600608
gc=args.gc,
601609
)
@@ -644,7 +652,7 @@ def _handle_run(args):
644652
all_threads=args.all_threads,
645653
realtime_stats=args.realtime_stats,
646654
mode=mode,
647-
async_aware=args.async_aware,
655+
async_aware=args.async_mode if args.async_aware else None,
648656
native=args.native,
649657
gc=args.gc,
650658
)
@@ -677,7 +685,7 @@ def _handle_live_attach(args, pid):
677685
limit=20, # Default limit
678686
pid=pid,
679687
mode=mode,
680-
async_aware=args.async_aware,
688+
async_aware=args.async_mode if args.async_aware else None,
681689
)
682690

683691
# Sample in live mode
@@ -688,7 +696,7 @@ def _handle_live_attach(args, pid):
688696
all_threads=args.all_threads,
689697
realtime_stats=args.realtime_stats,
690698
mode=mode,
691-
async_aware=args.async_aware,
699+
async_aware=args.async_mode if args.async_aware else None,
692700
native=args.native,
693701
gc=args.gc,
694702
)
@@ -718,7 +726,7 @@ def _handle_live_run(args):
718726
limit=20, # Default limit
719727
pid=process.pid,
720728
mode=mode,
721-
async_aware=args.async_aware,
729+
async_aware=args.async_mode if args.async_aware else None,
722730
)
723731

724732
# Profile the subprocess in live mode
@@ -730,7 +738,7 @@ def _handle_live_run(args):
730738
all_threads=args.all_threads,
731739
realtime_stats=args.realtime_stats,
732740
mode=mode,
733-
async_aware=args.async_aware,
741+
async_aware=args.async_mode if args.async_aware else None,
734742
native=args.native,
735743
gc=args.gc,
736744
)

Lib/test/test_profiling/test_sampling_profiler/test_cli.py

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -548,9 +548,9 @@ def test_sort_options(self):
548548
mock_sample.assert_called_once()
549549
mock_sample.reset_mock()
550550

551-
def test_async_aware_argument_all(self):
552-
"""Test --async-aware all argument is parsed correctly."""
553-
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "all"]
551+
def test_async_aware_flag_defaults_to_running(self):
552+
"""Test --async-aware flag enables async profiling with default 'running' mode."""
553+
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware"]
554554

555555
with (
556556
mock.patch("sys.argv", test_args),
@@ -560,13 +560,13 @@ def test_async_aware_argument_all(self):
560560
main()
561561

562562
mock_sample.assert_called_once()
563-
# Verify async_aware was passed
563+
# Verify async_aware was passed with default "running" mode
564564
call_kwargs = mock_sample.call_args[1]
565-
self.assertEqual(call_kwargs.get("async_aware"), "all")
565+
self.assertEqual(call_kwargs.get("async_aware"), "running")
566566

567-
def test_async_aware_argument_running(self):
568-
"""Test --async-aware running argument is parsed correctly."""
569-
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "running"]
567+
def test_async_aware_with_async_mode_all(self):
568+
"""Test --async-aware with --async-mode all."""
569+
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "--async-mode", "all"]
570570

571571
with (
572572
mock.patch("sys.argv", test_args),
@@ -577,10 +577,10 @@ def test_async_aware_argument_running(self):
577577

578578
mock_sample.assert_called_once()
579579
call_kwargs = mock_sample.call_args[1]
580-
self.assertEqual(call_kwargs.get("async_aware"), "running")
580+
self.assertEqual(call_kwargs.get("async_aware"), "all")
581581

582582
def test_async_aware_default_is_none(self):
583-
"""Test --async-aware defaults to None when not specified."""
583+
"""Test async_aware defaults to None when --async-aware not specified."""
584584
test_args = ["profiling.sampling.cli", "attach", "12345"]
585585

586586
with (
@@ -594,9 +594,9 @@ def test_async_aware_default_is_none(self):
594594
call_kwargs = mock_sample.call_args[1]
595595
self.assertIsNone(call_kwargs.get("async_aware"))
596596

597-
def test_async_aware_invalid_choice(self):
598-
"""Test --async-aware with invalid choice raises error."""
599-
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "invalid"]
597+
def test_async_mode_invalid_choice(self):
598+
"""Test --async-mode with invalid choice raises error."""
599+
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "--async-mode", "invalid"]
600600

601601
with (
602602
mock.patch("sys.argv", test_args),
@@ -608,9 +608,25 @@ def test_async_aware_invalid_choice(self):
608608

609609
self.assertEqual(cm.exception.code, 2) # argparse error
610610

611+
def test_async_mode_requires_async_aware(self):
612+
"""Test --async-mode without --async-aware raises error."""
613+
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-mode", "all"]
614+
615+
with (
616+
mock.patch("sys.argv", test_args),
617+
mock.patch("sys.stderr", io.StringIO()) as mock_stderr,
618+
self.assertRaises(SystemExit) as cm,
619+
):
620+
from profiling.sampling.cli import main
621+
main()
622+
623+
self.assertEqual(cm.exception.code, 2) # argparse error
624+
error_msg = mock_stderr.getvalue()
625+
self.assertIn("--async-mode requires --async-aware", error_msg)
626+
611627
def test_async_aware_incompatible_with_native(self):
612628
"""Test --async-aware is incompatible with --native."""
613-
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "all", "--native"]
629+
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "--native"]
614630

615631
with (
616632
mock.patch("sys.argv", test_args),
@@ -627,7 +643,7 @@ def test_async_aware_incompatible_with_native(self):
627643

628644
def test_async_aware_incompatible_with_no_gc(self):
629645
"""Test --async-aware is incompatible with --no-gc."""
630-
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "running", "--no-gc"]
646+
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "--no-gc"]
631647

632648
with (
633649
mock.patch("sys.argv", test_args),
@@ -644,7 +660,7 @@ def test_async_aware_incompatible_with_no_gc(self):
644660

645661
def test_async_aware_incompatible_with_both_native_and_no_gc(self):
646662
"""Test --async-aware is incompatible with both --native and --no-gc."""
647-
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "all", "--native", "--no-gc"]
663+
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "--native", "--no-gc"]
648664

649665
with (
650666
mock.patch("sys.argv", test_args),
@@ -662,7 +678,7 @@ def test_async_aware_incompatible_with_both_native_and_no_gc(self):
662678

663679
def test_async_aware_incompatible_with_mode(self):
664680
"""Test --async-aware is incompatible with --mode (non-wall)."""
665-
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "all", "--mode", "cpu"]
681+
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "--mode", "cpu"]
666682

667683
with (
668684
mock.patch("sys.argv", test_args),
@@ -679,7 +695,7 @@ def test_async_aware_incompatible_with_mode(self):
679695

680696
def test_async_aware_incompatible_with_all_threads(self):
681697
"""Test --async-aware is incompatible with --all-threads."""
682-
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "running", "--all-threads"]
698+
test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "--all-threads"]
683699

684700
with (
685701
mock.patch("sys.argv", test_args),

0 commit comments

Comments
 (0)