diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f92994c7..3bff2012 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,6 @@ name: Build and Test on: - workflow_dispatch: push: branches: [ main ] pull_request: @@ -58,12 +57,12 @@ jobs: env: OMP_NUM_THREADS: 1 working-directory: ${{github.workspace}}/build - run: ctest -VV -C ${{env.BUILD_TYPE}} + run: ctest -C ${{env.BUILD_TYPE}} # Run tests on 4 threads - name: "Test (4 threads)" env: OMP_NUM_THREADS: 4 working-directory: ${{github.workspace}}/build - run: ctest -VV -C ${{env.BUILD_TYPE}} + run: ctest -C ${{env.BUILD_TYPE}} diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 836a708a..4d3ca3d8 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,7 +1,6 @@ name: Deploy Docs on: - workflow_dispatch: push: branches: [ main ] paths: diff --git a/CHANGELOG.md b/CHANGELOG.md index c80e042b..6a8d6619 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,8 +20,4 @@ 15/07/2022 PR #30 for #29: Unit testing for Fortran interface. \ 04/08/2022 PR #32 for #31: Functionality improvements (walltime, swap to unordered_map, sort entries in output. \ 10/08/2022 PR #41 for #37 Add null terminated strings in Fortran interface. \ -16/08/2022 PR #48 Add hashtable.h to installed header files. \ -30/08/2022 PR #51 for #34: Including the number of times a region is called. \ -09/09/2022 PR #39 for #35: Initial profiler unit tests. \ -08/09/2022 PR #55 for #33: Replace omp_get_wtime(). (Chrono) \ -13/09/2022 PR #60 for #59: Allow manual triggering of workflows. +16/08/2022 PR #48 Add hashtable.h to installed header files. diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake index 781f23ff..dcba7716 100644 --- a/cmake/CompilerWarnings.cmake +++ b/cmake/CompilerWarnings.cmake @@ -2,7 +2,7 @@ # different compilers and build settings. function(set_project_warnings project_name) # Create option for forcing errors for all warnings. - option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" OFF) + option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" ON) # Create option to turn on sanitizers. option(USE_SANITIZERS "Turn on sanitizers to help reporting of runtime errors" OFF) diff --git a/cmake/Doxygen.cmake b/cmake/Doxygen.cmake index 9f350a20..8e842043 100644 --- a/cmake/Doxygen.cmake +++ b/cmake/Doxygen.cmake @@ -21,8 +21,11 @@ function(enable_doxygen) set(DOXYGEN_JAVADOC_BLOCK NO) set(DOXYGEN_FULL_PATH_NAMES NO) set(DOXYGEN_STRIP_CODE_COMMENTS NO) - set(DOXYGEN_FILE_PATTERNS *.c *.cpp *.h *.f90 *.F90 *.md) + set(DOXYGEN_FILE_PATTERNS *.c *.cpp *.h *.f90 *.F90 *.md ) set(DOXYGEN_EXTENSION_MAPPING "F90=Fortran") + set(DOXYGEN_SORT_MEMBER_DOCS NO) + set(DOXYGEN_WARN_AS_ERROR NO) + set(DOXYGEN_LAYOUT_FILE ${PROJECT_SOURCE_DIR}/documentation/Doxygen/DoxygenLayout.xml) set(DOXYGEN_HTML_HEADER ${PROJECT_SOURCE_DIR}/documentation/Doxygen/html/header.html) set(DOXYGEN_HTML_FOOTER ${PROJECT_SOURCE_DIR}/documentation/Doxygen/html/footer.html) diff --git a/documentation/Doxygen/DoxygenLayout.xml b/documentation/Doxygen/DoxygenLayout.xml new file mode 100644 index 00000000..775b6d93 --- /dev/null +++ b/documentation/Doxygen/DoxygenLayout.xml @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/Doxygen/Profiler.md b/documentation/Doxygen/Profiler.md index 80c03010..922b3165 100644 --- a/documentation/Doxygen/Profiler.md +++ b/documentation/Doxygen/Profiler.md @@ -48,4 +48,4 @@ class A { ## Communication Efficiency {#comme} -TBD \ No newline at end of file +TBD diff --git a/src/c++/hashtable.cpp b/src/c++/hashtable.cpp index a9f8262e..bf710ae2 100644 --- a/src/c++/hashtable.cpp +++ b/src/c++/hashtable.cpp @@ -13,21 +13,20 @@ #include /** - * @brief Constructs a new entry in the hash table. + * @brief Constructs a new entry in the hash table. * */ HashEntry::HashEntry(std::string_view region_name) : region_name_(region_name) - , total_walltime_(time_duration_t::zero()) - , self_walltime_(time_duration_t::zero()) - , child_walltime_(time_duration_t::zero()) - , call_count_(0) + , total_walltime_(0.0) + , self_walltime_(0.0) + , child_walltime_(0.0) {} /** * @brief Hashtable constructor - * + * */ HashTable::HashTable(int const tid) @@ -56,7 +55,7 @@ size_t HashTable::query_insert(std::string_view region_name) noexcept * */ -void HashTable::update(size_t hash, time_duration_t time_delta) +void HashTable::update(size_t hash, double time_delta) { // Assertions assert (table_.size() > 0); @@ -66,8 +65,6 @@ void HashTable::update(size_t hash, time_duration_t time_delta) auto& entry = table_.at(hash); entry.total_walltime_ += time_delta; - // Update the number of times this region has been called - entry.call_count_++; } /** @@ -75,7 +72,7 @@ void HashTable::update(size_t hash, time_duration_t time_delta) * */ -void HashTable::add_child_time(size_t hash, time_duration_t time_delta) +void HashTable::add_child_time(size_t hash, double time_delta) { // Assertions assert (table_.size() > 0); @@ -103,32 +100,29 @@ void HashTable::write() std::cout << std::setw(40) << std::left << routine_at_thread << " " << std::setw(15) << std::right << "Self (s)" << " " - << std::setw(15) << std::right << "Total (s)" << " " - << std::setw(10) << std::right << "Calls" << "\n"; - + << std::setw(15) << std::right << "Total (s)" << "\n"; + std::cout << std::setfill('-'); std::cout << std::setw(40) << "-" << " " << std::setw(15) << "-" << " " - << std::setw(15) << "-" << " " - << std::setw(10) << "-" << "\n"; + << std::setw(15) << "-" << "\n"; std::cout << std::setfill(' '); // Create a vector from the hashtable and sort the entries according to self // walltime. If optimisation of this is needed, it ought to be possible to // acquire a vector of hash-selftime pairs in the correct order, then use the // hashes to look up other information directly from the hashtable. - hashvec = std::vector>(table_.cbegin(), table_.cend()); - std::sort(begin(hashvec), end(hashvec), + auto hashvec = std::vector>(begin(table_), end(table_)); + std::sort(begin(hashvec), end(hashvec), [](auto a, auto b) { return a.second.self_walltime_ > b.second.self_walltime_;}); - + // Data entries for (auto& [hash, entry] : hashvec) { - std::cout - << std::setw(40) << std::left << entry.region_name_ << " " - << std::setw(15) << std::right << entry.self_walltime_.count() << " " - << std::setw(15) << std::right << entry.total_walltime_.count() << " " - << std::setw(10) << std::right << entry.call_count_ << "\n"; + std::cout + << std::setw(40) << std::left << entry.region_name_ << " " + << std::setw(15) << std::right << entry.self_walltime_ << " " + << std::setw(15) << std::right << entry.total_walltime_ << "\n"; } } @@ -161,78 +155,12 @@ std::vector HashTable::list_keys() /** * @brief Get the total (inclusive) time corresponding to the input hash. - * - */ - -double HashTable::get_total_walltime(size_t const hash) const -{ - return table_.at(hash).total_walltime_.count(); -} - -/** - * @brief Get the profiler self time corresponding to the input hash. - * - */ - -double HashTable::get_self_walltime(size_t const hash) -{ - this->compute_self_times(); - return table_.at(hash).self_walltime_.count(); -} - -/** - * @brief Get the child time corresponding to the input hash. - * - */ - -double HashTable::get_child_walltime(size_t const hash) const -{ - return table_.at(hash).child_walltime_.count(); -} - -/** - * @brief Get the region name corresponding to the input hash. - * + * */ -std::string const& HashTable::get_region_name(size_t const hash) const +double HashTable::get_total_walltime(size_t const hash) { - return table_.at(hash).region_name_; + return table_.at(hash).total_walltime_; } - /** - * @brief Get the number of times the input hash region has been called. - * - * @param[in] hash The hash corresponding to the region of interest. - * - * @returns Returns an integer corresponding to the number of the times the - * region of interest has been called within the code being profiled. - * - */ - -unsigned long long int const& HashTable::get_region_call_count(size_t const hash) const -{ - return table_.at(hash).call_count_; -} -/** - * @brief Get the vector in profiler.write() which is used to sort entries in - * the hashtable from high to low self walltime. - * - */ - -std::vector> const& HashTable::get_hashvec() const -{ - return hashvec; -} - -/** - * @brief Get the actual std::unordered_map hashtable, "table_" wherein hashes - * and hash entries are stored. - * - */ - -std::unordered_map const& HashTable::get_hashtable() const -{ - return table_; -} diff --git a/src/c++/hashtable.h b/src/c++/hashtable.h index f57c979e..2000cc80 100644 --- a/src/c++/hashtable.h +++ b/src/c++/hashtable.h @@ -28,12 +28,6 @@ #include #include #include -#include - -// Type definitions for chrono steady clock time points and durations -using time_duration_t = std::chrono::duration; -using time_point_t = std::chrono::time_point; - /** * @brief Structure to hold information for a particular routine. @@ -50,11 +44,10 @@ struct HashEntry{ explicit HashEntry(std::string_view); // Data members - std::string region_name_; - time_duration_t total_walltime_; - time_duration_t self_walltime_; - time_duration_t child_walltime_; - unsigned long long int call_count_; + std::string region_name_; + double total_walltime_; + double self_walltime_; + double child_walltime_; }; @@ -67,14 +60,13 @@ struct HashEntry{ */ class HashTable{ - + private: // Members int tid_; std::unordered_map table_; std::hash hash_function_; - std::vector> hashvec; public: @@ -84,22 +76,14 @@ class HashTable{ // Prototypes size_t query_insert(std::string_view) noexcept; - void update(size_t, time_duration_t); + void update(size_t, double); void write(); // Member functions std::vector list_keys(); - void add_child_time(size_t, time_duration_t); + void add_child_time(size_t, double); void compute_self_times(); + double get_total_walltime(size_t const); - // Getters - double get_total_walltime(size_t const hash) const; - double get_self_walltime(size_t const hash); - double get_child_walltime(size_t const hash) const; - std::string const& get_region_name(size_t const hash) const; - unsigned long long int const& get_region_call_count(size_t const hash) const; - - std::vector> const& get_hashvec() const; - std::unordered_map const& get_hashtable() const; }; #endif diff --git a/src/c++/profiler.cpp b/src/c++/profiler.cpp index 9ea7f317..bbe2b432 100644 --- a/src/c++/profiler.cpp +++ b/src/c++/profiler.cpp @@ -9,7 +9,6 @@ #include #include -#include /** * @brief Constructor @@ -30,21 +29,16 @@ Profiler::Profiler(){ HashTable new_table(tid); thread_hashtables_.push_back(new_table); - std::vector> new_list; + std::vector> new_list; thread_traceback_.push_back(new_list); } - // Assertions + // Assertions assert ( static_cast (thread_hashtables_.size()) == max_threads_); assert ( static_cast (thread_traceback_.size() ) == max_threads_); } -/** - * @brief Start timing. - * - */ - size_t Profiler::start(std::string_view region_name) { @@ -61,22 +55,17 @@ size_t Profiler::start(std::string_view region_name) size_t const hash = thread_hashtables_[tid].query_insert(region_name); // Add routine to the traceback. - auto start_time = std::chrono::steady_clock::now(); + double start_time = omp_get_wtime(); thread_traceback_[tid].push_back(std::make_pair(hash, start_time)); return hash; } -/** - * @brief Stop timing. - * - */ - void Profiler::stop(size_t const hash) { // First job: log the stop time. - auto stop_time = std::chrono::steady_clock::now(); + double stop_time = omp_get_wtime(); // Determine the thread number auto tid = static_cast(0); @@ -90,13 +79,13 @@ void Profiler::stop(size_t const hash) // Check that the hash is the one we expect. If it isn't, there is an error in // the instrumentation. if (hash != last_hash_on_list){ - std::cerr << "EMERGENCY STOP: hashes don't match." << "\n"; + std::cout << "EMERGENCY STOP: hashes don't match." << "\n"; exit (100); } // Increment the time for this - auto start_time = thread_traceback_[tid].back().second; - auto deltatime = stop_time - start_time; + double start_time = thread_traceback_[tid].back().second; + double deltatime = stop_time - start_time; thread_hashtables_[tid].update(hash, deltatime); // Remove from the end of the list. @@ -110,11 +99,6 @@ void Profiler::stop(size_t const hash) } -/** - * @brief Write profile information. - * - */ - void Profiler::write() { // Write each one @@ -125,115 +109,15 @@ void Profiler::write() } /** - * @brief Get the total (inclusive) time of everything below the specified hash. - * - * @param[in] hash The hash corresponding to the region of interest. - * - * @note This function is normally expected to be used to return the total - * wallclock time for whole run. Since this value is required only from - * thread 0, the function does not take a thread ID argument and returns - * the value for thread 0 only. Taking the hash argument avoids the need - * to store the top-level hash inside the profiler itself. - * - */ - -double Profiler::get_thread0_walltime(size_t const hash) const +* @note This function is normally expected to be used to return the total +* wallclock time for whole run. Since this value is required only from +* thread 0, the function does not take a thread ID argument and returns +* the value for thread 0 only. Taking the hash argument avoids the need +* to store the top-level hash inside the profiler itself. +*/ +double Profiler::get_thread0_walltime(size_t const hash) { auto tid = static_cast(0); return thread_hashtables_[tid].get_total_walltime(hash); } -/** - * @brief Get the self walltime for the specified hash. - * - */ - -double Profiler::get_self_walltime(size_t const hash, int const input_tid) -{ - auto tid = static_cast(input_tid); - return thread_hashtables_[tid].get_self_walltime(hash); -} - -/** - * @brief Get the child walltime for the specified hash. - * - */ - -double Profiler::get_child_walltime(size_t const hash, int const input_tid) const -{ - auto tid = static_cast(input_tid); - return thread_hashtables_[tid].get_child_walltime(hash); -} - -/** - * @brief Get the region name corresponding to the input hash. - * - */ - -std::string Profiler::get_region_name(size_t const hash, int const input_tid) const -{ - auto tid = static_cast(input_tid); - return thread_hashtables_[tid].get_region_name(hash); -} - -/** - * @brief Get the number of times the input hash region has been called on the - * input thread ID. - * - * @param[in] hash The hash corresponding to the region of interest. - * @param[in] tid The ID corresponding to the thread of interest. - * - * @returns Returns an integer corresponding to the number of times the - * region of interest has been called on the specified thread. - * - */ - -unsigned long long int Profiler::get_region_call_count(size_t const hash, int const input_tid) const -{ - auto tid = static_cast(input_tid); - return thread_hashtables_[tid].get_region_call_count(hash); -} - -/** - * @brief Gets the std::unordered_map "table_" hashtable. - * - */ - -std::unordered_map const& Profiler::get_hashtable(int const input_tid) const -{ - auto tid = static_cast(input_tid); - return thread_hashtables_.at(tid).get_hashtable(); -} - -/** - * @brief Gets the inner layer vector in thread_traceback_. - * - */ - -std::vector> const& Profiler::get_inner_traceback_vector(int const input_tid) const -{ - auto tid = static_cast(input_tid); - return thread_traceback_.at(tid); -} - -/** - * @brief Gets the vector of (hash,HashEntry) pairs in Profiler.write() known as hashvec, the desired - * behaviour of which is to sort the entries from high to low self walltime. - * - */ - -std::vector> const& Profiler::get_hashvec(int const input_tid) const -{ - auto tid = static_cast(input_tid); - return thread_hashtables_.at(tid).get_hashvec(); -} - -/** - * @brief Return the value of max_threads_ - * - */ - -int Profiler::get_max_threads() const -{ - return max_threads_; -} diff --git a/src/c++/profiler.h b/src/c++/profiler.h index dc4891a9..caec6353 100644 --- a/src/c++/profiler.h +++ b/src/c++/profiler.h @@ -11,7 +11,7 @@ * * Contains the top-level class, whose methods are called from client code. Also * declares a top-level, global, profiler object. - * + * */ #ifndef PROFILER_H @@ -25,6 +25,11 @@ #include "hashtable.h" +/** + * @defgroup API C++ + * @brief C++ API for the profiler + */ + /** * @brief Top-level profiler class. * @@ -35,40 +40,60 @@ class Profiler { - private: + private: // Data members int max_threads_; - std::vector thread_hashtables_; - std::vector>> thread_traceback_; + std::vector thread_hashtables_; + std::vector>> thread_traceback_; // Type definitions for vector array indexing. - typedef std::vector::size_type hashtable_iterator_t_; - typedef std::vector>>::size_type pair_iterator_t_; + typedef std::vector::size_type hashtable_iterator_t_; + typedef std::vector>>::size_type pair_iterator_t_; public: // Constructors +/** + * @ingroup API + * @brief Constructor for profiler class + */ Profiler(); // Member functions - size_t start(std::string_view); - void stop (size_t const); - void write(); - // HashEntry getters - double get_thread0_walltime(size_t const hash) const; - double get_self_walltime(size_t const hash, int const input_tid); - double get_child_walltime(size_t const hash, int const input_tid) const; - std::string get_region_name(size_t const hash, int const input_tid) const; - unsigned long long int get_region_call_count(size_t const hash, int const input_tid) const; +/** + * @ingroup API + * @brief Start timing. + * + * @param[in] region_name A unique name for the region being timed. + * + */ + size_t start(std::string_view region_name); - // Getters that return a constant, referenced instance of a private data member - std::unordered_map const& get_hashtable(int const input_tid) const; - std::vector> const& get_inner_traceback_vector(int const input_tid) const; - std::vector> const& get_hashvec(int const input_tid) const; - int get_max_threads() const; +/** + * @ingroup API + * @brief Stop timing. + * + * @param[in] hash The hash corresponding to the region to be stopped. + */ + void stop (size_t hash); +/** + * @ingroup API + * @brief Write profile information. + * + */ + void write(); + + +/** + * @ingroup API + * @brief Get the total (inclusive) time of everything below the specified hash. + * + * @param[in] hash The hash corresponding to the region of interest. + */ + double get_thread0_walltime(size_t hash); }; // Declare global profiler diff --git a/src/c/profiler_c.cpp b/src/c/profiler_c.cpp index f28e8fd6..50c63af9 100644 --- a/src/c/profiler_c.cpp +++ b/src/c/profiler_c.cpp @@ -13,7 +13,7 @@ * C-language interfaces are needed to call the profiler from C and Fortran. * * Since Fortran is pass by reference, arguments are received as references (&). - * + * */ #include "profiler.h" @@ -21,6 +21,11 @@ #include #include + +/** + * @defgroup CAPI C + * @brief A simple C API for the profiler + */ extern "C" { void c_profiler_start(long int&, char const*); void c_profiler_stop (long int const&); @@ -29,6 +34,7 @@ extern "C" { } /** + * @ingroup CAPI * @brief Start timing a named region and return a unique handle. */ @@ -42,6 +48,7 @@ void c_profiler_start(long int& hash_out, char const* name) } /** + * @ingroup CAPI * @brief Stop timing the region with the specified handle. */ @@ -57,6 +64,7 @@ void c_profiler_stop(long int const& hash_in) } /** + * @ingroup CAPI * @brief Write the profile itself. */ @@ -66,7 +74,8 @@ void c_profiler_write() } /** - * Get the total wallclock time for the specified region on thread 0. + * @ingroup CAPI + * @brief Get the total wallclock time for the specified region on thread 0. */ double c_get_thread0_walltime(long int const& hash_in) diff --git a/src/f/profiler_mod.F90 b/src/f/profiler_mod.F90 index a7960474..66310a54 100644 --- a/src/f/profiler_mod.F90 +++ b/src/f/profiler_mod.F90 @@ -11,7 +11,6 @@ module profiler_mod use, intrinsic :: iso_c_binding, only: c_char, c_long, c_double, c_null_char implicit none - private !----------------------------------------------------------------------------- ! Public parameters @@ -27,6 +26,8 @@ module profiler_mod ! Public interfaces / subroutines !----------------------------------------------------------------------------- + !> @defgroup FortranAPI Fortran + !> @brief Fortran API for the profiler public :: profiler_start public :: profiler_stop public :: profiler_write @@ -38,22 +39,27 @@ module profiler_mod interface - subroutine interface_profiler_start(hash_out, region_name) & - bind(C, name='c_profiler_start') - import :: c_char, pik - character(kind=c_char, len=1), intent(in) :: region_name(*) - integer(kind=pik), intent(out) :: hash_out - end subroutine interface_profiler_start - + !> @ingroup FortranAPI + !> @fn profiler_mod::profiler_stop::profiler_stop(hash_in) + !> @brief Stop profiler + !> @param [in] hash_in The hash of the region to be stopped. subroutine profiler_stop(hash_in) bind(C, name='c_profiler_stop') import :: pik integer(kind=pik), intent(in) :: hash_in end subroutine profiler_stop + !> @ingroup FortranAPI + !> @fn profiler_mod::profiler_write::profiler_write() + !> @brief Write timings out for all profiling regions. subroutine profiler_write() bind(C, name='c_profiler_write') !No arguments to handle end subroutine profiler_write + !> @ingroup FortranAPI + !> @fn profiler_mod::profiler_get_thread0_walltime::profiler_get_thread0_walltime(hash_in) + !> @brief Write profiling data out + !> @param [in] hash_in The hash of the region to return the time. + !> @returns The Walltime within the region. function profiler_get_thread0_walltime(hash_in) result(walltime) & bind(C, name='c_get_thread0_walltime') import :: pik, prk @@ -63,11 +69,22 @@ end function profiler_get_thread0_walltime end interface + private + interface + subroutine interface_profiler_start(hash_out, region_name) & + bind(C, name='c_profiler_start') + import :: c_char, pik + character(kind=c_char, len=1), intent(in) :: region_name + integer(kind=pik), intent(out) :: hash_out + end subroutine interface_profiler_start + end interface + !----------------------------------------------------------------------------- ! Contained functions / subroutines !----------------------------------------------------------------------------- contains + !> @ingroup FortranAPI !> @brief Start profiling a code region. !> @param [out] hash_out The unique hash for this region. !> @param [in] region_name The region name. @@ -82,7 +99,7 @@ subroutine profiler_start(hash_out, region_name) !Local variables character(len=len_trim(region_name)+1) :: local_region_name - + local_region_name = trim(region_name) // c_null_char call interface_profiler_start(hash_out, local_region_name) diff --git a/tests/unit_tests/c++/CMakeLists.txt b/tests/unit_tests/c++/CMakeLists.txt index bfe4b601..521a1b61 100644 --- a/tests/unit_tests/c++/CMakeLists.txt +++ b/tests/unit_tests/c++/CMakeLists.txt @@ -4,7 +4,6 @@ function(add_unit_test test_name cpp_file) target_link_libraries(${test_name} OpenMP::OpenMP_CXX GTest::gtest_main - GTest::gmock_main ${CMAKE_PROJECT_NAME}) set_project_warnings(${test_name}) target_include_directories(${test_name} PRIVATE @@ -15,8 +14,4 @@ endfunction() # List of unit-tests added to CTest. Add a line calling the 'add_unit_test' # function to add an additional file of tests. add_unit_test(test_profiler test_profiler.cpp) -add_unit_test(test_hashtiming test_hashtiming.cpp) -add_unit_test(test_regionname test_regionname.cpp) -add_unit_test(test_hashtable test_hashtable.cpp) -add_unit_test(test_proftests test_proftests.cpp) -add_unit_test(test_callcount test_callcount.cpp) + diff --git a/tests/unit_tests/c++/test_callcount.cpp b/tests/unit_tests/c++/test_callcount.cpp deleted file mode 100644 index 804e0a03..00000000 --- a/tests/unit_tests/c++/test_callcount.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* ----------------------------------------------------------------------------- - * (c) Crown copyright 2022 Met Office. All rights reserved. - * The file LICENCE, distributed with this code, contains details of the terms - * under which the code may be used. - * ----------------------------------------------------------------------------- - */ - -#include - -#include "omp.h" - -#include "profiler.h" - -TEST(HashEntryTest,CallCountTest) -{ - // Start main region - auto prof_main = prof.start("MainRegion"); - - // Declare a shared sub-region hash. Initialise num_threads so that the - // compiler knows the 'for' loop inside the parallel region will definitely - // happen, and therefore doesn't think prof_sub_private remains unitialised. - size_t prof_sub_shared; - int num_threads = 1; - - // Start parallel region -#pragma omp parallel default(none) shared(prof_sub_shared, prof, num_threads) - { - // Get total number of threads, only need to calculate on a single thread - // since value won't change. -#pragma omp single - { - num_threads = omp_get_num_threads(); - } - - // Current thread ID - int thread_id = omp_get_thread_num(); - - // Also initialise prof_sub_private. The compiler doesn't know how many - // iterations of the subsequent 'for' loop there will be, and may flag - // warnings about it being potentially uninitialised when assigning to - // prof_sub_shared. - size_t prof_sub_private = 0; - - // Call a subregion a differing number of times depending on the thread ID. - // The highest thread ID will have the fewest calls: just 1. - for (int i = 0; i < num_threads-thread_id; ++i) - { - prof_sub_private = prof.start("SubRegion"); - prof.stop(prof_sub_private); - } - - // Give prof_sub_shared a value for later use in EXPECT's -#pragma omp single - { - prof_sub_shared = prof_sub_private; - } - } - - // Stop main region - prof.stop(prof_main); - - // Check call_count_ is the number expected on all threads - for (int j = 0; j < num_threads; ++j) - { - EXPECT_EQ(prof.get_region_call_count(prof_sub_shared,j),num_threads-j); - } -} diff --git a/tests/unit_tests/c++/test_hashtable.cpp b/tests/unit_tests/c++/test_hashtable.cpp deleted file mode 100644 index 81c72fa5..00000000 --- a/tests/unit_tests/c++/test_hashtable.cpp +++ /dev/null @@ -1,161 +0,0 @@ -#include -#include -#include -#include - -using ::testing::AllOf; -using ::testing::An; -using ::testing::Gt; - -// -// Testing some hashtable member variables and functions, such as query_insert() -// and the std::vector thread_traceback_. The desired behaviour of calling -// get_thread0_walltime before profiler.stop() is a bit fuzzy at the time of -// writing, but currently a test is done to make sure it returns the MDI of 0.0 -// - -TEST(HashTableTest,QueryInsertTest) { - - // Hashtable instance to poke and prod without changing any private data - const auto& htable = prof.get_hashtable(0); - - // Nothing has been done yet, hashtable should be empty - EXPECT_TRUE(htable.empty()); - - // Create new hashes via HashTable::query_insert, which is used in Profiler::start - const auto& prof_rigatoni = prof.start("Rigatoni"); - const auto& prof_penne = prof.start("Penne"); - prof.stop(prof_penne); - prof.stop(prof_rigatoni); - - { - SCOPED_TRACE("HashTable still empty after start is called"); - - // Table no longer empty - EXPECT_FALSE(htable.empty()); - - // .count() returns 1 if map already has an entry associated with input hash - EXPECT_EQ(htable.count(prof_rigatoni), 1); - EXPECT_EQ(htable.count(prof_penne), 1); - } - - { - SCOPED_TRACE("Hashing related fault"); - - // Checking that: - // - query_insert'ing Penne or Rigatoni just returns the hash - // - the regions have different hashes - // - the regions have the hashes returned by hash_function_ which uses std::hash - EXPECT_EQ(prof.start("Rigatoni"), std::hash{}("Rigatoni")); - EXPECT_EQ(prof.start("Penne"), std::hash{}("Penne")); - } - -} - -TEST(HashTableTest,ThreadsEqualsEntries) { - - // Trying to access an entry one higher than the value of max_threads_ should - // throw an exception as it won't exist assuming - // max_threads_ == thread_hashtables_.size(). This is just a different way of - // testing the assertion that already exists in the code. - - EXPECT_THROW(prof.get_hashtable(prof.get_max_threads()+1), std::out_of_range); - -} - -/** - * @TODO Decide how to handle the MDI stuff and update the following test - * accordingly. See Issue #53. - * - */ - -TEST(HashTableTest,UpdateAndMdiTest) { - - // Create new hash - size_t prof_pie = std::hash{}("Pie"); - - // Trying to find a time before .start() will throw an exception - EXPECT_THROW(prof.get_thread0_walltime(prof_pie), std::out_of_range); - - // Start timing - auto const& expected_hash = prof.start("Pie"); - EXPECT_EQ(expected_hash,prof_pie); // Make sure prof_pie has the hash we expect - - sleep(1); - - // Time t1 declared inbetween .start() and first .stop() - double const t1 = prof.get_thread0_walltime(prof_pie); - - //Stop timing - prof.stop(prof_pie); - - // Time t2 declared after first profiler.stop() - double const t2 = prof.get_thread0_walltime(prof_pie); - - // Start and stop same region again - prof.start("Pie"); - sleep(1); - prof.stop(prof_pie); - - // Time t3 declared after second profiler.stop() - double const t3 = prof.get_thread0_walltime(prof_pie); - - // Expected behaviour: t1 return the MDI and t3 > t2 > 0 - constexpr double MDI = 0.0; // Missing Data Indicator (MDI) - - { - SCOPED_TRACE("MDI missing from time points expected to return it"); - EXPECT_EQ(t1, MDI); - } - - { - SCOPED_TRACE("Update potentially not incrementing times correctly"); - EXPECT_GT(t2, 0.0); - EXPECT_GT(t3, t2 ); - } -} - -TEST(HashTableTest,TracebackTest) { - - const auto& traceback_vec = prof.get_inner_traceback_vector(0); - - { - SCOPED_TRACE("traceback.at() not throwing exception before profiler.start()"); - - // .at() throws exception when trying to access entry which isn't there - EXPECT_THROW( traceback_vec.at(0), std::out_of_range ); - } - - // Start profiler - const auto& prof_main = prof.start("Main"); - - { - SCOPED_TRACE("Traceback vector setup incorrectly"); - - // The traceback vector for this thread is a vector of pairs of (hash,StartTime) - // Therefore... - // - It should have a size of 1 in this example - // - The first entry in the pair should be prof_main - // - The second entry is some time, here a check done to make sure it has the type "time_point_t" - EXPECT_EQ( traceback_vec.size(), 1); - EXPECT_EQ( traceback_vec.back().first, prof_main ); - EXPECT_THAT( traceback_vec.back().second, An() ); - } - - // Stop profiler - prof.stop(prof_main); - - { - SCOPED_TRACE("Traceback vector not empty, pop_back() failed or still an unexpected entry left?"); - - // Shouldn't be any elements left - EXPECT_TRUE( traceback_vec.empty() ); - } - - { - SCOPED_TRACE("traceback.at() not throwing exception after element is deleted by pop_back()"); - - // .at() should throw exception again, only existing element was deleted by .pop_back() inside profiler.stop() - EXPECT_ANY_THROW( traceback_vec.at(0) ); - } -} diff --git a/tests/unit_tests/c++/test_hashtiming.cpp b/tests/unit_tests/c++/test_hashtiming.cpp deleted file mode 100644 index c50aba6a..00000000 --- a/tests/unit_tests/c++/test_hashtiming.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include -#include -#include -#include - -// -// A "timings" test that has expectations about the profiler walltime. -// In particular, a test is done to make sure the final selfwalltime is -// equal to the total walltime minus child walltime. Also, std::chrono -// is used to time a main and sub-region, it should return durations equal -// to the profiler total walltimes, within tolerance. -// - -TEST(HashEntryTest, TimingsTest) { - - // Start main profiler region and chrono timing - const auto& prof_main = prof.start("QuicheLorraine"); - const auto chrono_main_start = std::chrono::steady_clock::now(); - - sleep(1); - - // Start a sub-region and chrono timing - const auto& prof_sub = prof.start("SalmonQuiche"); - const auto chrono_sub_start = std::chrono::steady_clock::now(); - - sleep(1); - - // Stop profiler sub-region and respective chrono time - const auto chrono_sub_end = std::chrono::steady_clock::now(); - prof.stop(prof_sub); - - // Stop profiler main region and respective chrono time - const auto chrono_main_end = std::chrono::steady_clock::now(); - prof.stop(prof_main); - - { - SCOPED_TRACE("Self walltime calculation failed"); - - // Grab the total, child and self wallclock times - const double& total = prof.get_thread0_walltime(prof_main); - const double& child = prof.get_child_walltime(prof_main,0); - const double& self = prof.get_self_walltime(prof_main,0); - - // Test that self_walltime = total_walltime - child_walltime - EXPECT_EQ(self,total-child); - } - - // Work out chrono durations in seconds - std::chrono::duration main_region_duration = chrono_main_end - chrono_main_start; - double main_in_s = main_region_duration.count(); - - std::chrono::duration sub_region_duration = chrono_sub_end - chrono_sub_start; - double sub_in_s = sub_region_duration.count(); - - { - SCOPED_TRACE("Chrono and profiler times not within tolerance"); - - // Specify a time tolerance - const double time_tolerance = 0.0001; - - // Expect profiler & chrono times to be within tolerance - EXPECT_NEAR( prof.get_thread0_walltime(prof_main), main_in_s, time_tolerance ); - EXPECT_NEAR( prof.get_thread0_walltime(prof_sub) , sub_in_s , time_tolerance ); - } -} diff --git a/tests/unit_tests/c++/test_proftests.cpp b/tests/unit_tests/c++/test_proftests.cpp deleted file mode 100644 index 235a8a18..00000000 --- a/tests/unit_tests/c++/test_proftests.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include -#include -#include -#include -#include - -using ::testing::ExitedWithCode; -using ::testing::KilledBySignal; - -// -// Tests and death tests more related to profiler class members. WriteTest -// checks 'hashvec' in profiler.write() is correctly ordered. One death test -// makes sure the code breaks when a hash mismatch happens, and the other tests -// for a segfault when stopping before anything else. -// - -TEST(ProfilerTest,WriteTest) { - - // Start 3 nested regions - const auto& prof_shortbread = prof.start("Shortbread"); - const auto& prof_brownie = prof.start("Brownie"); - const auto& prof_rockyroad = prof.start("RockyRoad"); - - // Stop them 1 by 1 - prof.stop(prof_rockyroad); - prof.stop(prof_brownie); - prof.stop(prof_shortbread); - - // Call write to ensure hashvec is filled & sorted - prof.write(); - - // Get the Hashvec - const auto& local_hashvec = prof.get_hashvec(0); - - { - SCOPED_TRACE("Hashvec has incorrect size!"); - - // Since hashvec is a vector of (hash,HashEntry) pairs - // it should be equal in size to the number of regions - ASSERT_EQ(local_hashvec.size(), 3); - } - - { - SCOPED_TRACE("Entries in hashvec incorrectly sorted"); - - // hashvec is ordered from high to low so... [lastEntry] < [firstEntry] - const double& val1 = local_hashvec[0].second.self_walltime_.count(); - const double& val2 = local_hashvec[1].second.self_walltime_.count(); - const double& val3 = local_hashvec[2].second.self_walltime_.count(); - - EXPECT_LT(val3, val2); - EXPECT_LT(val2, val1); - } -} - -TEST(ProfilerDeathTest,WrongHashTest) { - - EXPECT_EXIT({ - - // Start main - const auto& prof_main = prof.start("Chocolate"); - - // A subregion - const auto& prof_sub = prof.start("Vanilla"); - prof.stop(prof_sub); - - // Wrong hash in profiler.stop() - prof.stop(prof_sub); - - // Eventually stop prof_main to avoid Wunused telling me off... - prof.stop(prof_main); - - }, ExitedWithCode(100), "EMERGENCY STOP: hashes don't match."); - -} - -TEST(ProfilerDeathTest,StopBeforeStartTest) { - - EXPECT_DEATH({ - - const auto prof_main = std::hash{}("Main"); - - // Stop the profiler before anything is done - prof.stop(prof_main); - - }, "" ); - -} diff --git a/tests/unit_tests/c++/test_regionname.cpp b/tests/unit_tests/c++/test_regionname.cpp deleted file mode 100644 index 38d1388a..00000000 --- a/tests/unit_tests/c++/test_regionname.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include -#include -#include - - -// -// Tests focused on the "region name" of a particular section. -// All main and sub-regions should give the expected string. Various -// other funky region names will potentially be tested in the future. -// - -TEST(RegionNameTest,NamesMatchTest) { - - //Start main region with name "Cappucino" - const auto& prof_cappucino = prof.start("Cappucino"); - - { - SCOPED_TRACE("Problem with sub-region name"); - - // Start timing a sub-region with name "Latte" - std::string myString = "Latte"; - const auto& prof_latte = prof.start(myString); - - // Get subregion name out from profiler and check it is what we expect - std::string subregionName = prof.get_region_name(prof_latte,0); - EXPECT_EQ("Latte", subregionName); - - prof.stop(prof_latte); - } - - { - SCOPED_TRACE("Problem with main region name"); - - // Get main region name out from profiler and test - std::string regionName = prof.get_region_name(prof_cappucino,0); - EXPECT_EQ("Cappucino", regionName); - } - - prof.stop(prof_cappucino); - -} - -/** - * @TODO Think about whether or not we want region name checks within code, i.e - * rules for what is and isn't allowed. See Issue #54. - * - */ -/* Placeholder for testing different region names, which there are no checks for within the code yet... - -TEST(RegionNameTest,InvalidsTest) { - - // Long name - const auto& A = prof.hashtable_query_insert("AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"); - - // Nothing / blank space - - // Non-null-terminated string - - // Special characters - - // Initalise inside brackets - -}*/