Skip to content

Commit 2d6e994

Browse files
Add --browser flag to auto-open HTML output after generation
Add a --browser flag that automatically opens the generated flamegraph and heatmap HTML files in the default browser once profiling completes.
1 parent b9a4806 commit 2d6e994

File tree

3 files changed

+53
-0
lines changed

3 files changed

+53
-0
lines changed

Doc/library/profiling.sampling.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,6 +1491,13 @@ Output options
14911491
named ``<format>_<PID>.<ext>`` (for example, ``flamegraph_12345.html``).
14921492
:option:`--heatmap` creates a directory named ``heatmap_<PID>``.
14931493

1494+
.. option:: --browser
1495+
1496+
Automatically open HTML output (:option:`--flamegraph` and
1497+
:option:`--heatmap`) in your default web browser after generation.
1498+
When profiling with :option:`--subprocesses`, only the main process
1499+
opens the browser; subprocess outputs are never auto-opened.
1500+
14941501

14951502
pstats display options
14961503
----------------------

Lib/profiling/sampling/cli.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import subprocess
1111
import sys
1212
import time
13+
import webbrowser
1314
from contextlib import nullcontext
1415

1516
from .errors import SamplingUnknownProcessError, SamplingModuleNotFoundError, SamplingScriptNotFoundError
@@ -492,6 +493,12 @@ def _add_format_options(parser, include_compression=True, include_binary=True):
492493
help="Output path (default: stdout for pstats, auto-generated for others). "
493494
"For heatmap: directory name (default: heatmap_PID)",
494495
)
496+
output_group.add_argument(
497+
"--browser",
498+
action="store_true",
499+
help="Automatically open HTML output (flamegraph, heatmap) in browser. "
500+
"When using --subprocesses, only the main process opens the browser",
501+
)
495502

496503

497504
def _add_pstats_options(parser):
@@ -591,6 +598,32 @@ def _generate_output_filename(format_type, pid):
591598
return f"{format_type}_{pid}.{extension}"
592599

593600

601+
def _open_in_browser(path):
602+
"""Open a file or directory in the default web browser.
603+
604+
Args:
605+
path: File path or directory path to open
606+
607+
For directories (heatmap), opens the index.html file inside.
608+
"""
609+
abs_path = os.path.abspath(path)
610+
611+
# For heatmap directories, open the index.html file
612+
if os.path.isdir(abs_path):
613+
index_path = os.path.join(abs_path, 'index.html')
614+
if os.path.exists(index_path):
615+
abs_path = index_path
616+
else:
617+
print(f"Warning: Could not find index.html in {path}", file=sys.stderr)
618+
return
619+
620+
file_url = f"file://{abs_path}"
621+
try:
622+
webbrowser.open(file_url)
623+
except Exception as e:
624+
print(f"Warning: Could not open browser: {e}", file=sys.stderr)
625+
626+
594627
def _handle_output(collector, args, pid, mode):
595628
"""Handle output for the collector based on format and arguments.
596629
@@ -630,6 +663,10 @@ def _handle_output(collector, args, pid, mode):
630663
filename = args.outfile or _generate_output_filename(args.format, pid)
631664
collector.export(filename)
632665

666+
# Auto-open browser for HTML output if --browser flag is set
667+
if args.format in ('flamegraph', 'heatmap') and getattr(args, 'browser', False):
668+
_open_in_browser(filename)
669+
633670

634671
def _validate_args(args, parser):
635672
"""Validate format-specific options and live mode requirements.
@@ -1153,6 +1190,10 @@ def progress_callback(current, total):
11531190
filename = args.outfile or _generate_output_filename(args.format, os.getpid())
11541191
collector.export(filename)
11551192

1193+
# Auto-open browser for HTML output if --browser flag is set
1194+
if args.format in ('flamegraph', 'heatmap') and getattr(args, 'browser', False):
1195+
_open_in_browser(filename)
1196+
11561197
print(f"Replayed {count} samples")
11571198

11581199

Lib/test/test_profiling/test_sampling_profiler/test_children.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,11 @@ def assert_flag_value_pair(flag, value):
438438
child_args,
439439
f"Flag '--flamegraph' not found in args: {child_args}",
440440
)
441+
self.assertNotIn(
442+
"--browser",
443+
child_args,
444+
f"Flag '--browser' should not be in child args: {child_args}",
445+
)
441446

442447
def test_build_child_profiler_args_no_gc(self):
443448
"""Test building CLI args with --no-gc."""

0 commit comments

Comments
 (0)