mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 05:37:35 +00:00
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.
This commit is contained in:
parent
bc82b3eaec
commit
d084f8d90a
6 changed files with 323 additions and 0 deletions
|
@ -9,6 +9,7 @@ set(SOURCES
|
|||
main.cpp
|
||||
IndividualSampleModel.cpp
|
||||
FlameGraphView.cpp
|
||||
FilesystemEventModel.cpp
|
||||
Gradient.cpp
|
||||
Process.cpp
|
||||
Profile.cpp
|
||||
|
|
189
Userland/DevTools/Profiler/FilesystemEventModel.cpp
Normal file
189
Userland/DevTools/Profiler/FilesystemEventModel.cpp
Normal file
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Jakub Berkop <jakub.berkop@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "FilesystemEventModel.h"
|
||||
#include "Profile.h"
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibGUI/FileIconProvider.h>
|
||||
#include <LibSymbolication/Symbolication.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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<void(FileEventNode&)> 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<FileEventNode*>(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<FileEventNode*>(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<FileEventNode*>(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<FileEventNode*>(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 {};
|
||||
}
|
||||
|
||||
}
|
94
Userland/DevTools/Profiler/FilesystemEventModel.h
Normal file
94
Userland/DevTools/Profiler/FilesystemEventModel.h
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Jakub Berkop <jakub.berkop@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibGUI/Model.h>
|
||||
|
||||
namespace Profiler {
|
||||
|
||||
class Profile;
|
||||
|
||||
class FileEventNode : public RefCounted<FileEventNode> {
|
||||
public:
|
||||
static NonnullRefPtr<FileEventNode> create(String const& path, FileEventNode* parent = nullptr)
|
||||
{
|
||||
return adopt_ref(*new FileEventNode(path, parent));
|
||||
}
|
||||
|
||||
FileEventNode& find_or_create_node(String const&);
|
||||
|
||||
Vector<NonnullRefPtr<FileEventNode>>& children() { return m_children; }
|
||||
Vector<NonnullRefPtr<FileEventNode>> const& children() const { return m_children; }
|
||||
|
||||
FileEventNode* parent() { return m_parent; };
|
||||
|
||||
FileEventNode& create_recursively(String);
|
||||
|
||||
void for_each_parent_node(Function<void(FileEventNode&)> 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<NonnullRefPtr<FileEventNode>> m_children;
|
||||
FileEventNode* m_parent = nullptr;
|
||||
};
|
||||
|
||||
class FileEventModel final : public GUI::Model {
|
||||
public:
|
||||
static NonnullRefPtr<FileEventModel> 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;
|
||||
};
|
||||
|
||||
}
|
|
@ -36,6 +36,7 @@ static void sort_profile_nodes(Vector<NonnullRefPtr<ProfileNode>>& nodes)
|
|||
Profile::Profile(Vector<Process> processes, Vector<Event> 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<Event::SignpostData>())
|
||||
|
@ -48,6 +49,7 @@ Profile::Profile(Vector<Process> processes, Vector<Event> 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<Event::ReadData>()) {
|
||||
auto const& read_event = event.data.get<Event::ReadData>();
|
||||
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)
|
||||
|
|
|
@ -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<Event> const& events() const { return m_events; }
|
||||
Vector<size_t> const& filtered_event_indices() const { return m_filtered_event_indices; }
|
||||
Vector<size_t> const& filtered_signpost_indices() const { return m_filtered_signpost_indices; }
|
||||
NonnullRefPtr<FileEventNode> 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<SignpostsModel> m_signposts_model;
|
||||
RefPtr<DisassemblyModel> m_disassembly_model;
|
||||
RefPtr<SourceModel> m_source_model;
|
||||
RefPtr<FileEventModel> m_file_event_model;
|
||||
|
||||
GUI::ModelIndex m_disassembly_index;
|
||||
GUI::ModelIndex m_source_index;
|
||||
|
@ -313,6 +317,8 @@ private:
|
|||
|
||||
Vector<ProcessFilter> m_process_filters;
|
||||
|
||||
NonnullRefPtr<FileEventNode> m_file_event_nodes;
|
||||
|
||||
bool m_inverted { false };
|
||||
bool m_show_top_functions { false };
|
||||
bool m_show_percentages { false };
|
||||
|
|
|
@ -252,6 +252,16 @@ ErrorOr<int> 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<GUI::Widget>("Filesystem events"));
|
||||
filesystem_events_tab->set_layout<GUI::VerticalBoxLayout>();
|
||||
filesystem_events_tab->layout()->set_margins(4);
|
||||
|
||||
auto filesystem_events_tree_view = TRY(filesystem_events_tab->try_add<GUI::TreeView>());
|
||||
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(); })));
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue