-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement CallProfiler #259
Changes from 31 commits
65ad817
772d7cb
ce92877
47c4135
8fbe787
d1bc0d1
090a6ad
317cbb7
621a2da
d94c1c0
6c7b356
adc81c7
79b3354
4b7e36c
b5ad167
025491f
de53ae4
1b68d63
49da1dd
513ce2b
891f08e
a9af889
92344d4
e2ac398
766c0ec
fac22d6
d5953b1
94f4140
25a3623
e58a83b
3ac9b1e
3d87ba7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* | ||
* Copyright (c) 2023, Yung-Yu Chen <[email protected]> | ||
* | ||
* Redistribution and use in source and binary forms, with or without | ||
* modification, are permitted provided that the following conditions are met: | ||
* | ||
* - Redistributions of source code must retain the above copyright notice, | ||
* this list of conditions and the following disclaimer. | ||
* - Redistributions in binary form must reproduce the above copyright notice, | ||
* this list of conditions and the following disclaimer in the documentation | ||
* and/or other materials provided with the distribution. | ||
* - Neither the name of the copyright holder nor the names of its contributors | ||
* may be used to endorse or promote products derived from this software | ||
* without specific prior written permission. | ||
* | ||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | ||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
* POSSIBILITY OF SUCH DAMAGE. | ||
*/ | ||
|
||
#include <modmesh/toggle/RadixTree.hpp> | ||
|
||
namespace modmesh | ||
{ | ||
|
||
void CallProfiler::reset() | ||
{ | ||
while (!m_radix_tree.is_root()) | ||
{ | ||
CallerProfile & profile = m_radix_tree.get_current_node()->data(); | ||
if (profile.is_running) | ||
{ | ||
profile.stop_stopwatch(); | ||
} | ||
m_radix_tree.move_current_to_parent(); | ||
} | ||
m_radix_tree.reset(); | ||
} | ||
|
||
// NOLINTNEXTLINE(misc-no-recursion) | ||
void CallProfiler::print_profiling_result(const RadixTreeNode<CallerProfile> & node, const int depth, std::ostream & outstream) const | ||
{ | ||
for (int i = 0; i < depth; ++i) | ||
{ | ||
outstream << " "; | ||
} | ||
|
||
auto profile = node.data(); | ||
|
||
if (depth == 0) | ||
{ | ||
outstream << "Profiling Result" << std::endl; | ||
} | ||
else | ||
{ | ||
outstream << profile.caller_name << " - Total Time: " << profile.total_time.count() / 1e6 << " ms, Call Count: " << profile.call_count << std::endl; | ||
} | ||
|
||
for (const auto & child : node.children()) | ||
{ | ||
// NOLINTNEXTLINE(misc-no-recursion) | ||
print_profiling_result(*child, depth + 1, outstream); | ||
} | ||
} | ||
|
||
} /* end namespace modmesh */ | ||
|
||
// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,13 +28,15 @@ | |
* POSSIBILITY OF SUCH DAMAGE. | ||
*/ | ||
|
||
#include <sstream> | ||
#include <algorithm> | ||
#include <chrono> | ||
#include <functional> | ||
#include <iostream> | ||
#include <memory> | ||
#include <list> | ||
#include <unordered_map> | ||
#include <memory> | ||
#include <sstream> | ||
#include <stack> | ||
#include <algorithm> | ||
#include <unordered_map> | ||
|
||
namespace modmesh | ||
{ | ||
|
@@ -110,6 +112,13 @@ class RadixTreeNode | |
return (it != m_children.end()) ? it->get() : nullptr; | ||
} | ||
|
||
RadixTreeNode<T> * get_child(std::string name) const | ||
{ | ||
auto it = std::find_if(m_children.begin(), m_children.end(), [&](const auto & child) | ||
{ return child->name() == name; }); | ||
return (it != m_children.end()) ? it->get() : nullptr; | ||
} | ||
|
||
RadixTreeNode<T> * get_prev() const { return m_prev; } | ||
|
||
private: | ||
|
@@ -162,6 +171,19 @@ class RadixTree | |
} | ||
} | ||
|
||
void reset() | ||
{ | ||
m_root = std::move(std::make_unique<RadixTreeNode<T>>()); | ||
m_current_node = m_root.get(); | ||
m_id_map.clear(); | ||
m_unique_id = 0; | ||
} | ||
|
||
bool is_root() const | ||
{ | ||
return m_current_node == m_root.get(); | ||
} | ||
|
||
RadixTreeNode<T> * get_current_node() const { return m_current_node; } | ||
key_type get_unique_node() const { return m_unique_id; } | ||
|
||
|
@@ -179,5 +201,146 @@ class RadixTree | |
key_type m_unique_id = 0; | ||
}; /* end class RadixTree */ | ||
|
||
// The profiling result of the caller | ||
struct CallerProfile | ||
{ | ||
void start_stopwatch() | ||
{ | ||
start_time = std::chrono::high_resolution_clock::now(); | ||
is_running = true; | ||
} | ||
|
||
void stop_stopwatch() | ||
{ | ||
auto end_time = std::chrono::high_resolution_clock::now(); | ||
auto elapsed_time = std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time); | ||
total_time += elapsed_time; | ||
call_count++; | ||
} | ||
|
||
std::chrono::high_resolution_clock::time_point start_time; | ||
std::function<void()> cancel_callback; | ||
std::string caller_name; | ||
std::chrono::nanoseconds total_time = std::chrono::nanoseconds(0); /// use nanoseconds to have higher precision | ||
int call_count = 0; | ||
bool is_running = false; | ||
}; /* end struct CallerProfile */ | ||
|
||
namespace detail | ||
{ | ||
class CallProfilerTest; // for gtest | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You forgot the end marking for the namespace There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @yungyuc sorry, fixed. |
||
|
||
/// The profiler that profiles the hierarchical caller stack. | ||
class CallProfiler | ||
{ | ||
private: | ||
CallProfiler() = default; | ||
|
||
public: | ||
/// A singleton. | ||
static CallProfiler & instance() | ||
{ | ||
static CallProfiler instance; | ||
return instance; | ||
} | ||
|
||
CallProfiler(CallProfiler const &) = delete; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's hard to read because the default constructor is too far away from here. It's more readable to move the private default constructor here, e.g., private:
CallProfiler() = default;
public:
CallProfiler(CallProfiler const &) = delete;
//... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed. |
||
CallProfiler(CallProfiler &&) = delete; | ||
CallProfiler & operator=(CallProfiler const &) = delete; | ||
CallProfiler & operator=(CallProfiler &&) = delete; | ||
~CallProfiler() = default; | ||
|
||
// Called when a function starts | ||
void start_caller(const std::string & caller_name, std::function<void()> cancel_callback) | ||
{ | ||
auto start_time = std::chrono::high_resolution_clock::now(); | ||
m_radix_tree.entry(caller_name); | ||
CallerProfile & callProfile = m_radix_tree.get_current_node()->data(); | ||
callProfile.caller_name = caller_name; | ||
callProfile.start_stopwatch(); | ||
} | ||
|
||
// Called when a function ends | ||
void end_caller(const std::string & caller_name) | ||
{ | ||
CallerProfile & call_profile = m_radix_tree.get_current_node()->data(); | ||
call_profile.stop_stopwatch(); // Update profiling information | ||
m_radix_tree.move_current_to_parent(); // Pop the caller from the call stack | ||
} | ||
|
||
/// Print the profiling information | ||
void print_profiling_result(std::ostream & outstream) const | ||
{ | ||
print_profiling_result(*(m_radix_tree.get_current_node()), 0, outstream); | ||
} | ||
|
||
/// Reset the profiler | ||
void reset(); | ||
|
||
private: | ||
void print_profiling_result(const RadixTreeNode<CallerProfile> & node, const int depth, std::ostream & outstream) const; | ||
|
||
private: | ||
RadixTree<CallerProfile> m_radix_tree; /// the data structure of the callers | ||
|
||
friend detail::CallProfilerTest; | ||
}; /* end class CallProfiler */ | ||
|
||
/// Utility to profile a call | ||
class CallProfilerProbe | ||
{ | ||
public: | ||
CallProfilerProbe(CallProfiler & profiler, const char * caller_name) | ||
: m_profiler(profiler) | ||
, m_caller_name(caller_name) | ||
{ | ||
auto cancel_callback = [&]() | ||
{ | ||
cancel(); | ||
}; | ||
m_profiler.start_caller(m_caller_name, cancel_callback); | ||
} | ||
|
||
CallProfilerProbe(CallProfilerProbe const &) = delete; | ||
CallProfilerProbe(CallProfilerProbe &&) = delete; | ||
CallProfilerProbe & operator=(CallProfilerProbe const &) = delete; | ||
CallProfilerProbe & operator=(CallProfilerProbe &&) = delete; | ||
|
||
~CallProfilerProbe() | ||
{ | ||
if (!m_cancel) | ||
{ | ||
m_profiler.end_caller(m_caller_name); | ||
} | ||
} | ||
|
||
void cancel() | ||
{ | ||
m_cancel = true; | ||
} | ||
|
||
private: | ||
const char * m_caller_name; | ||
bool m_cancel = false; | ||
CallProfiler & m_profiler; | ||
}; /* end struct CallProfilerProbe */ | ||
|
||
#ifdef CALLPROFILER | ||
|
||
#ifdef _MSC_VER | ||
// ref: https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros | ||
#define __CROSS_PRETTY_FUNCTION__ __FUNCSIG__ | ||
#else | ||
// ref: https://gcc.gnu.org/onlinedocs/gcc/Function-Names.html | ||
#define __CROSS_PRETTY_FUNCTION__ __PRETTY_FUNCTION__ | ||
#endif | ||
#define USE_CALLPROFILER_PROFILE_THIS_FUNCTION() modmesh::CallProfilerProbe __profilerProbe##__COUNTER__(modmesh::CallProfiler::instance(), __CROSS_PRETTY_FUNCTION__) | ||
#define USE_CALLPROFILER_PROFILE_THIS_SCOPE(scopeName) modmesh::CallProfilerProbe __profilerProbe##__COUNTER__(modmesh::CallProfiler::instance(), scopeName) | ||
#else | ||
#define USE_CALLPROFILER_PROFILE_THIS_FUNCTION() // do nothing | ||
#define USE_CALLPROFILER_PROFILE_THIS_SCOPE(scopeName) // do nothing | ||
#endif | ||
|
||
} /* end namespace modmesh */ | ||
// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't you put it in gtest file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we cannot do that, otherwise it cannot build. If I put this line to gtests file, then we get:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, for accessing private members of
CallProfiler
? Ok, although I don't like it, it's not intolerable.But I think this
CallProfilerTest
should be at least put indetail
namespace to highlight that it is not meant as part of the API.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done