diff --git a/projects/rocprofiler-systems/scripts/build-release.sh b/projects/rocprofiler-systems/scripts/build-release.sh index ad507a128c..83de6f748c 100755 --- a/projects/rocprofiler-systems/scripts/build-release.sh +++ b/projects/rocprofiler-systems/scripts/build-release.sh @@ -22,9 +22,9 @@ if [ -z "${DISTRO}" ]; then fi CMAKE_ARGS="-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=OFF -DCPACK_GENERATOR=STGZ" -OMNITRACE_GENERAL_ARGS="-DOMNITRACE_CPACK_SYSTEM_NAME=${DISTRO} -DOMNITRACE_ROCM_VERSION=${ROCM_VERSION} -DOMNITRACE_MAX_THREADS=2048 " -OMNITRACE_BUILD_ARGS="-DOMNITRACE_BUILD_TESTING=OFF -DOMNITRACE_BUILD_EXAMPLES=OFF -DOMNITRACE_BUILD_PAPI=ON -DOMNITRACE_BUILD_LTO=${LTO}" -OMNITRACE_USE_ARGS="-DOMNITRACE_USE_MPI_HEADERS=ON -DOMNITRACE_USE_OMPT=ON -DOMNITRACE_USE_PAPI=OFF" +OMNITRACE_GENERAL_ARGS="-DOMNITRACE_CPACK_SYSTEM_NAME=${DISTRO} -DOMNITRACE_ROCM_VERSION=${ROCM_VERSION} -DOMNITRACE_MAX_THREADS=2048 -DOMNITRACE_STRIP_LIBRARIES=OFF" +OMNITRACE_BUILD_ARGS="-DOMNITRACE_BUILD_TESTING=OFF -DOMNITRACE_BUILD_EXAMPLES=OFF -DOMNITRACE_BUILD_PAPI=ON -DOMNITRACE_BUILD_LTO=${LTO} -DOMNITRACE_BUILD_HIDDEN_VISIBILITY=OFF" +OMNITRACE_USE_ARGS="-DOMNITRACE_USE_MPI_HEADERS=ON -DOMNITRACE_USE_OMPT=ON -DOMNITRACE_USE_PAPI=ON" TIMEMORY_ARGS="-DTIMEMORY_USE_LIBUNWIND=ON -DTIMEMORY_BUILD_LIBUNWIND=ON -DTIMEMORY_BUILD_PORTABLE=ON" DYNINST_ARGS="${STANDARD_ARGS} -DOMNITRACE_BUILD_DYNINST=ON $(echo -DDYNINST_BUILD_{TBB,BOOST,ELFUTILS,LIBIBERTY}=ON)" STANDARD_ARGS="${CMAKE_ARGS} ${OMNITRACE_GENERAL_ARGS} ${OMNITRACE_USE_ARGS} ${OMNITRACE_BUILD_ARGS} ${TIMEMORY_ARGS} ${DYNINST_ARGS} ${EXTRA_ARGS}" diff --git a/projects/rocprofiler-systems/source/bin/omnitrace/function_signature.cpp b/projects/rocprofiler-systems/source/bin/omnitrace/function_signature.cpp index d29dde8886..ab43349a51 100644 --- a/projects/rocprofiler-systems/source/bin/omnitrace/function_signature.cpp +++ b/projects/rocprofiler-systems/source/bin/omnitrace/function_signature.cpp @@ -90,7 +90,7 @@ function_signature::get(bool _all, bool _save) const else if(!m_info_end && !_rc1.empty()) ss << " [" << _rc1 << "]"; else - errprintf(1, "loop line info is empty!"); + errprintf(3, "line info for %s is empty!\n", m_name.c_str()); } if((_all || use_file_info) && m_file.length() > 0) ss << " [" << m_file; if((_all || use_line_info) && m_row.first > 0) ss << ":" << m_row.first; @@ -130,7 +130,7 @@ function_signature::get_coverage(bool _basic_block) const else if(!m_info_end && !_rc1.empty()) ss << " [" << _rc1 << "]"; else - errprintf(1, "loop line info is empty!"); + errprintf(3, "line info for %s is empty!\n", m_name.c_str()); } else { diff --git a/projects/rocprofiler-systems/source/bin/omnitrace/module_function.cpp b/projects/rocprofiler-systems/source/bin/omnitrace/module_function.cpp index 6b9e47a75c..0b8cdcff80 100644 --- a/projects/rocprofiler-systems/source/bin/omnitrace/module_function.cpp +++ b/projects/rocprofiler-systems/source/bin/omnitrace/module_function.cpp @@ -121,7 +121,36 @@ module_function::should_instrument() const bool module_function::should_coverage_instrument() const { - return should_instrument(true); + // hard constraints + if(!is_instrumentable()) return false; + if(!can_instrument_entry()) return false; + if(is_module_constrained()) return false; + if(is_routine_constrained()) return false; + + // should be before user selection + constexpr int absolute_min_instructions = 2; + if(num_instructions < absolute_min_instructions) + { + messages.emplace_back( + 2, "Skipping", "function", + TIMEMORY_JOIN("-", "less-than", absolute_min_instructions, "instructions")); + return false; + } + + // user selection + if(is_user_excluded()) return false; + + if(is_overlapping_constrained()) return false; + if(is_entry_trap_constrained()) return false; + + // user selection + if(!file_restrict.empty() || !func_restrict.empty()) return !is_user_restricted(); + if(is_user_included()) return true; + + if(is_address_range_constrained()) return false; + if(is_num_instructions_constrained()) return false; + + return true; } bool @@ -150,7 +179,7 @@ module_function::should_instrument(bool coverage) const // should be applied before dynamic-callsite check if(is_overlapping_constrained()) return false; if(is_entry_trap_constrained()) return false; - if(is_exit_trap_constrained()) return false; + if(!coverage && is_exit_trap_constrained()) return false; // needs to be applied before address range and number of instruction constraints if(is_dynamic_callsite_forced()) return true; @@ -737,7 +766,6 @@ module_function::register_coverage(address_space_t* _addr_space, "no-constraint"); } } - verbprintf(0, "Basic-block code coverage is not available yet\n"); break; } case CODECOV_NONE: break; diff --git a/projects/rocprofiler-systems/source/lib/omnitrace/library/coverage.cpp b/projects/rocprofiler-systems/source/lib/omnitrace/library/coverage.cpp index c0317c7099..d68c3557ee 100644 --- a/projects/rocprofiler-systems/source/lib/omnitrace/library/coverage.cpp +++ b/projects/rocprofiler-systems/source/lib/omnitrace/library/coverage.cpp @@ -24,6 +24,7 @@ #include "library/api.hpp" #include "library/config.hpp" #include "library/debug.hpp" +#include "library/impl/coverage.hpp" #include "library/thread_data.hpp" #include @@ -53,33 +54,11 @@ using uomap_t = std::unordered_map; using coverage_thread_data_type = uomap_t>>; // -template -inline std::set -get_uncovered(const std::set& _covered, - const std::set& _possible) -{ - std::set _v{}; - for(auto&& itr : _possible) - { - if(_covered.count(itr) == 0) _v.emplace(itr); - } - return _v; -} +using coverage_data_vector = std::vector; // -template -inline std::vector -get_uncovered(const std::vector& _covered, - const std::vector& _possible) -{ - std::vector _v{}; - for(auto&& itr : _possible) - { - if(!std::any_of(_covered.begin(), _covered.end(), - [itr](auto&& _entry) { return _entry == itr; })) - _v.emplace_back(itr); - } - return _v; -} +using coverage_data_map = + uomap_t>>; // using coverage_thread_data = omnitrace::thread_data; @@ -92,9 +71,16 @@ get_code_coverage() } // auto& +get_post_processed() +{ + static auto* _v = new bool{ false }; + return *_v; +} +// +auto& get_coverage_data() { - static auto _v = std::vector{}; + static auto _v = coverage_data_vector{}; return _v; } // @@ -109,109 +95,19 @@ get_coverage_count(int64_t _tid = tim::threading::get_id()) //--------------------------------------------------------------------------------------// -double -code_coverage::operator()(Category _c) const -{ - switch(_c) - { - case STANDARD: return static_cast(count) / static_cast(size); - case ADDRESS: - return static_cast(covered.addresses.size()) / - static_cast(possible.addresses.size()); - case MODULE: - return static_cast(covered.modules.size()) / - static_cast(possible.modules.size()); - case FUNCTION: - return static_cast(covered.functions.size()) / - static_cast(possible.functions.size()); - } - return 0.0; -} - -code_coverage::int_set_t -code_coverage::get_uncovered_addresses() const -{ - return get_uncovered(covered.addresses, possible.addresses); -} - -code_coverage::str_set_t -code_coverage::get_uncovered_modules() const -{ - return get_uncovered(covered.modules, possible.modules); -} - -code_coverage::str_set_t -code_coverage::get_uncovered_functions() const -{ - return get_uncovered(covered.functions, possible.functions); -} - -//--------------------------------------------------------------------------------------// - -coverage_data& -coverage_data::operator+=(const coverage_data& rhs) -{ - count += rhs.count; - return *this; -} - -bool -coverage_data::operator==(const coverage_data& rhs) const -{ - return std::tie(module, function, address) == - std::tie(rhs.module, rhs.function, rhs.address); -} - -bool -coverage_data::operator==(const data_tuple_t& rhs) const -{ - return std::tie(module, function, address) == - std::tie(std::get<0>(rhs), std::get<1>(rhs), std::get<2>(rhs)); -} - -bool -coverage_data::operator!=(const coverage_data& rhs) const -{ - return !(*this == rhs); -} - -bool -coverage_data::operator<(const coverage_data& rhs) const -{ - if(count != rhs.count) return count < rhs.count; - if(module != rhs.module) return module < rhs.module; - if(function != rhs.function) return function < rhs.function; - if(address != rhs.address) return address < rhs.address; - if(line != rhs.line) return line < rhs.line; - return source < rhs.source; -} - -bool -coverage_data::operator<=(const coverage_data& rhs) const -{ - return (*this == rhs || *this < rhs); -} - -bool -coverage_data::operator>(const coverage_data& rhs) const -{ - return (*this != rhs && !(*this < rhs)); -} - -bool -coverage_data::operator>=(const coverage_data& rhs) const -{ - return !(*this < rhs); -} - -//--------------------------------------------------------------------------------------// - void post_process() { + using data_tuple_t = coverage_data::data_tuple_t; + + if(get_post_processed()) return; + get_post_processed() = true; + if(!config::get_use_code_coverage()) return; - code_coverage& _coverage = get_code_coverage(); + auto& _coverage = get_code_coverage(); + auto& _coverage_data = get_coverage_data(); + if(_coverage.size == 0) { OMNITRACE_VERBOSE_F( @@ -220,39 +116,48 @@ post_process() return; } - using data_tuple_t = coverage_data::data_tuple_t; - auto& _coverage_data = get_coverage_data(); - - auto _find = [&_coverage_data](data_tuple_t&& _v) { - for(auto itr = _coverage_data.begin(); itr != _coverage_data.end(); ++itr) - { - if(*itr == _v) return std::make_pair(itr, true); - } - return std::make_pair(_coverage_data.end(), false); - }; - auto _data = coverage_thread_data_type{}; - for(size_t i = 0; i < coverage_thread_data::size(); ++i) { - const auto& _thr_data = *get_coverage_count(i); - for(const auto& file : _thr_data) - { - for(const auto& func : file.second) + auto _coverage_map = coverage_data_map{}; + auto _find = [&_coverage_data, &_coverage_map](data_tuple_t&& _v) { + auto& _cache = _coverage_map[std::get<0>(_v)][std::get<1>(_v)]; + auto mitr = _cache.find(std::get<2>(_v)); + if(mitr != _cache.end()) return std::make_pair(mitr->second, true); + + for(auto itr = _coverage_data.begin(); itr != _coverage_data.end(); ++itr) { - for(const auto& addr : func.second) + if(*itr == _v) { - _data[file.first][func.first][addr.first] += addr.second; - auto&& _v = _find({ file.first, func.first, addr.first }); - if(_v.second) + _cache[std::get<2>(_v)] = itr; + return std::make_pair(itr, true); + } + } + return std::make_pair(_coverage_data.end(), false); + }; + + for(size_t i = 0; i < coverage_thread_data::size(); ++i) + { + const auto& _thr_data = *get_coverage_count(i); + for(const auto& file : _thr_data) + { + for(const auto& func : file.second) + { + for(const auto& addr : func.second) { - _v.first->count += addr.second; - } - else - { - OMNITRACE_VERBOSE_F( - 0, "Warning! No matching coverage data for %s :: %s (0x%x)\n", - func.first.data(), file.first.data(), - (unsigned int) addr.first); + _data[file.first][func.first][addr.first] += addr.second; + auto&& _v = _find({ file.first, func.first, addr.first }); + if(_v.second) + { + _v.first->count += addr.second; + } + else + { + OMNITRACE_VERBOSE_F(0, + "Warning! No matching coverage data for " + "%s :: %s (0x%x)\n", + func.first.data(), file.first.data(), + (unsigned int) addr.first); + } } } } @@ -280,13 +185,21 @@ post_process() std::greater{}); { + auto _tmp_map = coverage_data_map{}; auto _tmp = std::decay_t{}; - auto _find_in_tmp = [&_tmp](const auto& _v) { + auto _find_in_tmp = [&_tmp, &_tmp_map](const auto& _v) { + auto& _cache = _tmp_map[_v.module][_v.function]; + auto mitr = _cache.find(_v.address); + if(mitr != _cache.end()) return std::make_pair(mitr->second, true); + for(auto itr = _tmp.begin(); itr != _tmp.end(); ++itr) { if(itr->source == _v.source && itr->address != _v.address && itr->count == _v.count) + { + _cache[_v.address] = itr; return std::make_pair(itr, true); + } } return std::make_pair(_tmp.end(), false); }; @@ -350,6 +263,21 @@ post_process() if(_json_output) { + std::stringstream oss{}; + { + namespace cereal = tim::cereal; + auto ar = + tim::policy::output_archive::get(oss); + + ar->setNextName("omnitrace"); + ar->startNode(); + ar->setNextName("coverage"); + ar->startNode(); + (*ar)(cereal::make_nvp("summary", _coverage)); + (*ar)(cereal::make_nvp("details", _coverage_data)); + ar->finishNode(); + ar->finishNode(); + } auto _fname = tim::settings::compose_output_filename("coverage", ".json"); std::ofstream ofs{}; if(tim::filepath::open(ofs, _fname)) @@ -357,21 +285,7 @@ post_process() if(get_verbose() >= 0) fprintf(stderr, "[%s][coverage]|%i> Outputting '%s'...\n", TIMEMORY_PROJECT_NAME, dmp::rank(), _fname.c_str()); - { - namespace cereal = tim::cereal; - auto ar = - tim::policy::output_archive::get( - ofs); - - ar->setNextName("omnitrace"); - ar->startNode(); - ar->setNextName("coverage"); - ar->startNode(); - (*ar)(cereal::make_nvp("summary", _coverage)); - (*ar)(cereal::make_nvp("details", _coverage_data)); - ar->finishNode(); - ar->finishNode(); - } + ofs << oss.str() << "\n"; } else { @@ -392,9 +306,11 @@ extern "C" void omnitrace_register_source_hidden(const char* file, const char* func, size_t line, size_t address, const char* source) { + if(coverage::get_post_processed()) return; + using coverage_data = coverage::coverage_data; - OMNITRACE_BASIC_VERBOSE_F(2, "[0x%x] :: %-20s :: %20s:%zu :: %s\n", + OMNITRACE_BASIC_VERBOSE_F(4, "[0x%x] :: %-20s :: %20s:%zu :: %s\n", (unsigned int) address, func, file, line, source); coverage::get_coverage_data().emplace_back( @@ -408,7 +324,9 @@ omnitrace_register_source_hidden(const char* file, const char* func, size_t line // initialize for(size_t i = 0; i < coverage::coverage_thread_data::size(); ++i) - (*coverage::get_coverage_count(i))[file][func][address] = 0; + { + (*coverage::get_coverage_count(i))[file][func].emplace(address, 0); + } } //--------------------------------------------------------------------------------------// @@ -416,9 +334,16 @@ omnitrace_register_source_hidden(const char* file, const char* func, size_t line extern "C" void omnitrace_register_coverage_hidden(const char* file, const char* func, size_t address) { + if(coverage::get_post_processed()) return; + if(omnitrace::get_state() < omnitrace::State::Active && + !omnitrace_init_tooling_hidden()) + return; + else if(omnitrace::get_state() == omnitrace::State::Finalized) + return; + OMNITRACE_BASIC_VERBOSE_F(3, "[0x%x] %-20s :: %20s\n", (unsigned int) address, func, file); - coverage::get_coverage_count()->at(file).at(func).at(address) += 1; + (*coverage::get_coverage_count())[file][func][address] += 1; } //--------------------------------------------------------------------------------------// diff --git a/projects/rocprofiler-systems/source/lib/omnitrace/library/coverage.hpp b/projects/rocprofiler-systems/source/lib/omnitrace/library/coverage.hpp index 217db52742..9dcfc31e9d 100644 --- a/projects/rocprofiler-systems/source/lib/omnitrace/library/coverage.hpp +++ b/projects/rocprofiler-systems/source/lib/omnitrace/library/coverage.hpp @@ -22,9 +22,9 @@ #pragma once -#include "timemory/mpl/concepts.hpp" -#include "timemory/tpls/cereal/cereal/cereal.hpp" +#include #include +#include #include #include @@ -39,8 +39,10 @@ namespace omnitrace { namespace coverage { +#if !defined(OMNITRACE_PYBIND11_SOURCE) || OMNITRACE_PYBIND11_SOURCE == 0 void post_process(); +#endif //--------------------------------------------------------------------------------------// // @@ -68,6 +70,9 @@ struct code_coverage str_set_t modules = {}; str_set_t functions = {}; + data& operator+=(const data& rhs); + data operator+(const data& rhs) const; + template void serialize(ArchiveT& ar, const unsigned version); }; @@ -134,6 +139,7 @@ struct coverage_data void serialize(ArchiveT& ar, const unsigned version); coverage_data& operator+=(const coverage_data& rhs); + coverage_data operator+(const coverage_data& rhs) const; bool operator==(const coverage_data& rhs) const; bool operator==(const data_tuple_t& rhs) const; bool operator!=(const coverage_data& rhs) const; diff --git a/projects/rocprofiler-systems/source/lib/omnitrace/library/impl/coverage.hpp b/projects/rocprofiler-systems/source/lib/omnitrace/library/impl/coverage.hpp new file mode 100644 index 0000000000..24ac324e57 --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/omnitrace/library/impl/coverage.hpp @@ -0,0 +1,194 @@ +// MIT License +// +// Copyright (c) 2022 Advanced Micro Devices, Inc. All Rights Reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +// IMPORTANT NOTE: +// this may be a header file but will lead to ODR violations if included +// more than once. The reason it is in a header file is so that the python +// bindings can also build these symbols. The absence of inline is intentional! + +#include "library/coverage.hpp" + +#include +#include +#include +#include +#include +#include + +namespace omnitrace +{ +namespace coverage +{ +namespace +{ +template +inline std::set +get_uncovered(const std::set& _covered, + const std::set& _possible) +{ + std::set _v{}; + for(auto&& itr : _possible) + { + if(_covered.count(itr) == 0) _v.emplace(itr); + } + return _v; +} +// +template +inline std::vector +get_uncovered(const std::vector& _covered, + const std::vector& _possible) +{ + std::vector _v{}; + for(auto&& itr : _possible) + { + if(!std::any_of(_covered.begin(), _covered.end(), + [itr](auto&& _entry) { return _entry == itr; })) + _v.emplace_back(itr); + } + return _v; +} +} // namespace + +code_coverage::data& +code_coverage::data::operator+=(const data& rhs) +{ + for(auto&& itr : rhs.addresses) + addresses.emplace(itr); + for(auto&& itr : rhs.modules) + modules.emplace(itr); + for(auto&& itr : rhs.modules) + modules.emplace(itr); + return *this; +} + +code_coverage::data +code_coverage::data::operator+(const data& rhs) const +{ + return data{ *this } += rhs; +} + +double +code_coverage::operator()(Category _c) const +{ + switch(_c) + { + case STANDARD: return static_cast(count) / static_cast(size); + case ADDRESS: + return static_cast(covered.addresses.size()) / + static_cast(possible.addresses.size()); + case MODULE: + return static_cast(covered.modules.size()) / + static_cast(possible.modules.size()); + case FUNCTION: + return static_cast(covered.functions.size()) / + static_cast(possible.functions.size()); + } + return 0.0; +} + +code_coverage::int_set_t +code_coverage::get_uncovered_addresses() const +{ + return get_uncovered(covered.addresses, possible.addresses); +} + +code_coverage::str_set_t +code_coverage::get_uncovered_modules() const +{ + return get_uncovered(covered.modules, possible.modules); +} + +code_coverage::str_set_t +code_coverage::get_uncovered_functions() const +{ + return get_uncovered(covered.functions, possible.functions); +} + +//--------------------------------------------------------------------------------------// + +coverage_data& +coverage_data::operator+=(const coverage_data& rhs) +{ + count += rhs.count; + return *this; +} + +coverage_data +coverage_data::operator+(const coverage_data& rhs) const +{ + return coverage_data{ *this } += rhs; +} + +bool +coverage_data::operator==(const coverage_data& rhs) const +{ + return std::tie(module, function, address) == + std::tie(rhs.module, rhs.function, rhs.address); +} + +bool +coverage_data::operator==(const data_tuple_t& rhs) const +{ + return std::tie(module, function, address) == + std::tie(std::get<0>(rhs), std::get<1>(rhs), std::get<2>(rhs)); +} + +bool +coverage_data::operator!=(const coverage_data& rhs) const +{ + return !(*this == rhs); +} + +bool +coverage_data::operator<(const coverage_data& rhs) const +{ + if(count != rhs.count) return count < rhs.count; + if(module != rhs.module) return module < rhs.module; + if(function != rhs.function) return function < rhs.function; + if(address != rhs.address) return address < rhs.address; + if(line != rhs.line) return line < rhs.line; + return source < rhs.source; +} + +bool +coverage_data::operator<=(const coverage_data& rhs) const +{ + return (*this == rhs || *this < rhs); +} + +bool +coverage_data::operator>(const coverage_data& rhs) const +{ + return (*this != rhs && !(*this < rhs)); +} + +bool +coverage_data::operator>=(const coverage_data& rhs) const +{ + return !(*this < rhs); +} +// +} // namespace coverage +} // namespace omnitrace diff --git a/projects/rocprofiler-systems/source/python/CMakeLists.txt b/projects/rocprofiler-systems/source/python/CMakeLists.txt index f7f9afdd1c..047896d600 100644 --- a/projects/rocprofiler-systems/source/python/CMakeLists.txt +++ b/projects/rocprofiler-systems/source/python/CMakeLists.txt @@ -16,6 +16,7 @@ function(OMNITRACE_CONFIGURE_PYTARGET _TARGET _VERSION) add_library(omnitrace::${_TARGET} ALIAS ${_TARGET}) target_link_libraries(${_TARGET} PRIVATE libpyomnitrace-interface) + add_dependencies(libpyomnitrace ${_TARGET}) set_target_properties( ${_TARGET} @@ -86,7 +87,8 @@ target_link_libraries( omnitrace::omnitrace-python omnitrace::omnitrace-python-compile-options) -target_compile_definitions(libpyomnitrace-interface INTERFACE OMNITRACE_PYBIND11_SOURCE) +omnitrace_target_compile_definitions(libpyomnitrace-interface + INTERFACE OMNITRACE_PYBIND11_SOURCE) include(PyBind11Tools) @@ -160,6 +162,8 @@ omnitrace_watch_for_change(OMNITRACE_PYTHON_VERSIONS) omnitrace_check_python_dirs_and_versions(FAIL) +add_custom_target(libpyomnitrace) + file(GLOB_RECURSE PYTHON_FILES ${CMAKE_CURRENT_SOURCE_DIR}/omnitrace/*.py) foreach(_IN ${PYTHON_FILES}) string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/omnitrace" diff --git a/projects/rocprofiler-systems/source/python/libpyomnitrace.cpp b/projects/rocprofiler-systems/source/python/libpyomnitrace.cpp index 4095e0e0bf..a22ccb0431 100644 --- a/projects/rocprofiler-systems/source/python/libpyomnitrace.cpp +++ b/projects/rocprofiler-systems/source/python/libpyomnitrace.cpp @@ -22,16 +22,22 @@ #include "libpyomnitrace.hpp" #include "dl.hpp" +#include "library/coverage.hpp" +#include "library/impl/coverage.hpp" #include #include #include #include +#include +#include +#include #include #include #include #include +#include #include #include @@ -51,8 +57,16 @@ namespace pyprofile py::module generate(py::module& _pymod); } +namespace pycoverage +{ +py::module +generate(py::module& _pymod); +} } // namespace pyomnitrace +template +using uomap_t = std::unordered_map; + PYBIND11_MODULE(libpyomnitrace, omni) { using namespace pyomnitrace; @@ -144,6 +158,8 @@ PYBIND11_MODULE(libpyomnitrace, omni) py::doc("omnitrace profiler for python"); pyprofile::generate(omni); + + pycoverage::generate(omni); } //======================================================================================// @@ -555,6 +571,259 @@ generate(py::module& _pymod) return _prof; } } // namespace pyprofile + +namespace pycoverage +{ +py::module +generate(py::module& _pymod) +{ + namespace coverage = omnitrace::coverage; + using coverage_data_vector_t = std::vector; + + py::module _pycov = _pymod.def_submodule("coverage", "Code coverage"); + +#define DEFINE_PROPERTY(PYCLASS, CLASS, TYPE, NAME, ...) \ + PYCLASS.def_property( \ + #NAME, [](CLASS* _object) { return _object->NAME; }, \ + [](CLASS* _object, TYPE v) { return _object->NAME = v; }, __VA_ARGS__) + + py::class_ _pycov_summary{ _pymod, "summary", + "Code coverage summary" }; + + _pycov_summary.def(py::init([]() { return new coverage::code_coverage{}; }), + "Create a default instance"); + + _pycov_summary.def( + "get_code_coverage", + [](coverage::code_coverage* _v) { + return _v->get(coverage::code_coverage::STANDARD); + }, + "Get coverage fraction"); + _pycov_summary.def( + "get_module_coverage", + [](coverage::code_coverage* _v) { + return _v->get(coverage::code_coverage::MODULE); + }, + "Get coverage fraction"); + _pycov_summary.def( + "get_function_coverage", + [](coverage::code_coverage* _v) { + return _v->get(coverage::code_coverage::FUNCTION); + }, + "Get coverage fraction"); + _pycov_summary.def("get_uncovered_modules", + &coverage::code_coverage::get_uncovered_modules, + "List of uncovered modules"); + _pycov_summary.def("get_uncovered_functions", + &coverage::code_coverage::get_uncovered_functions, + "List of uncovered functions"); + + DEFINE_PROPERTY(_pycov_summary, coverage::code_coverage, size_t, count, + "Number of times covered"); + DEFINE_PROPERTY(_pycov_summary, coverage::code_coverage, size_t, size, + "Total number of coverage entries"); + DEFINE_PROPERTY(_pycov_summary, coverage::code_coverage, + coverage::code_coverage::data, covered, "Covered information"); + DEFINE_PROPERTY(_pycov_summary, coverage::code_coverage, + coverage::code_coverage::data, possible, "Possible information"); + + py::class_ _pycov_summary_data{ _pycov_summary, "data", + "Code coverage data" }; + + DEFINE_PROPERTY(_pycov_summary_data, coverage::code_coverage::data, std::set, + addresses, "Addresses"); + DEFINE_PROPERTY(_pycov_summary_data, coverage::code_coverage::data, + std::set, modules, "Modules"); + DEFINE_PROPERTY(_pycov_summary_data, coverage::code_coverage::data, + std::set, functions, "Functions"); + + py::class_ _pycov_details{ _pycov, "details", + "Code coverage data" }; + + _pycov_details.def(py::init([]() { return new coverage::coverage_data{}; }), + "Create a default instance"); + + DEFINE_PROPERTY(_pycov_details, coverage::coverage_data, size_t, count, + "Number of times invoked"); + DEFINE_PROPERTY(_pycov_details, coverage::coverage_data, size_t, address, + "Address of coverage entity (in binary)"); + DEFINE_PROPERTY(_pycov_details, coverage::coverage_data, size_t, line, + "Line number of coverage entity"); + DEFINE_PROPERTY(_pycov_details, coverage::coverage_data, std::string, module, + "Name of the containing module"); + DEFINE_PROPERTY(_pycov_details, coverage::coverage_data, std::string, function, + "Name of the function (basic)"); + DEFINE_PROPERTY(_pycov_details, coverage::coverage_data, std::string, source, + "Full signature of the function"); + + _pycov_details.def(py::self + py::self); + _pycov_details.def(py::self += py::self); + _pycov_details.def(py::self == py::self); + _pycov_details.def(py::self != py::self); + _pycov_details.def(py::self < py::self); + _pycov_details.def(py::self > py::self); + _pycov_details.def(py::self <= py::self); + _pycov_details.def(py::self >= py::self); + + auto _load_coverage = [](const std::string& _inp) { + coverage::code_coverage* _summary = nullptr; + coverage_data_vector_t* _details = nullptr; + std::ifstream ifs{ _inp }; + if(ifs) + { + namespace cereal = tim::cereal; + auto ar = tim::policy::input_archive::get(ifs); + + try + { + ar->setNextName("omnitrace"); + ar->startNode(); + ar->setNextName("coverage"); + ar->startNode(); + _summary = new coverage::code_coverage{}; + _details = new coverage_data_vector_t{}; + (*ar)(cereal::make_nvp("summary", *_summary)); + (*ar)(cereal::make_nvp("details", *_details)); + ar->finishNode(); + ar->finishNode(); + } catch(std::exception&) + {} + } + return std::make_tuple(_summary, _details); + }; + + auto _save_coverage = [](coverage::code_coverage* _summary, + coverage_data_vector_t* _details, std::string _name) { + std::stringstream oss{}; + { + namespace cereal = tim::cereal; + auto ar = + tim::policy::output_archive::get(oss); + + ar->setNextName("omnitrace"); + ar->startNode(); + ar->setNextName("coverage"); + ar->startNode(); + (*ar)(cereal::make_nvp("summary", *_summary)); + (*ar)(cereal::make_nvp("details", *_details)); + ar->finishNode(); + ar->finishNode(); + } + _name = TIMEMORY_JOIN( + '.', std::regex_replace(_name, std::regex{ "(.*)(\\.json$)" }, "$1"), "json"); + std::ofstream ofs{}; + if(tim::filepath::open(ofs, _name)) + { + fprintf(stderr, "[%s][coverage]> Outputting '%s'...\n", TIMEMORY_PROJECT_NAME, + _name.c_str()); + ofs << oss.str() << "\n"; + } + else + { + throw std::runtime_error( + TIMEMORY_JOIN("", "Error opening coverage output file: ", _name)); + } + }; + + _pycov.def("load", _load_coverage, "Load code coverage data"); + _pycov.def("save", _save_coverage, "Save code coverage data", py::arg("summary"), + py::arg("details"), py::arg("filename") = "coverage.json"); + + auto _concat_coverage = [](coverage_data_vector_t* _lhs, + coverage_data_vector_t* _rhs) { + std::sort(_rhs->begin(), _rhs->end(), std::greater{}); + + auto _find = [_lhs](const auto& _v) { + for(auto itr = _lhs->begin(); itr != _lhs->end(); ++itr) + { + if(*itr == _v) return std::make_pair(itr, true); + } + return std::make_pair(_lhs->end(), false); + }; + + std::vector _new_entries{}; + _new_entries.reserve(_rhs->size()); + for(auto& itr : *_rhs) + { + auto litr = _find(itr); + if(!litr.second) + _new_entries.emplace_back(&itr); + else + *litr.first += itr; + } + + _lhs->reserve(_lhs->size() + _new_entries.size()); + for(auto& itr : _new_entries) + _lhs->emplace_back(std::move(*itr)); + _rhs->clear(); + + std::sort(_lhs->begin(), _lhs->end(), std::greater{}); + return _lhs; + }; + + _pycov.def("concat", _concat_coverage, "Combined code coverage details"); + + using coverage_data_map = + uomap_t>>; + + auto _coverage_summary = [](coverage_data_vector_t* _data) { + coverage::code_coverage _summary{}; + coverage_data_map _mdata{}; + + for(auto& itr : *_data) + _mdata[itr.module][itr.function][itr.address] += itr.count; + + for(const auto& file : _mdata) + { + for(const auto& func : file.second) + { + for(const auto& addr : func.second) + { + if(addr.second > 0) + { + _summary.count += 1; + _summary.covered.modules.emplace(file.first); + _summary.covered.functions.emplace(func.first); + _summary.covered.addresses.emplace(addr.first); + } + _summary.size += 1; + _summary.possible.modules.emplace(file.first); + _summary.possible.functions.emplace(func.first); + _summary.possible.addresses.emplace(addr.first); + } + } + } + + return _summary; + }; + + _pycov.def("get_summary", _coverage_summary, "Generate a code coverage summary"); + + auto _get_top = [](coverage_data_vector_t* _data, size_t _n) { + auto _ret = *_data; + std::sort(_ret.begin(), _ret.end(), std::greater{}); + _ret.resize(std::min(_n, _ret.size())); + _ret.shrink_to_fit(); + return _ret; + }; + + _pycov.def("get_top", _get_top, "Get the top covered functions", py::arg("details"), + py::arg("n") = 10); + + auto _get_bottom = [](coverage_data_vector_t* _data, size_t _n) { + auto _ret = *_data; + std::sort(_ret.begin(), _ret.end(), std::less{}); + _ret.resize(std::min(_n, _ret.size())); + _ret.shrink_to_fit(); + return _ret; + }; + + _pycov.def("get_bottom", _get_bottom, "Get the bottom covered functions", + py::arg("details"), py::arg("n") = 10); + + return _pycov; +} +} // namespace pycoverage } // namespace pyomnitrace // //======================================================================================// diff --git a/projects/rocprofiler-systems/source/python/omnitrace/__init__.py b/projects/rocprofiler-systems/source/python/omnitrace/__init__.py index e1684702c5..203f98b81c 100644 --- a/projects/rocprofiler-systems/source/python/omnitrace/__init__.py +++ b/projects/rocprofiler-systems/source/python/omnitrace/__init__.py @@ -35,6 +35,7 @@ This submodule imports the timemory Python function profiler """ try: + from .libpyomnitrace import coverage from .profiler import Profiler, FakeProfiler from .libpyomnitrace.profiler import ( profiler_function, @@ -65,6 +66,7 @@ try: "config", "profile", "noprofile", + "coverage", ] import atexit