mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 03:55:24 +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:
parent
46cc1f718e
commit
be4005cb9e
Notes:
sideshowbarker
2024-07-19 01:50:34 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/be4005cb9eb
3 changed files with 83 additions and 19 deletions
|
@ -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 = [&]<typename Callback>(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)
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Bitmap.h>
|
||||
#include <AK/JsonArray.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/JsonValue.h>
|
||||
|
@ -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<NonnullRefPtr<ProfileNode>> m_children;
|
||||
HashMap<FlatPtr, size_t> 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 };
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Reference in a new issue