diff options
author | Andreas Kling <awesomekling@gmail.com> | 2019-12-14 18:44:29 +0100 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-12-14 18:44:29 +0100 |
commit | a3e7c99ffea8bd5f70edf9d6ef941c4e7303833f (patch) | |
tree | 6a4577fc2d7c6f74f4d4172fa6d7e32e244318b0 | |
parent | 46a57c7f5905b8d82f1c810df8b6cc5a7f5970a6 (diff) | |
download | serenity-a3e7c99ffea8bd5f70edf9d6ef941c4e7303833f.zip |
ProfileViewer: Add a timeline widget for a visual view of the profile
Userspace stack frames are in blue, kernel stack frames in red :^)
-rw-r--r-- | DevTools/ProfileViewer/Makefile | 1 | ||||
-rw-r--r-- | DevTools/ProfileViewer/Profile.cpp | 22 | ||||
-rw-r--r-- | DevTools/ProfileViewer/Profile.h | 29 | ||||
-rw-r--r-- | DevTools/ProfileViewer/ProfileTimelineWidget.cpp | 53 | ||||
-rw-r--r-- | DevTools/ProfileViewer/ProfileTimelineWidget.h | 19 | ||||
-rw-r--r-- | DevTools/ProfileViewer/main.cpp | 13 |
6 files changed, 121 insertions, 16 deletions
diff --git a/DevTools/ProfileViewer/Makefile b/DevTools/ProfileViewer/Makefile index d405715d7a..3c8c334c01 100644 --- a/DevTools/ProfileViewer/Makefile +++ b/DevTools/ProfileViewer/Makefile @@ -3,6 +3,7 @@ include ../../Makefile.common OBJS = \ Profile.o \ ProfileModel.o \ + ProfileTimelineWidget.o \ main.o APP = ProfileViewer diff --git a/DevTools/ProfileViewer/Profile.cpp b/DevTools/ProfileViewer/Profile.cpp index 0196635d7c..4c24ae4394 100644 --- a/DevTools/ProfileViewer/Profile.cpp +++ b/DevTools/ProfileViewer/Profile.cpp @@ -4,9 +4,11 @@ #include <LibCore/CFile.h> #include <stdio.h> -Profile::Profile(const JsonArray& json, Vector<NonnullRefPtr<ProfileNode>>&& roots) +Profile::Profile(const JsonArray& json, Vector<NonnullRefPtr<ProfileNode>>&& roots, u64 first_timestamp, u64 last_timestamp) : m_json(json) , m_roots(move(roots)) + , m_first_timestamp(first_timestamp) + , m_last_timestamp(last_timestamp) { m_model = ProfileModel::create(*this); } @@ -34,22 +36,27 @@ OwnPtr<Profile> Profile::load_from_file(const StringView& path) return nullptr; } - auto samples = json.as_array(); + auto& samples = json.as_array(); + if (samples.is_empty()) + return nullptr; NonnullRefPtrVector<ProfileNode> roots; - auto find_or_create_root = [&roots](const String& symbol, u32 address, u32 offset) -> ProfileNode& { + auto find_or_create_root = [&roots](const String& symbol, u32 address, u32 offset, u64 timestamp) -> 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); + auto new_root = ProfileNode::create(symbol, address, offset, timestamp); roots.append(new_root); return new_root; }; + u64 first_timestamp = samples.at(0).as_object().get("timestamp").to_number<u64>(); + u64 last_timestamp = samples.at(samples.size() - 1).as_object().get("timestamp").to_number<u64>(); + samples.for_each([&](const JsonValue& sample) { auto frames_value = sample.as_object().get("frames"); auto& frames = frames_value.as_array(); @@ -60,14 +67,15 @@ OwnPtr<Profile> Profile::load_from_file(const StringView& path) 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(); + auto timestamp = frame.as_object().get("timestamp").to_number<u64>(); if (symbol.is_empty()) break; if (!node) - node = &find_or_create_root(symbol, address, offset); + node = &find_or_create_root(symbol, address, offset, timestamp); else - node = &node->find_or_create_child(symbol, address, offset); + node = &node->find_or_create_child(symbol, address, offset, timestamp); node->increment_sample_count(); } @@ -77,7 +85,7 @@ OwnPtr<Profile> Profile::load_from_file(const StringView& path) root.sort_children(); } - return NonnullOwnPtr<Profile>(NonnullOwnPtr<Profile>::Adopt, *new Profile(move(samples), move(roots))); + return NonnullOwnPtr<Profile>(NonnullOwnPtr<Profile>::Adopt, *new Profile(move(samples), move(roots), first_timestamp, last_timestamp)); } void ProfileNode::sort_children() diff --git a/DevTools/ProfileViewer/Profile.h b/DevTools/ProfileViewer/Profile.h index 0b78f82d16..13c83beb46 100644 --- a/DevTools/ProfileViewer/Profile.h +++ b/DevTools/ProfileViewer/Profile.h @@ -11,14 +11,15 @@ class ProfileModel; class ProfileNode : public RefCounted<ProfileNode> { public: - static NonnullRefPtr<ProfileNode> create(const String& symbol, u32 address, u32 offset) + static NonnullRefPtr<ProfileNode> create(const String& symbol, u32 address, u32 offset, u64 timestamp) { - return adopt(*new ProfileNode(symbol, address, offset)); + return adopt(*new ProfileNode(symbol, address, offset, timestamp)); } const String& symbol() const { return m_symbol; } u32 address() const { return m_address; } u32 offset() const { return m_offset; } + u64 timestamp() const { return m_timestamp; } u32 sample_count() const { return m_sample_count; } @@ -34,7 +35,7 @@ public: m_children.append(child); } - ProfileNode& find_or_create_child(const String& symbol, u32 address, u32 offset) + ProfileNode& find_or_create_child(const String& symbol, u32 address, u32 offset, u64 timestamp) { for (int i = 0; i < m_children.size(); ++i) { auto& child = m_children[i]; @@ -42,7 +43,7 @@ public: return child; } } - auto new_child = ProfileNode::create(symbol, address, offset); + auto new_child = ProfileNode::create(symbol, address, offset, timestamp); add_child(new_child); return new_child; }; @@ -55,10 +56,11 @@ public: void sort_children(); private: - explicit ProfileNode(const String& symbol, u32 address, u32 offset) + explicit ProfileNode(const String& symbol, u32 address, u32 offset, u64 timestamp) : m_symbol(symbol) , m_address(address) , m_offset(offset) + , m_timestamp(timestamp) { } @@ -67,6 +69,7 @@ private: u32 m_address { 0 }; u32 m_offset { 0 }; u32 m_sample_count { 0 }; + u64 m_timestamp { 0 }; Vector<NonnullRefPtr<ProfileNode>> m_children; }; @@ -79,10 +82,24 @@ public: const Vector<NonnullRefPtr<ProfileNode>>& roots() const { return m_roots; } + template<typename Callback> + void for_each_sample(Callback callback) + { + m_json.for_each([&](auto& value) { + callback(value.as_object()); + }); + } + + u64 length_in_ms() const { return m_last_timestamp - m_first_timestamp; } + u64 first_timestamp() const { return m_first_timestamp; } + u64 last_timestamp() const { return m_first_timestamp; } + private: - explicit Profile(const JsonArray&, Vector<NonnullRefPtr<ProfileNode>>&&); + explicit Profile(const JsonArray&, Vector<NonnullRefPtr<ProfileNode>>&&, u64 first_timestamp, u64 last_timestamp); JsonArray m_json; RefPtr<ProfileModel> m_model; Vector<NonnullRefPtr<ProfileNode>> m_roots; + u64 m_first_timestamp { 0 }; + u64 m_last_timestamp { 0 }; }; diff --git a/DevTools/ProfileViewer/ProfileTimelineWidget.cpp b/DevTools/ProfileViewer/ProfileTimelineWidget.cpp new file mode 100644 index 0000000000..6637d196c0 --- /dev/null +++ b/DevTools/ProfileViewer/ProfileTimelineWidget.cpp @@ -0,0 +1,53 @@ +#include "ProfileTimelineWidget.h" +#include "Profile.h" +#include <LibGUI/GPainter.h> + +ProfileTimelineWidget::ProfileTimelineWidget(Profile& profile, GWidget* parent) + : GFrame(parent) + , m_profile(profile) +{ + set_frame_thickness(2); + set_frame_shadow(FrameShadow::Sunken); + set_frame_shape(FrameShape::Container); + set_background_color(Color::White); + set_fill_with_background_color(true); + set_size_policy(SizePolicy::Fill, SizePolicy::Fixed); + set_preferred_size(0, 80); +} + +ProfileTimelineWidget::~ProfileTimelineWidget() +{ +} + +void ProfileTimelineWidget::paint_event(GPaintEvent& event) +{ + GFrame::paint_event(event); + + GPainter painter(*this); + painter.add_clip_rect(event.rect()); + + float column_width = (float)frame_inner_rect().width() / (float)m_profile.length_in_ms(); + + m_profile.for_each_sample([&](const JsonObject& sample) { + u64 t = sample.get("timestamp").to_number<u64>() - m_profile.first_timestamp(); + int x = (int)((float)t * column_width); + int cw = max(1, (int)column_width); + + bool in_kernel = sample.get("frames").as_array().at(1).as_object().get("address").to_number<u32>() < (8 * MB); + Color color = in_kernel ? Color::from_rgb(0xc25e5a) : Color::from_rgb(0x5a65c2); + for (int i = 0; i < cw; ++i) + painter.draw_line({ x + i, frame_thickness() }, { x + i, height() - frame_thickness() * 2 }, color); + }); +} + +void ProfileTimelineWidget::mousedown_event(GMouseEvent&) +{ +} + +void ProfileTimelineWidget::mousemove_event(GMouseEvent&) +{ +} + +void ProfileTimelineWidget::mouseup_event(GMouseEvent&) +{ +} diff --git a/DevTools/ProfileViewer/ProfileTimelineWidget.h b/DevTools/ProfileViewer/ProfileTimelineWidget.h new file mode 100644 index 0000000000..eca99d3c58 --- /dev/null +++ b/DevTools/ProfileViewer/ProfileTimelineWidget.h @@ -0,0 +1,19 @@ +#include <LibGUI/GFrame.h> + +class Profile; + +class ProfileTimelineWidget final : public GFrame { + C_OBJECT(ProfileTimelineWidget) +public: + virtual ~ProfileTimelineWidget() override; + +private: + virtual void paint_event(GPaintEvent&) override; + virtual void mousedown_event(GMouseEvent&) override; + virtual void mousemove_event(GMouseEvent&) override; + virtual void mouseup_event(GMouseEvent&) override; + + ProfileTimelineWidget(Profile&, GWidget* parent); + + Profile& m_profile; +}; diff --git a/DevTools/ProfileViewer/main.cpp b/DevTools/ProfileViewer/main.cpp index 0a4185bbe5..67d3123bda 100644 --- a/DevTools/ProfileViewer/main.cpp +++ b/DevTools/ProfileViewer/main.cpp @@ -1,5 +1,7 @@ #include "Profile.h" +#include "ProfileTimelineWidget.h" #include <LibGUI/GApplication.h> +#include <LibGUI/GBoxLayout.h> #include <LibGUI/GTreeView.h> #include <LibGUI/GWindow.h> #include <stdio.h> @@ -25,13 +27,18 @@ int main(int argc, char** argv) window->set_title("ProfileViewer"); window->set_rect(100, 100, 800, 600); - auto tree_view = GTreeView::construct(nullptr); + auto main_widget = GWidget::construct(); + window->set_main_widget(main_widget); + main_widget->set_fill_with_background_color(true); + main_widget->set_layout(make<GBoxLayout>(Orientation::Vertical)); + + auto timeline_widget = ProfileTimelineWidget::construct(*profile, main_widget); + + auto tree_view = GTreeView::construct(main_widget); tree_view->set_headers_visible(true); tree_view->set_size_columns_to_fit_content(true); tree_view->set_model(profile->model()); - window->set_main_widget(tree_view); - window->show(); return app.exec(); } |