1
Fork 0
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:
Jakub Berkop 2022-02-17 22:26:20 +01:00 committed by Andreas Kling
parent bc82b3eaec
commit d084f8d90a
6 changed files with 323 additions and 0 deletions

View file

@ -9,6 +9,7 @@ set(SOURCES
main.cpp
IndividualSampleModel.cpp
FlameGraphView.cpp
FilesystemEventModel.cpp
Gradient.cpp
Process.cpp
Profile.cpp

View 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 {};
}
}

View 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;
};
}

View file

@ -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)

View file

@ -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 };

View file

@ -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(); })));