|
10 | 10 | import subprocess |
11 | 11 | import sys |
12 | 12 | import time |
| 13 | +import webbrowser |
13 | 14 | from contextlib import nullcontext |
14 | 15 |
|
15 | 16 | from .errors import SamplingUnknownProcessError, SamplingModuleNotFoundError, SamplingScriptNotFoundError |
@@ -492,6 +493,11 @@ def _add_format_options(parser, include_compression=True, include_binary=True): |
492 | 493 | help="Output path (default: stdout for pstats, auto-generated for others). " |
493 | 494 | "For heatmap: directory name (default: heatmap_PID)", |
494 | 495 | ) |
| 496 | + output_group.add_argument( |
| 497 | + "--no-browser", |
| 498 | + action="store_true", |
| 499 | + help="Disable automatic browser opening for HTML output (flamegraph, heatmap)", |
| 500 | + ) |
495 | 501 |
|
496 | 502 |
|
497 | 503 | def _add_pstats_options(parser): |
@@ -591,6 +597,32 @@ def _generate_output_filename(format_type, pid): |
591 | 597 | return f"{format_type}_{pid}.{extension}" |
592 | 598 |
|
593 | 599 |
|
| 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 | + |
594 | 626 | def _handle_output(collector, args, pid, mode): |
595 | 627 | """Handle output for the collector based on format and arguments. |
596 | 628 |
|
@@ -630,6 +662,10 @@ def _handle_output(collector, args, pid, mode): |
630 | 662 | filename = args.outfile or _generate_output_filename(args.format, pid) |
631 | 663 | collector.export(filename) |
632 | 664 |
|
| 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 | + |
633 | 669 |
|
634 | 670 | def _validate_args(args, parser): |
635 | 671 | """Validate format-specific options and live mode requirements. |
@@ -1153,6 +1189,10 @@ def progress_callback(current, total): |
1153 | 1189 | filename = args.outfile or _generate_output_filename(args.format, os.getpid()) |
1154 | 1190 | collector.export(filename) |
1155 | 1191 |
|
| 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 | + |
1156 | 1196 | print(f"Replayed {count} samples") |
1157 | 1197 |
|
1158 | 1198 |
|
|
0 commit comments