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.
Files changed (85) hide show
  1. {pyperf-2.6.3/pyperf.egg-info → pyperf-2.8.0}/PKG-INFO +1 -1
  2. {pyperf-2.6.3 → pyperf-2.8.0}/doc/analyze.rst +12 -6
  3. {pyperf-2.6.3 → pyperf-2.8.0}/doc/api.rst +1 -1
  4. {pyperf-2.6.3 → pyperf-2.8.0}/doc/changelog.rst +29 -1
  5. {pyperf-2.6.3 → pyperf-2.8.0}/doc/cli.rst +2 -2
  6. {pyperf-2.6.3 → pyperf-2.8.0}/doc/conf.py +8 -9
  7. {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/bench_time_func.py +3 -2
  8. {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples.rst +3 -2
  9. {pyperf-2.6.3 → pyperf-2.8.0}/doc/index.rst +1 -1
  10. {pyperf-2.6.3 → pyperf-2.8.0}/doc/run_benchmark.rst +4 -2
  11. {pyperf-2.6.3 → pyperf-2.8.0}/doc/runner.rst +4 -0
  12. {pyperf-2.6.3 → pyperf-2.8.0}/doc/system.rst +1 -1
  13. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/__init__.py +1 -1
  14. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_bench.py +5 -5
  15. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_cli.py +2 -2
  16. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_collect_metadata.py +8 -7
  17. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_command.py +26 -9
  18. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_compare.py +1 -1
  19. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_formatter.py +2 -2
  20. pyperf-2.8.0/pyperf/_hooks.py +110 -0
  21. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_linux_memory.py +1 -1
  22. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_manager.py +16 -5
  23. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_metadata.py +1 -1
  24. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_process_time.py +54 -9
  25. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_runner.py +20 -11
  26. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_system.py +109 -64
  27. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_utils.py +37 -4
  28. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_worker.py +14 -15
  29. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/__init__.py +1 -1
  30. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/replay.py +1 -1
  31. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_metadata.py +5 -5
  32. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_perf_cli.py +21 -8
  33. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_runner.py +27 -0
  34. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_utils.py +2 -2
  35. {pyperf-2.6.3 → pyperf-2.8.0/pyperf.egg-info}/PKG-INFO +1 -1
  36. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf.egg-info/SOURCES.txt +1 -0
  37. pyperf-2.8.0/pyperf.egg-info/entry_points.txt +6 -0
  38. {pyperf-2.6.3 → pyperf-2.8.0}/pyproject.toml +11 -0
  39. {pyperf-2.6.3 → pyperf-2.8.0}/tox.ini +3 -9
  40. pyperf-2.6.3/pyperf.egg-info/entry_points.txt +0 -2
  41. {pyperf-2.6.3 → pyperf-2.8.0}/COPYING +0 -0
  42. {pyperf-2.6.3 → pyperf-2.8.0}/MANIFEST.in +0 -0
  43. {pyperf-2.6.3 → pyperf-2.8.0}/README.rst +0 -0
  44. {pyperf-2.6.3 → pyperf-2.8.0}/TODO.rst +0 -0
  45. {pyperf-2.6.3 → pyperf-2.8.0}/doc/Makefile +0 -0
  46. {pyperf-2.6.3 → pyperf-2.8.0}/doc/developer_guide.rst +0 -0
  47. {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/bench_async_func.py +0 -0
  48. {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/bench_async_func_with_loop_factory.py +0 -0
  49. {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/bench_command.py +0 -0
  50. {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/bench_func.py +0 -0
  51. {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/bench_timeit.py +0 -0
  52. {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/export_csv.py +0 -0
  53. {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/hist_scipy.py +0 -0
  54. {pyperf-2.6.3 → pyperf-2.8.0}/doc/examples/plot.py +0 -0
  55. {pyperf-2.6.3 → pyperf-2.8.0}/doc/hist_scipy_telco.png +0 -0
  56. {pyperf-2.6.3 → pyperf-2.8.0}/doc/make.bat +0 -0
  57. {pyperf-2.6.3 → pyperf-2.8.0}/doc/outliers.json.gz +0 -0
  58. {pyperf-2.6.3 → pyperf-2.8.0}/doc/py2.json.gz +0 -0
  59. {pyperf-2.6.3 → pyperf-2.8.0}/doc/py3.json.gz +0 -0
  60. {pyperf-2.6.3 → pyperf-2.8.0}/doc/timeit_strip.json.gz +0 -0
  61. {pyperf-2.6.3 → pyperf-2.8.0}/doc/user_guide.rst +0 -0
  62. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/__main__.py +0 -0
  63. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_cpu_utils.py +0 -0
  64. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_psutil_memory.py +0 -0
  65. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_timeit.py +0 -0
  66. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_timeit_cli.py +0 -0
  67. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/_win_memory.py +0 -0
  68. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/mult_list_bench.py +0 -0
  69. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/mult_list_py36.json +0 -0
  70. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/mult_list_py36_tags.json +0 -0
  71. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/mult_list_py37.json +0 -0
  72. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/mult_list_py37_tags.json +0 -0
  73. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/mult_list_py38.json +0 -0
  74. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/telco.json +0 -0
  75. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_bench.py +0 -0
  76. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_cli.py +0 -0
  77. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_examples.py +0 -0
  78. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_misc.py +0 -0
  79. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_system.py +0 -0
  80. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/test_timeit.py +0 -0
  81. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf/tests/track_memory.json +0 -0
  82. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf.egg-info/dependency_links.txt +0 -0
  83. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf.egg-info/requires.txt +0 -0
  84. {pyperf-2.6.3 → pyperf-2.8.0}/pyperf.egg-info/top_level.txt +0 -0
  85. {pyperf-2.6.3 → pyperf-2.8.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyperf
3
- Version: 2.6.3
3
+ Version: 2.8.0
4
4
  Summary: Python module to run and analyze benchmarks
5
5
  Author-email: Victor Stinner <vstinner@redhat.com>
6
6
  Maintainer-email: Dong-hee Na <donghee.na@python.org>
@@ -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 py36.json py38.json --table
227
- +-----------+---------+------------------------------+
228
- | Benchmark | py36 | py38 |
229
- +===========+=========+==============================+
230
- | timeit | 4.70 us | 4.22 us: 1.11x faster (-10%) |
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 aggreggated by each tag.
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 differenly which introduced confusion.
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 (standard error)
233
- <https://en.wikipedia.org/wiki/Standard_error>`_
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 = u'pyperf'
46
- copyright = u'2016, Victor Stinner'
47
- author = u'Victor Stinner'
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.6.3'
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', u'pyperf Documentation',
220
- u'Victor Stinner', 'manual'),
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', u'pyperf Documentation',
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', u'pyperf Documentation',
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 = pyperf.perf_counter()
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 pyperf.perf_counter() - t0
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 sorting a
35
- sorted list of 1000 numbers using a ``key`` function (which does nothing):
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
 
@@ -4,7 +4,7 @@ Python pyperf module
4
4
 
5
5
  The Python ``pyperf`` module is a toolkit to write, run and analyze benchmarks.
6
6
 
7
- Documenation:
7
+ Documentation:
8
8
 
9
9
  .. toctree::
10
10
  :maxdepth: 2
@@ -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 automatically collect ``pystats`` on the benchmark code by calling ``sys._stats_on`` immediately before the benchmark and calling ``sys._stats_off`` immediately after.
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 pining makes benchmarks more stable: use the
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.
@@ -1,6 +1,6 @@
1
1
  from time import perf_counter
2
2
 
3
- VERSION = (2, 6, 3)
3
+ VERSION = (2, 8, 0)
4
4
  __version__ = '.'.join(map(str, VERSION))
5
5
 
6
6
  # Export pyperf.perf_counter for backward compatibility with pyperf 1.7
@@ -86,7 +86,7 @@ def _cached_attr(func):
86
86
  return method
87
87
 
88
88
 
89
- class Run(object):
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(object):
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(object):
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 = u'.gz'
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 = u'.gz'
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 IOError as exc:
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 IOError:
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 (OSError, IOError):
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 IOError:
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 = None
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
 
@@ -53,7 +53,7 @@ def get_tags_for_result(result):
53
53
  return result.ref.benchmark.get_metadata().get("tags", [])
54
54
 
55
55
 
56
- class CompareResult(object):
56
+ class CompareResult:
57
57
  def __init__(self, ref, changed, min_speed=None):
58
58
  # CompareData object
59
59
  self.ref = ref
@@ -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 MB' % (size / (1024.0 * 1024.0))
33
+ return '%.1f MiB' % (size / (1024.0 * 1024.0))
34
34
 
35
- return '%.1f kB' % (size / 1024.0)
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()
@@ -54,7 +54,7 @@ def check_tracking_memory():
54
54
  mem_thread = PeakMemoryUsageThread()
55
55
  try:
56
56
  mem_thread.get()
57
- except IOError as exc:
57
+ except OSError as exc:
58
58
  path = proc_path("self/smaps")
59
59
  return "unable to read %s: %s" % (path, exc)
60
60
 
@@ -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(object):
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
- with rpipe.open_text() as rfile:
102
- bench_json = rfile.read()
103
-
104
- exitcode = proc.wait()
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"
@@ -137,7 +137,7 @@ def format_metadata(name, value):
137
137
  return info.formatter(value)
138
138
 
139
139
 
140
- class Metadata(object):
140
+ class Metadata:
141
141
  def __init__(self, name, value):
142
142
  self._name = name
143
143
  self._value = value