From 45c1559023a422016965d10d6b02b9bbbb20f3a1 Mon Sep 17 00:00:00 2001 From: joanvallve <jvallve@iri.upc.edu> Date: Tue, 14 Jan 2025 16:21:13 +0100 Subject: [PATCH] profiling unit implemented and working --- CMakeLists.txt | 1 + include/core/common/profiling_unit.h | 208 +++++++++++++--------- include/core/processor/processor_base.h | 61 ++----- src/common/profiling_unit.cpp | 139 +++++++++++++++ src/problem/problem.cpp | 1 + src/processor/processor_base.cpp | 79 +++------ test/CMakeLists.txt | 18 +- test/gtest_profiling_unit.cpp | 220 ++++++++++++++++++++++++ 8 files changed, 533 insertions(+), 194 deletions(-) create mode 100644 src/common/profiling_unit.cpp create mode 100644 test/gtest_profiling_unit.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ed049c6c3..dab498ba7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,6 +120,7 @@ SET(SRCS # common src/common/node_base.cpp src/common/node_state_blocks.cpp + src/common/profiling_unit.cpp src/common/time_stamp.cpp # composite src/composite/prior_composite.cpp diff --git a/include/core/common/profiling_unit.h b/include/core/common/profiling_unit.h index b25b58000..90a3fc160 100644 --- a/include/core/common/profiling_unit.h +++ b/include/core/common/profiling_unit.h @@ -20,123 +20,169 @@ #pragma once -// wolf includes -#include "core/common/wolf.h" - -// C, std -#include <sys/time.h> #include <iostream> #include <iomanip> - -static const unsigned int NANOSECS = 1000000000; +#include <chrono> +#include <cmath> namespace wolf { /** - * \brief TimeStamp implements basic functionalities for time stamps + * @class ProfilingUnit + * @brief A class for profiling code execution time and statistics. * - * TimeStamp implements basic functionalities for time stamps + * The ProfilingUnit class provides methods to start and stop profiling, and to + * print profiling results. */ class ProfilingUnit { protected: - double desired_period_; - unsigned int n_callbacks_; - double average_period_; - std::chrono::microseconds acc_duration_; - std::chrono::microseconds max_duration_; - double var_duration_; - std::chrono::time_point<std::chrono::high_resolution_clock> first_clock_; - std::chrono::time_point<std::chrono::high_resolution_clock> start_clock_; + bool running_; ///< True if profiling is running. + double desired_period_; ///< Desired period in microseconds (< 0 for not specified) + unsigned int n_callbacks_; ///< Number of callbacks. + double average_period_; ///< Average period in microseconds. + std::chrono::microseconds acc_duration_; ///< Accumulated duration in microseconds. + std::chrono::microseconds max_duration_; ///< Maximum duration in microseconds. + double var_duration_; ///< Variance of the duration in microseconds squared. + std::chrono::time_point<std::chrono::high_resolution_clock> first_clock_; ///< Time point of the first clock. + std::chrono::time_point<std::chrono::high_resolution_clock> start_clock_; ///< Time point of the start clock. public: - /** \brief Constructor - * - * Constructor without arguments. Sets time stamp to now. - * + /** + * @brief Constructor + * @param desired_period_sec Desired period in seconds. Negative value means not specified. Default is -1. */ ProfilingUnit(double desired_period_sec = -1); + /** + * @brief Start profiling. + */ void startProfiling(); + /** + * @brief Stop profiling. + */ void stopProfiling(); - void printProfiling(std::ostream& stream = std::cout) const; + /** + * @brief Print profiling results. + * @param _tabs Tabs to be used for indentation. + * @param stream Output stream to print the results. Default is std::cout. + */ + void printProfiling(const std::string& _tabs, std::ostream& stream = std::cout) const; + + /** + * @brief Print provided duration (in us) in the most appropiate units with specified precision. + * @param _duration_microsec Duration in microseconds. + * @param precision Precision for printing the duration. Default is 2. + * @return A string representing the duration. + */ + std::string printDuration(double _duration_microsec, int precision = 2) const; + + /** + * @brief Set the desired period. + * @param _desired_period_sec Desired period in seconds. + */ + void setDesiredPeriod(double _desired_period_sec); + + /** + * @brief Check if profiling is running. + * @return True if profiling is running, false otherwise. + */ + bool isRunning() const; + + /** + * @brief Get the number of callbacks. + * @return Number of callbacks. + */ + unsigned int getNCallbacks() const; + + /** + * @brief Get the desired period. + * @return Desired period in seconds. + */ + double getDesiredPeriod() const; + + /** + * @brief Get the average period. + * @return Average period in seconds. + */ + double getAveragePeriod() const; + + /** + * @brief Get the accumulated duration. + * @return Accumulated duration in seconds. + */ + double getAccDuration() const; + + /** + * @brief Get the average duration. + * @return Average duration in seconds. + */ + double getAvgDuration() const; + + /** + * @brief Get the maximum duration. + * @return Maximum duration in seconds. + */ + double getMaxDuration() const; + + /** + * @brief Get the variance of the duration. + * @return Variance of the duration in seconds squared. + */ + double getVarDuration() const; - std::stringstream printDuration(double _duration_ms, int precision = 2) const; + /** + * @brief Get the standard deviation of the duration. + * @return Standard deviation of the duration in seconds. + */ + double getStDevDuration() const; }; -ProfilingUnit::ProfilingUnit(desired_period_sec) - : desired_period_(1e6 * desired_period_sec), - n_callbacks_(0), - average_period_(0), - acc_duration_(0), - max_duration_(0), - var_duration_(0) +inline bool ProfilingUnit::isRunning() const +{ + return running_; +} + +inline unsigned int ProfilingUnit::getNCallbacks() const +{ + return n_callbacks_; +} + +inline double ProfilingUnit::getDesiredPeriod() const +{ + return 1e-6 * desired_period_; +} + +inline double ProfilingUnit::getAveragePeriod() const +{ + return 1e-6 * average_period_; +} + +inline double ProfilingUnit::getAccDuration() const { + return 1e-6 * acc_duration_.count(); } -inline void ProfilingUnit::startCaptureProfiling() +inline double ProfilingUnit::getAvgDuration() const { - start_ = std::chrono::high_resolution_clock::now(); - n_callback_++; - if (n_callback_ == 1) - first_clock_ = start_; - else - { - auto duration = std::chrono::duration_cast<std::chrono::microseconds>(start_ - first_clock_); - average_period_ = duration.count() / (n_callback_ - 1); - } + return 1e-6 * acc_duration_.count() / n_callbacks_; } -inline void ProfilingUnit::stopCaptureProfiling() +inline double ProfilingUnit::getMaxDuration() const { - auto prev_mean_duration = n_callback_ < 2 ? 0 : acc_duration_.count() / (n_callback_ - 1); - - auto duration = - std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - start_); - - max_duration_ = std::max(max_duration_, duration); - acc_duration_ += duration; - var_duration_ = - n_callback_ < 2 - ? 0 - : ((n_callback_ - 2) * var_duration_ + - (duration.count() - prev_mean_duration) * (duration.count() - acc_duration_.count() / n_callback_)) / - (n_callback_ - 1); + return 1e-6 * max_duration_.count(); } -inline std::stringstream ProfilingUnit::printDuration(double _duration_microsec, int precision) const +inline double ProfilingUnit::getVarDuration() const { - std::stringstream stream; - if (_duration_microsec > 1e6) - stream << std::fixed << std::setprecision(precision) << 1e-6 * _duration_microsec << " s"; - else if (_duration_microsec > 1e3) - stream << std::fixed << std::setprecision(precision) << 1e-3 * _duration_microsec << " ms"; - else - stream << std::fixed << std::setprecision(precision) << _duration_microsec << " us"; - - return stream; + return 1e-12 * var_duration_; } -inline void ProcessorBase::printProfiling(std::ostream& _stream) const +inline double ProfilingUnit::getStDevDuration() const { - auto ref_period = desired_period_ > 0 ? desired_period_ : average_period_; - - unsigned int average_percent = - (n_callbacks_ > 0) ? std::round(100 * acc_duration_.count() / n_callbacks_ / ref_period) : 0; - unsigned int max_percent = (n_callbacks_ > 0) ? std::round(100 * max_duration_.count() / ref_period) : 0; - unsigned int stdev_percent = (n_callbacks_ > 0) ? std::round(100 * sqrt(var_duration_) / ref_period) : 0; - - _stream << "\n\t\tinstances: " << n_callbacks_ // - << "\n\t\taverage period: " << printDuration(average_period_) // - << "\n\t\ttotal time: " << printDuration(acc_duration_.count()) // - << "\n\t\tavg time: " << printDuration(acc_duration_.count() / n_callbacks_) // - << " (" << average_percent << "%)" // - << "\n\t\tstdev time: " << printDuration(sqrt(var_duration_)) // - << " (" << stdev_percent << "%)" // - << "\n\t\tmax time: " << printDuration(max_duration_.count()) // - << " (" << max_percent << "%)" // + return 1e-6 * sqrt(var_duration_); } } // namespace wolf diff --git a/include/core/processor/processor_base.h b/include/core/processor/processor_base.h index 6ce77b5f7..305db223a 100644 --- a/include/core/processor/processor_base.h +++ b/include/core/processor/processor_base.h @@ -29,6 +29,7 @@ #include "core/frame/frame_base.h" #include "core/common/time_stamp.h" #include "core/processor/buffer.h" +#include "core/common/profiling_unit.h" // std #include <memory> @@ -127,26 +128,18 @@ class ProcessorBase : public NodeBase, public std::enable_shared_from_this<Proce static unsigned int processor_id_count_; // CHECKING captures period and time_tolerance - double capture_period_max_, capture_period_min_, capture_period_mean_; + double capture_period_mean_; TimeStamp prev_capture_stamp_; - public: // PROFILING - unsigned int n_capture_callback_; - unsigned int n_kf_callback_; - std::chrono::microseconds acc_duration_capture_; - std::chrono::microseconds acc_duration_kf_; - std::chrono::microseconds max_duration_capture_; - std::chrono::microseconds max_duration_kf_; - double var_duration_capture_; - double var_duration_kf_; - std::chrono::time_point<std::chrono::high_resolution_clock> start_capture_; - std::chrono::time_point<std::chrono::high_resolution_clock> start_kf_; - void startCaptureProfiling(); - void stopCaptureProfiling(); - void startKFProfiling(); - void stopKFProfiling(); - void printProfiling(std::ostream& stream = std::cout) const; + ProfilingUnit profiling_capture_, profiling_kf_; + + public: + void startCaptureProfiling(); + void stopCaptureProfiling(); + void startKFProfiling(); + void stopKFProfiling(); + void printProfiling(std::ostream& stream = std::cout) const; /** \brief constructor * @@ -286,42 +279,12 @@ inline bool ProcessorBase::hasChildren() const inline void ProcessorBase::startCaptureProfiling() { - start_capture_ = std::chrono::high_resolution_clock::now(); -} - -inline void ProcessorBase::stopCaptureProfiling() -{ - //auto prev_mean_duration_capture = n_capture_callback_ < 2 ? 0 : acc_duration_capture_.count() / (n_capture_callback_ - 1); - - auto duration_capture = std::chrono::duration_cast<std::chrono::microseconds>( - std::chrono::high_resolution_clock::now() - start_capture_); - - max_duration_capture_ = std::max(max_duration_capture_, duration_capture); - acc_duration_capture_ += duration_capture; - // var_duration_capture_ = n_capture_callback_ < 2 ? 0 : ((n_capture_callback_ - 2) * var_duration_capture_ + - // (duration_capture.count() - prev_mean_duration_capture) * - // (duration_capture.count() - acc_duration_capture_.count() / n_capture_callback_)) / - // (n_capture_callback_ - 1); + profiling_capture_.startProfiling(); } inline void ProcessorBase::startKFProfiling() { - start_kf_ = std::chrono::high_resolution_clock::now(); -} - -inline void ProcessorBase::stopKFProfiling() -{ - //auto prev_mean_duration_kf = n_kf_callback_ < 2 ? 0 : acc_duration_kf_.count() / (n_kf_callback_ - 1) ; - - auto duration_kf = - std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - start_kf_); - - max_duration_kf_ = std::max(max_duration_kf_, duration_kf); - acc_duration_kf_ += duration_kf; - // var_duration_kf_ = n_kf_callback_ < 2 ? 0 : ((n_kf_callback_ - 2) * var_duration_kf_ + - // (duration_kf.count() - prev_mean_duration_kf) * - // (duration_kf.count() - acc_duration_kf_.count() / n_kf_callback_)) / - // (n_kf_callback_ - 1); + profiling_kf_.startProfiling(); } inline YAML::Node ProcessorBase::getParams() const diff --git a/src/common/profiling_unit.cpp b/src/common/profiling_unit.cpp new file mode 100644 index 000000000..19fdab32c --- /dev/null +++ b/src/common/profiling_unit.cpp @@ -0,0 +1,139 @@ +// WOLF - Copyright (C) 2020,2021,2022,2023,2024,2025 +// Institut de Robòtica i Informà tica Industrial, CSIC-UPC. +// Authors: Joan Solà Ortega (jsola@iri.upc.edu) and +// Joan Vallvé Navarro (jvallve@iri.upc.edu) +// All rights reserved. +// +// This file is part of WOLF: http://www.iri.upc.edu/wolf +// WOLF is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "core/common/profiling_unit.h" +#include <cassert> + +namespace wolf +{ + +ProfilingUnit::ProfilingUnit(double desired_period_sec) + : running_(false), + desired_period_(1e6 * desired_period_sec), + n_callbacks_(0), + average_period_(0), + acc_duration_(0), + max_duration_(0), + var_duration_(0) +{ +} + +void ProfilingUnit::startProfiling() +{ + if (running_) throw std::runtime_error("ProfilingUnit::startProfiling: profiling already running"); + + n_callbacks_++; + running_ = true; + start_clock_ = std::chrono::high_resolution_clock::now(); +} + +void ProfilingUnit::stopProfiling() +{ + if (not running_) return; + + assert(n_callbacks_ > 0); + + // compute duration first of all + auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - + start_clock_); + + // average period + if (n_callbacks_ == 1) + first_clock_ = start_clock_; + else + { + auto duration_from_start = std::chrono::duration_cast<std::chrono::microseconds>(start_clock_ - first_clock_); + average_period_ = duration_from_start.count() / (n_callbacks_ - 1); + } + + // recursive formula for variance: https://math.stackexchange.com/a/2746016 + if (n_callbacks_ == 1) + var_duration_ = 0; + else // n_callbacks_ > 1 + { + double n_callbacks_d = n_callbacks_; + auto prev_mean_duration = acc_duration_.count() / (n_callbacks_d - 1); + var_duration_ = ((n_callbacks_d - 1) / n_callbacks_d) * + (var_duration_ + std::pow(prev_mean_duration - duration.count(), 2) / n_callbacks_d); + } + // max and accumulated duration + max_duration_ = std::max(max_duration_, duration); + acc_duration_ += duration; + + running_ = false; +} + +std::string ProfilingUnit::printDuration(double _duration_microsec, int precision) const +{ + std::stringstream stream; + if (_duration_microsec >= 3600 * 1e6) + { + auto hours = static_cast<int>(_duration_microsec / (3600 * 1e6)); + auto minutes = static_cast<int>((_duration_microsec - hours * 3600 * 1e6) / (60 * 1e6)); + auto seconds = (_duration_microsec - hours * 3600 * 1e6 - minutes * 60 * 1e6) * 1e-6; + stream << hours << " h " << minutes << " min " << std::fixed << std::setprecision(precision) << seconds + << " s"; + } + else if (_duration_microsec >= 60 * 1e6) + { + auto minutes = static_cast<int>(_duration_microsec / (60 * 1e6)); + auto seconds = (_duration_microsec - minutes * 60 * 1e6) * 1e-6; + stream << minutes << " min " << std::fixed << std::setprecision(precision) << seconds << " s"; + } + else if (_duration_microsec >= 1e6) + stream << std::fixed << std::setprecision(precision) << 1e-6 * _duration_microsec << " s"; + else if (_duration_microsec >= 1e3) + stream << std::fixed << std::setprecision(precision) << 1e-3 * _duration_microsec << " ms"; + else + stream << std::fixed << std::setprecision(precision) << _duration_microsec << " us"; + + return stream.str(); +} + +void ProfilingUnit::printProfiling(const std::string& _tabs, std::ostream& _stream) const +{ + auto ref_period = desired_period_ > 0 ? desired_period_ : average_period_; + + unsigned int average_percent = + (n_callbacks_ > 0) ? std::round(100 * acc_duration_.count() / n_callbacks_ / ref_period) : 0; + unsigned int max_percent = (n_callbacks_ > 0) ? std::round(100 * max_duration_.count() / ref_period) : 0; + unsigned int stdev_percent = (n_callbacks_ > 0) ? std::round(100 * sqrt(var_duration_) / ref_period) : 0; + + _stream << "\n" + << _tabs << "instances: " << n_callbacks_ // + << "\n" + << _tabs << "total time: " << printDuration(acc_duration_.count()) // + << "\n"; + if (desired_period_ > 0) + _stream << _tabs << "desired period: " << printDuration(desired_period_) // + << " (reference)\n"; + _stream << _tabs << "avg period: " << printDuration(average_period_) // + << (desired_period_ > 0 ? "\n" : " (reference)\n") + << _tabs << "avg time: " << printDuration(acc_duration_.count() / n_callbacks_) // + << " (" << average_percent << "%)" + << "\n" + << _tabs << "stdev time: " << printDuration(sqrt(var_duration_)) // + << " (" << stdev_percent << "%)" + << "\n" + << _tabs << "max time: " << printDuration(max_duration_.count()) // + << " (" << max_percent << "%)"; +} + +} // namespace wolf diff --git a/src/problem/problem.cpp b/src/problem/problem.cpp index 4055a51c4..6d4bbe712 100644 --- a/src/problem/problem.cpp +++ b/src/problem/problem.cpp @@ -868,6 +868,7 @@ void Problem::keyFrameCallback(FrameBasePtr _keyframe_ptr, ProcessorBasePtr _pro // pause processor profiling if (_processor_ptr) _processor_ptr->stopCaptureProfiling(); + // notify other processors for (auto sensor : hardware_ptr_->getSensorList()) for (auto processor : sensor->getProcessorList()) if (processor && (processor != _processor_ptr)) processor->keyFrameCallback(_keyframe_ptr); diff --git a/src/processor/processor_base.cpp b/src/processor/processor_base.cpp index 52864d47e..78d716785 100644 --- a/src/processor/processor_base.cpp +++ b/src/processor/processor_base.cpp @@ -33,18 +33,8 @@ ProcessorBase::ProcessorBase(const std::string& _type, unsigned int _dim_compati params_(Clone(_params)), dim_compatible_(_dim_compatible), sensor_ptr_(), - capture_period_max_(0), - capture_period_min_(1e12), capture_period_mean_(0), - prev_capture_stamp_(TimeStamp::Invalid()), - n_capture_callback_(0), - n_kf_callback_(0), - acc_duration_capture_(0), - acc_duration_kf_(0), - max_duration_capture_(0), - max_duration_kf_(0), - var_duration_capture_(0), - var_duration_kf_(0) + prev_capture_stamp_(TimeStamp::Invalid()) { assert(dim_compatible_ == 0 or dim_compatible_ == 2 or dim_compatible_ == 3); WOLF_DEBUG("Constructed ProcessorBase - ", getType(), " id: ", id(), " name: ", getName()); @@ -69,7 +59,6 @@ void ProcessorBase::keyFrameCallback(FrameBasePtr _keyframe) _keyframe->getTimeStamp()); // profiling - n_kf_callback_++; startKFProfiling(); // asking if frame should be stored @@ -94,18 +83,16 @@ void ProcessorBase::captureCallback(CaptureBasePtr _capture) _capture->getTimeStamp()); // profiling - n_capture_callback_++; startCaptureProfiling(); // CHECKING captures period and time_tolerance - if (n_capture_callback_ > 1) + if (profiling_capture_.getNCallbacks() > 1) { assert(prev_capture_stamp_.ok()); double dt = _capture->getTimeStamp() - prev_capture_stamp_; - capture_period_mean_ = ((n_capture_callback_ - 2) * capture_period_mean_ + dt) / (n_capture_callback_ - 1); - capture_period_min_ = std::min(dt, capture_period_min_); - capture_period_max_ = std::max(dt, capture_period_max_); + double n_captures = profiling_capture_.getNCallbacks(); + capture_period_mean_ = ((n_captures - 2) * capture_period_mean_ + dt) / (n_captures - 1); WOLF_ERROR_COND(capture_period_mean_ < getTimeTolerance() * 2, getType(), @@ -240,50 +227,28 @@ bool ProcessorBase::check(CheckLog& _log, bool _verbose, std::ostream& _stream, return _log.is_consistent_; } -void ProcessorBase::printProfiling(std::ostream& _stream) const +void ProcessorBase::stopCaptureProfiling() { - unsigned int kf_average_period = - (n_kf_callback_ > 0) ? std::round(100 * capture_period_mean_ * n_capture_callback_ / n_kf_callback_) : 0; - unsigned int average_proc_cap_percent = - (n_capture_callback_ > 0) - ? std::round(100 * 1e-6 * acc_duration_capture_.count() / n_capture_callback_ / capture_period_mean_) - : 0; - unsigned int max_proc_cap_percent = - (n_capture_callback_ > 0) ? std::round(100 * 1e-6 * max_duration_capture_.count() / capture_period_min_) : 0; - unsigned int stdev_proc_cap_percent = - (n_capture_callback_ > 0) ? std::round(100 * 1e-6 * sqrt(var_duration_capture_) / capture_period_mean_) - : 0; - unsigned int average_proc_kf_percent = - (n_kf_callback_ > 0) ? std::round(100 * 1e-6 * acc_duration_kf_.count() / n_kf_callback_ / kf_average_period) - : 0; - unsigned int max_proc_kf_percent = - (n_kf_callback_ > 0) ? std::round(100 * 1e-6 * max_duration_kf_.count() / kf_average_period) : 0; - unsigned int std_dev_proc_kf_percent = - (n_kf_callback_ > 0) ? std::round(100 * 1e-6 * sqrt(var_duration_kf_) / kf_average_period) : 0; + profiling_capture_.stopProfiling(); +} +void ProcessorBase::stopKFProfiling() +{ + profiling_kf_.stopProfiling(); +} + +void ProcessorBase::printProfiling(std::ostream& _stream) const +{ _stream << "\n" << getType() << " - " << getName() << ":" - << "\n\ttotal proc. time: " << 1e-6 * (acc_duration_capture_ + acc_duration_kf_).count() << " s" - << "\n\tProcessing captures:" - << "\n\t\tcaptures processed: " << n_capture_callback_ - << "\n\t\taverage period: " << 1e3 * capture_period_mean_ << " ms" - << "\n\t\ttotal proc. time: " << 1e-6 * acc_duration_capture_.count() << " s" - << "\n\t\taverage proc. time: " << 1e-3 * acc_duration_capture_.count() / n_capture_callback_ << " ms" - << " (" << average_proc_cap_percent << "%)" - << "\n\t\tstd dev proc. time: " << 1e-3 * sqrt(var_duration_capture_) << " ms" - << " (" << stdev_proc_cap_percent << "%)" - << "\n\t\tmax proc. time: " << 1e-3 * max_duration_capture_.count() << " ms" - << " (" << max_proc_cap_percent << "%)" - << "\n\tProcessing keyframes:" - << "\n\t\tkf processed: " << n_kf_callback_ - << "\n\t\taverage period: " << kf_average_period << " s" - << "\n\t\ttotal proc. time: " << 1e-6 * acc_duration_kf_.count() << " s" - << "\n\t\taverage proc. time: " << 1e-3 * acc_duration_kf_.count() / n_kf_callback_ << " ms" - << " (" << average_proc_kf_percent << "%)" - << "\n\t\tstd dev proc. time: " << 1e-3 * sqrt(var_duration_kf_) << " ms" - << " (" << std_dev_proc_kf_percent << "%)" - << "\n\t\tmax proc. time: " << 1e-3 * max_duration_kf_.count() << " ms" - << " (" << max_proc_kf_percent << "%)" << std::endl; + << "\n\ttotal time: " + << profiling_capture_.printDuration(1e6 * + (profiling_capture_.getAccDuration() + profiling_kf_.getAccDuration())) + << "\n\tProcessing captures:"; + profiling_capture_.printProfiling("\t\t", _stream); + _stream << "\n\tProcessing keyframes:"; + profiling_kf_.printProfiling("\t\t", _stream); + _stream << std::endl; } bool ProcessorBase::checkTimeTolerance(const TimeStamp& _ts1, const TimeStamp& _ts2) const diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e924582eb..31836743b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -50,6 +50,10 @@ wolf_add_gtest(gtest_capture_base gtest_capture_base.cpp) # Composite class test wolf_add_gtest(gtest_composite gtest_composite.cpp) +# Emplace test +wolf_add_gtest(gtest_emplace gtest_emplace.cpp) +target_link_libraries(gtest_emplace PUBLIC wolfcore_dummy) + # FactorBase class test wolf_add_gtest(gtest_factor_base gtest_factor_base.cpp) @@ -66,10 +70,6 @@ target_link_libraries(gtest_factory PUBLIC wolfcore_dummy) # FactoryStateBlock factory test wolf_add_gtest(gtest_factory_state_block gtest_factory_state_block.cpp) -# Node Emplace test -wolf_add_gtest(gtest_emplace gtest_emplace.cpp) -target_link_libraries(gtest_emplace PUBLIC wolfcore_dummy) - # FeatureBase classes test wolf_add_gtest(gtest_feature_base gtest_feature_base.cpp) @@ -79,9 +79,6 @@ wolf_add_gtest(gtest_frame_base gtest_frame_base.cpp) # GraphSearch class test wolf_add_gtest(gtest_graph_search gtest_graph_search.cpp) -# MotionProvider classes test -wolf_add_gtest(gtest_motion_provider gtest_motion_provider.cpp) -target_link_libraries(gtest_motion_provider PUBLIC wolfcore_dummy) # LocalParametrizationXxx classes test wolf_add_gtest(gtest_loader gtest_loader.cpp) @@ -92,6 +89,10 @@ wolf_add_gtest(gtest_local_param gtest_local_param.cpp) # Logging test wolf_add_gtest(gtest_logging gtest_logging.cpp) +# MotionProvider classes test +wolf_add_gtest(gtest_motion_provider gtest_motion_provider.cpp) +target_link_libraries(gtest_motion_provider PUBLIC wolfcore_dummy) + # MotionBuffer class test wolf_add_gtest(gtest_motion_buffer gtest_motion_buffer.cpp) @@ -109,6 +110,9 @@ target_link_libraries(gtest_processor_base PUBLIC wolfcore_dummy) # ProcessorMotion class test wolf_add_gtest(gtest_processor_motion gtest_processor_motion.cpp) +# ProfilingUnit class test +wolf_add_gtest(gtest_profiling_unit gtest_profiling_unit.cpp) + # Rotation test wolf_add_gtest(gtest_rotation gtest_rotation.cpp) diff --git a/test/gtest_profiling_unit.cpp b/test/gtest_profiling_unit.cpp new file mode 100644 index 000000000..ec35836b2 --- /dev/null +++ b/test/gtest_profiling_unit.cpp @@ -0,0 +1,220 @@ +// WOLF - Copyright (C) 2020,2021,2022,2023,2024,2025 +// Institut de Robòtica i Informà tica Industrial, CSIC-UPC. +// Authors: Joan Solà Ortega (jsola@iri.upc.edu) and +// Joan Vallvé Navarro (jvallve@iri.upc.edu) +// All rights reserved. +// +// This file is part of WOLF: http://www.iri.upc.edu/wolf +// WOLF is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "core/utils/utils_gtest.h" +#include "core/common/profiling_unit.h" +#include <thread> +#include <list> + +using namespace wolf; + +TEST(ProfilingUnitTest, ConstructorInitializesCorrectly) +{ + ProfilingUnit prof_unit(1.0); + + EXPECT_EQ(prof_unit.getDesiredPeriod(), 1); + EXPECT_EQ(prof_unit.getNCallbacks(), 0); + EXPECT_EQ(prof_unit.getAveragePeriod(), 0); + EXPECT_EQ(prof_unit.getAccDuration(), 0); + EXPECT_EQ(prof_unit.getMaxDuration(), 0); + EXPECT_EQ(prof_unit.getVarDuration(), 0); +} + +TEST(ProfilingUnitTest, StartProfilingUpdatesCorrectly) +{ + ProfilingUnit prof_unit(1.0); + + prof_unit.startProfiling(); + EXPECT_EQ(prof_unit.getNCallbacks(), 1); + + EXPECT_THROW(prof_unit.startProfiling(), std::runtime_error); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + prof_unit.stopProfiling(); + prof_unit.startProfiling(); + EXPECT_EQ(prof_unit.getNCallbacks(), 2); + EXPECT_GT(prof_unit.getAccDuration(), 0); + EXPECT_GT(prof_unit.getAvgDuration(), 0); + EXPECT_GT(prof_unit.getMaxDuration(), 0); +} + +TEST(ProfilingUnitTest, StopProfilingUpdatesCorrectly) +{ + ProfilingUnit prof_unit(1.0); + + prof_unit.startProfiling(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + prof_unit.stopProfiling(); + + EXPECT_NEAR(prof_unit.getAccDuration(), 0.1, 1e-2); + EXPECT_NEAR(prof_unit.getAvgDuration(), 0.1, 1e-2); + EXPECT_NEAR(prof_unit.getMaxDuration(), 0.1, 1e-2); + EXPECT_NEAR(prof_unit.getVarDuration(), 0, 1e-12); + + prof_unit.startProfiling(); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + prof_unit.stopProfiling(); + + EXPECT_GT(prof_unit.getAveragePeriod(), 0); + + EXPECT_NEAR(prof_unit.getAccDuration(), 0.3, 1e-2); + EXPECT_NEAR(prof_unit.getAvgDuration(), 0.15, 1e-2); + EXPECT_NEAR(prof_unit.getMaxDuration(), 0.2, 1e-2); +} + +TEST(ProfilingUnitTest, ProfilingUnitTest_statistics_Test) +{ + ProfilingUnit prof_unit; + + std::list<double> durations; + double acc_duration = 0; + double max_duration = 0; + double avg_duration = 0; + double var_duration = 0; + int n_samples = 10; + + std::chrono::high_resolution_clock::time_point start, stop; + + for (auto i = 0; i < n_samples; i++) + { + int random_ms = rand() % 100; + double prev_duration = prof_unit.getAccDuration(); + prof_unit.startProfiling(); + start = std::chrono::high_resolution_clock::now(); + std::this_thread::sleep_for(std::chrono::milliseconds(random_ms)); + stop = std::chrono::high_resolution_clock::now(); + prof_unit.stopProfiling(); + + // Check measured duration + auto meas_duration_us = std::chrono::duration_cast<std::chrono::microseconds>(stop - start); + auto duration = prof_unit.getAccDuration() - prev_duration; + + EXPECT_NEAR(1e-6 * meas_duration_us.count(), 1e-3 * random_ms, 1e-2); + EXPECT_NEAR(1e-6 * meas_duration_us.count(), duration, 1e-5); + + durations.push_back(duration); + acc_duration += duration; + + if (durations.back() > max_duration) + { + max_duration = durations.back(); + } + } + avg_duration = acc_duration / n_samples; + + for (auto duration : durations) + { + var_duration += std::pow(duration - avg_duration, 2); + } + var_duration /= n_samples; + + EXPECT_NEAR(prof_unit.getAccDuration(), acc_duration, 1e-9); + EXPECT_NEAR(prof_unit.getAvgDuration(), avg_duration, 1e-9); + EXPECT_NEAR(prof_unit.getMaxDuration(), max_duration, 1e-9); + EXPECT_NEAR(prof_unit.getVarDuration(), var_duration, 1e-9); + EXPECT_NEAR(prof_unit.getStDevDuration(), std::sqrt(var_duration), 1e-9); +} + +TEST(ProfilingUnitTest, PrintDurationFormatsCorrectly) +{ + ProfilingUnit prof_unit(1.0); + + EXPECT_EQ(prof_unit.printDuration(3600 * 1e6), "1 h 0 min 0.00 s"); + EXPECT_EQ(prof_unit.printDuration(60 * 1e6), "1 min 0.00 s"); + EXPECT_EQ(prof_unit.printDuration(1e6), "1.00 s"); + EXPECT_EQ(prof_unit.printDuration(1e3), "1.00 ms"); + EXPECT_EQ(prof_unit.printDuration(1), "1.00 us"); +} + +TEST(ProfilingUnitTest, PrintProfilingOutputsWithDesiredPeriod) +{ + ProfilingUnit prof_unit(1.0); + + prof_unit.startProfiling(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + prof_unit.stopProfiling(); + + std::stringstream ss; + prof_unit.printProfiling("", ss); + + std::cout << ss.str() << std::endl; + + std::string output = ss.str(); + EXPECT_NE(output.find("instances: 1"), std::string::npos); + EXPECT_NE(output.find(std::string("desired period: ") + // + prof_unit.printDuration(1e6 * prof_unit.getDesiredPeriod())), + std::string::npos); + EXPECT_NE(output.find(std::string("avg period: ") + // + prof_unit.printDuration(1e6 * prof_unit.getAveragePeriod())), + std::string::npos); + EXPECT_NE(output.find(std::string("total time: ") + // + prof_unit.printDuration(1e6 * prof_unit.getAccDuration())), + std::string::npos); + EXPECT_NE(output.find(std::string("avg time: ") + // + prof_unit.printDuration(1e6 * prof_unit.getAvgDuration())), + std::string::npos); + EXPECT_NE(output.find(std::string("stdev time: ") + // + prof_unit.printDuration(1e6 * prof_unit.getStDevDuration())), + std::string::npos); + EXPECT_NE(output.find(std::string("max time: ") + // + prof_unit.printDuration(1e6 * prof_unit.getMaxDuration())), + std::string::npos); +} + +TEST(ProfilingUnitTest, PrintProfilingOutputs) +{ + ProfilingUnit prof_unit; + + prof_unit.startProfiling(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + prof_unit.stopProfiling(); + + std::stringstream ss; + prof_unit.printProfiling("", ss); + + std::cout << ss.str() << std::endl; + + std::string output = ss.str(); + EXPECT_NE(output.find("instances: 1"), std::string::npos); + EXPECT_EQ(output.find(std::string("desired period: ") + // + prof_unit.printDuration(1e6 * prof_unit.getDesiredPeriod())), + std::string::npos); + EXPECT_NE(output.find(std::string("avg period: ") + // + prof_unit.printDuration(1e6 * prof_unit.getAveragePeriod())), + std::string::npos); + EXPECT_NE(output.find(std::string("total time: ") + // + prof_unit.printDuration(1e6 * prof_unit.getAccDuration())), + std::string::npos); + EXPECT_NE(output.find(std::string("avg time: ") + // + prof_unit.printDuration(1e6 * prof_unit.getAvgDuration())), + std::string::npos); + EXPECT_NE(output.find(std::string("stdev time: ") + // + prof_unit.printDuration(1e6 * prof_unit.getStDevDuration())), + std::string::npos); + EXPECT_NE(output.find(std::string("max time: ") + // + prof_unit.printDuration(1e6 * prof_unit.getMaxDuration())), + std::string::npos); +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file -- GitLab