Skip to content

Commit 8e2fcfb

Browse files
authored
Merge branch 'main' into check-callable-callmethodformat
2 parents cf8b9c3 + cd2ca74 commit 8e2fcfb

Some content is hidden

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

48 files changed

+2939
-632
lines changed

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/profiling.sampling.rst

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

297297
* - Option
298-
- Default behavior
299-
* - ``--interval`` / ``-i``
298+
- Default
299+
* - Default for ``--interval`` / ``-i``
300300
- 100 µs between samples (~10,000 samples/sec)
301-
* - ``--duration`` / ``-d``
302-
- Profile for 10 seconds
303-
* - ``--all-threads`` / ``-a``
304-
- Sample main thread only
305-
* - ``--native``
301+
* - Default for ``--duration`` / ``-d``
302+
- 10 seconds
303+
* - Default for ``--all-threads`` / ``-a``
304+
- Main thread only
305+
* - Default for ``--native``
306306
- No ``<native>`` frames (C code time attributed to caller)
307-
* - ``--no-gc``
308-
- Include ``<GC>`` frames when garbage collection is active
309-
* - ``--mode``
307+
* - Default for ``--no-gc``
308+
- ``<GC>`` frames included when garbage collection is active
309+
* - Default for ``--mode``
310310
- Wall-clock mode (all samples recorded)
311-
* - ``--realtime-stats``
312-
- No live statistics display during profiling
311+
* - Default for ``--realtime-stats``
312+
- Disabled
313+
* - Default for ``--subprocesses``
314+
- Disabled
313315

314316

315317
Sampling interval and duration
@@ -442,6 +444,78 @@ working correctly and that sufficient samples are being collected. See
442444
:ref:`sampling-efficiency` for details on interpreting these metrics.
443445

444446

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

447521
Sampling efficiency
@@ -1217,6 +1291,11 @@ Sampling options
12171291
Compatible with ``--live``, ``--flamegraph``, ``--heatmap``, and ``--gecko``
12181292
formats only.
12191293

1294+
.. option:: --subprocesses
1295+
1296+
Also profile subprocesses. Each subprocess gets its own profiler
1297+
instance and output file. Incompatible with ``--live``.
1298+
12201299

12211300
Mode options
12221301
------------

Doc/whatsnew/3.15.rst

Lines changed: 9 additions & 0 deletions
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`

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
@@ -717,6 +717,7 @@ struct _Py_global_strings {
717717
STRUCT_FOR_ID(readline)
718718
STRUCT_FOR_ID(readonly)
719719
STRUCT_FOR_ID(real)
720+
STRUCT_FOR_ID(recursive)
720721
STRUCT_FOR_ID(reducer_override)
721722
STRUCT_FOR_ID(registry)
722723
STRUCT_FOR_ID(rel_tol)

Include/internal/pycore_runtime_init_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_unicodeobject_generated.h

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

Lib/ctypes/__init__.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,16 @@
55
import sysconfig as _sysconfig
66
import types as _types
77

8-
__version__ = "1.1.0"
9-
108
from _ctypes import Union, Structure, Array
119
from _ctypes import _Pointer
1210
from _ctypes import CFuncPtr as _CFuncPtr
13-
from _ctypes import __version__ as _ctypes_version
1411
from _ctypes import RTLD_LOCAL, RTLD_GLOBAL
1512
from _ctypes import ArgumentError
1613
from _ctypes import SIZEOF_TIME_T
1714
from _ctypes import CField
1815

1916
from struct import calcsize as _calcsize
2017

21-
if __version__ != _ctypes_version:
22-
raise Exception("Version number mismatch", __version__, _ctypes_version)
23-
2418
if _os.name == "nt":
2519
from _ctypes import COMError, CopyComPointer, FormatError
2620

@@ -673,3 +667,12 @@ def DllCanUnloadNow():
673667
raise SystemError(f"Unexpected sizeof(time_t): {SIZEOF_TIME_T=}")
674668

675669
_reset_cache()
670+
671+
672+
def __getattr__(name):
673+
if name == "__version__":
674+
from warnings import _deprecated
675+
676+
_deprecated("__version__", remove=(3, 20))
677+
return "1.1.0" # Do not change
678+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

Lib/importlib/_bootstrap_external.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,19 @@ def set_data(self, path, data, *, _mode=0o666):
967967
_bootstrap._verbose_message('could not create {!r}: {!r}',
968968
parent, exc)
969969
return
970+
971+
if part == _PYCACHE:
972+
gitignore = _path_join(parent, '.gitignore')
973+
try:
974+
_path_stat(gitignore)
975+
except FileNotFoundError:
976+
gitignore_content = b'# Created by CPython\n*\n'
977+
try:
978+
_write_atomic(gitignore, gitignore_content, _mode)
979+
except OSError:
980+
pass
981+
except OSError:
982+
pass
970983
try:
971984
_write_atomic(path, data, _mode)
972985
_bootstrap._verbose_message('created {!r}', path)

0 commit comments

Comments
 (0)