diff --git a/setup.py b/setup.py index 58f216d..07e76c1 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ 'Score-P not build with "--enable-shared". Link mode is:\n{}'.format(link_mode) ) -check_compiler = scorep.helper.get_scorep_config("C99 compiler used:") +check_compiler = scorep.helper.get_scorep_config("C99 compiler") if "gcc" in check_compiler: gcc_plugin = scorep.helper.get_scorep_config("GCC plug-in support:") if not ("yes" in gcc_plugin): diff --git a/src/scorepy/cInstrumenter.cpp b/src/scorepy/cInstrumenter.cpp index 5dd0ba7..ec7db5f 100644 --- a/src/scorepy/cInstrumenter.cpp +++ b/src/scorepy/cInstrumenter.cpp @@ -28,9 +28,8 @@ void CInstrumenter::deinit() void CInstrumenter::enable_instrumenter() { - const auto callback = [](PyObject* obj, PyFrameObject* frame, int what, PyObject* arg) -> int { - return from_PyObject(obj)->on_event(*frame, what, arg) ? 0 : -1; - }; + const auto callback = [](PyObject* obj, PyFrameObject* frame, int what, PyObject* arg) -> int + { return from_PyObject(obj)->on_event(*frame, what, arg) ? 0 : -1; }; if (threading_set_instrumenter) { PyRefObject result(PyObject_CallFunction(threading_set_instrumenter, "O", to_PyObject()), @@ -115,16 +114,55 @@ bool CInstrumenter::on_event(PyFrameObject& frame, int what, PyObject*) case PyTrace_CALL: { PyCodeObject* code = frame.f_code; - bool success = try_region_begin(code); - if (!success) + if (frame.f_back == nullptr) { - std::string_view name = compat::get_string_as_utf_8(code->co_name); - std::string_view module_name = get_module_name(frame); - if (name.compare("_unsetprofile") != 0 && module_name.compare(0, 6, "scorep") != 0) + bool success = try_region_begin(code); + if (!success) { - const int line_number = code->co_firstlineno; - const std::string file_name = get_file_name(frame); - region_begin(name, module_name, file_name, line_number, code); + std::string_view name = compat::get_string_as_utf_8(code->co_name); + std::string_view module_name = get_module_name(frame); + if (name.compare("_unsetprofile") != 0 && module_name.compare(0, 6, "scorep") != 0) + { + const int line_number = code->co_firstlineno; + const std::string file_name = get_file_name(frame); + region_begin(name, module_name, file_name, line_number, code); + } + } + } + else + { + PyCodeObject* callsite_code = frame.f_back->f_code; + bool success = try_region_begin_with_callsite(code, callsite_code, + PyFrame_GetLineNumber(frame.f_back)); + if (!success) + { + std::string_view name = compat::get_string_as_utf_8(code->co_name); + std::string_view module_name = get_module_name(frame); + if (name.compare("_unsetprofile") != 0 && module_name.compare(0, 6, "scorep") != 0) + { + const int line_number = code->co_firstlineno; + const std::string file_name = get_file_name(frame); + + std::string_view callsite_name = + compat::get_string_as_utf_8(callsite_code->co_name); + std::string_view callsite_module_name = get_module_name(*frame.f_back); + if (callsite_name.compare("_unsetprofile") == 0 || + callsite_module_name.compare(0, 6, "scorep") == 0 || + (callsite_module_name.compare("threading") == 0 && + (callsite_name.compare("_bootstrap_inner") == 0 || + callsite_name.compare("_bootstrap")))) // there needs to be a better way + { + callsite_code = nullptr; // dont save that handle + } + + const int callsite_line_number_start = code->co_firstlineno; + const std::string callsite_file_name = get_file_name(frame); + + region_begin_with_callsite(name, module_name, file_name, line_number, code, + callsite_code, callsite_name, callsite_module_name, + callsite_file_name, callsite_line_number_start, + PyFrame_GetLineNumber(frame.f_back)); + } } } break; @@ -132,6 +170,7 @@ bool CInstrumenter::on_event(PyFrameObject& frame, int what, PyObject*) case PyTrace_RETURN: { PyCodeObject* code = frame.f_code; + bool success = try_region_end(code); if (!success) { diff --git a/src/scorepy/events.cpp b/src/scorepy/events.cpp index 7a25405..dede701 100644 --- a/src/scorepy/events.cpp +++ b/src/scorepy/events.cpp @@ -36,7 +36,7 @@ void region_begin(std::string_view& function_name, std::string_view& module, if (region == uninitialised_region_handle) { - auto& region_name = make_region_name(module, function_name); + auto region_name = make_region_name(module, function_name); SCOREP_User_RegionInit(®ion.value, NULL, NULL, region_name.c_str(), SCOREP_USER_REGION_TYPE_FUNCTION, file_name.c_str(), line_number); @@ -63,6 +63,46 @@ void region_begin(std::string_view& function_name, std::string_view& module, SCOREP_User_RegionEnter(region.value); } +void region_begin_with_callsite( + std::string_view& function_name, std::string_view& module, const std::string& file_name, + const std::uint64_t line_number, compat::PyCodeObject* identifier, + compat::PyCodeObject* callsite_identifier, std::string_view& callsite_function_name, + std::string_view& callsite_module, const std::string& callsite_file_name, + const std::uint64_t callsite_line_number_start, uint32_t callsite_line) +{ + region_handle& region = regions[identifier]; + + if (region == uninitialised_region_handle) + { + auto region_name = make_region_name(module, function_name); + SCOREP_User_RegionInit(®ion.value, NULL, NULL, region_name.c_str(), + SCOREP_USER_REGION_TYPE_FUNCTION, file_name.c_str(), line_number); + + SCOREP_User_RegionSetGroup(region.value, std::string(module, 0, module.find('.')).c_str()); + } + + if (callsite_identifier == nullptr) + { + SCOREP_User_RegionEnter(region.value); + return; + } + + region_handle& callsite_region = regions[callsite_identifier]; + + if (callsite_region == uninitialised_region_handle) + { + auto region_name = make_region_name(callsite_module, callsite_function_name); + SCOREP_User_RegionInit(&callsite_region.value, NULL, NULL, region_name.c_str(), + SCOREP_USER_REGION_TYPE_FUNCTION, callsite_file_name.c_str(), + callsite_line_number_start); + + SCOREP_User_RegionSetGroup( + callsite_region.value, + std::string(callsite_module, 0, callsite_module.find('.')).c_str()); + } + SCOREP_User_RegionEnterWithCallsite(region.value, callsite_region.value, callsite_line); +} + // Used for regions, that have an identifier, aka a code object id. (instrumenter regions and // some decorated regions) void region_end(std::string_view& function_name, std::string_view& module, diff --git a/src/scorepy/events.hpp b/src/scorepy/events.hpp index b3e56ea..df0dd5f 100644 --- a/src/scorepy/events.hpp +++ b/src/scorepy/events.hpp @@ -30,10 +30,9 @@ struct region_handle constexpr region_handle uninitialised_region_handle = region_handle(); /// 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(std::string_view& module_name, std::string_view& name) +inline std::string make_region_name(std::string_view& module_name, std::string_view& name) { - static std::string region; + std::string region; region = module_name; region += ":"; region += name; @@ -59,12 +58,40 @@ inline bool try_region_begin(compat::PyCodeObject* identifier) } } +/** tries to enter a region. Return true on success + * + */ +inline bool try_region_begin_with_callsite(compat::PyCodeObject* identifier, + compat::PyCodeObject* callsite_identifier, + uint32_t callsite_line) +{ + auto it = regions.find(identifier); + if (it != regions.end()) + { + auto callsite_it = regions.find(callsite_identifier); + if (callsite_it != regions.end()) + { + SCOREP_User_RegionEnterWithCallsite(it->second.value, callsite_it->second.value, + callsite_line); + return true; + } + } + return false; +} + void region_begin(std::string_view& function_name, std::string_view& module, const std::string& file_name, const std::uint64_t line_number, compat::PyCodeObject* identifier); void region_begin(std::string_view& function_name, std::string_view& module, const std::string& file_name, const std::uint64_t line_number); +void region_begin_with_callsite( + std::string_view& function_name, std::string_view& module, const std::string& file_name, + const std::uint64_t line_number, compat::PyCodeObject* identifier, + compat::PyCodeObject* callsite_identifier, std::string_view& callsite_function_name, + std::string_view& callsite_module, const std::string& callsite_file_name, + const std::uint64_t callsite_line_number_start, uint32_t callsite_line); + /** tries to end a region. Return true on success * */ diff --git a/test/cases/callsite.py b/test/cases/callsite.py new file mode 100644 index 0000000..da98b0e --- /dev/null +++ b/test/cases/callsite.py @@ -0,0 +1,9 @@ + +def bar(): + print("bar") + +def foo(): + print("foo") + bar() + +foo() diff --git a/test/test_scorep.py b/test/test_scorep.py index d9249a5..782b892 100755 --- a/test/test_scorep.py +++ b/test/test_scorep.py @@ -76,6 +76,7 @@ def requires_package(name): ] foreach_instrumenter = pytest.mark.parametrize("instrumenter", ALL_INSTRUMENTERS) +foreach_cinstrumenter = pytest.mark.parametrize("instrumenter", ALL_INSTRUMENTERS[2:3]) @pytest.fixture @@ -552,3 +553,31 @@ def test_io(scorep_env, instrumenter): ) print(regex_str) assert re.search(regex_str, io_trace) + + +@foreach_cinstrumenter +def test_callsite(scorep_env, instrumenter): + trace_path = get_trace_path(scorep_env) + + # Also test when no instrumenter is given + instrumenter_type = ["--instrumenter-type=" + instrumenter] + std_out, std_err = call_with_scorep( + "cases/callsite.py", ["--nocompiler"] + instrumenter_type, env=scorep_env + ) + + assert std_err == "" + assert std_out == "foo\nbar\n" + + std_out, std_err = call(["otf2-print", trace_path]) + + assert std_err == "" + region_callsite = [("__main__:foo", "__main__:", 9), + ("__main__:bar", "__main__:foo", 7)] + + for region, callsite, callsite_line in region_callsite: + assert re.search( + 'ENTER[ ]*[0-9 ]*[0-9 ]*Region: "%s" \\<[0-9]*\\>\n' + '[ ]*ADDITIONAL ATTRIBUTES: \\("CALLSITE_REGION" \\<[0-9]*\\>; REGION; "%s" \\<[0-9]*\\>\\)\\, ' + '\\("CALLSITE_LINE" \\<[0-9]*\\>; UINT32; %s\\)' % ( + region, callsite, callsite_line), std_out + )