| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 89a914c commit 336366f
18 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -265,6 +265,14 @@ Profile with real-time sampling statistics:: | |||
| 265 | 265 | ||
| 266 | 266 | Sample all threads in the process instead of just the main thread | |
| 267 | 267 | ||
| 268 | + .. option:: --native | ||
| 269 | + | ||
| 270 | + Include artificial ``<native>`` frames to denote calls to non-Python code. | ||
| 271 | + | ||
| 272 | + .. option:: --no-gc | ||
| 273 | + | ||
| 274 | + Don't include artificial ``<GC>`` frames to denote active garbage collection. | ||
| 275 | + | ||
| 268 | 276 | .. option:: --realtime-stats | |
| 269 | 277 | ||
| 270 | 278 | Print real-time sampling statistics during profiling | |
@@ -349,7 +357,7 @@ This section documents the programmatic interface for the :mod:`!profiling.sampl | |||
| 349 | 357 | For command-line usage, see :ref:`sampling-profiler-cli`. For conceptual information | |
| 350 | 358 | about statistical profiling, see :ref:`statistical-profiling` | |
| 351 | 359 | ||
| 352 | - .. function:: sample(pid, *, sort=2, sample_interval_usec=100, duration_sec=10, filename=None, all_threads=False, limit=None, show_summary=True, output_format="pstats", realtime_stats=False) | ||
| 360 | + .. function:: sample(pid, *, sort=2, sample_interval_usec=100, duration_sec=10, filename=None, all_threads=False, limit=None, show_summary=True, output_format="pstats", realtime_stats=False, native=False, gc=True) | ||
| 353 | 361 | ||
| 354 | 362 | Sample a Python process and generate profiling data. | |
| 355 | 363 | ||
@@ -367,6 +375,8 @@ about statistical profiling, see :ref:`statistical-profiling` | |||
| 367 | 375 | :param bool show_summary: Whether to show summary statistics (default: True) | |
| 368 | 376 | :param str output_format: Output format - 'pstats' or 'collapsed' (default: 'pstats') | |
| 369 | 377 | :param bool realtime_stats: Whether to display real-time statistics (default: False) | |
| 378 | + :param bool native: Whether to include ``<native>`` frames (default: False) | ||
| 379 | + :param bool gc: Whether to include ``<GC>`` frames (default: True) | ||
| 370 | 380 | ||
| 371 | 381 | :raises ValueError: If output_format is not 'pstats' or 'collapsed' | |
| 372 | 382 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -212,6 +212,7 @@ typedef struct _Py_DebugOffsets { | |||
| 212 | 212 | struct _gc { | |
| 213 | 213 | uint64_t size; | |
| 214 | 214 | uint64_t collecting; | |
| 215 | + uint64_t frame; | ||
| 215 | 216 | } gc; | |
| 216 | 217 | ||
| 217 | 218 | // Generator object offset; | |
@@ -355,6 +356,7 @@ typedef struct _Py_DebugOffsets { | |||
| 355 | 356 | .gc = { \ | |
| 356 | 357 | .size = sizeof(struct _gc_runtime_state), \ | |
| 357 | 358 | .collecting = offsetof(struct _gc_runtime_state, collecting), \ | |
| 359 | + .frame = offsetof(struct _gc_runtime_state, frame), \ | ||
| 358 | 360 | }, \ | |
| 359 | 361 | .gen_object = { \ | |
| 360 | 362 | .size = sizeof(PyGenObject), \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -46,10 +46,12 @@ struct _Py_global_strings { | |||
| 46 | 46 | STRUCT_FOR_STR(dot_locals, ".<locals>") | |
| 47 | 47 | STRUCT_FOR_STR(empty, "") | |
| 48 | 48 | STRUCT_FOR_STR(format, ".format") | |
| 49 | + STRUCT_FOR_STR(gc, "<GC>") | ||
| 49 | 50 | STRUCT_FOR_STR(generic_base, ".generic_base") | |
| 50 | 51 | STRUCT_FOR_STR(json_decoder, "json.decoder") | |
| 51 | 52 | STRUCT_FOR_STR(kwdefaults, ".kwdefaults") | |
| 52 | 53 | STRUCT_FOR_STR(list_err, "list index out of range") | |
| 54 | + STRUCT_FOR_STR(native, "<native>") | ||
| 53 | 55 | STRUCT_FOR_STR(str_replace_inf, "1e309") | |
| 54 | 56 | STRUCT_FOR_STR(type_params, ".type_params") | |
| 55 | 57 | STRUCT_FOR_STR(utf_8, "utf-8") | |
@@ -486,6 +488,7 @@ struct _Py_global_strings { | |||
| 486 | 488 | STRUCT_FOR_ID(fullerror) | |
| 487 | 489 | STRUCT_FOR_ID(func) | |
| 488 | 490 | STRUCT_FOR_ID(future) | |
| 491 | + STRUCT_FOR_ID(gc) | ||
| 489 | 492 | STRUCT_FOR_ID(generation) | |
| 490 | 493 | STRUCT_FOR_ID(get) | |
| 491 | 494 | STRUCT_FOR_ID(get_debug) | |
@@ -629,6 +632,7 @@ struct _Py_global_strings { | |||
| 629 | 632 | STRUCT_FOR_ID(name_from) | |
| 630 | 633 | STRUCT_FOR_ID(namespace_separator) | |
| 631 | 634 | STRUCT_FOR_ID(namespaces) | |
| 635 | + STRUCT_FOR_ID(native) | ||
| 632 | 636 | STRUCT_FOR_ID(ndigits) | |
| 633 | 637 | STRUCT_FOR_ID(nested) | |
| 634 | 638 | STRUCT_FOR_ID(new_file_name) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -212,6 +212,9 @@ struct _gc_runtime_state { | |||
| 212 | 212 | struct gc_generation_stats generation_stats[NUM_GENERATIONS]; | |
| 213 | 213 | /* true if we are currently running the collector */ | |
| 214 | 214 | int collecting; | |
| 215 | + // The frame that started the current collection. It might be NULL even when | ||
| 216 | + // collecting (if no Python frame is running): | ||
| 217 | + _PyInterpreterFrame *frame; | ||
| 215 | 218 | /* list of uncollectable objects */ | |
| 216 | 219 | PyObject *garbage; | |
| 217 | 220 | /* a list of callbacks to be invoked when collection is performed */ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -24,7 +24,6 @@ enum _frameowner { | |||
| 24 | 24 | FRAME_OWNED_BY_GENERATOR = 1, | |
| 25 | 25 | FRAME_OWNED_BY_FRAME_OBJECT = 2, | |
| 26 | 26 | FRAME_OWNED_BY_INTERPRETER = 3, | |
| 27 | - FRAME_OWNED_BY_CSTACK = 4, | ||
| 28 | 27 | }; | |
| 29 | 28 | ||
| 30 | 29 | struct _PyInterpreterFrame { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -151,17 +151,22 @@ function createPythonTooltip(data) { | |||
| 151 | 151 | const funcname = resolveString(d.data.funcname) || resolveString(d.data.name); | |
| 152 | 152 | const filename = resolveString(d.data.filename) || ""; | |
| 153 | 153 | ||
| 154 | + // Don't show file location for special frames like <GC> and <native> | ||
| 155 | + const isSpecialFrame = filename === "~"; | ||
| 156 | + const fileLocationHTML = isSpecialFrame ? "" : ` | ||
| 157 | + <div style="color: #5a6c7d; font-size: 13px; margin-bottom: 12px; | ||
| 158 | + font-family: monospace; background: #f8f9fa; | ||
| 159 | + padding: 4px 8px; border-radius: 4px; word-break: break-all; overflow-wrap: break-word;"> | ||
| 160 | + ${filename}${d.data.lineno ? ":" + d.data.lineno : ""} | ||
| 161 | + </div>`; | ||
| 162 | + | ||
| 154 | 163 | const tooltipHTML = ` | |
| 155 | 164 | <div> | |
| 156 | 165 | <div style="color: #3776ab; font-weight: 600; font-size: 16px; | |
| 157 | 166 | margin-bottom: 8px; line-height: 1.3; word-break: break-word; overflow-wrap: break-word;"> | |
| 158 | 167 | ${funcname} | |
| 159 | 168 | </div> | |
| 160 | - <div style="color: #5a6c7d; font-size: 13px; margin-bottom: 12px; | ||
| 161 | - font-family: monospace; background: #f8f9fa; | ||
| 162 | - padding: 4px 8px; border-radius: 4px; word-break: break-all; overflow-wrap: break-word;"> | ||
| 163 | - ${filename}${d.data.lineno ? ":" + d.data.lineno : ""} | ||
| 164 | - </div> | ||
| 169 | + ${fileLocationHTML} | ||
| 165 | 170 | <div style="display: grid; grid-template-columns: auto 1fr; | |
| 166 | 171 | gap: 8px 16px; font-size: 14px;"> | |
| 167 | 172 | <span style="color: #5a6c7d; font-weight: 500;">Execution Time:</span> | |
@@ -474,14 +479,23 @@ function populateStats(data) { | |||
| 474 | 479 | if (i < hotSpots.length && hotSpots[i]) { | |
| 475 | 480 | const hotspot = hotSpots[i]; | |
| 476 | 481 | const filename = hotspot.filename || 'unknown'; | |
| 477 | - const basename = filename !== 'unknown' ? filename.split('/').pop() : 'unknown'; | ||
| 478 | 482 | const lineno = hotspot.lineno ?? '?'; | |
| 479 | 483 | let funcDisplay = hotspot.funcname || 'unknown'; | |
| 480 | 484 | if (funcDisplay.length > 35) { | |
| 481 | 485 | funcDisplay = funcDisplay.substring(0, 32) + '...'; | |
| 482 | 486 | } | |
| 483 | 487 | ||
| 484 | - document.getElementById(`hotspot-file-${num}`).textContent = `${basename}:${lineno}`; | ||
| 488 | + // Don't show file:line for special frames like <GC> and <native> | ||
| 489 | + const isSpecialFrame = filename === '~' && (lineno === 0 || lineno === '?'); | ||
| 490 | + let fileDisplay; | ||
| 491 | + if (isSpecialFrame) { | ||
| 492 | + fileDisplay = '--'; | ||
| 493 | + } else { | ||
| 494 | + const basename = filename !== 'unknown' ? filename.split('/').pop() : 'unknown'; | ||
| 495 | + fileDisplay = `${basename}:${lineno}`; | ||
| 496 | + } | ||
| 497 | + | ||
| 498 | + document.getElementById(`hotspot-file-${num}`).textContent = fileDisplay; | ||
| 485 | 499 | document.getElementById(`hotspot-func-${num}`).textContent = funcDisplay; | |
| 486 | 500 | document.getElementById(`hotspot-detail-${num}`).textContent = `${hotspot.directPercent.toFixed(1)}% samples (${hotspot.directSamples.toLocaleString()})`; | |
| 487 | 501 | } else { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -137,19 +137,19 @@ def _run_with_sync(original_cmd): | |||
| 137 | 137 | ||
| 138 | 138 | ||
| 139 | 139 | class SampleProfiler: | |
| 140 | - def __init__(self, pid, sample_interval_usec, all_threads, *, mode=PROFILING_MODE_WALL, skip_non_matching_threads=True): | ||
| 140 | + def __init__(self, pid, sample_interval_usec, all_threads, *, mode=PROFILING_MODE_WALL, native=False, gc=True, skip_non_matching_threads=True): | ||
| 141 | 141 | self.pid = pid | |
| 142 | 142 | self.sample_interval_usec = sample_interval_usec | |
| 143 | 143 | self.all_threads = all_threads | |
| 144 | 144 | if _FREE_THREADED_BUILD: | |
| 145 | 145 | self.unwinder = _remote_debugging.RemoteUnwinder( | |
| 146 | - self.pid, all_threads=self.all_threads, mode=mode, | ||
| 146 | + self.pid, all_threads=self.all_threads, mode=mode, native=native, gc=gc, | ||
| 147 | 147 | skip_non_matching_threads=skip_non_matching_threads | |
| 148 | 148 | ) | |
| 149 | 149 | else: | |
| 150 | 150 | only_active_threads = bool(self.all_threads) | |
| 151 | 151 | self.unwinder = _remote_debugging.RemoteUnwinder( | |
| 152 | - self.pid, only_active_thread=only_active_threads, mode=mode, | ||
| 152 | + self.pid, only_active_thread=only_active_threads, mode=mode, native=native, gc=gc, | ||
| 153 | 153 | skip_non_matching_threads=skip_non_matching_threads | |
| 154 | 154 | ) | |
| 155 | 155 | # Track sample intervals and total sample count | |
@@ -616,6 +616,8 @@ def sample( | |||
| 616 | 616 | output_format="pstats", | |
| 617 | 617 | realtime_stats=False, | |
| 618 | 618 | mode=PROFILING_MODE_WALL, | |
| 619 | + native=False, | ||
| 620 | + gc=True, | ||
| 619 | 621 | ): | |
| 620 | 622 | # PROFILING_MODE_ALL implies no skipping at all | |
| 621 | 623 | if mode == PROFILING_MODE_ALL: | |
@@ -627,7 +629,7 @@ def sample( | |||
| 627 | 629 | skip_idle = mode != PROFILING_MODE_WALL | |
| 628 | 630 | ||
| 629 | 631 | profiler = SampleProfiler( | |
| 630 | - pid, sample_interval_usec, all_threads=all_threads, mode=mode, | ||
| 632 | + pid, sample_interval_usec, all_threads=all_threads, mode=mode, native=native, gc=gc, | ||
| 631 | 633 | skip_non_matching_threads=skip_non_matching_threads | |
| 632 | 634 | ) | |
| 633 | 635 | profiler.realtime_stats = realtime_stats | |
@@ -717,6 +719,8 @@ def wait_for_process_and_sample(pid, sort_value, args): | |||
| 717 | 719 | output_format=args.format, | |
| 718 | 720 | realtime_stats=args.realtime_stats, | |
| 719 | 721 | mode=mode, | |
| 722 | + native=args.native, | ||
| 723 | + gc=args.gc, | ||
| 720 | 724 | ) | |
| 721 | 725 | ||
| 722 | 726 | ||
@@ -767,9 +771,19 @@ def main(): | |||
| 767 | 771 | sampling_group.add_argument( | |
| 768 | 772 | "--realtime-stats", | |
| 769 | 773 | action="store_true", | |
| 770 | - default=False, | ||
| 771 | 774 | help="Print real-time sampling statistics (Hz, mean, min, max, stdev) during profiling", | |
| 772 | 775 | ) | |
| 776 | + sampling_group.add_argument( | ||
| 777 | + "--native", | ||
| 778 | + action="store_true", | ||
| 779 | + help="Include artificial \"<native>\" frames to denote calls to non-Python code.", | ||
| 780 | + ) | ||
| 781 | + sampling_group.add_argument( | ||
| 782 | + "--no-gc", | ||
| 783 | + action="store_false", | ||
| 784 | + dest="gc", | ||
| 785 | + help="Don't include artificial \"<GC>\" frames to denote active garbage collection.", | ||
| 786 | + ) | ||
| 773 | 787 | ||
| 774 | 788 | # Mode options | |
| 775 | 789 | mode_group = parser.add_argument_group("Mode options") | |
@@ -934,6 +948,8 @@ def main(): | |||
| 934 | 948 | output_format=args.format, | |
| 935 | 949 | realtime_stats=args.realtime_stats, | |
| 936 | 950 | mode=mode, | |
| 951 | + native=args.native, | ||
| 952 | + gc=args.gc, | ||
| 937 | 953 | ) | |
| 938 | 954 | elif args.module or args.args: | |
| 939 | 955 | if args.module: | |
| Back | FazBrowse Home | New Git URL |
0 commit comments