1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-26 01:45:06 +00:00

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.
This commit is contained in:
Andreas Kling 2020-10-19 15:04:54 +02:00
parent 46cc1f718e
commit be4005cb9e
3 changed files with 83 additions and 19 deletions

View file

@ -102,7 +102,8 @@ void Profile::rebuild_tree()
live_allocations.remove(event.ptr); 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()) { if (has_timestamp_filter_range()) {
auto timestamp = event.timestamp; auto timestamp = event.timestamp;
if (timestamp < m_timestamp_filter_range_start || timestamp > m_timestamp_filter_range_end) 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") if (event.type == "free")
continue; continue;
ProfileNode* node = nullptr;
auto for_each_frame = [&]<typename Callback>(Callback callback) { auto for_each_frame = [&]<typename Callback>(Callback callback) {
if (!m_inverted) { if (!m_inverted) {
for (size_t i = 0; i < event.frames.size(); ++i) { 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) { if (!m_show_top_functions) {
auto& symbol = frame.symbol; ProfileNode* node = nullptr;
auto& address = frame.address; for_each_frame([&](const Frame& frame, bool is_innermost_frame) {
auto& offset = frame.offset; auto& symbol = frame.symbol;
auto& address = frame.address;
auto& offset = frame.offset;
if (symbol.is_empty()) if (symbol.is_empty())
return IterationDecision::Break; return IterationDecision::Break;
if (!node) if (!node)
node = &find_or_create_root(symbol, address, offset, event.timestamp); node = &find_or_create_root(symbol, address, offset, event.timestamp);
else else
node = &node->find_or_create_child(symbol, address, offset, event.timestamp); node = &node->find_or_create_child(symbol, address, offset, event.timestamp);
node->increment_event_count(); node->increment_event_count();
if (is_innermost_frame) { if (is_innermost_frame) {
node->add_event_address(address); node->add_event_address(address);
node->increment_self_count(); 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; ++filtered_event_count;
} }
@ -283,6 +318,14 @@ void Profile::set_inverted(bool inverted)
rebuild_tree(); 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) void Profile::set_show_percentages(bool show_percentages)
{ {
if (m_show_percentages == show_percentages) if (m_show_percentages == show_percentages)

View file

@ -26,6 +26,7 @@
#pragma once #pragma once
#include <AK/Bitmap.h>
#include <AK/JsonArray.h> #include <AK/JsonArray.h>
#include <AK/JsonObject.h> #include <AK/JsonObject.h>
#include <AK/JsonValue.h> #include <AK/JsonValue.h>
@ -44,6 +45,15 @@ public:
return adopt(*new ProfileNode(symbol, address, offset, timestamp)); 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; } const String& symbol() const { return m_symbol; }
u32 address() const { return m_address; } u32 address() const { return m_address; }
u32 offset() const { return m_offset; } u32 offset() const { return m_offset; }
@ -113,6 +123,7 @@ private:
u64 m_timestamp { 0 }; u64 m_timestamp { 0 };
Vector<NonnullRefPtr<ProfileNode>> m_children; Vector<NonnullRefPtr<ProfileNode>> m_children;
HashMap<FlatPtr, size_t> m_events_per_address; HashMap<FlatPtr, size_t> m_events_per_address;
Bitmap m_seen_events;
}; };
class Profile { class Profile {
@ -158,6 +169,8 @@ public:
bool is_inverted() const { return m_inverted; } bool is_inverted() const { return m_inverted; }
void set_inverted(bool); void set_inverted(bool);
void set_show_top_functions(bool);
bool show_percentages() const { return m_show_percentages; } bool show_percentages() const { return m_show_percentages; }
void set_show_percentages(bool); void set_show_percentages(bool);
@ -188,5 +201,6 @@ private:
u32 m_deepest_stack_depth { 0 }; u32 m_deepest_stack_depth { 0 };
bool m_inverted { false }; bool m_inverted { false };
bool m_show_top_functions { false };
bool m_show_percentages { false }; bool m_show_percentages { false };
}; };

View file

@ -107,12 +107,19 @@ int main(int argc, char** argv)
app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(); })); app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(); }));
auto& view_menu = menubar->add_menu("View"); auto& view_menu = menubar->add_menu("View");
auto invert_action = GUI::Action::create_checkable("Invert tree", { Mod_Ctrl, Key_I }, [&](auto& action) { auto invert_action = GUI::Action::create_checkable("Invert tree", { Mod_Ctrl, Key_I }, [&](auto& action) {
profile->set_inverted(action.is_checked()); profile->set_inverted(action.is_checked());
}); });
invert_action->set_checked(false); invert_action->set_checked(false);
view_menu.add_action(invert_action); 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) { auto percent_action = GUI::Action::create_checkable("Show percentages", { Mod_Ctrl, Key_P }, [&](auto& action) {
profile->set_show_percentages(action.is_checked()); profile->set_show_percentages(action.is_checked());
tree_view.update(); tree_view.update();