mirror of
https://github.com/RGBCube/serenity
synced 2025-05-20 18:05:07 +00:00
ProfileViewer: Add a timeline widget for a visual view of the profile
Userspace stack frames are in blue, kernel stack frames in red :^)
This commit is contained in:
parent
46a57c7f59
commit
a3e7c99ffe
6 changed files with 121 additions and 16 deletions
|
@ -3,6 +3,7 @@ include ../../Makefile.common
|
||||||
OBJS = \
|
OBJS = \
|
||||||
Profile.o \
|
Profile.o \
|
||||||
ProfileModel.o \
|
ProfileModel.o \
|
||||||
|
ProfileTimelineWidget.o \
|
||||||
main.o
|
main.o
|
||||||
|
|
||||||
APP = ProfileViewer
|
APP = ProfileViewer
|
||||||
|
|
|
@ -4,9 +4,11 @@
|
||||||
#include <LibCore/CFile.h>
|
#include <LibCore/CFile.h>
|
||||||
#include <stdio.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_json(json)
|
||||||
, m_roots(move(roots))
|
, m_roots(move(roots))
|
||||||
|
, m_first_timestamp(first_timestamp)
|
||||||
|
, m_last_timestamp(last_timestamp)
|
||||||
{
|
{
|
||||||
m_model = ProfileModel::create(*this);
|
m_model = ProfileModel::create(*this);
|
||||||
}
|
}
|
||||||
|
@ -34,22 +36,27 @@ OwnPtr<Profile> Profile::load_from_file(const StringView& path)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto samples = json.as_array();
|
auto& samples = json.as_array();
|
||||||
|
if (samples.is_empty())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
NonnullRefPtrVector<ProfileNode> roots;
|
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) {
|
for (int i = 0; i < roots.size(); ++i) {
|
||||||
auto& root = roots[i];
|
auto& root = roots[i];
|
||||||
if (root.symbol() == symbol) {
|
if (root.symbol() == symbol) {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto new_root = ProfileNode::create(symbol, address, offset);
|
auto new_root = ProfileNode::create(symbol, address, offset, timestamp);
|
||||||
roots.append(new_root);
|
roots.append(new_root);
|
||||||
return 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) {
|
samples.for_each([&](const JsonValue& sample) {
|
||||||
auto frames_value = sample.as_object().get("frames");
|
auto frames_value = sample.as_object().get("frames");
|
||||||
auto& frames = frames_value.as_array();
|
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 symbol = frame.as_object().get("symbol").as_string_or({});
|
||||||
auto address = frame.as_object().get("address").as_u32();
|
auto address = frame.as_object().get("address").as_u32();
|
||||||
auto offset = frame.as_object().get("offset").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())
|
if (symbol.is_empty())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (!node)
|
if (!node)
|
||||||
node = &find_or_create_root(symbol, address, offset);
|
node = &find_or_create_root(symbol, address, offset, timestamp);
|
||||||
else
|
else
|
||||||
node = &node->find_or_create_child(symbol, address, offset);
|
node = &node->find_or_create_child(symbol, address, offset, timestamp);
|
||||||
|
|
||||||
node->increment_sample_count();
|
node->increment_sample_count();
|
||||||
}
|
}
|
||||||
|
@ -77,7 +85,7 @@ OwnPtr<Profile> Profile::load_from_file(const StringView& path)
|
||||||
root.sort_children();
|
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()
|
void ProfileNode::sort_children()
|
||||||
|
|
|
@ -11,14 +11,15 @@ class ProfileModel;
|
||||||
|
|
||||||
class ProfileNode : public RefCounted<ProfileNode> {
|
class ProfileNode : public RefCounted<ProfileNode> {
|
||||||
public:
|
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; }
|
const String& symbol() const { return m_symbol; }
|
||||||
u32 address() const { return m_address; }
|
u32 address() const { return m_address; }
|
||||||
u32 offset() const { return m_offset; }
|
u32 offset() const { return m_offset; }
|
||||||
|
u64 timestamp() const { return m_timestamp; }
|
||||||
|
|
||||||
u32 sample_count() const { return m_sample_count; }
|
u32 sample_count() const { return m_sample_count; }
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ public:
|
||||||
m_children.append(child);
|
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) {
|
for (int i = 0; i < m_children.size(); ++i) {
|
||||||
auto& child = m_children[i];
|
auto& child = m_children[i];
|
||||||
|
@ -42,7 +43,7 @@ public:
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto new_child = ProfileNode::create(symbol, address, offset);
|
auto new_child = ProfileNode::create(symbol, address, offset, timestamp);
|
||||||
add_child(new_child);
|
add_child(new_child);
|
||||||
return new_child;
|
return new_child;
|
||||||
};
|
};
|
||||||
|
@ -55,10 +56,11 @@ public:
|
||||||
void sort_children();
|
void sort_children();
|
||||||
|
|
||||||
private:
|
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_symbol(symbol)
|
||||||
, m_address(address)
|
, m_address(address)
|
||||||
, m_offset(offset)
|
, m_offset(offset)
|
||||||
|
, m_timestamp(timestamp)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +69,7 @@ private:
|
||||||
u32 m_address { 0 };
|
u32 m_address { 0 };
|
||||||
u32 m_offset { 0 };
|
u32 m_offset { 0 };
|
||||||
u32 m_sample_count { 0 };
|
u32 m_sample_count { 0 };
|
||||||
|
u64 m_timestamp { 0 };
|
||||||
Vector<NonnullRefPtr<ProfileNode>> m_children;
|
Vector<NonnullRefPtr<ProfileNode>> m_children;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -79,10 +82,24 @@ public:
|
||||||
|
|
||||||
const Vector<NonnullRefPtr<ProfileNode>>& roots() const { return m_roots; }
|
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:
|
private:
|
||||||
explicit Profile(const JsonArray&, Vector<NonnullRefPtr<ProfileNode>>&&);
|
explicit Profile(const JsonArray&, Vector<NonnullRefPtr<ProfileNode>>&&, u64 first_timestamp, u64 last_timestamp);
|
||||||
|
|
||||||
JsonArray m_json;
|
JsonArray m_json;
|
||||||
RefPtr<ProfileModel> m_model;
|
RefPtr<ProfileModel> m_model;
|
||||||
Vector<NonnullRefPtr<ProfileNode>> m_roots;
|
Vector<NonnullRefPtr<ProfileNode>> m_roots;
|
||||||
|
u64 m_first_timestamp { 0 };
|
||||||
|
u64 m_last_timestamp { 0 };
|
||||||
};
|
};
|
||||||
|
|
53
DevTools/ProfileViewer/ProfileTimelineWidget.cpp
Normal file
53
DevTools/ProfileViewer/ProfileTimelineWidget.cpp
Normal file
|
@ -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&)
|
||||||
|
{
|
||||||
|
}
|
19
DevTools/ProfileViewer/ProfileTimelineWidget.h
Normal file
19
DevTools/ProfileViewer/ProfileTimelineWidget.h
Normal file
|
@ -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;
|
||||||
|
};
|
|
@ -1,5 +1,7 @@
|
||||||
#include "Profile.h"
|
#include "Profile.h"
|
||||||
|
#include "ProfileTimelineWidget.h"
|
||||||
#include <LibGUI/GApplication.h>
|
#include <LibGUI/GApplication.h>
|
||||||
|
#include <LibGUI/GBoxLayout.h>
|
||||||
#include <LibGUI/GTreeView.h>
|
#include <LibGUI/GTreeView.h>
|
||||||
#include <LibGUI/GWindow.h>
|
#include <LibGUI/GWindow.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -25,13 +27,18 @@ int main(int argc, char** argv)
|
||||||
window->set_title("ProfileViewer");
|
window->set_title("ProfileViewer");
|
||||||
window->set_rect(100, 100, 800, 600);
|
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_headers_visible(true);
|
||||||
tree_view->set_size_columns_to_fit_content(true);
|
tree_view->set_size_columns_to_fit_content(true);
|
||||||
tree_view->set_model(profile->model());
|
tree_view->set_model(profile->model());
|
||||||
|
|
||||||
window->set_main_widget(tree_view);
|
|
||||||
|
|
||||||
window->show();
|
window->show();
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue