https://github.com/python/cpython/commit/5d133351c63b20882d85f92c2942c7d99066cebb
commit: 5d133351c63b20882d85f92c2942c7d99066cebb
branch: main
author: ivonastojanovic <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-01-01T19:05:45Z
summary:
gh-142927: Auto-open HTML output in browser after generation (#143178)
files:
M Doc/library/profiling.sampling.rst
M Lib/profiling/sampling/cli.py
M Lib/test/test_profiling/test_sampling_profiler/test_children.py
diff --git a/Doc/library/profiling.sampling.rst
b/Doc/library/profiling.sampling.rst
index dae67cca66d9b4..9bc58b4d1bc976 100644
--- a/Doc/library/profiling.sampling.rst
+++ b/Doc/library/profiling.sampling.rst
@@ -1490,6 +1490,13 @@ Output options
named ``<format>_<PID>.<ext>`` (for example, ``flamegraph_12345.html``).
:option:`--heatmap` creates a directory named ``heatmap_<PID>``.
+.. option:: --browser
+
+ Automatically open HTML output (:option:`--flamegraph` and
+ :option:`--heatmap`) in your default web browser after generation.
+ When profiling with :option:`--subprocesses`, only the main process
+ opens the browser; subprocess outputs are never auto-opened.
+
pstats display options
----------------------
diff --git a/Lib/profiling/sampling/cli.py b/Lib/profiling/sampling/cli.py
index e43925ea8595f0..ea3926c9565809 100644
--- a/Lib/profiling/sampling/cli.py
+++ b/Lib/profiling/sampling/cli.py
@@ -10,6 +10,7 @@
import subprocess
import sys
import time
+import webbrowser
from contextlib import nullcontext
from .errors import SamplingUnknownProcessError, SamplingModuleNotFoundError,
SamplingScriptNotFoundError
@@ -487,6 +488,12 @@ def _add_format_options(parser, include_compression=True,
include_binary=True):
help="Output path (default: stdout for pstats, auto-generated for
others). "
"For heatmap: directory name (default: heatmap_PID)",
)
+ output_group.add_argument(
+ "--browser",
+ action="store_true",
+ help="Automatically open HTML output (flamegraph, heatmap) in browser.
"
+ "When using --subprocesses, only the main process opens the browser",
+ )
def _add_pstats_options(parser):
@@ -586,6 +593,32 @@ def _generate_output_filename(format_type, pid):
return f"{format_type}_{pid}.{extension}"
+def _open_in_browser(path):
+ """Open a file or directory in the default web browser.
+
+ Args:
+ path: File path or directory path to open
+
+ For directories (heatmap), opens the index.html file inside.
+ """
+ abs_path = os.path.abspath(path)
+
+ # For heatmap directories, open the index.html file
+ if os.path.isdir(abs_path):
+ index_path = os.path.join(abs_path, 'index.html')
+ if os.path.exists(index_path):
+ abs_path = index_path
+ else:
+ print(f"Warning: Could not find index.html in {path}",
file=sys.stderr)
+ return
+
+ file_url = f"file://{abs_path}"
+ try:
+ webbrowser.open(file_url)
+ except Exception as e:
+ print(f"Warning: Could not open browser: {e}", file=sys.stderr)
+
+
def _handle_output(collector, args, pid, mode):
"""Handle output for the collector based on format and arguments.
@@ -625,6 +658,10 @@ def _handle_output(collector, args, pid, mode):
filename = args.outfile or _generate_output_filename(args.format,
pid)
collector.export(filename)
+ # Auto-open browser for HTML output if --browser flag is set
+ if args.format in ('flamegraph', 'heatmap') and getattr(args,
'browser', False):
+ _open_in_browser(filename)
+
def _validate_args(args, parser):
"""Validate format-specific options and live mode requirements.
@@ -1161,6 +1198,10 @@ def progress_callback(current, total):
filename = args.outfile or _generate_output_filename(args.format,
os.getpid())
collector.export(filename)
+ # Auto-open browser for HTML output if --browser flag is set
+ if args.format in ('flamegraph', 'heatmap') and getattr(args,
'browser', False):
+ _open_in_browser(filename)
+
print(f"Replayed {count} samples")
diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_children.py
b/Lib/test/test_profiling/test_sampling_profiler/test_children.py
index b7dc878a238f8d..84d50cd2088a9e 100644
--- a/Lib/test/test_profiling/test_sampling_profiler/test_children.py
+++ b/Lib/test/test_profiling/test_sampling_profiler/test_children.py
@@ -438,6 +438,11 @@ def assert_flag_value_pair(flag, value):
child_args,
f"Flag '--flamegraph' not found in args: {child_args}",
)
+ self.assertNotIn(
+ "--browser",
+ child_args,
+ f"Flag '--browser' should not be in child args: {child_args}",
+ )
def test_build_child_profiler_args_no_gc(self):
"""Test building CLI args with --no-gc."""
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]