Skip to content

Commit 7084a7c

Browse files
Add ability to register hooks from code (#236)
Add hooks parameter to Runner. Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent 3f0cfc8 commit 7084a7c

5 files changed

Lines changed: 87 additions & 8 deletions

File tree

doc/api.rst

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,10 +485,39 @@ BenchmarkSuite class
485485
It can be ``None``.
486486

487487

488+
HookBase class
489+
--------------
490+
491+
.. class:: HookBase()
492+
493+
Hook used to do some actions before and after benchmark's run. It can collect
494+
statistics, run external tools.
495+
496+
Methods:
497+
498+
.. method:: __enter__()
499+
500+
Called immediately before runnig benchmark code.
501+
502+
May be called multiple times per instance.
503+
504+
.. method:: __exit__(exc_type, exc_value, traceback):
505+
506+
Called immediately after running benchmark code.
507+
508+
May be called multiple times per instance.
509+
510+
.. method:: teardown(metadata)
511+
512+
Called when the hook is completed for a process.
513+
514+
May add any information collected to the passed-in ``metadata`` dictionary.
515+
516+
488517
Runner class
489518
------------
490519

491-
.. class:: Runner(values=3, warmups=1, processes=20, loops=0, min_time=0.1, metadata=None, show_name=True, program_args=None, add_cmdline_args=None)
520+
.. class:: Runner(values=3, warmups=1, processes=20, loops=0, min_time=0.1, metadata=None, show_name=True, program_args=None, add_cmdline_args=None, hooks=None)
492521

493522
Tool to run a benchmark in text mode.
494523

@@ -512,6 +541,10 @@ Runner class
512541
(``list``) which must be modified in place and *args* is the :attr:`args`
513542
attribute of the runner.
514543

544+
*hooks* is a list or tuple of custom hooks. Each hook should implement
545+
:class:`HookBase` and have ``name`` attribute and ``load()`` method that
546+
returns instance of the hook.
547+
515548
If *show_name* is true, displays the benchmark name.
516549

517550
If isolated CPUs are detected, the CPU affinity is automatically

pyperf/_hooks.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,18 @@ def get_hook_names():
2929
return (x.name for x in get_hooks())
3030

3131

32-
def get_selected_hooks(hook_names):
32+
def get_selected_hooks(hook_names, hooks=None):
3333
if hook_names is None:
3434
return
3535

36-
hook_mapping = {hook.name: hook for hook in get_hooks()}
36+
hook_mapping = {hook.name: hook for hook in get_hooks() + tuple(hooks or ())}
3737
for hook_name in hook_names:
3838
yield hook_mapping[hook_name]
3939

4040

41-
def instantiate_selected_hooks(hook_names):
41+
def instantiate_selected_hooks(hook_names, hooks=None):
4242
hook_managers = {}
43-
for hook in get_selected_hooks(hook_names):
43+
for hook in get_selected_hooks(hook_names, hooks):
4444
try:
4545
hook_managers[hook.name] = hook.load()()
4646
except HookError as e:

pyperf/_runner.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def __init__(self, values=None, processes=None,
7777
loops=0, min_time=0.1, metadata=None,
7878
show_name=True,
7979
program_args=None, add_cmdline_args=None,
80-
_argparser=None, warmups=1):
80+
_argparser=None, warmups=1, hooks=None):
8181

8282
# Watchdog: ensure that only once instance of Runner (or a Runner
8383
# subclass) is created per process to prevent bad surprises
@@ -247,7 +247,11 @@ def __init__(self, values=None, processes=None,
247247
help='Collect profile data using cProfile '
248248
'and output to the given file.')
249249

250-
hook_names = list(get_hook_names())
250+
self._custom_hooks = hooks or ()
251+
if not isinstance(self._custom_hooks, (list, tuple)):
252+
raise RuntimeError("hooks parameter should be list, tuple or None")
253+
254+
hook_names = list(get_hook_names()) + [hook.name for hook in self._custom_hooks]
251255
parser.add_argument(
252256
'--hook', action="append", choices=hook_names,
253257
metavar=f"{', '.join(x for x in hook_names if not x.startswith('_'))}",

pyperf/_worker.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def __init__(self, runner, name, task_func, func_metadata):
3131
self.args = args
3232
self.task_func = task_func
3333
self.loops = args.loops
34+
self._custom_hooks = runner._custom_hooks
3435

3536
self.metadata = dict(runner.metadata)
3637
if func_metadata:
@@ -61,7 +62,7 @@ def _compute_values(self, values, nvalue,
6162

6263
task_func = self.task_func
6364

64-
hook_managers = instantiate_selected_hooks(args.hook)
65+
hook_managers = instantiate_selected_hooks(args.hook, self._custom_hooks)
6566
if len(hook_managers):
6667
self.metadata["hooks"] = ", ".join(hook_managers.keys())
6768

pyperf/tests/test_runner.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import pyperf
1212
from pyperf import tests
13+
from pyperf._hooks import HookBase
1314
from pyperf._utils import create_pipe, MS_WINDOWS, shell_quote
1415

1516

@@ -518,6 +519,46 @@ def test_hook_command(self):
518519
self.assertEqual(bench.get_metadata()["hooks"],
519520
"_test_hook")
520521

522+
def test_custom_hook(self):
523+
class State:
524+
inited = 0
525+
entered = 0
526+
exited = 0
527+
528+
s = State()
529+
530+
class CustomHook(HookBase):
531+
name = "custom_hook"
532+
533+
@staticmethod
534+
def load():
535+
return lambda: CustomHook(s)
536+
537+
def __init__(self, state):
538+
self.state = state
539+
self.state.inited += 1
540+
541+
def __enter__(self):
542+
self.state.entered += 1
543+
544+
def __exit__(self, _exc_type, _exc_value, _traceback):
545+
self.state.exited += 1
546+
547+
def teardown(self, metadata):
548+
metadata[self.name] = "done"
549+
550+
args = '-l1 -w0 -n1 --worker --verbose --hook custom_hook'.split()
551+
runner = self.create_runner(args, hooks=[CustomHook])
552+
def time_func(loops):
553+
return 1.0
554+
555+
bench = runner.bench_time_func('bench1', time_func)
556+
self.assertEqual(bench.get_metadata()["hooks"], CustomHook.name)
557+
self.assertEqual(bench.get_metadata()[CustomHook.name], "done")
558+
self.assertEqual(s.inited, 1)
559+
self.assertEqual(s.entered, 1)
560+
self.assertEqual(s.exited, 1)
561+
521562
def test_single_instance(self):
522563
runner1 = self.create_runner([]) # noqa
523564
with self.assertRaises(RuntimeError):

0 commit comments

Comments
 (0)