@@ -191,6 +191,11 @@ production systems. The target process requires no modification and need not
191191be restarted. The profiler attaches, collects samples for the specified
192192duration, then detaches and produces output.
193193
194+ ::
195+
196+ python -m profiling.sampling attach --live 12345
197+ python -m profiling.sampling attach --flamegraph -d 30 -o profile.html 12345
198+
194199On most systems, attaching to another process requires appropriate permissions.
195200See :ref: `profiling-permissions ` for platform-specific requirements.
196201
@@ -470,9 +475,10 @@ which you can use to judge whether the data is sufficient for your analysis.
470475Profiling modes
471476===============
472477
473- The sampling profiler supports three modes that control which samples are
478+ The sampling profiler supports four modes that control which samples are
474479recorded. The mode determines what the profile measures: total elapsed time,
475- CPU execution time, or time spent holding the global interpreter lock.
480+ CPU execution time, time spent holding the global interpreter lock, or
481+ exception handling.
476482
477483
478484Wall-clock mode
@@ -528,6 +534,25 @@ I/O-bound or waiting. The function spends most of its time waiting for network,
528534disk, locks, or sleep. CPU optimization won't help here; consider async I/O,
529535connection pooling, or reducing wait time instead.
530536
537+ .. code-block :: python
538+
539+ import time
540+
541+ def do_sleep ():
542+ time.sleep(2 )
543+
544+ def do_compute ():
545+ sum (i** 2 for i in range (1000000 ))
546+
547+ if __name__ == " __main__" :
548+ do_sleep()
549+ do_compute()
550+
551+ ::
552+
553+ python -m profiling.sampling run --mode=wall script.py # do_sleep ~98%, do_compute ~1%
554+ python -m profiling.sampling run --mode=cpu script.py # do_sleep absent, do_compute dominates
555+
531556
532557GIL mode
533558--------
@@ -552,6 +577,90 @@ GIL?" and "why are my other threads starving?" It can also be useful in
552577single-threaded programs to distinguish Python execution time from time spent
553578in C extensions or I/O.
554579
580+ .. code-block :: python
581+
582+ import hashlib
583+
584+ def hash_work ():
585+ # C extension - releases GIL during computation
586+ for _ in range (200 ):
587+ hashlib.sha256(b " data" * 250000 ).hexdigest()
588+
589+ def python_work ():
590+ # Pure Python - holds GIL during computation
591+ for _ in range (3 ):
592+ sum (i** 2 for i in range (1000000 ))
593+
594+ if __name__ == " __main__" :
595+ hash_work()
596+ python_work()
597+
598+ ::
599+
600+ python -m profiling.sampling run --mode=cpu script.py # hash_work ~42%, python_work ~38%
601+ python -m profiling.sampling run --mode=gil script.py # hash_work ~5%, python_work ~60%
602+
603+
604+ Exception mode
605+ --------------
606+
607+ Exception mode (``--mode=exception ``) records samples only when a thread has
608+ an active exception::
609+
610+ python -m profiling.sampling run --mode=exception script.py
611+
612+ Samples are recorded in two situations: when an exception is being propagated
613+ up the call stack (after ``raise `` but before being caught), or when code is
614+ executing inside an ``except `` block where exception information is still
615+ present in the thread state.
616+
617+ The following example illustrates which code regions are captured:
618+
619+ .. code-block :: python
620+
621+ def example ():
622+ try :
623+ raise ValueError (" error" ) # Captured: exception being raised
624+ except ValueError :
625+ process_error() # Captured: inside except block
626+ finally :
627+ cleanup() # NOT captured: exception already handled
628+
629+ def example_propagating ():
630+ try :
631+ try :
632+ raise ValueError (" error" )
633+ finally :
634+ cleanup() # Captured: exception propagating through
635+ except ValueError :
636+ pass
637+
638+ def example_no_exception ():
639+ try :
640+ do_work()
641+ finally :
642+ cleanup() # NOT captured: no exception involved
643+
644+ Note that ``finally `` blocks are only captured when an exception is actively
645+ propagating through them. Once an ``except `` block finishes executing, Python
646+ clears the exception information before running any subsequent ``finally ``
647+ block. Similarly, ``finally `` blocks that run during normal execution (when no
648+ exception was raised) are not captured because no exception state is present.
649+
650+ This mode is useful for understanding where your program spends time handling
651+ errors. Exception handling can be a significant source of overhead in code
652+ that uses exceptions for flow control (such as ``StopIteration `` in iterators)
653+ or in applications that process many error conditions (such as network servers
654+ handling connection failures).
655+
656+ Exception mode helps answer questions like "how much time is spent handling
657+ exceptions?" and "which exception handlers are the most expensive?" It can
658+ reveal hidden performance costs in code that catches and processes many
659+ exceptions, even when those exceptions are handled gracefully. For example,
660+ if a parsing library uses exceptions internally to signal format errors, this
661+ mode will capture time spent in those handlers even if the calling code never
662+ sees the exceptions.
663+
555664
556665Output formats
557666==============
@@ -890,6 +999,25 @@ stack often shows event loop internals rather than the logical flow of your
890999coroutines. Async-aware mode addresses this by tracking which task is running
8911000and presenting stacks that reflect the ``await `` chain.
8921001
1002+ .. code-block :: python
1003+
1004+ import asyncio
1005+
1006+ async def fetch (url ):
1007+ await asyncio.sleep(0.1 )
1008+ return url
1009+
1010+ async def main ():
1011+ for _ in range (50 ):
1012+ await asyncio.gather(fetch(" a" ), fetch(" b" ), fetch(" c" ))
1013+
1014+ if __name__ == " __main__" :
1015+ asyncio.run(main())
1016+
1017+ ::
1018+
1019+ python -m profiling.sampling run --async-aware --flamegraph -o out.html script.py
1020+
8931021.. note ::
8941022
8951023 Async-aware profiling requires the target process to have the :mod: `asyncio `
@@ -1006,8 +1134,9 @@ Mode options
10061134
10071135.. option :: --mode <mode >
10081136
1009- Sampling mode: ``wall `` (default), ``cpu ``, or ``gil ``.
1010- The ``cpu `` and ``gil `` modes are incompatible with ``--async-aware ``.
1137+ Sampling mode: ``wall `` (default), ``cpu ``, ``gil ``, or ``exception ``.
1138+ The ``cpu ``, ``gil ``, and ``exception `` modes are incompatible with
1139+ ``--async-aware ``.
10111140
10121141.. option :: --async-mode <mode >
10131142
0 commit comments