Skip to content

Commit b90e37c

Browse files
pablogsalclaude
andcommitted
Merge upstream/main into file-output
Merged changes from upstream/main including: - Subprocess enumeration functionality (get_child_pids, is_python_process) - Various fixes and improvements Combined with file-output branch features: - Binary I/O writer and reader for profiling data - Binary format export/replay support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
2 parents 1e2400b + 1a9cdaf commit b90e37c

File tree

80 files changed

+4300
-1575
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+4300
-1575
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ gmon.out
4545
.pytest_cache/
4646
.ruff_cache/
4747
.DS_Store
48+
.pixi/
4849

4950
*.exe
5051

Doc/deprecations/c-api-pending-removal-in-3.20.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Pending removal in Python 3.20
33

44
* :c:func:`!_PyObject_CallMethodId`, :c:func:`!_PyObject_GetAttrId` and
55
:c:func:`!_PyUnicode_FromId` are deprecated since 3.15 and will be removed in
6-
3.20. Instead, use :c:func:`PyUnicode_FromString()` and cache the result in
6+
3.20. Instead, use :c:func:`PyUnicode_InternFromString()` and cache the result in
77
the module state, then call :c:func:`PyObject_CallMethod` or
88
:c:func:`PyObject_GetAttr`.
99
(Contributed by Victor Stinner in :gh:`141049`.)

Doc/deprecations/pending-removal-in-3.20.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Pending removal in Python 3.20
77

88
- :mod:`argparse`
99
- :mod:`csv`
10+
- :mod:`ctypes`
1011
- :mod:`!ctypes.macholib`
1112
- :mod:`decimal` (use :data:`decimal.SPEC_VERSION` instead)
1213
- :mod:`http.server`

Doc/library/ctypes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,6 +1388,9 @@ On Linux, :func:`~ctypes.util.find_library` tries to run external programs
13881388
(``/sbin/ldconfig``, ``gcc``, ``objdump`` and ``ld``) to find the library file.
13891389
It returns the filename of the library file.
13901390

1391+
Note that if the output of these programs does not correspond to the dynamic
1392+
linker used by Python, the result of this function may be misleading.
1393+
13911394
.. versionchanged:: 3.6
13921395
On Linux, the value of the environment variable ``LD_LIBRARY_PATH`` is used
13931396
when searching for libraries, if a library cannot be found by any other means.
@@ -2132,6 +2135,8 @@ Utility functions
21322135

21332136
The exact functionality is system dependent.
21342137

2138+
See :ref:`ctypes-finding-shared-libraries` for complete documentation.
2139+
21352140

21362141
.. function:: find_msvcrt()
21372142
:module: ctypes.util

Doc/library/profiling.sampling.rst

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -325,21 +325,23 @@ The default configuration works well for most use cases:
325325
:widths: 25 75
326326

327327
* - Option
328-
- Default behavior
329-
* - ``--interval`` / ``-i``
328+
- Default
329+
* - Default for ``--interval`` / ``-i``
330330
- 100 µs between samples (~10,000 samples/sec)
331-
* - ``--duration`` / ``-d``
332-
- Profile for 10 seconds
333-
* - ``--all-threads`` / ``-a``
334-
- Sample main thread only
335-
* - ``--native``
331+
* - Default for ``--duration`` / ``-d``
332+
- 10 seconds
333+
* - Default for ``--all-threads`` / ``-a``
334+
- Main thread only
335+
* - Default for ``--native``
336336
- No ``<native>`` frames (C code time attributed to caller)
337-
* - ``--no-gc``
338-
- Include ``<GC>`` frames when garbage collection is active
339-
* - ``--mode``
337+
* - Default for ``--no-gc``
338+
- ``<GC>`` frames included when garbage collection is active
339+
* - Default for ``--mode``
340340
- Wall-clock mode (all samples recorded)
341-
* - ``--realtime-stats``
342-
- No live statistics display during profiling
341+
* - Default for ``--realtime-stats``
342+
- Disabled
343+
* - Default for ``--subprocesses``
344+
- Disabled
343345

344346

345347
Sampling interval and duration
@@ -472,6 +474,78 @@ working correctly and that sufficient samples are being collected. See
472474
:ref:`sampling-efficiency` for details on interpreting these metrics.
473475

474476

477+
Subprocess profiling
478+
--------------------
479+
480+
The :option:`--subprocesses` option enables automatic profiling of subprocesses
481+
spawned by the target::
482+
483+
python -m profiling.sampling run --subprocesses script.py
484+
python -m profiling.sampling attach --subprocesses 12345
485+
486+
When enabled, the profiler monitors the target process for child process
487+
creation. When a new Python child process is detected, a separate profiler
488+
instance is automatically spawned to profile it. This is useful for
489+
applications that use :mod:`multiprocessing`, :mod:`subprocess`,
490+
:mod:`concurrent.futures` with :class:`~concurrent.futures.ProcessPoolExecutor`,
491+
or other process spawning mechanisms.
492+
493+
.. code-block:: python
494+
:caption: worker_pool.py
495+
496+
from concurrent.futures import ProcessPoolExecutor
497+
import math
498+
499+
def compute_factorial(n):
500+
total = 0
501+
for i in range(50):
502+
total += math.factorial(n)
503+
return total
504+
505+
if __name__ == "__main__":
506+
numbers = [5000 + i * 100 for i in range(50)]
507+
with ProcessPoolExecutor(max_workers=4) as executor:
508+
results = list(executor.map(compute_factorial, numbers))
509+
print(f"Computed {len(results)} factorials")
510+
511+
::
512+
513+
python -m profiling.sampling run --subprocesses --flamegraph worker_pool.py
514+
515+
This produces separate flame graphs for the main process and each worker
516+
process: ``flamegraph_<main_pid>.html``, ``flamegraph_<worker1_pid>.html``,
517+
and so on.
518+
519+
Each subprocess receives its own output file. The filename is derived from
520+
the specified output path (or the default) with the subprocess's process ID
521+
appended:
522+
523+
- If you specify ``-o profile.html``, subprocesses produce ``profile_12345.html``,
524+
``profile_12346.html``, and so on
525+
- With default output, subprocesses produce files like ``flamegraph_12345.html``
526+
or directories like ``heatmap_12345``
527+
- For pstats format (which defaults to stdout), subprocesses produce files like
528+
``profile_12345.pstats``
529+
530+
The subprocess profilers inherit most sampling options from the parent (interval,
531+
duration, thread selection, native frames, GC frames, async-aware mode, and
532+
output format). All Python descendant processes are profiled recursively,
533+
including grandchildren and further descendants.
534+
535+
Subprocess detection works by periodically scanning for new descendants of
536+
the target process and checking whether each new process is a Python process
537+
by probing the process memory for Python runtime structures. Non-Python
538+
subprocesses (such as shell commands or external tools) are ignored.
539+
540+
There is a limit of 100 concurrent subprocess profilers to prevent resource
541+
exhaustion in programs that spawn many processes. If this limit is reached,
542+
additional subprocesses are not profiled and a warning is printed.
543+
544+
The :option:`--subprocesses` option is incompatible with :option:`--live` mode
545+
because live mode uses an interactive terminal interface that cannot
546+
accommodate multiple concurrent profiler displays.
547+
548+
475549
.. _sampling-efficiency:
476550

477551
Sampling efficiency
@@ -1302,6 +1376,11 @@ Sampling options
13021376
Compatible with ``--live``, ``--flamegraph``, ``--heatmap``, and ``--gecko``
13031377
formats only.
13041378

1379+
.. option:: --subprocesses
1380+
1381+
Also profile subprocesses. Each subprocess gets its own profiler
1382+
instance and output file. Incompatible with ``--live``.
1383+
13051384

13061385
Mode options
13071386
------------

Doc/whatsnew/3.15.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ Summary -- Release highlights
7474
* :pep:`782`: :ref:`A new PyBytesWriter C API to create a Python bytes object
7575
<whatsnew315-pep782>`
7676
* :ref:`Improved error messages <whatsnew315-improved-error-messages>`
77+
* :ref:`__pycache__ directories now contain a .gitignore file
78+
<whatsnew315-pycache-gitignore>`
7779

7880

7981
New features
@@ -397,6 +399,12 @@ Other language changes
397399
for any class.
398400
(Contributed by Serhiy Storchaka in :gh:`41779`.)
399401

402+
.. _whatsnew315-pycache-gitignore:
403+
404+
* :file:`__pycache__` directories now contain a :file:`.gitignore` file for Git
405+
that ignores their contents.
406+
(Contributed by Stan Ulbrych in :gh:`141081`.)
407+
400408

401409
New modules
402410
===========
@@ -1024,6 +1032,7 @@ New deprecations
10241032

10251033
- :mod:`argparse`
10261034
- :mod:`csv`
1035+
- :mod:`ctypes`
10271036
- :mod:`!ctypes.macholib`
10281037
- :mod:`decimal` (use :data:`decimal.SPEC_VERSION` instead)
10291038
- :mod:`http.server`
@@ -1217,7 +1226,7 @@ Deprecated C APIs
12171226

12181227
* :c:func:`!_PyObject_CallMethodId`, :c:func:`!_PyObject_GetAttrId` and
12191228
:c:func:`!_PyUnicode_FromId` are deprecated since 3.15 and will be removed in
1220-
3.20. Instead, use :c:func:`PyUnicode_FromString()` and cache the result in
1229+
3.20. Instead, use :c:func:`PyUnicode_InternFromString()` and cache the result in
12211230
the module state, then call :c:func:`PyObject_CallMethod` or
12221231
:c:func:`PyObject_GetAttr`.
12231232
(Contributed by Victor Stinner in :gh:`141049`.)

Include/internal/pycore_context.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,8 @@ struct _pycontexttokenobject {
5555
// Export for '_testcapi' shared extension
5656
PyAPI_FUNC(PyObject*) _PyContext_NewHamtForTests(void);
5757

58+
PyAPI_FUNC(int) _PyContext_Enter(PyThreadState *ts, PyObject *octx);
59+
PyAPI_FUNC(int) _PyContext_Exit(PyThreadState *ts, PyObject *octx);
60+
5861

5962
#endif /* !Py_INTERNAL_CONTEXT_H */

Include/internal/pycore_global_objects_fini_generated.h

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_global_strings.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,7 @@ struct _Py_global_strings {
722722
STRUCT_FOR_ID(readline)
723723
STRUCT_FOR_ID(readonly)
724724
STRUCT_FOR_ID(real)
725+
STRUCT_FOR_ID(recursive)
725726
STRUCT_FOR_ID(reducer_override)
726727
STRUCT_FOR_ID(registry)
727728
STRUCT_FOR_ID(rel_tol)

Include/internal/pycore_object.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,9 @@ static inline void Py_DECREF_MORTAL_SPECIALIZED(PyObject *op, destructor destruc
496496
#define Py_DECREF_MORTAL_SPECIALIZED(op, destruct) Py_DECREF_MORTAL_SPECIALIZED(_PyObject_CAST(op), destruct)
497497

498498
#endif
499+
#else // Py_GIL_DISABLED
500+
# define Py_DECREF_MORTAL(op) Py_DECREF(op)
501+
# define Py_DECREF_MORTAL_SPECIALIZED(op, destruct) Py_DECREF(op)
499502
#endif
500503

501504
/* Inline functions trading binary compatibility for speed:
@@ -1045,6 +1048,8 @@ static inline Py_ALWAYS_INLINE void _Py_INCREF_MORTAL(PyObject *op)
10451048
}
10461049
#endif
10471050
}
1051+
#else
1052+
# define _Py_INCREF_MORTAL(op) Py_INCREF(op)
10481053
#endif
10491054

10501055
/* Utility for the tp_traverse slot of mutable heap types that have no other

0 commit comments

Comments
 (0)