diff --git a/README.md b/README.md index 476074d..9628352 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,7 @@ Please be aware the `--user` is always passed to Score-P, as this is needed for ## Not Working * python multiprocessing * Score-P does currently only support MPI or SHMEM. Any other multiprocessing approach cannot be traced. +* tracking `importlib.reload()` # Acknowledgments The European Union initially supported this work as part of the European Union’s Horizon 2020 project READEX (grant agreement number 671657). diff --git a/scorep/_instrumenters/base_instrumenter.py b/scorep/_instrumenters/base_instrumenter.py index 86db0b7..7e03d76 100644 --- a/scorep/_instrumenters/base_instrumenter.py +++ b/scorep/_instrumenters/base_instrumenter.py @@ -30,11 +30,11 @@ def run(self, cmd, globals=None, locals=None): pass @abc.abstractmethod - def region_begin(self, module_name, function_name, file_name, line_number): + def region_begin(self, module_name, function_name, file_name, line_number, code_object): pass @abc.abstractmethod - def region_end(self, module_name, function_name): + def region_end(self, module_name, function_name, code_object): pass @abc.abstractmethod diff --git a/scorep/_instrumenters/dummy.py b/scorep/_instrumenters/dummy.py index 7a64844..e3fbe85 100644 --- a/scorep/_instrumenters/dummy.py +++ b/scorep/_instrumenters/dummy.py @@ -23,10 +23,10 @@ def run(self, cmd, globals=None, locals=None): locals = {} exec(cmd, globals, locals) - def region_begin(self, module_name, function_name, file_name, line_number): + def region_begin(self, module_name, function_name, file_name, line_number, code_object=None): pass - def region_end(self, module_name, function_name): + def region_end(self, module_name, function_name, code_object=None): pass def rewind_begin(self, name, file_name=None, line_number=None): diff --git a/scorep/_instrumenters/scorep_instrumenter.py b/scorep/_instrumenters/scorep_instrumenter.py index 062194b..75384e7 100644 --- a/scorep/_instrumenters/scorep_instrumenter.py +++ b/scorep/_instrumenters/scorep_instrumenter.py @@ -56,14 +56,14 @@ def run(self, cmd, globals=None, locals=None): finally: self.unregister() - def region_begin(self, module_name, function_name, file_name, line_number): + def region_begin(self, module_name, function_name, file_name, line_number, code_object=None): """Record a region begin event""" scorep._bindings.region_begin( - module_name, function_name, file_name, line_number) + module_name, function_name, file_name, line_number, code_object) - def region_end(self, module_name, function_name): + def region_end(self, module_name, function_name, code_object=None): """Record a region end event""" - scorep._bindings.region_end(module_name, function_name) + scorep._bindings.region_end(module_name, function_name, code_object) def rewind_begin(self, name, file_name=None, line_number=None): """ diff --git a/scorep/_instrumenters/scorep_profile.py b/scorep/_instrumenters/scorep_profile.py index 1a656b0..479a23f 100644 --- a/scorep/_instrumenters/scorep_profile.py +++ b/scorep/_instrumenters/scorep_profile.py @@ -39,12 +39,13 @@ def _globaltrace(self, frame, why, arg): if why == 'call': code = frame.f_code modulename = get_module_name(frame) + if not code.co_name == "_unsetprofile" and not modulename[:6] == "scorep": full_file_name = get_file_name(frame) line_number = code.co_firstlineno - scorep._bindings.region_begin(modulename, code.co_name, full_file_name, line_number) + scorep._bindings.region_begin(modulename, code.co_name, full_file_name, line_number, code) elif why == 'return': code = frame.f_code modulename = get_module_name(frame) if not code.co_name == "_unsetprofile" and not modulename[:6] == "scorep": - scorep._bindings.region_end(modulename, code.co_name) + scorep._bindings.region_end(modulename, code.co_name, code) diff --git a/scorep/_instrumenters/scorep_trace.py b/scorep/_instrumenters/scorep_trace.py index 18cb629..bcbef66 100644 --- a/scorep/_instrumenters/scorep_trace.py +++ b/scorep/_instrumenters/scorep_trace.py @@ -40,7 +40,7 @@ def _globaltrace(self, frame, why, arg): if not code.co_name == "_unsettrace" and not modulename[:6] == "scorep": full_file_name = get_file_name(frame) line_number = code.co_firstlineno - scorep._bindings.region_begin(modulename, code.co_name, full_file_name, line_number) + scorep._bindings.region_begin(modulename, code.co_name, full_file_name, line_number, code) return self._localtrace return None @@ -48,5 +48,5 @@ def _localtrace(self, frame, why, arg): if why == 'return': code = frame.f_code modulename = get_module_name(frame) - scorep._bindings.region_end(modulename, code.co_name) + scorep._bindings.region_end(modulename, code.co_name, code) return self._localtrace diff --git a/scorep/user.py b/scorep/user.py index 1e016cb..ea2457e 100644 --- a/scorep/user.py +++ b/scorep/user.py @@ -74,6 +74,7 @@ def __enter__(self): initally_registered = scorep.instrumenter.get_instrumenter().get_registered() with scorep.instrumenter.disable(): if(self.user_region_name): + # The user did specify a region name, so its a user_region self.module_name = "user" frame = inspect.currentframe().f_back file_name = frame.f_globals.get('__file__', None) @@ -85,46 +86,53 @@ def __enter__(self): scorep.instrumenter.get_instrumenter().region_begin( self.module_name, self.region_name, full_file_name, line_number) - elif(callable(self.func)): - """ - looks like the decorator is invoked - """ - if not initally_registered: - self.region_name = self.func.__name__ - self.module_name = self.func.__module__ - file_name = self.func.__code__.co_filename - line_number = self.func.__code__.co_firstlineno - - if file_name is not None: - full_file_name = os.path.abspath(file_name) - else: - full_file_name = "None" - - scorep.instrumenter.get_instrumenter().region_begin( - self.module_name, self.region_name, full_file_name, line_number) + elif callable(self.func) and not initally_registered: + # The user did not specify a region name, and it's a callable, so it's a semi instrumented region + self.region_name = self.func.__name__ + self.module_name = self.func.__module__ + self.code_obj = self.func.__code__ + file_name = self.func.__code__.co_filename + line_number = self.func.__code__.co_firstlineno + + if file_name is not None: + full_file_name = os.path.abspath(file_name) else: - """ - do not need to decorate a function, when we are registerd. It is instrumented any way. - """ - pass + full_file_name = "None" + + scorep.instrumenter.get_instrumenter().region_begin( + self.module_name, self.region_name, full_file_name, line_number, self.code_obj) + elif callable(self.func) and initally_registered: + # The user did not specify a region name, and it's a callable, so it's a + # semi instrumented region. However, the instrumenter is active, so there + # is nothing to do. + pass else: - raise RuntimeError("a region name needs to be specified") + # The user did not specify a region name, and it's not a callable. So it + # is a context region without a region name. Throw an error. + raise RuntimeError("A region name needs to be specified.") return self def __exit__(self, exc_type, exc_value, traceback): - if (callable(self.func) - and scorep.instrumenter.get_instrumenter().get_registered() - and not self.user_region_name): - """ - looks like there is a decorator, we are registered and the name is not specified by the user, - so we do not need to do anything. The Instrumentation will take care. - """ - return False - else: + initally_registered = scorep.instrumenter.get_instrumenter().get_registered() + if self.user_region_name: + # The user did specify a region name, so its a user_region scorep.instrumenter.get_instrumenter().region_end( self.module_name, self.region_name) - return False + elif callable(self.func) and not initally_registered: + # The user did not specify a region name, and it's a callable, so it's a semi instrumented region + scorep.instrumenter.get_instrumenter().region_end( + self.module_name, self.region_name, self.code_obj) + elif callable(self.func) and initally_registered: + # The user did not specify a region name, and it's a callable, so it's a + # semi instrumented region. However, the instrumenter is active, so there + # is nothing to do. + pass + else: + # The user did not specify a region name, and it's not a callable. So it + # is a context region without a region name. Throw an error. + raise RuntimeError("Something wen't wrong. Please do a Bug Report.") + return False def rewind_begin(name, file_name=None, line_number=None): diff --git a/src/methods.cpp b/src/methods.cpp index a06069f..68ed2e3 100644 --- a/src/methods.cpp +++ b/src/methods.cpp @@ -4,6 +4,8 @@ #include #include +#include + extern "C" { @@ -28,15 +30,26 @@ extern "C" static PyObject* region_begin(PyObject* self, PyObject* args) { const char* module; - const char* region_name; + const char* function_name; const char* file_name; + PyObject* identifier = nullptr; std::uint64_t line_number = 0; - if (!PyArg_ParseTuple(args, "sssK", &module, ®ion_name, &file_name, &line_number)) + if (!PyArg_ParseTuple(args, "sssKO", &module, &function_name, &file_name, &line_number, + &identifier)) + { return NULL; - - const std::string& region = scorepy::make_region_name(module, region_name); - scorepy::region_begin(region, module, file_name, line_number); + } + + if (identifier == nullptr or identifier == Py_None) + { + scorepy::region_begin(function_name, module, file_name, line_number); + } + else + { + scorepy::region_begin(function_name, module, file_name, line_number, + reinterpret_cast(identifier)); + } Py_RETURN_NONE; } @@ -47,13 +60,23 @@ extern "C" static PyObject* region_end(PyObject* self, PyObject* args) { const char* module; - const char* region_name; + const char* function_name; + PyObject* identifier = nullptr; - if (!PyArg_ParseTuple(args, "ss", &module, ®ion_name)) + if (!PyArg_ParseTuple(args, "ssO", &module, &function_name, &identifier)) + { return NULL; - - const std::string& region = scorepy::make_region_name(module, region_name); - scorepy::region_end(region); + } + + if (identifier == nullptr or identifier == Py_None) + { + scorepy::region_end(function_name, module); + } + else + { + scorepy::region_end(function_name, module, + reinterpret_cast(identifier)); + } Py_RETURN_NONE; } diff --git a/src/scorepy/cInstrumenter.cpp b/src/scorepy/cInstrumenter.cpp index 42719f0..d250566 100644 --- a/src/scorepy/cInstrumenter.cpp +++ b/src/scorepy/cInstrumenter.cpp @@ -3,6 +3,7 @@ #include "pythonHelpers.hpp" #include #include +#include #include namespace scorepy @@ -122,9 +123,9 @@ bool CInstrumenter::on_event(PyFrameObject& frame, int what, PyObject*) if (std::string(name) != "_unsetprofile" && std::string(module_name, 0, 6) != "scorep") { const int line_number = code.co_firstlineno; - const auto& region_name = make_region_name(module_name, name); const auto file_name = get_file_name(frame); - region_begin(region_name, module_name, file_name, line_number); + region_begin(name, module_name, file_name, line_number, + reinterpret_cast(&code)); } break; } @@ -138,8 +139,7 @@ bool CInstrumenter::on_event(PyFrameObject& frame, int what, PyObject*) // TODO: Use string_view/CString comparison? if (std::string(name) != "_unsetprofile" && std::string(module_name, 0, 6) != "scorep") { - const auto& region_name = make_region_name(module_name, name); - region_end(region_name); + region_end(name, module_name, reinterpret_cast(&code)); } break; } diff --git a/src/scorepy/events.cpp b/src/scorepy/events.cpp index f3b1a5e..5b2e378 100644 --- a/src/scorepy/events.cpp +++ b/src/scorepy/events.cpp @@ -23,73 +23,120 @@ struct region_handle constexpr region_handle uninitialised_region_handle = region_handle(); -static std::unordered_map regions; +static std::unordered_map regions; +static std::unordered_map user_regions; static std::unordered_map rewind_regions; -void region_begin(const std::string& region_name, std::string module, std::string file_name, - std::uint64_t line_number) +/// Region names that are known to have no region enter event and should not report an error +/// on region exit +static const std::array EXIT_REGION_WHITELIST = { +#if PY_MAJOR_VERSION >= 3 + "threading:_bootstrap_inner", "threading:_bootstrap" +#else + "threading:__bootstrap_inner", "threading:__bootstrap" +#endif +}; + +// Used for regions, that have an identifier, aka a code object id. (instrumenter regions and +// some decorated regions) +void region_begin(const std::string& function_name, const std::string& module, + const std::string& file_name, const std::uint64_t line_number, + const std::uintptr_t& identifier) { - auto& region_handle = regions[region_name]; + auto& region_handle = regions[identifier]; if (region_handle == uninitialised_region_handle) { - SCOREP_User_RegionInit(®ion_handle.value, NULL, &SCOREP_User_LastFileHandle, - region_name.c_str(), SCOREP_USER_REGION_TYPE_FUNCTION, - file_name.c_str(), line_number); + auto& region_name = make_region_name(module, function_name); + SCOREP_User_RegionInit(®ion_handle.value, NULL, NULL, region_name.c_str(), + SCOREP_USER_REGION_TYPE_FUNCTION, file_name.c_str(), line_number); + SCOREP_User_RegionSetGroup(region_handle.value, std::string(module, 0, module.find('.')).c_str()); } SCOREP_User_RegionEnter(region_handle.value); } -/// Region names that are known to have no region enter event and should not report an error -/// on region exit -static const std::array EXIT_REGION_WHITELIST = { -#if PY_MAJOR_VERSION >= 3 - "threading:_bootstrap_inner", "threading:_bootstrap" -#else - "threading:__bootstrap_inner", "threading:__bootstrap" -#endif -}; +// Used for regions, that only have a function name, a module, a file and a line number (user +// regions) +void region_begin(const std::string& function_name, const std::string& module, + const std::string& file_name, const std::uint64_t line_number) +{ + const auto& region_name = make_region_name(module, function_name); + auto& region_handle = user_regions[region_name]; + + if (region_handle == uninitialised_region_handle) + { + SCOREP_User_RegionInit(®ion_handle.value, NULL, NULL, region_name.c_str(), + SCOREP_USER_REGION_TYPE_FUNCTION, file_name.c_str(), line_number); -void region_end(const std::string& region_name) + SCOREP_User_RegionSetGroup(region_handle.value, + std::string(module, 0, module.find('.')).c_str()); + } + SCOREP_User_RegionEnter(region_handle.value); +} + +// Used for regions, that have an identifier, aka a code object id. (instrumenter regions and +// some decorated regions) +void region_end(const std::string& function_name, const std::string& module, + const std::uintptr_t& identifier) { - const auto itRegion = regions.find(region_name); - if (itRegion != regions.end()) + const auto it_region = regions.find(identifier); + if (it_region != regions.end()) { - SCOREP_User_RegionEnd(itRegion->second.value); + SCOREP_User_RegionEnd(it_region->second.value); } else { - static region_handle error_region; - static SCOREP_User_ParameterHandle scorep_param = SCOREP_USER_INVALID_PARAMETER; - static bool error_printed = false; - - if (std::find(EXIT_REGION_WHITELIST.begin(), EXIT_REGION_WHITELIST.end(), region_name) != - EXIT_REGION_WHITELIST.end()) - { - return; - } - - if (error_region.value == SCOREP_USER_INVALID_REGION) - { - SCOREP_User_RegionInit(&error_region.value, NULL, &SCOREP_User_LastFileHandle, - "error_region", SCOREP_USER_REGION_TYPE_FUNCTION, "scorep.cpp", - 0); - SCOREP_User_RegionSetGroup(error_region.value, "error"); - } - SCOREP_User_RegionEnter(error_region.value); - SCOREP_User_ParameterString(&scorep_param, "leave-region", region_name.c_str()); - SCOREP_User_RegionEnd(error_region.value); - - if (!error_printed) - { - std::cerr << "SCOREP_BINDING_PYTHON ERROR: There was a region exit without an enter!\n" - << "SCOREP_BINDING_PYTHON ERROR: For details look for \"error_region\" in " - "the trace or profile." - << std::endl; - error_printed = true; - } + auto& region_name = make_region_name(module, function_name); + region_end_error_handling(region_name); + } +} + +// Used for regions, that only have a function name, a module (user regions) +void region_end(const std::string& function_name, const std::string& module) +{ + auto& region_name = make_region_name(module, function_name); + const auto it_region = user_regions.find(region_name); + if (it_region != user_regions.end()) + { + SCOREP_User_RegionEnd(it_region->second.value); + } + else + { + region_end_error_handling(region_name); + } +} + +void region_end_error_handling(const std::string& region_name) +{ + static region_handle error_region; + static SCOREP_User_ParameterHandle scorep_param = SCOREP_USER_INVALID_PARAMETER; + static bool error_printed = false; + + if (std::find(EXIT_REGION_WHITELIST.begin(), EXIT_REGION_WHITELIST.end(), region_name) != + EXIT_REGION_WHITELIST.end()) + { + return; + } + + if (error_region.value == SCOREP_USER_INVALID_REGION) + { + SCOREP_User_RegionInit(&error_region.value, NULL, NULL, "error_region", + SCOREP_USER_REGION_TYPE_FUNCTION, "scorep.cpp", 0); + SCOREP_User_RegionSetGroup(error_region.value, "error"); + } + SCOREP_User_RegionEnter(error_region.value); + SCOREP_User_ParameterString(&scorep_param, "leave-region", region_name.c_str()); + SCOREP_User_RegionEnd(error_region.value); + + if (!error_printed) + { + std::cerr << "SCOREP_BINDING_PYTHON ERROR: There was a region exit without an enter!\n" + << "SCOREP_BINDING_PYTHON ERROR: For details look for \"error_region\" in " + "the trace or profile." + << std::endl; + error_printed = true; } } @@ -100,9 +147,8 @@ void rewind_begin(std::string region_name, std::string file_name, std::uint64_t auto& handle = pair.first->second; if (inserted_new) { - SCOREP_User_RegionInit(&handle.value, NULL, &SCOREP_User_LastFileHandle, - region_name.c_str(), SCOREP_USER_REGION_TYPE_FUNCTION, - file_name.c_str(), line_number); + SCOREP_User_RegionInit(&handle.value, NULL, NULL, region_name.c_str(), + SCOREP_USER_REGION_TYPE_FUNCTION, file_name.c_str(), line_number); } SCOREP_User_RewindRegionEnter(handle.value); } @@ -136,15 +182,14 @@ void parameter_string(std::string name, std::string value) void oa_region_begin(std::string region_name, std::string file_name, std::uint64_t line_number) { - auto& handle = regions[region_name]; - SCOREP_User_OaPhaseBegin(&handle.value, &SCOREP_User_LastFileName, &SCOREP_User_LastFileHandle, - region_name.c_str(), SCOREP_USER_REGION_TYPE_FUNCTION, - file_name.c_str(), line_number); + auto& handle = user_regions[region_name]; + SCOREP_User_OaPhaseBegin(&handle.value, NULL, NULL, region_name.c_str(), + SCOREP_USER_REGION_TYPE_FUNCTION, file_name.c_str(), line_number); } void oa_region_end(std::string region_name) { - auto& handle = regions[region_name]; + auto& handle = user_regions[region_name]; SCOREP_User_OaPhaseEnd(handle.value); } diff --git a/src/scorepy/events.hpp b/src/scorepy/events.hpp index 486ec3e..43400ca 100644 --- a/src/scorepy/events.hpp +++ b/src/scorepy/events.hpp @@ -7,7 +7,7 @@ namespace scorepy { /// Combine the arguments into a region name /// Return value is a statically allocated string to avoid memory (re)allocations -inline const std::string& make_region_name(const char* module_name, const char* name) +inline const std::string& make_region_name(const std::string& module_name, const std::string& name) { static std::string region; region = module_name; @@ -16,9 +16,17 @@ inline const std::string& make_region_name(const char* module_name, const char* return region; } -void region_begin(const std::string& region_name, std::string module, std::string file_name, - std::uint64_t line_number); -void region_end(const std::string& region_name); +void region_begin(const std::string& function_name, const std::string& module, + const std::string& file_name, const std::uint64_t line_number, + const std::uintptr_t& identifier); +void region_begin(const std::string& function_name, const std::string& module, + const std::string& file_name, const std::uint64_t line_number); + +void region_end(const std::string& function_name, const std::string& module, + const std::uintptr_t& identifier); +void region_end(const std::string& function_name, const std::string& module); + +void region_end_error_handling(const std::string& region_name); void rewind_begin(std::string region_name, std::string file_name, std::uint64_t line_number); void rewind_end(std::string region_name, bool value); diff --git a/test/cases/classes.py b/test/cases/classes.py new file mode 100644 index 0000000..8789e0e --- /dev/null +++ b/test/cases/classes.py @@ -0,0 +1,37 @@ +import scorep.user +import scorep.instrumenter + + +class TestClass: + def foo(self): + print("foo") + + def doo(arg): + print("doo") + arg.foo() + + +class TestClass2: + @scorep.user.region() + def foo(self): + print("foo-2") + + +def foo(): + print("bar") + + def doo(arg): + print("asdgh") + doo("test") + + +if __name__ == "__main__": + t = TestClass() + t2 = TestClass2() + + t2.foo() + t.doo() + foo() + + with scorep.instrumenter.disable(): + t2.foo() diff --git a/test/cases/classes2.py b/test/cases/classes2.py new file mode 100644 index 0000000..1c5ddbc --- /dev/null +++ b/test/cases/classes2.py @@ -0,0 +1,4 @@ +import classes + +t = classes.TestClass +t().doo() diff --git a/test/cases/decorator.py b/test/cases/decorator.py new file mode 100644 index 0000000..c991de6 --- /dev/null +++ b/test/cases/decorator.py @@ -0,0 +1,13 @@ +import scorep.user + + +@scorep.user.region() +def foo(): + print("hello world") + + +foo() +with scorep.instrumenter.disable(): + foo() + with scorep.instrumenter.enable(): + foo() diff --git a/test/cases/reload.py b/test/cases/reload.py new file mode 100644 index 0000000..36e4fc3 --- /dev/null +++ b/test/cases/reload.py @@ -0,0 +1,35 @@ +import reload_test +import importlib +import os + +data1 = """ +def foo(): + print("foo1") +""" + +data2 = """ +def foo(arg): + print(arg) +def bar(): + print("bar") +""" + + +with open("reload_test.py", "w") as f: + f.write(data1) + +reload_test.foo() +reload_test.foo() + +importlib.reload(reload_test) +reload_test.foo() + + +with open("reload_test.py", "w") as f: + f.write(data2) + +importlib.reload(reload_test) +reload_test.foo("foo2") +reload_test.bar() + +os.remove("reload_test.py") diff --git a/test/cases/user_regions.py b/test/cases/user_regions.py index 6994dfe..c3a3358 100644 --- a/test/cases/user_regions.py +++ b/test/cases/user_regions.py @@ -1,4 +1,5 @@ import scorep.user +import scorep.instrumenter def foo(): @@ -24,5 +25,8 @@ def foo4(): foo() foo2() -foo3() +with scorep.instrumenter.enable(): + foo3() +with scorep.instrumenter.disable(): + foo3() foo4() diff --git a/test/test_scorep.py b/test/test_scorep.py index a6ae672..87b4514 100755 --- a/test/test_scorep.py +++ b/test/test_scorep.py @@ -92,7 +92,7 @@ def test_user_regions(scorep_env, instrumenter): env=scorep_env) assert std_err == "" - assert std_out == "hello world\nhello world\nhello world3\nhello world4\n" + assert std_out == "hello world\nhello world\nhello world3\nhello world3\nhello world4\n" std_out, std_err = call(["otf2-print", trace_path]) @@ -100,8 +100,8 @@ def test_user_regions(scorep_env, instrumenter): assert re.search('LEAVE[ ]*[0-9 ]*[0-9 ]*Region: "user:test_region"', std_out) assert re.search('ENTER[ ]*[0-9 ]*[0-9 ]*Region: "user:test_region_2"', std_out) assert re.search('LEAVE[ ]*[0-9 ]*[0-9 ]*Region: "user:test_region_2"', std_out) - assert re.search('ENTER[ ]*[0-9 ]*[0-9 ]*Region: "__main__:foo3"', std_out) - assert re.search('LEAVE[ ]*[0-9 ]*[0-9 ]*Region: "__main__:foo3"', std_out) + assert len(re.findall('ENTER[ ]*[0-9 ]*[0-9 ]*Region: "__main__:foo3"', std_out)) == 2 + assert len(re.findall('LEAVE[ ]*[0-9 ]*[0-9 ]*Region: "__main__:foo3"', std_out)) == 2 assert re.search('ENTER[ ]*[0-9 ]*[0-9 ]*Region: "user:test_region_4"', std_out) assert re.search('LEAVE[ ]*[0-9 ]*[0-9 ]*Region: "user:test_region_4"', std_out) @@ -125,12 +125,28 @@ def test_context(scorep_env, instrumenter): assert re.search('LEAVE[ ]*[0-9 ]*[0-9 ]*Region: "__main__:foo"', std_out) +@foreach_instrumenter +def test_decorator(scorep_env, instrumenter): + trace_path = get_trace_path(scorep_env) + + std_out, std_err = call_with_scorep("cases/decorator.py", + ["--noinstrumenter", "--instrumenter-type=" + instrumenter], + env=scorep_env) + + assert std_err == "" + assert std_out == "hello world\nhello world\nhello world\n" + + std_out, std_err = call(["otf2-print", "-A", trace_path]) + + assert len(re.findall('REGION[ ]*[0-9 ]*Name: "__main__:foo"', std_out)) == 1 + + def test_user_regions_no_scorep(): std_out, std_err = call([sys.executable, "cases/user_regions.py"]) assert std_err == "" - assert std_out == "hello world\nhello world\nhello world3\nhello world4\n" + assert std_out == "hello world\nhello world\nhello world3\nhello world3\nhello world4\n" @foreach_instrumenter @@ -277,6 +293,45 @@ def test_call_main(scorep_env, instrumenter): assert std_out == expected_std_out +@foreach_instrumenter +def test_classes(scorep_env, instrumenter): + trace_path = get_trace_path(scorep_env) + std_out, std_err = call_with_scorep("cases/classes.py", + ["--nocompiler", "--instrumenter-type=" + instrumenter], + expected_returncode=0, + env=scorep_env) + + expected_std_err = "" + expected_std_out = "foo-2\ndoo\nfoo\nbar\nasdgh\nfoo-2\n" + + assert std_out == expected_std_out + assert std_err == expected_std_err + + std_out, std_err = call(["otf2-print", trace_path]) + + assert std_err == "" + + region_ids = [] + foo_count = 0 + for line in std_out.split("\n"): + m = re.search('ENTER[ ]*[0-9 ]*[0-9 ]*Region: "__main__:foo" <([0-9]*)>', line) + if m is not None: + foo_count += 1 + r_id = m.group(1) + + if foo_count == 1: + region_ids.append(r_id) + continue + + if foo_count < 4: + assert region_ids[-1] < r_id # check if foo regions are different + else: + assert r_id == region_ids[0] # check if last foo is fist foo + region_ids.append(r_id) + + assert len(region_ids) == 4 + + def test_dummy(scorep_env): std_out, std_err = call_with_scorep("cases/instrumentation.py", ["--instrumenter-type=dummy"], @@ -311,7 +366,7 @@ def test_threads(scorep_env, instrumenter): trace_path = get_trace_path(scorep_env) std_out, std_err = call_with_scorep("cases/use_threads.py", - ["--nocompiler", "--instrumenter-type=" + instrumenter], + ["--nocompiler", "--instrumenter-type=" + instrumenter], env=scorep_env) assert std_err == ""