pyperf 2.6.3__tar.gz → 2.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyperf-2.6.3/pyperf.egg-info → pyperf-2.8.0}/PKG-INFO +1 -1
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/analyze.rst +12 -6
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/api.rst +1 -1
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/changelog.rst +29 -1
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/cli.rst +2 -2
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/conf.py +8 -9
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/bench_time_func.py +3 -2
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples.rst +3 -2
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/index.rst +1 -1
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/run_benchmark.rst +4 -2
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/runner.rst +4 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/system.rst +1 -1
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/__init__.py +1 -1
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_bench.py +5 -5
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_cli.py +2 -2
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_collect_metadata.py +8 -7
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_command.py +26 -9
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_compare.py +1 -1
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_formatter.py +2 -2
- pyperf-2.8.0/pyperf/_hooks.py +110 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_linux_memory.py +1 -1
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_manager.py +16 -5
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_metadata.py +1 -1
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_process_time.py +54 -9
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_runner.py +20 -11
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_system.py +109 -64
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_utils.py +37 -4
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_worker.py +14 -15
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/__init__.py +1 -1
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/replay.py +1 -1
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_metadata.py +5 -5
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_perf_cli.py +21 -8
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_runner.py +27 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_utils.py +2 -2
- {pyperf-2.6.3 → pyperf-2.8.0/pyperf.egg-info}/PKG-INFO +1 -1
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf.egg-info/SOURCES.txt +1 -0
- pyperf-2.8.0/pyperf.egg-info/entry_points.txt +6 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyproject.toml +11 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/tox.ini +3 -9
- pyperf-2.6.3/pyperf.egg-info/entry_points.txt +0 -2
- {pyperf-2.6.3 → pyperf-2.8.0}/COPYING +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/MANIFEST.in +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/README.rst +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/TODO.rst +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/Makefile +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/developer_guide.rst +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/bench_async_func.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/bench_async_func_with_loop_factory.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/bench_command.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/bench_func.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/bench_timeit.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/export_csv.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/hist_scipy.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/plot.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/hist_scipy_telco.png +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/make.bat +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/outliers.json.gz +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/py2.json.gz +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/py3.json.gz +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/timeit_strip.json.gz +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/doc/user_guide.rst +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/__main__.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_cpu_utils.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_psutil_memory.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_timeit.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_timeit_cli.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_win_memory.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/mult_list_bench.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/mult_list_py36.json +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/mult_list_py36_tags.json +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/mult_list_py37.json +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/mult_list_py37_tags.json +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/mult_list_py38.json +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/telco.json +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_bench.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_cli.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_examples.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_misc.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_system.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_timeit.py +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/track_memory.json +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf.egg-info/dependency_links.txt +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf.egg-info/requires.txt +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/pyperf.egg-info/top_level.txt +0 -0
- {pyperf-2.6.3 → pyperf-2.8.0}/setup.cfg +0 -0
|
@@ -223,9 +223,15 @@ two-sample, two-tailed t-test
|
|
|
223
223
|
|
|
224
224
|
Render a table using ``--table`` option::
|
|
225
225
|
|
|
226
|
-
$ python3 -m pyperf compare_to
|
|
227
|
-
|
|
228
|
-
| Benchmark
|
|
229
|
-
|
|
230
|
-
|
|
|
231
|
-
|
|
226
|
+
$ python3 -m pyperf compare_to mult_list_py36.json mult_list_py38.json --table
|
|
227
|
+
+----------------+----------------+-----------------------+
|
|
228
|
+
| Benchmark | mult_list_py36 | mult_list_py38 |
|
|
229
|
+
+================+================+=======================+
|
|
230
|
+
| [1,2]*1000 | 3.70 us | 3.18 us: 1.16x faster |
|
|
231
|
+
+----------------+----------------+-----------------------+
|
|
232
|
+
| [1,2,3]*1000 | 4.61 us | 4.17 us: 1.11x faster |
|
|
233
|
+
+----------------+----------------+-----------------------+
|
|
234
|
+
| Geometric mean | (ref) | 1.09x faster |
|
|
235
|
+
+----------------+----------------+-----------------------+
|
|
236
|
+
|
|
237
|
+
Benchmark hidden because not significant (1): [1]*1000
|
|
@@ -679,7 +679,7 @@ Benchmark:
|
|
|
679
679
|
* ``inner_loops`` (``int >= 1``): number of inner-loops of the benchmark (``int``)
|
|
680
680
|
* ``timer``: Implementation of ``time.perf_counter()``, and also resolution if
|
|
681
681
|
available
|
|
682
|
-
* ``tags``: (list of str, optional): A list of tags associated with the benchmark. If provided, the results output will be
|
|
682
|
+
* ``tags``: (list of str, optional): A list of tags associated with the benchmark. If provided, the results output will be aggregated by each tag.
|
|
683
683
|
|
|
684
684
|
Python metadata:
|
|
685
685
|
|
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
* Benchmarks now have a timeout.
|
|
5
|
+
Patch by Diego Russo.
|
|
6
|
+
* The example code in bench_time_func.py uses the more modern `time.perf_counter()`.
|
|
7
|
+
Patch by Hajime Senuma.
|
|
8
|
+
* pystats are now correctly collected in subprocesses.
|
|
9
|
+
Patch by Michael Droettboom.
|
|
10
|
+
* pyperf now supports "hooks" to add custom functionality around each benchmarking call.
|
|
11
|
+
pystats are no longer collected automatically, and you must pass `--hook pystats`.
|
|
12
|
+
Patch by Michael Droettboom.
|
|
13
|
+
* Memory units are now correctly stated in KiB and MiB.
|
|
14
|
+
Patch by Ungve Mardal Moe.
|
|
15
|
+
* max_mem_rss is now correctly measured on macOS.
|
|
16
|
+
Patch by Michael Droettboom.
|
|
17
|
+
* `pyperf system tune` now works correctly on aarch64.
|
|
18
|
+
Patch by Diego Russo.
|
|
19
|
+
|
|
20
|
+
Version 2.7.0 (2024-05-18)
|
|
21
|
+
---------------------------
|
|
22
|
+
|
|
23
|
+
* Fix logic in MSR availability test.
|
|
24
|
+
Patch by Mike Droettboom.
|
|
25
|
+
* Disable tuning TurboBoost setting on non-Intel device.
|
|
26
|
+
Patch by Mike Droettboom.
|
|
27
|
+
* Include CONFIG_ARGS in Metadata.
|
|
28
|
+
Patch by Jeff Glass.
|
|
29
|
+
* Support environment variables PYTHON_GIL / PYTHON_CPU_COUNT.
|
|
30
|
+
Patch by Donghee Na.
|
|
31
|
+
|
|
4
32
|
Version 2.6.3 (2024-03-05)
|
|
5
33
|
---------------------------
|
|
6
34
|
|
|
@@ -88,7 +116,7 @@ Version 2.1.0 (2021-01-14)
|
|
|
88
116
|
* The :ref:`compare_to command <compare_to_cmd>` no longer displays
|
|
89
117
|
percentages: display less numbers to make the output easier to read. Also,
|
|
90
118
|
percentage were almost the same values than the xxx faster or xxx slower
|
|
91
|
-
values, but rounded
|
|
119
|
+
values, but rounded differently which introduced confusion.
|
|
92
120
|
* Project moved to https://github.com/psf/pyperf/
|
|
93
121
|
* system command now only emits a warning rather than failing with a hard error
|
|
94
122
|
if it fails to get or set the frequency of a CPU.
|
|
@@ -229,8 +229,8 @@ Example::
|
|
|
229
229
|
Values:
|
|
230
230
|
|
|
231
231
|
* `Median <https://en.wikipedia.org/wiki/Median>`_
|
|
232
|
-
* "std dev": `Standard deviation
|
|
233
|
-
<https://en.wikipedia.org/wiki/
|
|
232
|
+
* "std dev": `Standard deviation
|
|
233
|
+
<https://en.wikipedia.org/wiki/Standard_deviation>`_
|
|
234
234
|
|
|
235
235
|
See also `Outlier (Wikipedia) <https://en.wikipedia.org/wiki/Outlier>`_.
|
|
236
236
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
1
|
#
|
|
3
2
|
# pyperf documentation build configuration file, created by
|
|
4
3
|
# sphinx-quickstart on Wed Jun 1 15:28:03 2016.
|
|
@@ -42,16 +41,16 @@ source_suffix = '.rst'
|
|
|
42
41
|
master_doc = 'index'
|
|
43
42
|
|
|
44
43
|
# General information about the project.
|
|
45
|
-
project =
|
|
46
|
-
copyright =
|
|
47
|
-
author =
|
|
44
|
+
project = 'pyperf'
|
|
45
|
+
copyright = '2016, Victor Stinner'
|
|
46
|
+
author = 'Victor Stinner'
|
|
48
47
|
|
|
49
48
|
# The version info for the project you're documenting, acts as replacement for
|
|
50
49
|
# |version| and |release|, also used in various other places throughout the
|
|
51
50
|
# built documents.
|
|
52
51
|
#
|
|
53
52
|
# The short X.Y version.
|
|
54
|
-
version = release = '2.
|
|
53
|
+
version = release = '2.8.0'
|
|
55
54
|
|
|
56
55
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
57
56
|
# for a list of supported languages.
|
|
@@ -216,8 +215,8 @@ latex_elements = {
|
|
|
216
215
|
# (source start file, target name, title,
|
|
217
216
|
# author, documentclass [howto, manual, or own class]).
|
|
218
217
|
latex_documents = [
|
|
219
|
-
(master_doc, 'pyperf.tex',
|
|
220
|
-
|
|
218
|
+
(master_doc, 'pyperf.tex', 'pyperf Documentation',
|
|
219
|
+
'Victor Stinner', 'manual'),
|
|
221
220
|
]
|
|
222
221
|
|
|
223
222
|
# The name of an image file (relative to this directory) to place at the top of
|
|
@@ -246,7 +245,7 @@ latex_documents = [
|
|
|
246
245
|
# One entry per manual page. List of tuples
|
|
247
246
|
# (source start file, name, description, authors, manual section).
|
|
248
247
|
man_pages = [
|
|
249
|
-
(master_doc, 'pyperf',
|
|
248
|
+
(master_doc, 'pyperf', 'pyperf Documentation',
|
|
250
249
|
[author], 1)
|
|
251
250
|
]
|
|
252
251
|
|
|
@@ -260,7 +259,7 @@ man_pages = [
|
|
|
260
259
|
# (source start file, target name, title, author,
|
|
261
260
|
# dir menu entry, description, category)
|
|
262
261
|
texinfo_documents = [
|
|
263
|
-
(master_doc, 'pyperf',
|
|
262
|
+
(master_doc, 'pyperf', 'pyperf Documentation',
|
|
264
263
|
author, 'pyperf', 'One line description of project.',
|
|
265
264
|
'Miscellaneous'),
|
|
266
265
|
]
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
import time
|
|
2
3
|
import pyperf
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
def bench_dict(loops, mydict):
|
|
6
7
|
range_it = range(loops)
|
|
7
|
-
t0 =
|
|
8
|
+
t0 = time.perf_counter()
|
|
8
9
|
|
|
9
10
|
for _ in range_it:
|
|
10
11
|
mydict['0']
|
|
@@ -18,7 +19,7 @@ def bench_dict(loops, mydict):
|
|
|
18
19
|
mydict['800']
|
|
19
20
|
mydict['900']
|
|
20
21
|
|
|
21
|
-
return
|
|
22
|
+
return time.perf_counter() - t0
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
runner = pyperf.Runner()
|
|
@@ -31,8 +31,9 @@ bench_async_func() method
|
|
|
31
31
|
timeit() method
|
|
32
32
|
---------------
|
|
33
33
|
|
|
34
|
-
Benchmark using the :meth:`Runner.timeit` method to performance of
|
|
35
|
-
sorted list of 1000 numbers using a ``key`` function (which does
|
|
34
|
+
Benchmark using the :meth:`Runner.timeit` method to measure the performance of
|
|
35
|
+
sorting a sorted list of 1000 numbers using a ``key`` function (which does
|
|
36
|
+
nothing):
|
|
36
37
|
|
|
37
38
|
.. literalinclude:: examples/bench_timeit.py
|
|
38
39
|
|
|
@@ -192,12 +192,14 @@ the benchmark less reliable since two runs may use a different number of warmup
|
|
|
192
192
|
values.
|
|
193
193
|
|
|
194
194
|
Specializer statistics (``pystats``)
|
|
195
|
-
|
|
195
|
+
====================================
|
|
196
196
|
|
|
197
197
|
``pyperf`` has built-in support for `specializer statistics (``pystats``) <https://docs.python.org/dev/using/configure.html#cmdoption-enable-pystats>`_.
|
|
198
|
-
If running benchmarks on a CPython built with the ``--enable-pystats`` flag, pyperf will
|
|
198
|
+
If running benchmarks on a CPython built with the ``--enable-pystats`` flag, when you pass ``--hook pystats``, pyperf will collect ``pystats`` on the benchmark code by calling ``sys._stats_on`` immediately before the benchmark and calling ``sys._stats_off`` immediately after.
|
|
199
199
|
Stats are not collected when running ``pyperf``'s own code or when warming up or calibrating the benchmarks.
|
|
200
200
|
|
|
201
|
+
**New in 2.8.0:** The ``--hook pystats`` flag must be given to collect pystats.
|
|
202
|
+
|
|
201
203
|
Due to the overhead of collecting the statistics, the timing results will be meaningless.
|
|
202
204
|
|
|
203
205
|
The `Tools/scripts/summarize_stats.py <https://github.com/python/cpython/blob/main/Tools/scripts/summarize_stats.py>`_ script can be used to summarize the statistics in a human-readable form.
|
|
@@ -98,6 +98,7 @@ Option::
|
|
|
98
98
|
--inherit-environ=VARS
|
|
99
99
|
--copy-env
|
|
100
100
|
--no-locale
|
|
101
|
+
--timeout TIMEOUT
|
|
101
102
|
--track-memory
|
|
102
103
|
--tracemalloc
|
|
103
104
|
|
|
@@ -140,6 +141,9 @@ Option::
|
|
|
140
141
|
- ``LC_TELEPHONE``
|
|
141
142
|
- ``LC_TIME``
|
|
142
143
|
|
|
144
|
+
* ``--timeout``: set a timeout in seconds for an execution of the benchmark.
|
|
145
|
+
If the benchmark execution times out, pyperf exits with error code 124.
|
|
146
|
+
There is no time out by default.
|
|
143
147
|
* ``--tracemalloc``: Use the ``tracemalloc`` module to track Python memory
|
|
144
148
|
allocation and get the peak of memory usage in metadata
|
|
145
149
|
(``tracemalloc_peak``). The module is only available on Python 3.4 and newer.
|
|
@@ -23,7 +23,7 @@ be checked in benchmark metadata: it is enabled if the ``cpu_affinity``
|
|
|
23
23
|
|
|
24
24
|
:func:`os.sched_setaffinity` is used to pin processes.
|
|
25
25
|
|
|
26
|
-
Even if no CPU is isolated, CPU
|
|
26
|
+
Even if no CPU is isolated, CPU pinning makes benchmarks more stable: use the
|
|
27
27
|
``--affinity`` command line option.
|
|
28
28
|
|
|
29
29
|
Check the CPU topology for HyperThreading and NUMA for best performances.
|
|
@@ -86,7 +86,7 @@ def _cached_attr(func):
|
|
|
86
86
|
return method
|
|
87
87
|
|
|
88
88
|
|
|
89
|
-
class Run
|
|
89
|
+
class Run:
|
|
90
90
|
# Run is immutable, so it can be shared/exchanged between two benchmarks
|
|
91
91
|
|
|
92
92
|
__slots__ = ('_warmups', '_values', '_metadata')
|
|
@@ -319,7 +319,7 @@ class Run(object):
|
|
|
319
319
|
return self._replace(metadata=metadata2)
|
|
320
320
|
|
|
321
321
|
|
|
322
|
-
class Benchmark
|
|
322
|
+
class Benchmark:
|
|
323
323
|
def __init__(self, runs):
|
|
324
324
|
self._runs = [] # list of Run objects
|
|
325
325
|
self._clear_runs_cache()
|
|
@@ -627,7 +627,7 @@ class Benchmark(object):
|
|
|
627
627
|
self._replace_runs(new_runs)
|
|
628
628
|
|
|
629
629
|
|
|
630
|
-
class BenchmarkSuite
|
|
630
|
+
class BenchmarkSuite:
|
|
631
631
|
def __init__(self, benchmarks, filename=None):
|
|
632
632
|
if not benchmarks:
|
|
633
633
|
raise ValueError("benchmarks must be a non-empty "
|
|
@@ -727,7 +727,7 @@ class BenchmarkSuite(object):
|
|
|
727
727
|
if isinstance(filename, bytes):
|
|
728
728
|
suffix = b'.gz'
|
|
729
729
|
else:
|
|
730
|
-
suffix =
|
|
730
|
+
suffix = '.gz'
|
|
731
731
|
|
|
732
732
|
if filename.endswith(suffix):
|
|
733
733
|
# Use lazy import to limit imports on 'import pyperf'
|
|
@@ -770,7 +770,7 @@ class BenchmarkSuite(object):
|
|
|
770
770
|
if isinstance(filename, bytes):
|
|
771
771
|
suffix = b'.gz'
|
|
772
772
|
else:
|
|
773
|
-
suffix =
|
|
773
|
+
suffix = '.gz'
|
|
774
774
|
|
|
775
775
|
if not replace and os.path.exists(filename):
|
|
776
776
|
raise OSError(errno.EEXIST, "File already exists")
|
|
@@ -604,7 +604,7 @@ def catch_broken_pipe_error(file=None):
|
|
|
604
604
|
# was closed by the consumer
|
|
605
605
|
for file in files:
|
|
606
606
|
file.flush()
|
|
607
|
-
except
|
|
607
|
+
except OSError as exc:
|
|
608
608
|
if exc.errno != errno.EPIPE:
|
|
609
609
|
raise
|
|
610
610
|
# got a broken pipe error: ignore it
|
|
@@ -615,5 +615,5 @@ def catch_broken_pipe_error(file=None):
|
|
|
615
615
|
for file in files:
|
|
616
616
|
try:
|
|
617
617
|
file.close()
|
|
618
|
-
except
|
|
618
|
+
except OSError:
|
|
619
619
|
pass
|
|
@@ -111,6 +111,11 @@ def collect_python_metadata(metadata):
|
|
|
111
111
|
cflags = normalize_text(cflags)
|
|
112
112
|
metadata['python_cflags'] = cflags
|
|
113
113
|
|
|
114
|
+
config_args = sysconfig.get_config_var('CONFIG_ARGS')
|
|
115
|
+
if config_args:
|
|
116
|
+
config_args = normalize_text(config_args)
|
|
117
|
+
metadata['python_config_args'] = config_args
|
|
118
|
+
|
|
114
119
|
# GC disabled?
|
|
115
120
|
try:
|
|
116
121
|
import gc
|
|
@@ -120,10 +125,6 @@ def collect_python_metadata(metadata):
|
|
|
120
125
|
if not gc.isenabled():
|
|
121
126
|
metadata['python_gc'] = 'disabled'
|
|
122
127
|
|
|
123
|
-
# pystats enabled?
|
|
124
|
-
if hasattr(sys, "_stats_clear"):
|
|
125
|
-
metadata['pystats'] = 'enabled'
|
|
126
|
-
|
|
127
128
|
|
|
128
129
|
def read_proc(path):
|
|
129
130
|
path = proc_path(path)
|
|
@@ -131,7 +132,7 @@ def read_proc(path):
|
|
|
131
132
|
with open_text(path) as fp:
|
|
132
133
|
for line in fp:
|
|
133
134
|
yield line.rstrip()
|
|
134
|
-
except
|
|
135
|
+
except OSError:
|
|
135
136
|
return
|
|
136
137
|
|
|
137
138
|
|
|
@@ -203,7 +204,7 @@ def collect_system_metadata(metadata):
|
|
|
203
204
|
|
|
204
205
|
def collect_memory_metadata(metadata):
|
|
205
206
|
if resource is not None:
|
|
206
|
-
metadata["mem_max_rss"] = get_max_rss()
|
|
207
|
+
metadata["mem_max_rss"] = get_max_rss(children=False)
|
|
207
208
|
|
|
208
209
|
# Note: Don't collect VmPeak of /proc/self/status on Linux because it is
|
|
209
210
|
# not accurate. See pyperf._linux_memory for more accurate memory metrics.
|
|
@@ -327,7 +328,7 @@ def get_cpu_temperature(path, cpu_temp):
|
|
|
327
328
|
|
|
328
329
|
try:
|
|
329
330
|
temp_label = read_first_line(template % 'label', error=True)
|
|
330
|
-
except
|
|
331
|
+
except OSError:
|
|
331
332
|
break
|
|
332
333
|
|
|
333
334
|
temp_input = read_first_line(template % 'input', error=True)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import functools
|
|
2
|
+
import json
|
|
2
3
|
import os.path
|
|
3
4
|
import subprocess
|
|
4
5
|
import sys
|
|
@@ -7,6 +8,26 @@ from pyperf._utils import shell_quote, popen_communicate
|
|
|
7
8
|
from pyperf._worker import WorkerTask
|
|
8
9
|
|
|
9
10
|
|
|
11
|
+
def parse_subprocess_data(output):
|
|
12
|
+
# Parse the data send from the subprocess.
|
|
13
|
+
# It is three lines containing:
|
|
14
|
+
# - The runtime (in seconds)
|
|
15
|
+
# - max_rss (or -1, if not able to compute)
|
|
16
|
+
# - The metadata to add to the benchmark entry, as a JSON dictionary
|
|
17
|
+
|
|
18
|
+
rss = None
|
|
19
|
+
metadata = {}
|
|
20
|
+
try:
|
|
21
|
+
lines = output.splitlines()
|
|
22
|
+
timing = float(lines[0])
|
|
23
|
+
rss = int(lines[1])
|
|
24
|
+
metadata = json.loads(lines[2])
|
|
25
|
+
except ValueError:
|
|
26
|
+
raise ValueError("failed to parse worker output: %r" % output)
|
|
27
|
+
|
|
28
|
+
return timing, rss, metadata
|
|
29
|
+
|
|
30
|
+
|
|
10
31
|
def bench_command(command, task, loops):
|
|
11
32
|
path = os.path.dirname(__file__)
|
|
12
33
|
script = os.path.join(path, '_process_time.py')
|
|
@@ -22,19 +43,15 @@ def bench_command(command, task, loops):
|
|
|
22
43
|
raise Exception("Command failed with exit code %s"
|
|
23
44
|
% proc.returncode)
|
|
24
45
|
|
|
25
|
-
rss =
|
|
26
|
-
try:
|
|
27
|
-
lines = output.splitlines()
|
|
28
|
-
timing = float(lines[0])
|
|
29
|
-
if len(lines) >= 2:
|
|
30
|
-
rss = int(lines[1])
|
|
31
|
-
except ValueError:
|
|
32
|
-
raise ValueError("failed to parse script output: %r" % output)
|
|
46
|
+
timing, rss, metadata = parse_subprocess_data(output)
|
|
33
47
|
|
|
34
|
-
if rss:
|
|
48
|
+
if rss and rss > 0:
|
|
35
49
|
# store the maximum
|
|
36
50
|
max_rss = task.metadata.get('command_max_rss', 0)
|
|
37
51
|
task.metadata['command_max_rss'] = max(max_rss, rss)
|
|
52
|
+
|
|
53
|
+
task.metadata.update(metadata)
|
|
54
|
+
|
|
38
55
|
return timing
|
|
39
56
|
|
|
40
57
|
|
|
@@ -30,9 +30,9 @@ def format_filesize(size):
|
|
|
30
30
|
return '%.0f byte' % size
|
|
31
31
|
|
|
32
32
|
if size > 10 * 1024 * 1024:
|
|
33
|
-
return '%.1f
|
|
33
|
+
return '%.1f MiB' % (size / (1024.0 * 1024.0))
|
|
34
34
|
|
|
35
|
-
return '%.1f
|
|
35
|
+
return '%.1f KiB' % (size / 1024.0)
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
def format_filesizes(sizes):
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Hooks are installable context managers defined as entry points so that
|
|
2
|
+
# arbitrary code can by run right before and after the actual internal
|
|
3
|
+
# benchmarking code is run.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import abc
|
|
7
|
+
import importlib.metadata
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_hooks():
|
|
12
|
+
hook_prefix = "pyperf.hook"
|
|
13
|
+
entry_points = importlib.metadata.entry_points()
|
|
14
|
+
if sys.version_info[:2] < (3, 10):
|
|
15
|
+
group = entry_points[hook_prefix]
|
|
16
|
+
else:
|
|
17
|
+
group = entry_points.select(group=hook_prefix)
|
|
18
|
+
return group
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_hook_names():
|
|
22
|
+
return (x.name for x in get_hooks())
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_selected_hooks(hook_names):
|
|
26
|
+
if hook_names is None:
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
hook_mapping = {hook.name: hook for hook in get_hooks()}
|
|
30
|
+
for hook_name in hook_names:
|
|
31
|
+
yield hook_mapping[hook_name]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def instantiate_selected_hooks(hook_names):
|
|
35
|
+
hook_managers = {}
|
|
36
|
+
for hook in get_selected_hooks(hook_names):
|
|
37
|
+
try:
|
|
38
|
+
hook_managers[hook.name] = hook.load()()
|
|
39
|
+
except HookError as e:
|
|
40
|
+
print(f"ERROR setting up hook '{hook.name}':", file=sys.stderr)
|
|
41
|
+
print(str(e), file=sys.stderr)
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
|
|
44
|
+
return hook_managers
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class HookError(Exception):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class HookBase(abc.ABC):
|
|
52
|
+
def __init__(self):
|
|
53
|
+
"""
|
|
54
|
+
Create a new instance of the hook.
|
|
55
|
+
"""
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def teardown(self, _metadata):
|
|
59
|
+
"""
|
|
60
|
+
Called when the hook is completed for a process. May add any information
|
|
61
|
+
collected to the passed-in `metadata` dictionary.
|
|
62
|
+
"""
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
def __enter__(self):
|
|
66
|
+
"""
|
|
67
|
+
Called immediately before running benchmark code.
|
|
68
|
+
|
|
69
|
+
May be called multiple times per instance.
|
|
70
|
+
"""
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
def __exit__(self, _exc_type, _exc_value, _traceback):
|
|
74
|
+
"""
|
|
75
|
+
Called immediately after running benchmark code.
|
|
76
|
+
"""
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class _test_hook(HookBase):
|
|
81
|
+
def __init__(self):
|
|
82
|
+
self._count = 0
|
|
83
|
+
|
|
84
|
+
def teardown(self, metadata):
|
|
85
|
+
metadata["_test_hook"] = self._count
|
|
86
|
+
|
|
87
|
+
def __enter__(self):
|
|
88
|
+
self._count += 1
|
|
89
|
+
|
|
90
|
+
def __exit__(self, _exc_type, _exc_value, _traceback):
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class pystats(HookBase):
|
|
95
|
+
def __init__(self):
|
|
96
|
+
if not hasattr(sys, "_stats_on"):
|
|
97
|
+
raise HookError(
|
|
98
|
+
"Can not collect pystats because python was not built with --enable-pystats"
|
|
99
|
+
)
|
|
100
|
+
sys._stats_off()
|
|
101
|
+
sys._stats_clear()
|
|
102
|
+
|
|
103
|
+
def teardown(self, metadata):
|
|
104
|
+
metadata["pystats"] = "enabled"
|
|
105
|
+
|
|
106
|
+
def __enter__(self):
|
|
107
|
+
sys._stats_on()
|
|
108
|
+
|
|
109
|
+
def __exit__(self, _exc_type, _exc_value, _traceback):
|
|
110
|
+
sys._stats_off()
|
|
@@ -7,12 +7,14 @@ from pyperf._formatter import format_number
|
|
|
7
7
|
from pyperf._utils import MS_WINDOWS, create_environ, create_pipe, popen_killer
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
EXIT_TIMEOUT = 60
|
|
11
|
+
|
|
10
12
|
# Limit to 5 calibration processes
|
|
11
13
|
# (10 if calibration is needed for loops and warmups)
|
|
12
14
|
MAX_CALIBRATION = 5
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
class Manager
|
|
17
|
+
class Manager:
|
|
16
18
|
"""
|
|
17
19
|
Manager process which spawns worker processes to:
|
|
18
20
|
- calibrate warmups
|
|
@@ -69,6 +71,13 @@ class Manager(object):
|
|
|
69
71
|
if args.profile:
|
|
70
72
|
cmd.extend(['--profile', args.profile])
|
|
71
73
|
|
|
74
|
+
if args.timeout:
|
|
75
|
+
cmd.extend(['--timeout', str(args.timeout)])
|
|
76
|
+
|
|
77
|
+
if args.hook:
|
|
78
|
+
for hook in args.hook:
|
|
79
|
+
cmd.extend(['--hook', hook])
|
|
80
|
+
|
|
72
81
|
if self.runner._add_cmdline_args:
|
|
73
82
|
self.runner._add_cmdline_args(cmd, args)
|
|
74
83
|
|
|
@@ -98,10 +107,12 @@ class Manager(object):
|
|
|
98
107
|
proc = subprocess.Popen(cmd, env=env, **kw)
|
|
99
108
|
|
|
100
109
|
with popen_killer(proc):
|
|
101
|
-
|
|
102
|
-
bench_json =
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
try:
|
|
111
|
+
bench_json = rpipe.read_text(timeout=self.args.timeout)
|
|
112
|
+
exitcode = proc.wait(timeout=EXIT_TIMEOUT)
|
|
113
|
+
except TimeoutError as exc:
|
|
114
|
+
print(exc)
|
|
115
|
+
sys.exit(124)
|
|
105
116
|
|
|
106
117
|
if exitcode:
|
|
107
118
|
raise RuntimeError("%s failed with exit code %s"
|