Skip to content

Commit 7f32851

Browse files
Auto-open HTML output in browser after generation
Automatically open flamegraph and heatmap HTML output in the default browser after profiling completes. Add --no-browser flag to disable this behavior.
1 parent b9a4806 commit 7f32851

File tree

1 file changed

+40
-0
lines changed

1 file changed

+40
-0
lines changed

Lib/profiling/sampling/cli.py

Lines changed: 40 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,11 @@ 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+
"--no-browser",
498+
action="store_true",
499+
help="Disable automatic browser opening for HTML output (flamegraph, heatmap)",
500+
)
495501

496502

497503
def _add_pstats_options(parser):
@@ -591,6 +597,32 @@ def _generate_output_filename(format_type, pid):
591597
return f"{format_type}_{pid}.{extension}"
592598

593599

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

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

634670
def _validate_args(args, parser):
635671
"""Validate format-specific options and live mode requirements.
@@ -1153,6 +1189,10 @@ def progress_callback(current, total):
11531189
filename = args.outfile or _generate_output_filename(args.format, os.getpid())
11541190
collector.export(filename)
11551191

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

11581198

0 commit comments

Comments
 (0)