From 6cbd155ad974d1d7c90a2e7060776db88d15d67f Mon Sep 17 00:00:00 2001 From: Robin De Schepper <robin.deschepper93@gmail.com> Date: Thu, 7 Oct 2021 13:40:15 +0200 Subject: [PATCH] Expose profiler to Python (#1688) Adds the `profiler_initialize(ctx)` and `profiler_summary()` functions to the Python module and the `profiling` key to `arbor.config()`. closes #1685. --- .gitignore | 4 +- doc/install/build_install.rst | 23 ++++++- doc/python/profiler.rst | 16 +++++ python/config.cpp | 5 ++ python/profiler.cpp | 13 ++++ python/test/unit/runner.py | 3 + python/test/unit/test_profiling.py | 107 +++++++++++++++++++++++++++++ 7 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 python/test/unit/test_profiling.py diff --git a/.gitignore b/.gitignore index cb17f702..f2c26f5f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ # intermediate python files *.pyc +# python dev env files +.python-version + # graphviz files generated by executables *.dot @@ -83,4 +86,3 @@ dist # generated image files by Python examples python/example/*.svg - diff --git a/doc/install/build_install.rst b/doc/install/build_install.rst index 8fcf077b..95a75fa4 100644 --- a/doc/install/build_install.rst +++ b/doc/install/build_install.rst @@ -285,6 +285,12 @@ CMake parameters and flags, follow links to the more detailed descriptions below cmake -DARB_VECTORIZE=ON -DCMAKE_INSTALL_PREFIX=/opt/arbor +.. topic:: `Release <buildtarget_>`_ mode with profiling enabled + + .. code-block:: bash + + cmake -DARB_WITH_PROFILING=ON + .. _buildtarget: Build target @@ -459,7 +465,7 @@ use ``ARB_PYTHON_LIB_PATH`` to specify the location where the Python module is t The location of libraries under a prefix in only guaranteed to be standard for Python's global library location. Therefore, correct installation of the Python package to any other location using ``CMAKE_INSTALL_PREFIX``, such as user directory (e.g. `~/.local`), a Python or Conda virtual environment, may result in installation to a wrong path. - + ``python3 -m site --user-site`` (for user installations) or a path from ``python3 -c 'import site; print(site.getsitepackages())'`` (for virtual environment installation) can be used in combination with ``ARB_PYTHON_LIB_PATH``. @@ -551,6 +557,20 @@ component ``neuroml``. The corresponding CMake library target is ``arbor::arbori # ... target_link_libraries(myapp arbor::arborio) +.. install-profiling: + +Profiling +--------- + +Arbor has built in profiling that can report the time spent in each step during +the simulation that can be toggled with the ``-DARB_WITH_PROFILING`` CMake option: + +.. code-block:: bash + + cmake .. -DARB_WITH_PROFILING=ON + +By default ``ARB_WITH_PROFILING=OFF``. + .. _install: @@ -871,4 +891,3 @@ need to be `updated <install-downloading_>`_. git submodule update Or download submodules recursively when checking out: git clone --recurse-submodules https://github.com/arbor-sim/arbor.git - diff --git a/doc/python/profiler.rst b/doc/python/profiler.rst index 9df7a77b..6940682c 100644 --- a/doc/python/profiler.rst +++ b/doc/python/profiler.rst @@ -5,6 +5,22 @@ Profiler ======== +If Arbor is built with :ref:`profiling support <install-profiling>` the profiler +can be initialized after the context is created and a summary is available after +the simulation has concluded: + +.. code-block:: python + + arbor.profiler_initialize(context) + simulation.run(tfinal) + summary = arbor.profiler_summary() + print(summary) + + + +Meter manager +============= + Arbor's python module :py:mod:`arbor` has a :class:`meter_manager` for measuring time (and if applicable memory) consumptions of regions of interest in the python code. Users manually instrument the regions to measure. diff --git a/python/config.cpp b/python/config.cpp index 69188a7a..3de7f86d 100644 --- a/python/config.cpp +++ b/python/config.cpp @@ -33,6 +33,11 @@ pybind11::dict config() { dict[pybind11::str("vectorize")] = pybind11::bool_(true); #else dict[pybind11::str("vectorize")] = pybind11::bool_(false); +#endif +#ifdef ARB_PROFILE_ENABLED + dict[pybind11::str("profiling")] = pybind11::bool_(true); +#else + dict[pybind11::str("profiling")] = pybind11::bool_(false); #endif dict[pybind11::str("version")] = pybind11::str(ARB_VERSION); dict[pybind11::str("source")] = pybind11::str(ARB_SOURCE_ID); diff --git a/python/profiler.cpp b/python/profiler.cpp index e08b4402..31fe6b06 100644 --- a/python/profiler.cpp +++ b/python/profiler.cpp @@ -4,6 +4,8 @@ #include <pybind11/stl.h> #include <arbor/profile/meter_manager.hpp> +#include <arbor/profile/profiler.hpp> +#include <arbor/version.hpp> #include "context.hpp" #include "strprintf.hpp" @@ -52,6 +54,17 @@ void register_profiler(pybind11::module& m) { "manager"_a, "context"_a) .def("__str__", [](arb::profile::meter_report& r){return util::pprintf("{}", r);}) .def("__repr__", [](arb::profile::meter_report& r){return "<arbor.meter_report>";}); + +#ifdef ARB_PROFILE_ENABLED + m.def("profiler_initialize", [](context_shim& ctx) { + arb::profile::profiler_initialize(ctx.context); + }); + m.def("profiler_summary", [](){ + std::stringstream stream; + stream << arb::profile::profiler_summary(); + return stream.str(); + }); +#endif } } // namespace pyarb diff --git a/python/test/unit/runner.py b/python/test/unit/runner.py index 3b12c402..75550dee 100644 --- a/python/test/unit/runner.py +++ b/python/test/unit/runner.py @@ -18,6 +18,7 @@ try: import test_event_generators import test_identifiers import test_morphology + import test_profiling import test_schedules import test_spikes import test_tests @@ -32,6 +33,7 @@ except ModuleNotFoundError: from test.unit import test_event_generators from test.unit import test_identifiers from test.unit import test_morphology + from test.unit import test_profiling from test.unit import test_schedules from test.unit import test_spikes # add more if needed @@ -45,6 +47,7 @@ test_modules = [\ test_event_generators,\ test_identifiers,\ test_morphology,\ + test_profiling,\ test_schedules,\ test_spikes,\ ] # add more if needed diff --git a/python/test/unit/test_profiling.py b/python/test/unit/test_profiling.py new file mode 100644 index 00000000..bd0a99b5 --- /dev/null +++ b/python/test/unit/test_profiling.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +# +# test_schedules.py + +import unittest + +import arbor as arb +import functools + +# to be able to run .py file from child directory +import sys, os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) + +try: + import options +except ModuleNotFoundError: + from test import options + +""" +all tests for profiling +""" + +def lazy_skipIf(condition, reason): + """ + Postpone skip evaluation until test is ran by evaluating callable `condition` + """ + def inner_decorator(f): + @functools.wraps(f) + def wrapped(*args, **kwargs): + if condition(): + raise unittest.SkipTest(reason) + else: + return f(*args, **kwargs) + + return wrapped + + return inner_decorator + +class a_recipe(arb.recipe): + def __init__(self): + arb.recipe.__init__(self) + self.props = arb.neuron_cable_properties() + self.trains = [ + [0.8, 2, 2.1, 3], + [0.4, 2, 2.2, 3.1, 4.5], + [0.2, 2, 2.8, 3]] + + def num_cells(self): + return 3 + + def cell_kind(self, gid): + return arb.cell_kind.spike_source + + def connections_on(self, gid): + return [] + + def event_generators(self, gid): + return [] + + def global_properties(self, kind): + return self.the_props + + def probes(self, gid): + return [] + + def cell_description(self, gid): + return arb.spike_source_cell("src", arb.explicit_schedule(self.trains[gid])) + +def skipWithoutSupport(): + return not bool(arb.config().get("profiling", False)) + +class TestProfiling(unittest.TestCase): + def test_support(self): + self.assertTrue("profiling" in arb.config(), 'profiling key not in config') + profiling_support = arb.config()["profiling"] + self.assertEqual(bool, type(profiling_support), 'profiling flag should be bool') + if profiling_support: + self.assertTrue(hasattr(arb, "profiler_initialize"), 'missing profiling interface with profiling support') + self.assertTrue(hasattr(arb, "profiler_summary"), 'missing profiling interface with profiling support') + else: + self.assertFalse(hasattr(arb, "profiler_initialize"), 'profiling interface without profiling support') + self.assertFalse(hasattr(arb, "profiler_summary"), 'profiling interface without profiling support') + + @lazy_skipIf(skipWithoutSupport, "run test only with profiling support") + def test_summary(self): + context = arb.context() + arb.profiler_initialize(context) + recipe = a_recipe() + dd = arb.partition_load_balance(recipe, context) + arb.simulation(recipe, dd, context).run(1) + summary = arb.profiler_summary() + self.assertEqual(str, type(summary), 'profiler summary must be str') + self.assertTrue(summary, 'empty summary') + +def suite(): + # specify class and test functions in tuple (here: all tests starting with 'test' from classes RegularSchedule, ExplicitSchedule and PoissonSchedule + suite = unittest.TestSuite() + suite.addTests(unittest.makeSuite(TestProfiling, ('test'))) + return suite + +def run(): + v = options.parse_arguments().verbosity + runner = unittest.TextTestRunner(verbosity = v) + runner.run(suite()) + +if __name__ == "__main__": + run() -- GitLab