From be4005cb9ebc9d45f7e9f1e0129db66c6ecc36cd Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 19 Oct 2020 15:04:54 +0200 Subject: [PATCH] Profiler: Implement "Top functions" feature like Instruments.app has This view mode takes every stack frame and turns it into a root in the profile graph. This allows functions that are called from many places to bubble up to the top. It's a very handy way to discover heavy things in a profile that are otherwise obscured by having many callers. --- DevTools/Profiler/Profile.cpp | 81 +++++++++++++++++++++++++++-------- DevTools/Profiler/Profile.h | 14 ++++++ DevTools/Profiler/main.cpp | 7 +++ 3 files changed, 83 insertions(+), 19 deletions(-) diff --git a/DevTools/Profiler/Profile.cpp b/DevTools/Profiler/Profile.cpp index 685671ce51..8e5a7892f1 100644 --- a/DevTools/Profiler/Profile.cpp +++ b/DevTools/Profiler/Profile.cpp @@ -102,7 +102,8 @@ void Profile::rebuild_tree() live_allocations.remove(event.ptr); } - for (auto& event : m_events) { + for (size_t event_index = 0; event_index < m_events.size(); ++event_index) { + auto& event = m_events.at(event_index); if (has_timestamp_filter_range()) { auto timestamp = event.timestamp; if (timestamp < m_timestamp_filter_range_start || timestamp > m_timestamp_filter_range_end) @@ -115,8 +116,6 @@ void Profile::rebuild_tree() if (event.type == "free") continue; - ProfileNode* node = nullptr; - auto for_each_frame = [&](Callback callback) { if (!m_inverted) { for (size_t i = 0; i < event.frames.size(); ++i) { @@ -131,26 +130,62 @@ void Profile::rebuild_tree() } }; - for_each_frame([&](const Frame& frame, bool is_innermost_frame) { - auto& symbol = frame.symbol; - auto& address = frame.address; - auto& offset = frame.offset; + if (!m_show_top_functions) { + ProfileNode* node = nullptr; + for_each_frame([&](const Frame& frame, bool is_innermost_frame) { + auto& symbol = frame.symbol; + auto& address = frame.address; + auto& offset = frame.offset; - if (symbol.is_empty()) - return IterationDecision::Break; + if (symbol.is_empty()) + return IterationDecision::Break; - if (!node) - node = &find_or_create_root(symbol, address, offset, event.timestamp); - else - node = &node->find_or_create_child(symbol, address, offset, event.timestamp); + if (!node) + node = &find_or_create_root(symbol, address, offset, event.timestamp); + else + node = &node->find_or_create_child(symbol, address, offset, event.timestamp); - node->increment_event_count(); - if (is_innermost_frame) { - node->add_event_address(address); - node->increment_self_count(); + node->increment_event_count(); + if (is_innermost_frame) { + node->add_event_address(address); + node->increment_self_count(); + } + return IterationDecision::Continue; + }); + } else { + for (size_t i = 0; i < event.frames.size(); ++i) { + ProfileNode* node = nullptr; + ProfileNode* root = nullptr; + for (size_t j = i; j < event.frames.size(); ++j) { + auto& frame = event.frames.at(j); + auto& symbol = frame.symbol; + auto& address = frame.address; + auto& offset = frame.offset; + if (symbol.is_empty()) + break; + + if (!node) { + node = &find_or_create_root(symbol, address, offset, event.timestamp); + root = node; + root->will_track_seen_events(m_events.size()); + } else { + node = &node->find_or_create_child(symbol, address, offset, event.timestamp); + } + + if (!root->has_seen_event(event_index)) { + root->did_see_event(event_index); + root->increment_event_count(); + } else if (node != root) { + node->increment_event_count(); + } + + if (j == event.frames.size() - 1) { + node->add_event_address(address); + node->increment_self_count(); + } + } } - return IterationDecision::Continue; - }); + } ++filtered_event_count; } @@ -283,6 +318,14 @@ void Profile::set_inverted(bool inverted) rebuild_tree(); } +void Profile::set_show_top_functions(bool show) +{ + if (m_show_top_functions == show) + return; + m_show_top_functions = show; + rebuild_tree(); +} + void Profile::set_show_percentages(bool show_percentages) { if (m_show_percentages == show_percentages) diff --git a/DevTools/Profiler/Profile.h b/DevTools/Profiler/Profile.h index 45a02ddfe7..88cb07339f 100644 --- a/DevTools/Profiler/Profile.h +++ b/DevTools/Profiler/Profile.h @@ -26,6 +26,7 @@ #pragma once +#include #include #include #include @@ -44,6 +45,15 @@ public: return adopt(*new ProfileNode(symbol, address, offset, timestamp)); } + // These functions are only relevant for root nodes + void will_track_seen_events(size_t profile_event_count) + { + if (m_seen_events.size() != profile_event_count) + m_seen_events = Bitmap::create(profile_event_count, false); + } + bool has_seen_event(size_t event_index) const { return m_seen_events.get(event_index); } + void did_see_event(size_t event_index) { m_seen_events.set(event_index, true); } + const String& symbol() const { return m_symbol; } u32 address() const { return m_address; } u32 offset() const { return m_offset; } @@ -113,6 +123,7 @@ private: u64 m_timestamp { 0 }; Vector> m_children; HashMap m_events_per_address; + Bitmap m_seen_events; }; class Profile { @@ -158,6 +169,8 @@ public: bool is_inverted() const { return m_inverted; } void set_inverted(bool); + void set_show_top_functions(bool); + bool show_percentages() const { return m_show_percentages; } void set_show_percentages(bool); @@ -188,5 +201,6 @@ private: u32 m_deepest_stack_depth { 0 }; bool m_inverted { false }; + bool m_show_top_functions { false }; bool m_show_percentages { false }; }; diff --git a/DevTools/Profiler/main.cpp b/DevTools/Profiler/main.cpp index cd42414b91..fce799e725 100644 --- a/DevTools/Profiler/main.cpp +++ b/DevTools/Profiler/main.cpp @@ -107,12 +107,19 @@ int main(int argc, char** argv) app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(); })); auto& view_menu = menubar->add_menu("View"); + auto invert_action = GUI::Action::create_checkable("Invert tree", { Mod_Ctrl, Key_I }, [&](auto& action) { profile->set_inverted(action.is_checked()); }); invert_action->set_checked(false); view_menu.add_action(invert_action); + auto top_functions_action = GUI::Action::create_checkable("Top functions", { Mod_Ctrl, Key_T }, [&](auto& action) { + profile->set_show_top_functions(action.is_checked()); + }); + top_functions_action->set_checked(false); + view_menu.add_action(top_functions_action); + auto percent_action = GUI::Action::create_checkable("Show percentages", { Mod_Ctrl, Key_P }, [&](auto& action) { profile->set_show_percentages(action.is_checked()); tree_view.update();