From d084f8d90addad7bcbd086955ad0cc397967a399 Mon Sep 17 00:00:00 2001 From: Jakub Berkop Date: Thu, 17 Feb 2022 22:26:20 +0100 Subject: [PATCH] Profiler: Present read event info in tree structure This commit adds "Filesystem Events" View to the Profiler. This tab will present combined information for recorded Filesystem events. Currently only accumulated count and duration is presented. Duration amount currently only shows events that took over 1ms, which means that in most cases 0 is show. --- Userland/DevTools/Profiler/CMakeLists.txt | 1 + .../Profiler/FilesystemEventModel.cpp | 189 ++++++++++++++++++ .../DevTools/Profiler/FilesystemEventModel.h | 94 +++++++++ Userland/DevTools/Profiler/Profile.cpp | 23 +++ Userland/DevTools/Profiler/Profile.h | 6 + Userland/DevTools/Profiler/main.cpp | 10 + 6 files changed, 323 insertions(+) create mode 100644 Userland/DevTools/Profiler/FilesystemEventModel.cpp create mode 100644 Userland/DevTools/Profiler/FilesystemEventModel.h diff --git a/Userland/DevTools/Profiler/CMakeLists.txt b/Userland/DevTools/Profiler/CMakeLists.txt index e3eacb425b..2380373878 100644 --- a/Userland/DevTools/Profiler/CMakeLists.txt +++ b/Userland/DevTools/Profiler/CMakeLists.txt @@ -9,6 +9,7 @@ set(SOURCES main.cpp IndividualSampleModel.cpp FlameGraphView.cpp + FilesystemEventModel.cpp Gradient.cpp Process.cpp Profile.cpp diff --git a/Userland/DevTools/Profiler/FilesystemEventModel.cpp b/Userland/DevTools/Profiler/FilesystemEventModel.cpp new file mode 100644 index 0000000000..96b9835cf1 --- /dev/null +++ b/Userland/DevTools/Profiler/FilesystemEventModel.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2022, Jakub Berkop + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "FilesystemEventModel.h" +#include "Profile.h" +#include +#include +#include +#include + +namespace Profiler { + +FileEventModel::FileEventModel(Profile& profile) + : m_profile(profile) +{ +} + +FileEventModel::~FileEventModel() +{ +} + +FileEventNode& FileEventNode::find_or_create_node(String const& searched_path) +{ + // TODO: Optimize this function. + + if (searched_path == ""sv) { + return *this; + } + + auto lex_path = LexicalPath(searched_path); + auto parts = lex_path.parts(); + auto current = parts.take_first(); + + StringBuilder sb; + sb.join("/", parts); + auto new_s = sb.to_string(); + + for (auto& child : m_children) { + if (child->m_path == current) { + return child->find_or_create_node(new_s); + } + } + + if (m_parent) { + for (auto& child : m_children) { + if (child->m_path == current) { + return child->find_or_create_node(new_s); + } + } + return create_recursively(searched_path); + } else { + if (!searched_path.starts_with("/"sv)) { + m_children.append(create(searched_path, this)); + return *m_children.last(); + } + + return create_recursively(searched_path); + } +} + +FileEventNode& FileEventNode::create_recursively(String new_path) +{ + auto const lex_path = LexicalPath(new_path); + auto parts = lex_path.parts(); + + if (parts.size() == 1) { + auto new_node = FileEventNode::create(new_path, this); + m_children.append(new_node); + return *new_node.ptr(); + } else { + auto new_node = FileEventNode::create(parts.take_first(), this); + m_children.append(new_node); + + StringBuilder sb; + sb.join("/", parts); + + return new_node->create_recursively(sb.to_string()); + } +} + +void FileEventNode::for_each_parent_node(Function callback) +{ + auto* current = this; + while (current) { + callback(*current); + current = current->m_parent; + } +} + +GUI::ModelIndex FileEventModel::index(int row, int column, GUI::ModelIndex const& parent) const +{ + if (!parent.is_valid()) { + return create_index(row, column, m_profile.file_event_nodes()->children()[row].ptr()); + } + auto& remote_parent = *static_cast(parent.internal_data()); + return create_index(row, column, remote_parent.children().at(row).ptr()); +} + +GUI::ModelIndex FileEventModel::parent_index(GUI::ModelIndex const& index) const +{ + if (!index.is_valid()) + return {}; + auto& node = *static_cast(index.internal_data()); + if (!node.parent()) + return {}; + + if (node.parent()->parent()) { + const auto& children = node.parent()->parent()->children(); + + for (size_t row = 0; row < children.size(); ++row) { + if (children.at(row).ptr() == node.parent()) { + return create_index(row, index.column(), node.parent()); + } + } + } + + const auto& children = node.parent()->children(); + + for (size_t row = 0; row < children.size(); ++row) { + if (children.at(row).ptr() == &node) { + return create_index(row, index.column(), node.parent()); + } + } + + VERIFY_NOT_REACHED(); + return {}; +} + +int FileEventModel::row_count(GUI::ModelIndex const& index) const +{ + if (!index.is_valid()) + return m_profile.file_event_nodes()->children().size(); + auto& node = *static_cast(index.internal_data()); + return node.children().size(); +} + +int FileEventModel::column_count(GUI::ModelIndex const&) const +{ + return Column::__Count; +} + +String FileEventModel::column_name(int column) const +{ + switch (column) { + case Column::Path: + return "Path"; + case Column::Count: + return "Event Count"; + case Column::Duration: + return "Duration [ms]"; + default: + VERIFY_NOT_REACHED(); + return {}; + } +} + +GUI::Variant FileEventModel::data(GUI::ModelIndex const& index, GUI::ModelRole role) const +{ + if (role == GUI::ModelRole::TextAlignment) { + if (index.column() == Path) + return Gfx::TextAlignment::CenterLeft; + return Gfx::TextAlignment::CenterRight; + } + + auto* node = static_cast(index.internal_data()); + + if (role == GUI::ModelRole::Display) { + if (index.column() == Column::Count) { + return node->count(); + } + + if (index.column() == Column::Path) { + return node->path(); + } + + if (index.column() == Column::Duration) { + return node->duration(); + } + + return {}; + } + + return {}; +} + +} diff --git a/Userland/DevTools/Profiler/FilesystemEventModel.h b/Userland/DevTools/Profiler/FilesystemEventModel.h new file mode 100644 index 0000000000..afea32653a --- /dev/null +++ b/Userland/DevTools/Profiler/FilesystemEventModel.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2022, Jakub Berkop + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Profiler { + +class Profile; + +class FileEventNode : public RefCounted { +public: + static NonnullRefPtr create(String const& path, FileEventNode* parent = nullptr) + { + return adopt_ref(*new FileEventNode(path, parent)); + } + + FileEventNode& find_or_create_node(String const&); + + Vector>& children() { return m_children; } + Vector> const& children() const { return m_children; } + + FileEventNode* parent() { return m_parent; }; + + FileEventNode& create_recursively(String); + + void for_each_parent_node(Function callback); + + String const& path() const { return m_path; } + + void increment_count() { m_count++; } + u64 count() const { return m_count; } + + void add_to_duration(u64 duration) { duration += duration; } + u64 duration() const { return m_duration; } + +private: + FileEventNode(String const& path, FileEventNode* parent = nullptr) + : m_path(path) + , m_count(0) + , m_duration(0) + , m_parent(parent) {}; + + String m_path; + u64 m_count; + u64 m_duration; + + Vector> m_children; + FileEventNode* m_parent = nullptr; +}; + +class FileEventModel final : public GUI::Model { +public: + static NonnullRefPtr create(Profile& profile) + { + return adopt_ref(*new FileEventModel(profile)); + } + + enum Column { + Path, + Count, + Duration, + __Count + }; + + virtual ~FileEventModel() override; + + virtual int row_count(GUI::ModelIndex const& = GUI::ModelIndex()) const override; + virtual int column_count(GUI::ModelIndex const& = GUI::ModelIndex()) const override; + virtual String column_name(int) const override; + virtual GUI::Variant data(GUI::ModelIndex const&, GUI::ModelRole) const override; + virtual GUI::ModelIndex index(int row, int column, GUI::ModelIndex const& parent = GUI::ModelIndex()) const override; + virtual GUI::ModelIndex parent_index(GUI::ModelIndex const&) const override; + virtual int tree_column() const override { return Column::Path; } + virtual bool is_column_sortable(int) const override { return false; } + virtual bool is_searchable() const override { return true; } + +private: + explicit FileEventModel(Profile&); + + Profile& m_profile; + + GUI::Icon m_user_frame_icon; + GUI::Icon m_kernel_frame_icon; +}; + +} diff --git a/Userland/DevTools/Profiler/Profile.cpp b/Userland/DevTools/Profiler/Profile.cpp index 1502e550fb..c94426fc0d 100644 --- a/Userland/DevTools/Profiler/Profile.cpp +++ b/Userland/DevTools/Profiler/Profile.cpp @@ -36,6 +36,7 @@ static void sort_profile_nodes(Vector>& nodes) Profile::Profile(Vector processes, Vector events) : m_processes(move(processes)) , m_events(move(events)) + , m_file_event_nodes(FileEventNode::create("")) { for (size_t i = 0; i < m_events.size(); ++i) { if (m_events[i].data.has()) @@ -48,6 +49,7 @@ Profile::Profile(Vector processes, Vector events) m_model = ProfileModel::create(*this); m_samples_model = SamplesModel::create(*this); m_signposts_model = SignpostsModel::create(*this); + m_file_event_model = FileEventModel::create(*this); rebuild_tree(); } @@ -101,6 +103,7 @@ void Profile::rebuild_tree() m_filtered_event_indices.clear(); m_filtered_signpost_indices.clear(); + m_file_event_nodes->children().clear(); for (size_t event_index = 0; event_index < m_events.size(); ++event_index) { auto& event = m_events.at(event_index); @@ -205,6 +208,21 @@ void Profile::rebuild_tree() } } } + + if (event.data.has()) { + auto const& read_event = event.data.get(); + auto& event_node = m_file_event_nodes->find_or_create_node(read_event.path); + + event_node.for_each_parent_node([&](FileEventNode& node) { + node.increment_count(); + + // Fixme: Currently events record 'timestamp' and 'start_timestamp' in ms resolution, + // which results in most durations equal to zero. Increasing the resolution should + // make the information more accurate. + auto const duration = event.timestamp - read_event.start_timestamp; + node.add_to_duration(duration); + }); + } } sort_profile_nodes(roots); @@ -581,6 +599,11 @@ GUI::Model* Profile::source_model() return m_source_model; } +GUI::Model* Profile::file_event_model() +{ + return m_file_event_model; +} + ProfileNode::ProfileNode(Process const& process) : m_root(true) , m_process(process) diff --git a/Userland/DevTools/Profiler/Profile.h b/Userland/DevTools/Profiler/Profile.h index 2881b8201f..27f24d4817 100644 --- a/Userland/DevTools/Profiler/Profile.h +++ b/Userland/DevTools/Profiler/Profile.h @@ -7,6 +7,7 @@ #pragma once #include "DisassemblyModel.h" +#include "FilesystemEventModel.h" #include "Process.h" #include "Profile.h" #include "ProfileModel.h" @@ -149,6 +150,7 @@ public: GUI::Model& signposts_model(); GUI::Model* disassembly_model(); GUI::Model* source_model(); + GUI::Model* file_event_model(); Process const* find_process(pid_t pid, EventSerialNumber serial) const { @@ -235,6 +237,7 @@ public: Vector const& events() const { return m_events; } Vector const& filtered_event_indices() const { return m_filtered_event_indices; } Vector const& filtered_signpost_indices() const { return m_filtered_signpost_indices; } + NonnullRefPtr const& file_event_nodes() { return m_file_event_nodes; } u64 length_in_ms() const { return m_last_timestamp - m_first_timestamp; } u64 first_timestamp() const { return m_first_timestamp; } @@ -293,6 +296,7 @@ private: RefPtr m_signposts_model; RefPtr m_disassembly_model; RefPtr m_source_model; + RefPtr m_file_event_model; GUI::ModelIndex m_disassembly_index; GUI::ModelIndex m_source_index; @@ -313,6 +317,8 @@ private: Vector m_process_filters; + NonnullRefPtr m_file_event_nodes; + bool m_inverted { false }; bool m_show_top_functions { false }; bool m_show_percentages { false }; diff --git a/Userland/DevTools/Profiler/main.cpp b/Userland/DevTools/Profiler/main.cpp index 59ace5eee7..3fd79f0b75 100644 --- a/Userland/DevTools/Profiler/main.cpp +++ b/Userland/DevTools/Profiler/main.cpp @@ -252,6 +252,16 @@ ErrorOr serenity_main(Main::Arguments arguments) timeline_view->on_selection_change = [&] { statusbar_update(); }; flamegraph_view->on_hover_change = [&] { statusbar_update(); }; + auto filesystem_events_tab = TRY(tab_widget->try_add_tab("Filesystem events")); + filesystem_events_tab->set_layout(); + filesystem_events_tab->layout()->set_margins(4); + + auto filesystem_events_tree_view = TRY(filesystem_events_tab->try_add()); + filesystem_events_tree_view->set_should_fill_selected_rows(true); + filesystem_events_tree_view->set_column_headers_visible(true); + filesystem_events_tree_view->set_selection_behavior(GUI::TreeView::SelectionBehavior::SelectRows); + filesystem_events_tree_view->set_model(profile->file_event_model()); + auto file_menu = TRY(window->try_add_menu("&File")); TRY(file_menu->try_add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(); })));