From 19d8c675f1d357d65580b2311a349e3b2a2ac036 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Thu, 12 Dec 2019 22:01:06 +0100 Subject: [PATCH] ProfileViewer: Begin work on a visualization tool for profiles :^) We begin with a simple treeview that shows a recorded profile. To record and view a profile of a process with , simply do this: $ profile on ... wait while PID does something interesting ... $ profile off $ cat /proc/profile > my-profile.prof $ ProfileViewer my-profile.prof --- DevTools/ProfileViewer/Makefile | 24 +++++++ DevTools/ProfileViewer/Profile.cpp | 91 +++++++++++++++++++++++++ DevTools/ProfileViewer/Profile.h | 88 ++++++++++++++++++++++++ DevTools/ProfileViewer/ProfileModel.cpp | 83 ++++++++++++++++++++++ DevTools/ProfileViewer/ProfileModel.h | 28 ++++++++ DevTools/ProfileViewer/main.cpp | 33 +++++++++ Kernel/build-root-filesystem.sh | 2 + Kernel/makeall.sh | 1 + 8 files changed, 350 insertions(+) create mode 100644 DevTools/ProfileViewer/Makefile create mode 100644 DevTools/ProfileViewer/Profile.cpp create mode 100644 DevTools/ProfileViewer/Profile.h create mode 100644 DevTools/ProfileViewer/ProfileModel.cpp create mode 100644 DevTools/ProfileViewer/ProfileModel.h create mode 100644 DevTools/ProfileViewer/main.cpp diff --git a/DevTools/ProfileViewer/Makefile b/DevTools/ProfileViewer/Makefile new file mode 100644 index 0000000000..d405715d7a --- /dev/null +++ b/DevTools/ProfileViewer/Makefile @@ -0,0 +1,24 @@ +include ../../Makefile.common + +OBJS = \ + Profile.o \ + ProfileModel.o \ + main.o + +APP = ProfileViewer + +DEFINES += -DUSERLAND + +all: $(APP) + +$(APP): $(OBJS) + $(LD) -o $(APP) $(LDFLAGS) $(OBJS) -lgui -ldraw -lipc -lcore -lc + +.cpp.o: + @echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $< + +-include $(OBJS:%.o=%.d) + +clean: + @echo "CLEAN"; rm -f $(APP) $(OBJS) *.d + diff --git a/DevTools/ProfileViewer/Profile.cpp b/DevTools/ProfileViewer/Profile.cpp new file mode 100644 index 0000000000..2271b710b0 --- /dev/null +++ b/DevTools/ProfileViewer/Profile.cpp @@ -0,0 +1,91 @@ +#include "Profile.h" +#include "ProfileModel.h" +#include +#include +#include + +Profile::Profile(const JsonArray& json, NonnullRefPtrVector&& roots) + : m_json(json) + , m_roots(move(roots)) +{ + m_model = ProfileModel::create(*this); +} + +Profile::~Profile() +{ +} + +GModel& Profile::model() +{ + return *m_model; +} + +OwnPtr Profile::load_from_file(const StringView& path) +{ + auto file = CFile::construct(path); + if (!file->open(CIODevice::ReadOnly)) { + fprintf(stderr, "Unable to open %s, error: %s\n", String(path).characters(), file->error_string()); + return nullptr; + } + + auto json = JsonValue::from_string(file->read_all()); + if (!json.is_array()) { + fprintf(stderr, "Invalid format (not a JSON array)\n"); + return nullptr; + } + + auto samples = json.as_array(); + + NonnullRefPtrVector roots; + + auto find_or_create_root = [&roots](const String& symbol, u32 address, u32 offset) -> ProfileNode& { + for (int i = 0; i < roots.size(); ++i) { + auto& root = roots[i]; + if (root.symbol() == symbol) { + return root; + } + } + auto new_root = ProfileNode::create(symbol, address, offset); + roots.append(new_root); + return new_root; + }; + + samples.for_each([&](const JsonValue& sample) { + auto frames_value = sample.as_object().get("frames"); + auto& frames = frames_value.as_array(); + ProfileNode* node = nullptr; + for (int i = frames.size() - 1; i >= 0; --i) { + auto& frame = frames.at(i); + + auto symbol = frame.as_object().get("symbol").as_string_or({}); + auto address = frame.as_object().get("address").as_u32(); + auto offset = frame.as_object().get("offset").as_u32(); + + if (symbol.is_empty()) + break; + + if (!node) + node = &find_or_create_root(symbol, address, offset); + else + node = &node->find_or_create_child(symbol, address, offset); + + node->increment_sample_count(); + } + }); + + for (auto& root : roots) { + root.sort_children(); + } + + return NonnullOwnPtr(NonnullOwnPtr::Adopt, *new Profile(move(samples), move(roots))); +} + +void ProfileNode::sort_children() +{ + quick_sort(m_children.begin(), m_children.end(), [](auto& a, auto& b) { + return a->sample_count() >= b->sample_count(); + }); + + for (auto& child : m_children) + child->sort_children(); +} diff --git a/DevTools/ProfileViewer/Profile.h b/DevTools/ProfileViewer/Profile.h new file mode 100644 index 0000000000..8e73b624e2 --- /dev/null +++ b/DevTools/ProfileViewer/Profile.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include +#include + +class GModel; +class ProfileModel; + +class ProfileNode : public RefCounted { +public: + static NonnullRefPtr create(const String& symbol, u32 address, u32 offset) + { + return adopt(*new ProfileNode(symbol, address, offset)); + } + + const String& symbol() const { return m_symbol; } + u32 address() const { return m_address; } + u32 offset() const { return m_offset; } + + u32 sample_count() const { return m_sample_count; } + + int child_count() const { return m_children.size(); } + const Vector>& children() const { return m_children; } + + void add_child(ProfileNode& child) + { + if (child.m_parent == this) + return; + ASSERT(!child.m_parent); + child.m_parent = this; + m_children.append(child); + } + + ProfileNode& find_or_create_child(const String& symbol, u32 address, u32 offset) + { + for (int i = 0; i < m_children.size(); ++i) { + auto& child = m_children[i]; + if (child->symbol() == symbol) { + return child; + } + } + auto new_child = ProfileNode::create(symbol, address, offset); + m_children.append(new_child); + return new_child; + }; + + ProfileNode* parent() { return m_parent; } + const ProfileNode* parent() const { return m_parent; } + + void increment_sample_count() { ++m_sample_count; } + + void sort_children(); + +private: + explicit ProfileNode(const String& symbol, u32 address, u32 offset) + : m_symbol(symbol) + , m_address(address) + , m_offset(offset) + { + } + + ProfileNode* m_parent { nullptr }; + String m_symbol; + u32 m_address { 0 }; + u32 m_offset { 0 }; + u32 m_sample_count { 0 }; + Vector> m_children; +}; + +class Profile { +public: + static OwnPtr load_from_file(const StringView& path); + ~Profile(); + + GModel& model(); + + const NonnullRefPtrVector& roots() const { return m_roots; } + +private: + explicit Profile(const JsonArray&, NonnullRefPtrVector&&); + + JsonArray m_json; + RefPtr m_model; + NonnullRefPtrVector m_roots; +}; diff --git a/DevTools/ProfileViewer/ProfileModel.cpp b/DevTools/ProfileViewer/ProfileModel.cpp new file mode 100644 index 0000000000..8e796d0096 --- /dev/null +++ b/DevTools/ProfileViewer/ProfileModel.cpp @@ -0,0 +1,83 @@ +#include "ProfileModel.h" +#include "Profile.h" +#include +#include +#include + +ProfileModel::ProfileModel(Profile& profile) + : m_profile(profile) +{ + m_frame_icon.set_bitmap_for_size(16, GraphicsBitmap::load_from_file("/res/icons/16x16/inspector-object.png")); +} + +ProfileModel::~ProfileModel() +{ +} + +GModelIndex ProfileModel::index(int row, int column, const GModelIndex& parent) const +{ + if (!parent.is_valid()) { + if (m_profile.roots().is_empty()) + return {}; + return create_index(row, column, &m_profile.roots().at(row)); + } + auto& remote_parent = *static_cast(parent.internal_data()); + return create_index(row, column, remote_parent.children().at(row).ptr()); +} + +GModelIndex ProfileModel::parent_index(const GModelIndex& index) const +{ + if (!index.is_valid()) + return {}; + auto& node = *static_cast(index.internal_data()); + if (!node.parent()) + return {}; + + // NOTE: If the parent has no parent, it's a root, so we have to look among the remote roots. + if (!node.parent()->parent()) { + for (int row = 0; row < m_profile.roots().size(); ++row) { + if (&m_profile.roots()[row] == node.parent()) + return create_index(row, 0, node.parent()); + } + ASSERT_NOT_REACHED(); + return {}; + } + + for (int row = 0; row < node.parent()->parent()->children().size(); ++row) { + if (node.parent()->parent()->children()[row].ptr() == node.parent()) + return create_index(row, 0, node.parent()); + } + + ASSERT_NOT_REACHED(); + return {}; +} + +int ProfileModel::row_count(const GModelIndex& index) const +{ + if (!index.is_valid()) + return m_profile.roots().size(); + auto& node = *static_cast(index.internal_data()); + return node.children().size(); +} + +int ProfileModel::column_count(const GModelIndex&) const +{ + return 1; +} + +GVariant ProfileModel::data(const GModelIndex& index, Role role) const +{ + auto* node = static_cast(index.internal_data()); + if (role == Role::Icon) { + return m_frame_icon; + } + if (role == Role::Display) { + return String::format("%s (%u)", node->symbol().characters(), node->sample_count()); + } + return {}; +} + +void ProfileModel::update() +{ + did_update(); +} diff --git a/DevTools/ProfileViewer/ProfileModel.h b/DevTools/ProfileViewer/ProfileModel.h new file mode 100644 index 0000000000..c3312b61ea --- /dev/null +++ b/DevTools/ProfileViewer/ProfileModel.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +class Profile; + +class ProfileModel final : public GModel { +public: + static NonnullRefPtr create(Profile& profile) + { + return adopt(*new ProfileModel(profile)); + } + + virtual ~ProfileModel() override; + + virtual int row_count(const GModelIndex& = GModelIndex()) const override; + virtual int column_count(const GModelIndex& = GModelIndex()) const override; + virtual GVariant data(const GModelIndex&, Role = Role::Display) const override; + virtual GModelIndex index(int row, int column, const GModelIndex& parent = GModelIndex()) const override; + virtual GModelIndex parent_index(const GModelIndex&) const override; + virtual void update() override; + +private: + explicit ProfileModel(Profile&); + + Profile& m_profile; + GIcon m_frame_icon; +}; diff --git a/DevTools/ProfileViewer/main.cpp b/DevTools/ProfileViewer/main.cpp new file mode 100644 index 0000000000..0ceca5bf68 --- /dev/null +++ b/DevTools/ProfileViewer/main.cpp @@ -0,0 +1,33 @@ +#include "Profile.h" +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (argc != 2) { + printf("usage: %s \n", argv[0]); + return 0; + } + + auto profile = Profile::load_from_file(argv[1]); + if (!profile) { + fprintf(stderr, "Unable to load profile '%s'\n", argv[1]); + return 1; + } + + GApplication app(argc, argv); + + auto window = GWindow::construct(); + window->set_title("ProfileViewer"); + window->set_rect(100, 100, 800, 600); + + auto tree_view = GTreeView::construct(nullptr); + tree_view->set_model(profile->model()); + + window->set_main_widget(tree_view); + + window->show(); + return app.exec(); +} diff --git a/Kernel/build-root-filesystem.sh b/Kernel/build-root-filesystem.sh index c513e10b42..769d0ca5df 100755 --- a/Kernel/build-root-filesystem.sh +++ b/Kernel/build-root-filesystem.sh @@ -98,6 +98,7 @@ cp ../Demos/Fire/Fire mnt/bin/Fire cp ../DevTools/HackStudio/HackStudio mnt/bin/HackStudio cp ../DevTools/VisualBuilder/VisualBuilder mnt/bin/VisualBuilder cp ../DevTools/Inspector/Inspector mnt/bin/Inspector +cp ../DevTools/ProfileViewer/ProfileViewer mnt/bin/ProfileViewer cp ../Games/Minesweeper/Minesweeper mnt/bin/Minesweeper cp ../Games/Snake/Snake mnt/bin/Snake cp ../Servers/LookupServer/LookupServer mnt/bin/LookupServer @@ -136,6 +137,7 @@ ln -s Help mnt/bin/help ln -s Browser mnt/bin/br ln -s HackStudio mnt/bin/hs ln -s SystemMonitor mnt/bin/sm +ln -s ProfileViewer mnt/bin/pv echo "done" mkdir -p mnt/boot/ diff --git a/Kernel/makeall.sh b/Kernel/makeall.sh index 309aafac43..d0d8879251 100755 --- a/Kernel/makeall.sh +++ b/Kernel/makeall.sh @@ -80,6 +80,7 @@ build_targets="$build_targets ../Demos/WidgetGallery" build_targets="$build_targets ../DevTools/HackStudio" build_targets="$build_targets ../DevTools/VisualBuilder" build_targets="$build_targets ../DevTools/Inspector" +build_targets="$build_targets ../DevTools/ProfileViewer" build_targets="$build_targets ../Games/Minesweeper" build_targets="$build_targets ../Games/Snake"