Skip to content
Snippets Groups Projects
Unverified Commit 6cbd155a authored by Robin De Schepper's avatar Robin De Schepper Committed by GitHub
Browse files

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.
parent 50468b09
No related branches found
No related tags found
No related merge requests found
...@@ -10,6 +10,9 @@ ...@@ -10,6 +10,9 @@
# intermediate python files # intermediate python files
*.pyc *.pyc
# python dev env files
.python-version
# graphviz files generated by executables # graphviz files generated by executables
*.dot *.dot
...@@ -83,4 +86,3 @@ dist ...@@ -83,4 +86,3 @@ dist
# generated image files by Python examples # generated image files by Python examples
python/example/*.svg python/example/*.svg
...@@ -285,6 +285,12 @@ CMake parameters and flags, follow links to the more detailed descriptions below ...@@ -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 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: .. _buildtarget:
Build target Build target
...@@ -459,7 +465,7 @@ use ``ARB_PYTHON_LIB_PATH`` to specify the location where the Python module is t ...@@ -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. 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``, 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. 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())'`` ``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``. (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 ...@@ -551,6 +557,20 @@ component ``neuroml``. The corresponding CMake library target is ``arbor::arbori
# ... # ...
target_link_libraries(myapp arbor::arborio) 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: .. _install:
...@@ -871,4 +891,3 @@ need to be `updated <install-downloading_>`_. ...@@ -871,4 +891,3 @@ need to be `updated <install-downloading_>`_.
git submodule update git submodule update
Or download submodules recursively when checking out: Or download submodules recursively when checking out:
git clone --recurse-submodules https://github.com/arbor-sim/arbor.git git clone --recurse-submodules https://github.com/arbor-sim/arbor.git
...@@ -5,6 +5,22 @@ ...@@ -5,6 +5,22 @@
Profiler 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. 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. Users manually instrument the regions to measure.
......
...@@ -33,6 +33,11 @@ pybind11::dict config() { ...@@ -33,6 +33,11 @@ pybind11::dict config() {
dict[pybind11::str("vectorize")] = pybind11::bool_(true); dict[pybind11::str("vectorize")] = pybind11::bool_(true);
#else #else
dict[pybind11::str("vectorize")] = pybind11::bool_(false); 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 #endif
dict[pybind11::str("version")] = pybind11::str(ARB_VERSION); dict[pybind11::str("version")] = pybind11::str(ARB_VERSION);
dict[pybind11::str("source")] = pybind11::str(ARB_SOURCE_ID); dict[pybind11::str("source")] = pybind11::str(ARB_SOURCE_ID);
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include <pybind11/stl.h> #include <pybind11/stl.h>
#include <arbor/profile/meter_manager.hpp> #include <arbor/profile/meter_manager.hpp>
#include <arbor/profile/profiler.hpp>
#include <arbor/version.hpp>
#include "context.hpp" #include "context.hpp"
#include "strprintf.hpp" #include "strprintf.hpp"
...@@ -52,6 +54,17 @@ void register_profiler(pybind11::module& m) { ...@@ -52,6 +54,17 @@ void register_profiler(pybind11::module& m) {
"manager"_a, "context"_a) "manager"_a, "context"_a)
.def("__str__", [](arb::profile::meter_report& r){return util::pprintf("{}", r);}) .def("__str__", [](arb::profile::meter_report& r){return util::pprintf("{}", r);})
.def("__repr__", [](arb::profile::meter_report& r){return "<arbor.meter_report>";}); .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 } // namespace pyarb
...@@ -18,6 +18,7 @@ try: ...@@ -18,6 +18,7 @@ try:
import test_event_generators import test_event_generators
import test_identifiers import test_identifiers
import test_morphology import test_morphology
import test_profiling
import test_schedules import test_schedules
import test_spikes import test_spikes
import test_tests import test_tests
...@@ -32,6 +33,7 @@ except ModuleNotFoundError: ...@@ -32,6 +33,7 @@ except ModuleNotFoundError:
from test.unit import test_event_generators from test.unit import test_event_generators
from test.unit import test_identifiers from test.unit import test_identifiers
from test.unit import test_morphology from test.unit import test_morphology
from test.unit import test_profiling
from test.unit import test_schedules from test.unit import test_schedules
from test.unit import test_spikes from test.unit import test_spikes
# add more if needed # add more if needed
...@@ -45,6 +47,7 @@ test_modules = [\ ...@@ -45,6 +47,7 @@ test_modules = [\
test_event_generators,\ test_event_generators,\
test_identifiers,\ test_identifiers,\
test_morphology,\ test_morphology,\
test_profiling,\
test_schedules,\ test_schedules,\
test_spikes,\ test_spikes,\
] # add more if needed ] # add more if needed
......
# -*- 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()
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment