summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Kling <awesomekling@gmail.com>2019-12-14 18:44:29 +0100
committerAndreas Kling <awesomekling@gmail.com>2019-12-14 18:44:29 +0100
commita3e7c99ffea8bd5f70edf9d6ef941c4e7303833f (patch)
tree6a4577fc2d7c6f74f4d4172fa6d7e32e244318b0
parent46a57c7f5905b8d82f1c810df8b6cc5a7f5970a6 (diff)
downloadserenity-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/Makefile1
-rw-r--r--DevTools/ProfileViewer/Profile.cpp22
-rw-r--r--DevTools/ProfileViewer/Profile.h29
-rw-r--r--DevTools/ProfileViewer/ProfileTimelineWidget.cpp53
-rw-r--r--DevTools/ProfileViewer/ProfileTimelineWidget.h19
-rw-r--r--DevTools/ProfileViewer/main.cpp13
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();
}