* silence SFINAE disabled for fork_gotcha

* Python updates

- Options for --{module,function}-include
- libpyomnitrace is_initialized and is_finalized
- source instrumentation auto init
- atexit finalization
- improved python testing

* Documentation Update

* Fix to 'cmake -E cat' not available < cmake v3.18

* Fix for inverse tests

* Update cancelling.yml

[ROCm/rocprofiler-systems commit: 593b3b69b8]
Этот коммит содержится в:
Jonathan R. Madsen
2022-04-05 20:40:27 -05:00
коммит произвёл GitHub
родитель 6daac0f60c
Коммит e7546b201a
21 изменённых файлов: 916 добавлений и 237 удалений
+223 -110
Просмотреть файл
@@ -25,15 +25,23 @@
#include <timemory/backends/process.hpp>
#include <timemory/backends/threading.hpp>
#include <timemory/environment.hpp>
#include <timemory/mpl/apply.hpp>
#include <timemory/utility/macros.hpp>
#include <timemory/utility/types.hpp>
#include <timemory/variadic/macros.hpp>
#include <pybind11/detail/common.h>
#include <pybind11/pybind11.h>
#include <pyerrors.h>
#include <cctype>
#include <cstdint>
#include <exception>
#include <locale>
#include <regex>
#include <set>
#include <stdexcept>
#include <unordered_map>
namespace pyomnitrace
@@ -48,18 +56,94 @@ generate(py::module& _pymod);
PYBIND11_MODULE(libpyomnitrace, omni)
{
using namespace pyomnitrace;
py::doc("omnitrace profiler for python");
pyprofile::generate(omni);
static bool _is_initialized = false;
static bool _is_finalized = false;
static auto _get_use_mpi = []() {
bool _use_mpi = false;
try
{
py::module::import("mpi4py");
_use_mpi = true;
} catch(py::error_already_set& _exc)
{
if(!_exc.matches(PyExc_ImportError)) throw;
}
return _use_mpi;
};
omni.def(
"is_initialized", []() { return _is_initialized; }, "Initialization state");
omni.def(
"is_finalized", []() { return _is_finalized; }, "Finalization state");
omni.def(
"initialize",
[](const std::string& _v) {
omnitrace_set_mpi(false, false);
if(_is_initialized)
throw std::runtime_error("Error! omnitrace is already initialized");
_is_initialized = true;
omnitrace_set_mpi(_get_use_mpi(), false);
omnitrace_init("trace", false, _v.c_str());
},
"Initialize omnitrace");
omni.def(
"finalize", []() { omnitrace_finalize(); }, "Initialize omnitrace");
"initialize",
[](const py::list& _v) {
if(_is_initialized)
throw std::runtime_error("Error! omnitrace is already initialized");
_is_initialized = true;
omnitrace_set_mpi(_get_use_mpi(), false);
std::string _cmd = {};
std::string _cmd_line = {};
for(auto&& itr : _v)
{
if(_cmd.empty()) _cmd = itr.cast<std::string>();
_cmd_line += " " + itr.cast<std::string>();
}
if(!_cmd_line.empty())
{
_cmd_line.substr(_cmd_line.find_first_not_of(' '));
tim::set_env("OMNITRACE_COMMAND_LINE", _cmd_line, 0);
}
omnitrace_init("trace", false, _cmd.c_str());
},
"Initialize omnitrace");
omni.def(
"initialize",
[](const std::string& _v) {
if(_is_initialized)
throw std::runtime_error("Error! omnitrace is already initialized");
_is_initialized = true;
bool _use_mpi = false;
try
{
py::module::import("mpi4py");
_use_mpi = true;
} catch(py::error_already_set& _exc)
{
if(!_exc.matches(PyExc_ImportError)) throw;
}
omnitrace_set_mpi(_use_mpi, false);
omnitrace_init("trace", false, _v.c_str());
},
"Initialize omnitrace");
omni.def(
"finalize",
[]() {
if(_is_finalized)
throw std::runtime_error("Error! omnitrace is already finalized");
_is_finalized = true;
omnitrace_finalize();
},
"Initialize omnitrace");
py::doc("omnitrace profiler for python");
pyprofile::generate(omni);
}
//======================================================================================//
@@ -69,38 +153,39 @@ namespace pyomnitrace
namespace pyprofile
{
//
using profiler_t = std::pair<std::function<void()>, std::function<void()>>;
using profiler_t = std::function<void()>;
using profiler_vec_t = std::vector<profiler_t>;
using profiler_label_map_t = std::unordered_map<std::string, profiler_vec_t>;
using profiler_index_map_t = std::unordered_map<uint32_t, profiler_label_map_t>;
using strset_t = std::unordered_set<std::string>;
//
namespace
{
strset_t default_exclude_functions = { "^<.*>$" };
strset_t default_exclude_filenames = { "(encoder|decoder|threading).py$", "^<.*>$" };
} // namespace
//
struct config
{
bool is_running = false;
bool trace_c = false;
bool include_internal = false;
bool include_args = false;
bool include_line = false;
bool include_filename = false;
bool full_filepath = false;
int32_t ignore_stack_depth = 0;
int32_t base_stack_depth = -1;
std::string base_module_path = {};
strset_t include_functions = {};
strset_t include_filenames = {};
strset_t exclude_functions = { "^(FILE|FUNC|LINE)$",
"^get_fcode$",
"^_(_exit__|handle_fromlist|shutdown|get_sep)$",
"^is(function|class)$",
"^basename$",
"^<.*>$" };
strset_t exclude_filenames = {
"(__init__|__main__|functools|encoder|decoder|_pylab_helpers|threading).py$",
"^<.*>$"
};
profiler_index_map_t records = {};
int32_t verbose = 0;
bool is_running = false;
bool trace_c = false;
bool include_internal = false;
bool include_args = false;
bool include_line = false;
bool include_filename = false;
bool full_filepath = false;
int32_t ignore_stack_depth = 0;
int32_t base_stack_depth = -1;
int32_t verbose = 0;
int64_t depth_tracker = 0;
std::string base_module_path = {};
strset_t restrict_functions = {};
strset_t restrict_filenames = {};
strset_t include_functions = {};
strset_t include_filenames = {};
strset_t exclude_functions = default_exclude_functions;
strset_t exclude_filenames = default_exclude_filenames;
std::vector<profiler_t> records = {};
};
//
inline config&
@@ -112,20 +197,24 @@ get_config()
auto _cnt = _count++;
if(_cnt == 0) return _instance;
auto* _tmp = new config{};
_tmp->is_running = _instance->is_running;
_tmp->trace_c = _instance->trace_c;
_tmp->include_internal = _instance->include_internal;
_tmp->include_args = _instance->include_args;
_tmp->include_line = _instance->include_line;
_tmp->include_filename = _instance->include_filename;
_tmp->full_filepath = _instance->full_filepath;
_tmp->base_module_path = _instance->base_module_path;
_tmp->include_functions = _instance->include_functions;
_tmp->include_filenames = _instance->include_filenames;
_tmp->exclude_functions = _instance->exclude_functions;
_tmp->exclude_filenames = _instance->exclude_filenames;
_tmp->verbose = _instance->verbose;
auto* _tmp = new config{};
_tmp->is_running = _instance->is_running;
_tmp->trace_c = _instance->trace_c;
_tmp->include_internal = _instance->include_internal;
_tmp->include_args = _instance->include_args;
_tmp->include_line = _instance->include_line;
_tmp->include_filename = _instance->include_filename;
_tmp->full_filepath = _instance->full_filepath;
_tmp->base_module_path = _instance->base_module_path;
_tmp->restrict_functions = _instance->restrict_functions;
_tmp->restrict_filenames = _instance->restrict_filenames;
_tmp->include_functions = _instance->include_functions;
_tmp->include_filenames = _instance->include_filenames;
_tmp->exclude_functions = _instance->exclude_functions;
_tmp->exclude_filenames = _instance->exclude_filenames;
_tmp->verbose = _instance->verbose;
// if full filepath is specified, include filename is implied
if(_tmp->full_filepath && !_tmp->include_filename) _tmp->include_filename = true;
return _tmp;
}();
return *_tl_instance;
@@ -160,7 +249,6 @@ profiler_function(py::object pframe, const char* swhat, py::object arg)
: (strcmp(swhat, "return") == 0) ? PyTrace_RETURN
: (strcmp(swhat, "c_return") == 0) ? PyTrace_C_RETURN
: -1;
// only support PyTrace_{CALL,C_CALL,RETURN,C_RETURN}
if(what < 0)
{
@@ -170,6 +258,29 @@ profiler_function(py::object pframe, const char* swhat, py::object arg)
return;
}
auto _update_ignore_stack_depth = [what]() {
switch(what)
{
case PyTrace_CALL: ++_config.ignore_stack_depth; break;
case PyTrace_RETURN: --_config.ignore_stack_depth; break;
default: break;
}
};
if(_config.ignore_stack_depth > 0)
{
if(_config.verbose > 2)
TIMEMORY_PRINT_HERE("%s :: %s :: %u", "Ignoring call/return", swhat,
_config.ignore_stack_depth);
_update_ignore_stack_depth();
return;
}
else if(_config.ignore_stack_depth < 0)
{
TIMEMORY_PRINT_HERE("WARNING! ignore_stack_depth is < 0 :: ",
_config.ignore_stack_depth);
}
// if PyTrace_C_{CALL,RETURN} is not enabled
if(!_config.trace_c && (what == PyTrace_C_CALL || what == PyTrace_C_RETURN))
{
@@ -178,23 +289,6 @@ profiler_function(py::object pframe, const char* swhat, py::object arg)
return;
}
// get the function name
auto _get_funcname = [&]() -> std::string {
return py::cast<std::string>(frame->f_code->co_name);
};
// get the filename
auto _get_filename = [&]() -> std::string {
return py::cast<std::string>(frame->f_code->co_filename);
};
// get the basename of the filename
auto _get_basename = [&](const std::string& _fullpath) {
if(_fullpath.find('/') != std::string::npos)
return _fullpath.substr(_fullpath.find_last_of('/') + 1);
return _fullpath;
};
// get the arguments
auto _get_args = [&]() {
auto inspect = py::module::import("inspect");
@@ -239,35 +333,54 @@ profiler_function(py::object pframe, const char* swhat, py::object arg)
auto _find_matching = [](const strset_t& _expr, const std::string& _name) {
const auto _rconstants =
std::regex_constants::egrep | std::regex_constants::optimize;
for(const auto& itr : _expr)
for(const auto& itr : _expr) // NOLINT
{
if(std::regex_search(_name, std::regex(itr, _rconstants))) return true;
}
return false;
};
auto& _only_funcs = _config.include_functions;
bool _force = false;
auto& _only_funcs = _config.restrict_functions;
auto& _incl_funcs = _config.include_functions;
auto& _skip_funcs = _config.exclude_functions;
auto _func = _get_funcname();
auto _func = py::cast<std::string>(frame->f_code->co_name);
if(!_only_funcs.empty() && !_find_matching(_only_funcs, _func))
if(!_only_funcs.empty())
{
if(_config.verbose > 1)
TIMEMORY_PRINT_HERE("Skipping non-included function: %s", _func.c_str());
return;
_force = _find_matching(_only_funcs, _func);
if(!_force)
{
if(_config.verbose > 2)
TIMEMORY_PRINT_HERE("Skipping non-restricted function: %s",
_func.c_str());
return;
}
}
if(_find_matching(_skip_funcs, _func))
if(!_force)
{
if(_config.verbose > 1)
TIMEMORY_PRINT_HERE("Skipping designated function: '%s'", _func.c_str());
return;
if(_find_matching(_incl_funcs, _func))
{
_force = true;
}
else if(_find_matching(_skip_funcs, _func))
{
if(_config.verbose > 1)
TIMEMORY_PRINT_HERE("Skipping designated function: '%s'", _func.c_str());
if(!_find_matching(default_exclude_functions, _func))
_update_ignore_stack_depth();
return;
}
}
auto& _only_files = _config.include_filenames;
auto& _only_files = _config.restrict_filenames;
auto& _incl_files = _config.include_filenames;
auto& _skip_files = _config.exclude_filenames;
auto _full = _get_filename();
auto _file = _get_basename(_full);
auto _full = py::cast<std::string>(frame->f_code->co_filename);
auto _file = (_full.find('/') != std::string::npos)
? _full.substr(_full.find_last_of('/') + 1)
: _full;
if(!_config.include_internal &&
strncmp(_full.c_str(), _omnitrace_path.c_str(), _omnitrace_path.length()) == 0)
@@ -277,18 +390,29 @@ profiler_function(py::object pframe, const char* swhat, py::object arg)
return;
}
if(!_only_files.empty() && !_find_matching(_only_files, _full))
if(!_force && !_only_files.empty())
{
if(_config.verbose > 2)
TIMEMORY_PRINT_HERE("Skipping non-included file: %s", _full.c_str());
return;
_force = _find_matching(_only_files, _full);
if(!_force)
{
if(_config.verbose > 2)
TIMEMORY_PRINT_HERE("Skipping non-restricted file: %s", _full.c_str());
return;
}
}
if(_find_matching(_skip_files, _full))
if(!_force)
{
if(_config.verbose > 2)
TIMEMORY_PRINT_HERE("Skipping non-included file: %s", _full.c_str());
return;
if(_find_matching(_incl_files, _full))
{
_force = true;
}
else if(_find_matching(_skip_files, _full))
{
if(_config.verbose > 2)
TIMEMORY_PRINT_HERE("Skipping non-included file: %s", _full.c_str());
return;
}
}
TIMEMORY_CONDITIONAL_PRINT_HERE(_config.verbose > 3, "%8s | %s%s | %s | %s", swhat,
@@ -301,36 +425,17 @@ profiler_function(py::object pframe, const char* swhat, py::object arg)
static thread_local strset_t _labels{};
const auto& _label_ref = *_labels.emplace(_label).first;
// get the depth of the frame
// auto _fdepth = get_depth(frame);
static thread_local int32_t _depth_tracker = 0;
auto _fdepth = _depth_tracker;
switch(what)
{
case PyTrace_CALL:
case PyTrace_C_CALL: _fdepth = _depth_tracker++; break;
case PyTrace_RETURN:
case PyTrace_C_RETURN: _fdepth = --_depth_tracker; break;
}
// start function
auto _profiler_call = [&]() {
auto& _entry = _config.records[_fdepth][_label];
_entry.emplace_back(
[&_label_ref]() { omnitrace_push_region(_label_ref.c_str()); },
_config.records.emplace_back(
[&_label_ref]() { omnitrace_pop_region(_label_ref.c_str()); });
_entry.back().first();
omnitrace_push_region(_label_ref.c_str());
};
// stop function
auto _profiler_return = [&]() {
auto fitr = _config.records.find(_fdepth);
if(fitr == _config.records.end()) return;
auto litr = fitr->second.find(_label);
if(litr == fitr->second.end()) return;
if(litr->second.empty()) return;
litr->second.back().second();
litr->second.pop_back();
_config.records.back()();
_config.records.pop_back();
};
// process what
@@ -430,13 +535,21 @@ generate(py::module& _pymod)
CONFIGURATION_PROPERTY_LAMBDA(NAME, DOC, GET, SET) \
}
CONFIGURATION_STRSET("only_functions", "Function regexes to collect exclusively",
CONFIGURATION_STRSET("restrict_functions", "Function regexes to collect exclusively",
get_config().restrict_functions)
CONFIGURATION_STRSET("restrict_modules", "Filename regexes to collect exclusively",
get_config().restrict_filenames)
CONFIGURATION_STRSET("include_functions",
"Function regexes to always include in collection",
get_config().include_functions)
CONFIGURATION_STRSET("only_filenames", "Filename regexes to collect exclusively",
CONFIGURATION_STRSET("include_modules",
"Filename regexes to always include in collection",
get_config().include_filenames)
CONFIGURATION_STRSET("skip_functions", "Function regexes to filter out of collection",
CONFIGURATION_STRSET("exclude_functions",
"Function regexes to filter out of collection",
get_config().exclude_functions)
CONFIGURATION_STRSET("skip_filenames", "Filename regexes to filter out of collection",
CONFIGURATION_STRSET("exclude_modules",
"Filename regexes to filter out of collection",
get_config().exclude_filenames)
return _prof;
+12
Просмотреть файл
@@ -43,6 +43,8 @@ try:
)
from .libpyomnitrace import initialize
from .libpyomnitrace import finalize
from .libpyomnitrace import is_initialized
from .libpyomnitrace import is_finalized
from .libpyomnitrace.profiler import config as Config
config = Config
@@ -52,6 +54,8 @@ try:
__all__ = [
"initialize",
"finalize",
"is_initialized",
"is_finalized",
"Profiler",
"Config",
"FakeProfiler",
@@ -63,5 +67,13 @@ try:
"noprofile",
]
import atexit
def _finalize_at_exit():
if not is_finalized():
finalize()
atexit.register(_finalize_at_exit)
except Exception as e:
print("{}".format(e))
+70 -29
Просмотреть файл
@@ -93,33 +93,42 @@ def parse_args(args=None):
else:
raise argparse.ArgumentTypeError("Boolean value expected.")
parser = argparse.ArgumentParser(add_help=True)
parser.add_argument(
"-c",
"--config",
default=None,
type=str,
help="Omnitrace configuration file",
parser = argparse.ArgumentParser(
"omnitrace",
add_help=True,
epilog="usage: {} -m omnitrace <OMNITRACE_ARGS> -- <SCRIPT> <SCRIPT_ARGS>".format(
os.path.basename(sys.executable)
),
)
parser.add_argument(
"-b",
"--builtin",
action="store_true",
help="Put 'profile' in the builtins. Use 'profile.enable()' and "
"'profile.disable()' in your code to turn it on and off, or "
"'@profile' to decorate a single function, or 'with profile:' "
"to profile a single section of code.",
help=(
"Put 'profile' in the builtins. Use '@profile' to decorate a single function, "
"or 'with profile:' to profile a single section of code."
),
)
parser.add_argument(
"-c",
"--config",
default=None,
type=str,
metavar="FILE",
help="Omnitrace configuration file",
)
parser.add_argument(
"-s",
"--setup",
default=None,
metavar="FILE",
help="Code to execute before the code to profile",
)
parser.add_argument(
"--trace-c",
type=str2bool,
nargs="?",
metavar="BOOL",
const=True,
default=_profiler_config.trace_c,
help="Enable profiling C functions",
@@ -129,6 +138,7 @@ def parse_args(args=None):
"--include-args",
type=str2bool,
nargs="?",
metavar="BOOL",
const=True,
default=_profiler_config.include_args,
help="Encode the argument values",
@@ -138,6 +148,7 @@ def parse_args(args=None):
"--include-line",
type=str2bool,
nargs="?",
metavar="BOOL",
const=True,
default=_profiler_config.include_line,
help="Encode the function line number",
@@ -147,6 +158,7 @@ def parse_args(args=None):
"--include-file",
type=str2bool,
nargs="?",
metavar="BOOL",
const=True,
default=_profiler_config.include_filename,
help="Encode the function filename",
@@ -156,36 +168,63 @@ def parse_args(args=None):
"--full-filepath",
type=str2bool,
nargs="?",
metavar="BOOL",
const=True,
default=_profiler_config.full_filepath,
help="Encode the full function filename (instead of basename)",
)
parser.add_argument(
"--skip-funcs",
"-I",
"--function-include",
type=str,
nargs="+",
default=_profiler_config.skip_functions,
metavar="FUNC",
default=_profiler_config.include_functions,
help="Include any entries with these function names",
)
parser.add_argument(
"-E",
"--function-exclude",
type=str,
nargs="+",
metavar="FUNC",
default=_profiler_config.exclude_functions,
help="Filter out any entries with these function names",
)
parser.add_argument(
"--skip-files",
"-R",
"--function-restrict",
type=str,
nargs="+",
default=_profiler_config.skip_filenames,
help="Filter out any entries from these files",
)
parser.add_argument(
"--only-funcs",
type=str,
nargs="+",
default=_profiler_config.only_functions,
metavar="FUNC",
default=_profiler_config.restrict_functions,
help="Select only entries with these function names",
)
parser.add_argument(
"--only-files",
"-MI",
"--module-include",
type=str,
nargs="+",
default=_profiler_config.only_filenames,
metavar="FILE",
default=_profiler_config.include_modules,
help="Include any entries from these files",
)
parser.add_argument(
"-ME",
"--module-exclude",
type=str,
nargs="+",
metavar="FILE",
default=_profiler_config.exclude_modules,
help="Filter out any entries from these files",
)
parser.add_argument(
"-MR",
"--module-restrict",
type=str,
nargs="+",
metavar="FILE",
default=_profiler_config.restrict_modules,
help="Select only entries from these files",
)
parser.add_argument(
@@ -282,7 +321,7 @@ def main():
if os.path.isfile(argv[0]):
argv[0] = os.path.realpath(argv[0])
initialize(argv[0])
initialize(argv)
from .libpyomnitrace.profiler import config as _profiler_config
@@ -291,10 +330,12 @@ def main():
_profiler_config.include_line = opts.include_line
_profiler_config.include_filename = opts.include_file
_profiler_config.full_filepath = opts.full_filepath
_profiler_config.skip_functions = opts.skip_funcs
_profiler_config.skip_filenames = opts.skip_files
_profiler_config.only_functions = opts.only_funcs
_profiler_config.only_filenames = opts.only_files
_profiler_config.include_functions = opts.function_include
_profiler_config.include_modules = opts.module_include
_profiler_config.exclude_functions = opts.function_exclude
_profiler_config.exclude_modules = opts.module_exclude
_profiler_config.restrict_functions = opts.function_restrict
_profiler_config.restrict_modules = opts.module_restrict
_profiler_config.verbosity = opts.verbosity
print("[omnitrace]> profiling: {}".format(argv))
+71 -1
Просмотреть файл
@@ -30,6 +30,7 @@ __version__ = "@PROJECT_VERSION@"
__maintainer__ = "AMD Research"
__status__ = "Development"
import os
import sys
import threading
from functools import wraps
@@ -80,6 +81,68 @@ config = _profiler_config
Config = _profiler_config
def _file(back=2, only_basename=True, use_dirname=False, noquotes=True):
"""
Returns the file name
"""
from os.path import basename, dirname
def get_fcode(back):
fname = "<module>"
try:
fname = sys._getframe(back).f_code.co_filename
except Exception as e:
print(e)
fname = "<module>"
return fname
result = None
if only_basename is True:
if use_dirname is True:
result = "{}".format(
join(
basename(dirname(get_fcode(back))),
basename(get_fcode(back)),
)
)
else:
result = "{}".format(basename(get_fcode(back)))
else:
result = "{}".format(get_fcode(back))
if noquotes is False:
result = "'{}'".format(result)
return result
def _get_argv(init_file, argv=None):
if argv is None:
argv = sys.argv[:]
if "--" in argv:
_idx = argv.index("--")
argv = sys.argv[(_idx + 1) :]
if len(argv) > 1:
if argv[0] == "-m":
argv = argv[1:]
elif argv[0] == "-c":
argv[0] = os.path.basename(sys.executable)
else:
while len(argv) > 1 and argv[0].startswith("-"):
argv = argv[1:]
if os.path.exists(argv[0]):
break
if len(argv) == 0:
argv = [init_file]
elif not os.path.exists(argv[0]):
argv[0] = init_file
return argv
#
class Profiler:
"""Provides decorators and context-manager for the omnitrace profilers"""
@@ -118,8 +181,11 @@ class Profiler:
)
self._unset = 0
self._use = (
not _profiler_config._is_running and Profiler.is_enabled() is True
not _profiler_config._is_running
and Profiler.is_enabled() is True
and not libpyomnitrace.is_finalized()
)
self._file = _file()
self.debug = kwargs["debug"] if "debug" in kwargs else False
# ---------------------------------------------------------------------------------- #
@@ -135,6 +201,9 @@ class Profiler:
def configure(self):
"""Initialize, configure the bundle, store original profiler function"""
if not libpyomnitrace.is_initialized():
libpyomnitrace.initialize(_get_argv(self._file))
_profiler_init()
# store original
@@ -157,6 +226,7 @@ class Profiler:
not _profiler_config._is_running
and Profiler.is_enabled() is True
and sys.getprofile() == self._original_function
and not libpyomnitrace.is_finalized()
)
# ---------------------------------------------------------------------------------- #