From 3f3ef7ddf9d274b26712aff4602e670b089f95e8 Mon Sep 17 00:00:00 2001 From: "Jonathan R. Madsen" Date: Tue, 16 Aug 2022 19:28:58 -0500 Subject: [PATCH] Python noprofile (#138) * Fixed noprofile / FakeProfiler - omnitrace.libpyomnitrace.profiler.profiler_pause() - omnitrace.libpyomnitrace.profiler.profiler_resume() * Python tests for noprofile * Remove static imported module --- examples/python/CMakeLists.txt | 2 +- examples/python/noprofile.py | 50 +++++++++++++++++++++++++++++ source/python/libpyomnitrace.cpp | 24 ++++++++++++++ source/python/omnitrace/profiler.py | 25 ++++++++++++--- source/python/pyproject.toml | 2 +- tests/CMakeLists.txt | 19 +++++++++++ 6 files changed, 115 insertions(+), 7 deletions(-) create mode 100755 examples/python/noprofile.py diff --git a/examples/python/CMakeLists.txt b/examples/python/CMakeLists.txt index e43eb3edbc..5b3777054a 100644 --- a/examples/python/CMakeLists.txt +++ b/examples/python/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16 FATAL_ERROR) project(omnitrace-python) -set(PYTHON_FILES builtin.py external.py source.py) +set(PYTHON_FILES builtin.py external.py source.py noprofile.py) if(OMNITRACE_INSTALL_EXAMPLES) find_package(Python3 COMPONENTS Interpreter) diff --git a/examples/python/noprofile.py b/examples/python/noprofile.py new file mode 100755 index 0000000000..db0aed2f60 --- /dev/null +++ b/examples/python/noprofile.py @@ -0,0 +1,50 @@ +#!@PYTHON_EXECUTABLE@ + +import os +import sys +import random + +_prefix = "" + + +@noprofile +def fib(n): + return n if n < 2 else (fib(n - 1) + fib(n - 2)) + + +@noprofile +def inefficient(n): + print(f"[{_prefix}] ... running inefficient({n})") + a = 0 + for i in range(n): + a += i + for j in range(n): + a += j + _len = a * n * n * n + _arr = [random.random() for _ in range(_len)] + _sum = sum(_arr) + print(f"[{_prefix}] ... sum of {_len} random elements: {_sum}") + return _sum + + +@profile +def run(n): + _ret = 0 + _ret += fib(n) + _ret += inefficient(n) + return _ret + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument("-n", "--num-iterations", help="Number", type=int, default=3) + parser.add_argument("-v", "--value", help="Starting value", type=int, default=20) + args = parser.parse_args() + + _prefix = os.path.basename(__file__) + print(f"[{_prefix}] Executing {args.num_iterations} iterations...\n") + for i in range(args.num_iterations): + ans = run(args.value) + print(f"[{_prefix}] [{i}] result of run({args.value}) = {ans}\n") diff --git a/source/python/libpyomnitrace.cpp b/source/python/libpyomnitrace.cpp index 30d9ef8fbe..0f2cbb4ff4 100644 --- a/source/python/libpyomnitrace.cpp +++ b/source/python/libpyomnitrace.cpp @@ -187,6 +187,13 @@ strset_t default_exclude_functions = { "^<.*>$" }; strset_t default_exclude_filenames = { "(encoder|decoder|threading).py$", "^<.*>$" }; } // namespace // +auto& +get_paused() +{ + static thread_local int64_t _v = 0; + return _v; +} +// struct config { bool is_running = false; @@ -251,6 +258,8 @@ get_depth(PyFrameObject* frame) void profiler_function(py::object pframe, const char* swhat, py::object arg) { + if(get_paused() > 0) return; + static thread_local auto& _config = get_config(); static thread_local auto _disable = false; @@ -504,9 +513,24 @@ generate(py::module& _pymod) get_config().records.clear(); }; + auto _sys = py::module::import("sys"); + auto _setprofile = _sys.attr("setprofile"); + _prof.def("profiler_function", &profiler_function, "Profiling function"); _prof.def("profiler_init", _init, "Initialize the profiler"); _prof.def("profiler_finalize", _fini, "Finalize the profiler"); + _prof.def( + "profiler_pause", + [_setprofile]() { + if(++get_paused() == 1) _setprofile(nullptr); + }, + "Pause the profiler"); + _prof.def( + "profiler_resume", + [_setprofile]() { + if(--get_paused() == 0) _setprofile(py::cpp_function{ profiler_function }); + }, + "Resume the profiler"); py::class_ _pyconfig(_prof, "config", "Profiler configuration"); diff --git a/source/python/omnitrace/profiler.py b/source/python/omnitrace/profiler.py index bca6ccbfd0..b95663c3ba 100644 --- a/source/python/omnitrace/profiler.py +++ b/source/python/omnitrace/profiler.py @@ -46,8 +46,17 @@ from .libpyomnitrace.profiler import ( from .libpyomnitrace.profiler import config as _profiler_config from .libpyomnitrace.profiler import profiler_init as _profiler_init from .libpyomnitrace.profiler import profiler_finalize as _profiler_fini +from .libpyomnitrace.profiler import profiler_pause as _profiler_pause +from .libpyomnitrace.profiler import profiler_resume as _profiler_resume -__all__ = ["profile", "config", "Profiler", "FakeProfiler", "Config"] +__all__ = [ + "profile", + "noprofile", + "config", + "Profiler", + "FakeProfiler", + "Config", +] config = _profiler_config @@ -227,7 +236,7 @@ profile = Profiler class FakeProfiler: - """Provides dummy decorators and context-manager for the omnitrace profiler""" + """Provides decorators and context-manager for disabling the omnitrace profiler""" @staticmethod def condition(functor): @@ -238,7 +247,6 @@ class FakeProfiler: return False def __init__(self, *args, **kwargs): - """ """ pass def __call__(self, func): @@ -246,18 +254,25 @@ class FakeProfiler: @wraps(func) def function_wrapper(*args, **kwargs): - return func(*args, **kwargs) + _profiler_pause() + ret = func(*args, **kwargs) + _profiler_resume() + return ret return function_wrapper def __enter__(self, *args, **kwargs): """Context manager begin""" - pass + _profiler_pause() def __exit__(self, exec_type, exec_value, exec_tb): """Context manager end""" + _profiler_resume() import traceback if exec_type is not None and exec_value is not None and exec_tb is not None: traceback.print_exception(exec_type, exec_value, exec_tb, limit=5) + + +noprofile = FakeProfiler diff --git a/source/python/pyproject.toml b/source/python/pyproject.toml index 12ae7d7d63..beadd1d80f 100644 --- a/source/python/pyproject.toml +++ b/source/python/pyproject.toml @@ -7,6 +7,6 @@ requires = [ build-backend = 'setuptools.build_meta' [tool.black] -line-length = 80 +line-length = 90 target-version = ['py38'] include = '\.py' diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5e67149cd7..238e27a886 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1229,6 +1229,15 @@ foreach(_VERSION ${OMNITRACE_PYTHON_VERSIONS}) RUN_ARGS -v 10 -n 5 ENVIRONMENT "${_python_environment}") + omnitrace_add_python_test( + NAME python-builtin-noprofile + PYTHON_EXECUTABLE ${_PYTHON_EXECUTABLE} + PYTHON_VERSION ${_VERSION} + FILE ${CMAKE_SOURCE_DIR}/examples/python/noprofile.py + PROFILE_ARGS "-b" "--label" "file" + RUN_ARGS -v 15 -n 5 + ENVIRONMENT "${_python_environment}") + omnitrace_add_python_test( STANDALONE NAME python-source @@ -1282,6 +1291,16 @@ foreach(_VERSION ${OMNITRACE_PYTHON_VERSIONS}) PASS_REGEX "\\\[inefficient\\\]\\\[builtin.py:14\\\]" DEPENDS python-builtin-${_VERSION} ENVIRONMENT "${_python_environment}") + + omnitrace_add_python_test( + NAME python-builtin-noprofile-check + COMMAND ${OMNITRACE_CAT_COMMAND} + PYTHON_VERSION ${_VERSION} + FILE omnitrace-tests-output/python-builtin-noprofile/${_VERSION}/trip_count.txt + PASS_REGEX ".(run)..(noprofile.py)." + FAIL_REGEX ".(fib|inefficient)..(noprofile.py)." + DEPENDS python-builtin-noprofile-${_VERSION} + ENVIRONMENT "${_python_environment}") else() omnitrace_message( WARNING