Skip to content
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

Merged
merged 32 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
65ad817
CallProfiler init
tigercosmos Nov 29, 2023
772d7cb
better comment
tigercosmos Nov 29, 2023
ce92877
fix value
tigercosmos Nov 29, 2023
47c4135
add cancel callback
tigercosmos Nov 29, 2023
8fbe787
add test_reset
tigercosmos Nov 29, 2023
d1bc0d1
correctly init std::chrono::milliseconds
tigercosmos Nov 29, 2023
090a6ad
fix include
tigercosmos Nov 29, 2023
317cbb7
tmp
tigercosmos Dec 1, 2023
621a2da
add get_child by string
tigercosmos Dec 3, 2023
d94c1c0
small fix
tigercosmos Dec 3, 2023
6c7b356
test for callprofiler
tigercosmos Dec 3, 2023
adc81c7
support windows
tigercosmos Dec 3, 2023
79b3354
try to make CI work
tigercosmos Dec 3, 2023
4b7e36c
try to fix error
tigercosmos Dec 3, 2023
b5ad167
update
tigercosmos Dec 3, 2023
025491f
fix time precision
tigercosmos Dec 3, 2023
de53ae4
Merge branch 'testProfiler' of github.com:tigercosmos/modmesh into te…
tigercosmos Dec 4, 2023
1b68d63
code review.
tigercosmos Dec 4, 2023
49da1dd
code review
tigercosmos Dec 4, 2023
513ce2b
put into detail
tigercosmos Dec 4, 2023
891f08e
fix lint
tigercosmos Dec 4, 2023
a9af889
fix test
tigercosmos Dec 4, 2023
92344d4
add is_root
tigercosmos Dec 4, 2023
e2ac398
update the implementation
tigercosmos Dec 4, 2023
766c0ec
update
tigercosmos Dec 3, 2023
fac22d6
fix macos test
tigercosmos Dec 4, 2023
d5953b1
fix windows naming
tigercosmos Dec 3, 2023
94f4140
code review
tigercosmos Dec 5, 2023
25a3623
missing include
tigercosmos Dec 4, 2023
e58a83b
format
tigercosmos Dec 5, 2023
3ac9b1e
Merge branch 'testProfiler' of github.com:tigercosmos/modmesh into te…
tigercosmos Dec 4, 2023
3d87ba7
add end namespace detail
tigercosmos Dec 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cpp/modmesh/toggle/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ set(MODMESH_TOGGLE_HEADERS

set(MODMESH_TOGGLE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/toggle.cpp
${CMAKE_CURRENT_SOURCE_DIR}/RadixTree.cpp
CACHE FILEPATH "" FORCE)

set(MODMESH_TOGGLE_PYMODHEADERS
Expand Down
76 changes: 76 additions & 0 deletions cpp/modmesh/toggle/RadixTree.cpp
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:
171 changes: 167 additions & 4 deletions cpp/modmesh/toggle/RadixTree.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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; }

Expand All @@ -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
Copy link
Member

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?

Copy link
Collaborator Author

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:

  RadixTree.cpp
C:\Users\tiger\modmesh\cpp\modmesh/toggle/RadixTree.hpp(297,28): error C2433: 'modmesh::CallProfilerTest': 'friend' not permitted on data decla
rations [C:\Users\tiger\modmesh\build\cpp\modmesh\modmesh_primary.vcxproj]
C:\Users\tiger\modmesh\cpp\modmesh/toggle/RadixTree.hpp(297,28): error C4430: missing type specifier - int assumed. Note: C++ does not support
default-int [C:\Users\tiger\modmesh\build\cpp\modmesh\modmesh_primary.vcxproj]

Copy link
Member

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 in detail namespace to highlight that it is not meant as part of the API.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

} /* end namespace detail */

/// 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;
Copy link
Member

Choose a reason for hiding this comment

The 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;
    //...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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:
2 changes: 2 additions & 0 deletions gtests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ add_executable(
test_nopython_modmesh.cpp
test_nopython_inout.cpp
test_nopython_radixtree.cpp
test_nopython_callprofiler.cpp
${MODMESH_TOGGLE_SOURCES}
)
target_link_libraries(
test_nopython
Expand Down
Loading
Loading