pyperf 2.8.0__tar.gz → 2.9.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.8.0/pyperf.egg-info → pyperf-2.9.0}/PKG-INFO +3 -4
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/api.rst +13 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/changelog.rst +20 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/conf.py +1 -1
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/run_benchmark.rst +14 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/__init__.py +1 -1
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/__main__.py +6 -2
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_bench.py +41 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_cli.py +25 -3
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_hooks.py +60 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_process_time.py +1 -1
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_utils.py +4 -1
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/test_misc.py +1 -5
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/test_perf_cli.py +13 -3
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/test_runner.py +14 -8
- {pyperf-2.8.0 → pyperf-2.9.0/pyperf.egg-info}/PKG-INFO +3 -4
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf.egg-info/entry_points.txt +1 -0
- pyperf-2.9.0/pyperf.egg-info/requires.txt +4 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyproject.toml +2 -2
- pyperf-2.8.0/pyperf.egg-info/requires.txt +0 -7
- {pyperf-2.8.0 → pyperf-2.9.0}/COPYING +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/MANIFEST.in +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/README.rst +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/TODO.rst +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/Makefile +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/analyze.rst +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/cli.rst +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/developer_guide.rst +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/examples/bench_async_func.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/examples/bench_async_func_with_loop_factory.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/examples/bench_command.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/examples/bench_func.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/examples/bench_time_func.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/examples/bench_timeit.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/examples/export_csv.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/examples/hist_scipy.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/examples/plot.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/examples.rst +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/hist_scipy_telco.png +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/index.rst +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/make.bat +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/outliers.json.gz +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/py2.json.gz +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/py3.json.gz +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/runner.rst +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/system.rst +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/timeit_strip.json.gz +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/doc/user_guide.rst +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_collect_metadata.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_command.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_compare.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_cpu_utils.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_formatter.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_linux_memory.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_manager.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_metadata.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_psutil_memory.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_runner.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_system.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_timeit.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_timeit_cli.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_win_memory.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/_worker.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/__init__.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/mult_list_bench.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/mult_list_py36.json +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/mult_list_py36_tags.json +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/mult_list_py37.json +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/mult_list_py37_tags.json +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/mult_list_py38.json +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/replay.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/telco.json +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/test_bench.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/test_cli.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/test_examples.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/test_metadata.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/test_system.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/test_timeit.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/test_utils.py +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf/tests/track_memory.json +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf.egg-info/SOURCES.txt +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf.egg-info/dependency_links.txt +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/pyperf.egg-info/top_level.txt +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/setup.cfg +0 -0
- {pyperf-2.8.0 → pyperf-2.9.0}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: pyperf
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9.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>
|
|
@@ -13,13 +13,12 @@ Classifier: Natural Language :: English
|
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
15
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
-
Requires-Python: >=3.
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
17
|
Description-Content-Type: text/x-rst
|
|
18
18
|
License-File: COPYING
|
|
19
19
|
Requires-Dist: psutil>=5.9.0
|
|
20
20
|
Provides-Extra: dev
|
|
21
21
|
Requires-Dist: tox; extra == "dev"
|
|
22
|
-
Requires-Dist: importlib-metadata; python_version < "3.8" and extra == "dev"
|
|
23
22
|
|
|
24
23
|
******
|
|
25
24
|
pyperf
|
|
@@ -345,6 +345,19 @@ Benchmark class
|
|
|
345
345
|
|
|
346
346
|
Raise an exception if the benchmark has no values.
|
|
347
347
|
|
|
348
|
+
.. method:: required_nprocesses()
|
|
349
|
+
|
|
350
|
+
Determines the number of separate process runs that would be required
|
|
351
|
+
achieve stable results. Specifically, the target is to have 95% certainty
|
|
352
|
+
that there is a variance of less than 1%. If the result is greater than
|
|
353
|
+
the number of processes recorded in the input data, the value is
|
|
354
|
+
meaningless and only means "more samples are required".
|
|
355
|
+
|
|
356
|
+
The method used is described in this Wikipedia article about estimating
|
|
357
|
+
the sampling of a mean:
|
|
358
|
+
|
|
359
|
+
https://en.wikipedia.org/wiki/Sample_size_determination#Estimation_of_a_mean
|
|
360
|
+
|
|
348
361
|
.. method:: update_metadata(metadata: dict)
|
|
349
362
|
|
|
350
363
|
Update metadata of all runs of the benchmark.
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
Version 2.9.0 (2025-03-03)
|
|
5
|
+
--------------------------
|
|
6
|
+
|
|
7
|
+
* Feature: A new hook, ``perf_record``, is available to turn Linux perf profiling on and off around the benchmark code.
|
|
8
|
+
To use it, pass ``--hook perf_record``.
|
|
9
|
+
Patch by Matt Page.
|
|
10
|
+
* Feature: pyperf will now warn if you are collecting too few or too many samples for stable results.
|
|
11
|
+
Patch by Michael Droettboom
|
|
12
|
+
|
|
13
|
+
Version 2.8.1 (2024-11-13)
|
|
14
|
+
--------------------------
|
|
15
|
+
|
|
16
|
+
* Bugfix: Hooks now work correctly in subprocess benchmarks (`bench.run_command`).
|
|
17
|
+
Patch by Michael Droettboom
|
|
18
|
+
* Python 3.8 is now unsupported.
|
|
19
|
+
Patch by Hugo van Kemenade
|
|
20
|
+
|
|
21
|
+
Version 2.8.0 (2024-09-30)
|
|
22
|
+
--------------------------
|
|
23
|
+
|
|
4
24
|
* Benchmarks now have a timeout.
|
|
5
25
|
Patch by Diego Russo.
|
|
6
26
|
* The example code in bench_time_func.py uses the more modern `time.perf_counter()`.
|
|
@@ -50,7 +50,7 @@ author = 'Victor Stinner'
|
|
|
50
50
|
# built documents.
|
|
51
51
|
#
|
|
52
52
|
# The short X.Y version.
|
|
53
|
-
version = release = '2.
|
|
53
|
+
version = release = '2.9.0'
|
|
54
54
|
|
|
55
55
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
56
56
|
# for a list of supported languages.
|
|
@@ -206,3 +206,17 @@ The `Tools/scripts/summarize_stats.py <https://github.com/python/cpython/blob/ma
|
|
|
206
206
|
|
|
207
207
|
Statistics are not cleared between runs.
|
|
208
208
|
If you need to delete statistics from a previous run, remove the files in ``/tmp/py_stats`` (Unix) or ``C:\temp\py_stats`` (Windows).
|
|
209
|
+
|
|
210
|
+
Profiling benchmarks using ``perf record``
|
|
211
|
+
==========================================
|
|
212
|
+
``pyperf`` supports profiling benchmark execution using ``perf
|
|
213
|
+
record``. ``perf`` is only enabled while the benchmark is running to avoid
|
|
214
|
+
profiling unrelated parts of ``pyperf`` itself.
|
|
215
|
+
|
|
216
|
+
One profile data file is generated for each benchmark run. These files have
|
|
217
|
+
the basename of ``perf.data.<uuid>`` and are written to the current directory
|
|
218
|
+
by default. The directory can be overridden by setting the
|
|
219
|
+
``PYPERF_PERF_RECORD_DATA_DIR`` environment variable.
|
|
220
|
+
|
|
221
|
+
The value of the ``PYPERF_PERF_RECORD_EXTRA_OPTS`` environment variable is
|
|
222
|
+
appended to the command line of ``perf record`` if it is provided.
|
|
@@ -455,7 +455,8 @@ def display_benchmarks(args, show_metadata=False, hist=False, stats=False,
|
|
|
455
455
|
dump=dump,
|
|
456
456
|
checks=checks,
|
|
457
457
|
result=result,
|
|
458
|
-
display_runs_args=display_runs_args
|
|
458
|
+
display_runs_args=display_runs_args,
|
|
459
|
+
only_checks=only_checks)
|
|
459
460
|
|
|
460
461
|
if bench_lines:
|
|
461
462
|
empty_line(lines)
|
|
@@ -491,10 +492,13 @@ def display_benchmarks(args, show_metadata=False, hist=False, stats=False,
|
|
|
491
492
|
empty_line(output)
|
|
492
493
|
output.extend(lines)
|
|
493
494
|
|
|
495
|
+
contains_warning = False
|
|
494
496
|
for line in output:
|
|
497
|
+
if line.startswith("WARNING:"):
|
|
498
|
+
contains_warning = True
|
|
495
499
|
print(line)
|
|
496
500
|
|
|
497
|
-
if not
|
|
501
|
+
if not contains_warning and only_checks:
|
|
498
502
|
if len(data) == 1:
|
|
499
503
|
print("The benchmark seems to be stable")
|
|
500
504
|
else:
|
|
@@ -424,6 +424,47 @@ class Benchmark:
|
|
|
424
424
|
raise ValueError("MAD must be >= 0")
|
|
425
425
|
return value
|
|
426
426
|
|
|
427
|
+
def required_nprocesses(self):
|
|
428
|
+
"""
|
|
429
|
+
Determines the number of separate process runs that would be required
|
|
430
|
+
achieve stable results. Specifically, the target is to have 95%
|
|
431
|
+
certainty that there is a variance of less than 1%. If the result is
|
|
432
|
+
greater than the number of processes recorded in the input data, the
|
|
433
|
+
value is meaningless and only means "more samples are required".
|
|
434
|
+
|
|
435
|
+
The method used is described in this Wikipedia article about estimating
|
|
436
|
+
the sampling of a mean:
|
|
437
|
+
|
|
438
|
+
https://en.wikipedia.org/wiki/Sample_size_determination#Estimation_of_a_mean
|
|
439
|
+
"""
|
|
440
|
+
# Get the means of the values per process. The values within the process
|
|
441
|
+
# often vary considerably (e.g. due to cache effects), but the variances
|
|
442
|
+
# between processes should be fairly consistent. Additionally, this
|
|
443
|
+
# value is intended to be advice for the number of processes to run.
|
|
444
|
+
values = []
|
|
445
|
+
for run in self._runs:
|
|
446
|
+
if len(run.values):
|
|
447
|
+
values.append(statistics.mean(run.values))
|
|
448
|
+
|
|
449
|
+
if len(values) < 2:
|
|
450
|
+
return None
|
|
451
|
+
|
|
452
|
+
total = math.fsum(values)
|
|
453
|
+
mean = total / len(values)
|
|
454
|
+
stddev = statistics.stdev(values)
|
|
455
|
+
|
|
456
|
+
# Normalize the stddev so we can target "percentage changed" rather than
|
|
457
|
+
# absolute time
|
|
458
|
+
sigma = stddev / mean
|
|
459
|
+
|
|
460
|
+
# 95% certainty
|
|
461
|
+
Z = 1.96
|
|
462
|
+
# 1% variation
|
|
463
|
+
W = 0.01
|
|
464
|
+
|
|
465
|
+
# (4Z²σ²)/(W²)
|
|
466
|
+
return math.ceil((4 * Z ** 2 * sigma ** 2) / (W ** 2))
|
|
467
|
+
|
|
427
468
|
def percentile(self, p):
|
|
428
469
|
if not (0 <= p <= 100):
|
|
429
470
|
raise ValueError("p must be in the range [0; 100]")
|
|
@@ -400,7 +400,7 @@ def format_histogram(benchmarks, bins=20, extend=False, lines=None,
|
|
|
400
400
|
return lines
|
|
401
401
|
|
|
402
402
|
|
|
403
|
-
def format_checks(bench, lines=None):
|
|
403
|
+
def format_checks(bench, lines=None, check_too_many_processes=False):
|
|
404
404
|
if lines is None:
|
|
405
405
|
lines = []
|
|
406
406
|
|
|
@@ -412,6 +412,7 @@ def format_checks(bench, lines=None):
|
|
|
412
412
|
mean = bench.mean()
|
|
413
413
|
warnings = []
|
|
414
414
|
warn = warnings.append
|
|
415
|
+
required_nprocesses = None
|
|
415
416
|
|
|
416
417
|
# Display a warning if the standard deviation is greater than 10%
|
|
417
418
|
# of the mean
|
|
@@ -421,6 +422,14 @@ def format_checks(bench, lines=None):
|
|
|
421
422
|
if percent >= 10.0:
|
|
422
423
|
warn("the standard deviation (%s) is %.0f%% of the mean (%s)"
|
|
423
424
|
% (bench.format_value(stdev), percent, bench.format_value(mean)))
|
|
425
|
+
else:
|
|
426
|
+
# display a warning if the number of samples isn't enough to get a stable result
|
|
427
|
+
required_nprocesses = bench.required_nprocesses()
|
|
428
|
+
if (
|
|
429
|
+
required_nprocesses is not None and
|
|
430
|
+
required_nprocesses > len(bench._runs)
|
|
431
|
+
):
|
|
432
|
+
warn("Not enough samples to get a stable result (95% certainly of less than 1% variation)")
|
|
424
433
|
|
|
425
434
|
# Minimum and maximum, detect obvious outliers
|
|
426
435
|
for minimum, value in (
|
|
@@ -457,6 +466,19 @@ def format_checks(bench, lines=None):
|
|
|
457
466
|
lines.append("Use pyperf stats, pyperf dump and pyperf hist to analyze results.")
|
|
458
467
|
lines.append("Use --quiet option to hide these warnings.")
|
|
459
468
|
|
|
469
|
+
if check_too_many_processes:
|
|
470
|
+
if required_nprocesses is None:
|
|
471
|
+
required_nprocesses = bench.required_nprocesses()
|
|
472
|
+
if (
|
|
473
|
+
required_nprocesses is not None and
|
|
474
|
+
required_nprocesses < len(bench._runs) * 0.75
|
|
475
|
+
):
|
|
476
|
+
lines.append("Benchmark was run more times than necessary to get a stable result.")
|
|
477
|
+
lines.append(
|
|
478
|
+
"Consider passing processes=%d to the Runner constructor to save time." %
|
|
479
|
+
required_nprocesses
|
|
480
|
+
)
|
|
481
|
+
|
|
460
482
|
# Warn if nohz_full+intel_pstate combo if found in cpu_config metadata
|
|
461
483
|
for run in bench._runs:
|
|
462
484
|
cpu_config = run._metadata.get('cpu_config')
|
|
@@ -549,7 +571,7 @@ def format_result(bench):
|
|
|
549
571
|
|
|
550
572
|
def format_benchmark(bench, checks=True, metadata=False,
|
|
551
573
|
dump=False, stats=False, hist=False, show_name=False,
|
|
552
|
-
result=True, display_runs_args=None):
|
|
574
|
+
result=True, display_runs_args=None, only_checks=False):
|
|
553
575
|
lines = []
|
|
554
576
|
|
|
555
577
|
if metadata:
|
|
@@ -568,7 +590,7 @@ def format_benchmark(bench, checks=True, metadata=False,
|
|
|
568
590
|
format_stats(bench, lines=lines)
|
|
569
591
|
|
|
570
592
|
if checks:
|
|
571
|
-
format_checks(bench, lines=lines)
|
|
593
|
+
format_checks(bench, lines=lines, check_too_many_processes=only_checks)
|
|
572
594
|
|
|
573
595
|
if result:
|
|
574
596
|
empty_line(lines)
|
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
import abc
|
|
7
7
|
import importlib.metadata
|
|
8
|
+
import os.path
|
|
9
|
+
import shlex
|
|
10
|
+
import subprocess
|
|
8
11
|
import sys
|
|
12
|
+
import tempfile
|
|
13
|
+
import uuid
|
|
9
14
|
|
|
10
15
|
|
|
11
16
|
def get_hooks():
|
|
@@ -108,3 +113,58 @@ class pystats(HookBase):
|
|
|
108
113
|
|
|
109
114
|
def __exit__(self, _exc_type, _exc_value, _traceback):
|
|
110
115
|
sys._stats_off()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class perf_record(HookBase):
|
|
119
|
+
"""Profile the benchmark using perf-record.
|
|
120
|
+
|
|
121
|
+
Profile data is written to the current directory directory by default, or
|
|
122
|
+
to the value of the `PYPERF_PERF_RECORD_DATA_DIR` environment variable, if
|
|
123
|
+
it is provided.
|
|
124
|
+
|
|
125
|
+
Profile data files have a basename of the form `perf.data.<uuid>`
|
|
126
|
+
|
|
127
|
+
The value of the `PYPERF_PERF_RECORD_EXTRA_OPTS` environment variable is
|
|
128
|
+
appended to the command line of perf-record, if provided.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
def __init__(self):
|
|
132
|
+
self.tempdir = tempfile.TemporaryDirectory()
|
|
133
|
+
self.ctl_fifo = self.mkfifo(self.tempdir.name, "ctl_fifo")
|
|
134
|
+
self.ack_fifo = self.mkfifo(self.tempdir.name, "ack_fifo")
|
|
135
|
+
perf_data_dir = os.environ.get("PYPERF_PERF_RECORD_DATA_DIR", "")
|
|
136
|
+
perf_data_basename = f"perf.data.{uuid.uuid4()}"
|
|
137
|
+
cmd = ["perf", "record",
|
|
138
|
+
"--pid", str(os.getpid()),
|
|
139
|
+
"--output", os.path.join(perf_data_dir, perf_data_basename),
|
|
140
|
+
"--control", f"fifo:{self.ctl_fifo},{self.ack_fifo}"]
|
|
141
|
+
extra_opts = os.environ.get("PYPERF_PERF_RECORD_EXTRA_OPTS", "")
|
|
142
|
+
cmd += shlex.split(extra_opts)
|
|
143
|
+
self.perf = subprocess.Popen(
|
|
144
|
+
cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
145
|
+
self.ctl_fd = open(self.ctl_fifo, "w")
|
|
146
|
+
self.ack_fd = open(self.ack_fifo, "r")
|
|
147
|
+
|
|
148
|
+
def __enter__(self):
|
|
149
|
+
self.exec_perf_cmd("enable")
|
|
150
|
+
|
|
151
|
+
def __exit__(self, _exc_type, _exc_value, _traceback):
|
|
152
|
+
self.exec_perf_cmd("disable")
|
|
153
|
+
|
|
154
|
+
def teardown(self, metadata):
|
|
155
|
+
try:
|
|
156
|
+
self.exec_perf_cmd("stop")
|
|
157
|
+
self.perf.wait(timeout=120)
|
|
158
|
+
finally:
|
|
159
|
+
self.ctl_fd.close()
|
|
160
|
+
self.ack_fd.close()
|
|
161
|
+
|
|
162
|
+
def mkfifo(self, tmpdir, basename):
|
|
163
|
+
path = os.path.join(tmpdir, basename)
|
|
164
|
+
os.mkfifo(path)
|
|
165
|
+
return path
|
|
166
|
+
|
|
167
|
+
def exec_perf_cmd(self, cmd):
|
|
168
|
+
self.ctl_fd.write(f"{cmd}\n")
|
|
169
|
+
self.ctl_fd.flush()
|
|
170
|
+
self.ack_fd.readline()
|
|
@@ -107,7 +107,7 @@ def load_hooks(metadata):
|
|
|
107
107
|
import pyperf._hooks
|
|
108
108
|
|
|
109
109
|
hook_managers = pyperf._hooks.instantiate_selected_hooks(hook_names)
|
|
110
|
-
metadata["hooks"] = ", ".join(hook_managers.
|
|
110
|
+
metadata["hooks"] = ", ".join(hook_managers.keys())
|
|
111
111
|
else:
|
|
112
112
|
hook_managers = {}
|
|
113
113
|
|
|
@@ -270,7 +270,10 @@ def create_environ(inherit_environ, locale, copy_all):
|
|
|
270
270
|
env = {}
|
|
271
271
|
copy_env = ["PATH", "HOME", "TEMP", "COMSPEC", "SystemRoot", "SystemDrive",
|
|
272
272
|
# Python specific variables
|
|
273
|
-
"PYTHONPATH", "PYTHON_CPU_COUNT", "PYTHON_GIL"
|
|
273
|
+
"PYTHONPATH", "PYTHON_CPU_COUNT", "PYTHON_GIL",
|
|
274
|
+
# Pyperf specific variables
|
|
275
|
+
"PYPERF_PERF_RECORD_DATA_DIR", "PYPERF_PERF_RECORD_EXTRA_OPTS",
|
|
276
|
+
]
|
|
274
277
|
if locale:
|
|
275
278
|
copy_env.extend(('LANG', 'LC_ADDRESS', 'LC_ALL', 'LC_COLLATE',
|
|
276
279
|
'LC_CTYPE', 'LC_IDENTIFICATION', 'LC_MEASUREMENT',
|
|
@@ -67,11 +67,7 @@ class MiscTests(unittest.TestCase):
|
|
|
67
67
|
self.assertRaises(ValueError, parse_run_list, '1,')
|
|
68
68
|
|
|
69
69
|
def test_setup_version(self):
|
|
70
|
-
|
|
71
|
-
from importlib.metadata import version
|
|
72
|
-
except ModuleNotFoundError:
|
|
73
|
-
# Workaround for Python 3.7
|
|
74
|
-
from importlib_metadata import version
|
|
70
|
+
from importlib.metadata import version
|
|
75
71
|
|
|
76
72
|
self.assertEqual(pyperf.__version__, version("pyperf"))
|
|
77
73
|
|
|
@@ -628,8 +628,18 @@ class TestPerfCLI(BaseTestCase, unittest.TestCase):
|
|
|
628
628
|
|
|
629
629
|
def test_check_stable(self):
|
|
630
630
|
stdout = self.run_command('check', TELCO)
|
|
631
|
-
self.
|
|
632
|
-
|
|
631
|
+
self.assertIn(
|
|
632
|
+
textwrap.dedent(
|
|
633
|
+
"""
|
|
634
|
+
Benchmark was run more times than necessary to get a stable result.
|
|
635
|
+
Consider passing processes=7 to the Runner constructor to save time.
|
|
636
|
+
"""
|
|
637
|
+
).strip(), stdout.rstrip()
|
|
638
|
+
)
|
|
639
|
+
self.assertIn(
|
|
640
|
+
'The benchmark seems to be stable',
|
|
641
|
+
stdout.rstrip()
|
|
642
|
+
)
|
|
633
643
|
|
|
634
644
|
def test_command(self):
|
|
635
645
|
command = [sys.executable, '-c', 'pass']
|
|
@@ -689,7 +699,7 @@ class TestPerfCLI(BaseTestCase, unittest.TestCase):
|
|
|
689
699
|
'[1,2]*1000',
|
|
690
700
|
'-o', tmp_name)
|
|
691
701
|
bench = pyperf.Benchmark.load(tmp_name)
|
|
692
|
-
|
|
702
|
+
|
|
693
703
|
self._check_track_memory_bench(bench, loops=5)
|
|
694
704
|
|
|
695
705
|
def test_track_memory(self):
|
|
@@ -97,10 +97,7 @@ class TestRunner(unittest.TestCase):
|
|
|
97
97
|
|
|
98
98
|
try:
|
|
99
99
|
s = pstats.Stats(name)
|
|
100
|
-
|
|
101
|
-
assert len(s.stats)
|
|
102
|
-
else:
|
|
103
|
-
assert len(s.get_stats_profile().func_profiles)
|
|
100
|
+
assert len(s.get_stats_profile().func_profiles)
|
|
104
101
|
finally:
|
|
105
102
|
if os.path.isfile(name):
|
|
106
103
|
os.unlink(name)
|
|
@@ -123,10 +120,7 @@ class TestRunner(unittest.TestCase):
|
|
|
123
120
|
try:
|
|
124
121
|
import pstats
|
|
125
122
|
s = pstats.Stats(name)
|
|
126
|
-
|
|
127
|
-
assert len(s.stats)
|
|
128
|
-
else:
|
|
129
|
-
assert len(s.get_stats_profile().func_profiles)
|
|
123
|
+
assert len(s.get_stats_profile().func_profiles)
|
|
130
124
|
finally:
|
|
131
125
|
if os.path.isfile(name):
|
|
132
126
|
os.unlink(name)
|
|
@@ -512,6 +506,18 @@ class TestRunner(unittest.TestCase):
|
|
|
512
506
|
self.assertEqual(bench.get_metadata()['command'],
|
|
513
507
|
' '.join(map(shell_quote, args)))
|
|
514
508
|
|
|
509
|
+
def test_hook_command(self):
|
|
510
|
+
args = [sys.executable, '-c', 'pass']
|
|
511
|
+
|
|
512
|
+
runner = self.create_runner('-l1 -w0 -n1 --worker --hook _test_hook'.split())
|
|
513
|
+
with tests.capture_stdout():
|
|
514
|
+
bench = runner.bench_command('bench', args)
|
|
515
|
+
|
|
516
|
+
self.assertEqual(bench.get_metadata()['command'],
|
|
517
|
+
' '.join(map(shell_quote, args)))
|
|
518
|
+
self.assertEqual(bench.get_metadata()["hooks"],
|
|
519
|
+
"_test_hook")
|
|
520
|
+
|
|
515
521
|
def test_single_instance(self):
|
|
516
522
|
runner1 = self.create_runner([]) # noqa
|
|
517
523
|
with self.assertRaises(RuntimeError):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: pyperf
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9.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>
|
|
@@ -13,13 +13,12 @@ Classifier: Natural Language :: English
|
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
15
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
-
Requires-Python: >=3.
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
17
|
Description-Content-Type: text/x-rst
|
|
18
18
|
License-File: COPYING
|
|
19
19
|
Requires-Dist: psutil>=5.9.0
|
|
20
20
|
Provides-Extra: dev
|
|
21
21
|
Requires-Dist: tox; extra == "dev"
|
|
22
|
-
Requires-Dist: importlib-metadata; python_version < "3.8" and extra == "dev"
|
|
23
22
|
|
|
24
23
|
******
|
|
25
24
|
pyperf
|
|
@@ -44,19 +44,19 @@ classifiers = [
|
|
|
44
44
|
"Programming Language :: Python :: 3",
|
|
45
45
|
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
46
46
|
]
|
|
47
|
-
requires-python = ">=3.
|
|
47
|
+
requires-python = ">=3.9"
|
|
48
48
|
dependencies = ["psutil>=5.9.0"]
|
|
49
49
|
|
|
50
50
|
[project.optional-dependencies]
|
|
51
51
|
dev = [
|
|
52
52
|
'tox',
|
|
53
|
-
'importlib-metadata; python_version < "3.8"'
|
|
54
53
|
]
|
|
55
54
|
|
|
56
55
|
[project.scripts]
|
|
57
56
|
pyperf = "pyperf.__main__:main"
|
|
58
57
|
|
|
59
58
|
[project.entry-points."pyperf.hook"]
|
|
59
|
+
perf_record = "pyperf._hooks:perf_record"
|
|
60
60
|
pystats = "pyperf._hooks:pystats"
|
|
61
61
|
_test_hook = "pyperf._hooks:_test_hook"
|
|
62
62
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|