mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 15:57:45 +00:00
Profiler: Add a new "Samples" view to the main UI
You can now view the individual samples in a profile one by one with the new "Samples" view. The "old" main view moves into a "Call Tree" tab (but it remains the default view.) When you select a sample in the samples view, we show you the full symbolicated backtrace in a separate view on the right hand side. :^)
This commit is contained in:
parent
2c1f71055f
commit
1fb1279cfd
8 changed files with 369 additions and 11 deletions
|
@ -1,9 +1,11 @@
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
DisassemblyModel.cpp
|
DisassemblyModel.cpp
|
||||||
main.cpp
|
main.cpp
|
||||||
Profile.cpp
|
IndividualSampleModel.cpp
|
||||||
|
Profile.cpp
|
||||||
ProfileModel.cpp
|
ProfileModel.cpp
|
||||||
ProfileTimelineWidget.cpp
|
ProfileTimelineWidget.cpp
|
||||||
|
SamplesModel.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
serenity_app(Profiler ICON app-profiler)
|
serenity_app(Profiler ICON app-profiler)
|
||||||
|
|
91
Userland/DevTools/Profiler/IndividualSampleModel.cpp
Normal file
91
Userland/DevTools/Profiler/IndividualSampleModel.cpp
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IndividualSampleModel.h"
|
||||||
|
#include "Profile.h"
|
||||||
|
#include <AK/StringBuilder.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
IndividualSampleModel::IndividualSampleModel(Profile& profile, size_t event_index)
|
||||||
|
: m_profile(profile)
|
||||||
|
, m_event_index(event_index)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
IndividualSampleModel::~IndividualSampleModel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int IndividualSampleModel::row_count(const GUI::ModelIndex&) const
|
||||||
|
{
|
||||||
|
auto& event = m_profile.events().at(m_event_index);
|
||||||
|
return event.frames.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
int IndividualSampleModel::column_count(const GUI::ModelIndex&) const
|
||||||
|
{
|
||||||
|
return Column::__Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
String IndividualSampleModel::column_name(int column) const
|
||||||
|
{
|
||||||
|
switch (column) {
|
||||||
|
case Column::Address:
|
||||||
|
return "Address";
|
||||||
|
case Column::ObjectName:
|
||||||
|
return "Object";
|
||||||
|
case Column::Symbol:
|
||||||
|
return "Symbol";
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI::Variant IndividualSampleModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
|
||||||
|
{
|
||||||
|
auto& event = m_profile.events().at(m_event_index);
|
||||||
|
auto& frame = event.frames[event.frames.size() - index.row() - 1];
|
||||||
|
|
||||||
|
if (role == GUI::ModelRole::Display) {
|
||||||
|
if (index.column() == Column::Address)
|
||||||
|
return String::formatted("{:08x}", frame.address);
|
||||||
|
|
||||||
|
if (index.column() == Column::Symbol) {
|
||||||
|
return frame.symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index.column() == Column::ObjectName) {
|
||||||
|
return frame.object_name;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndividualSampleModel::update()
|
||||||
|
{
|
||||||
|
did_update(Model::InvalidateAllIndexes);
|
||||||
|
}
|
60
Userland/DevTools/Profiler/IndividualSampleModel.h
Normal file
60
Userland/DevTools/Profiler/IndividualSampleModel.h
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibGUI/Model.h>
|
||||||
|
|
||||||
|
class Profile;
|
||||||
|
|
||||||
|
class IndividualSampleModel final : public GUI::Model {
|
||||||
|
public:
|
||||||
|
static NonnullRefPtr<IndividualSampleModel> create(Profile& profile, size_t event_index)
|
||||||
|
{
|
||||||
|
return adopt(*new IndividualSampleModel(profile, event_index));
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Column {
|
||||||
|
Address,
|
||||||
|
ObjectName,
|
||||||
|
Symbol,
|
||||||
|
__Count
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual ~IndividualSampleModel() override;
|
||||||
|
|
||||||
|
virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
|
||||||
|
virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
|
||||||
|
virtual String column_name(int) const override;
|
||||||
|
virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
|
||||||
|
virtual void update() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
IndividualSampleModel(Profile&, size_t event_index);
|
||||||
|
|
||||||
|
Profile& m_profile;
|
||||||
|
const size_t m_event_index { 0 };
|
||||||
|
};
|
|
@ -27,6 +27,7 @@
|
||||||
#include "Profile.h"
|
#include "Profile.h"
|
||||||
#include "DisassemblyModel.h"
|
#include "DisassemblyModel.h"
|
||||||
#include "ProfileModel.h"
|
#include "ProfileModel.h"
|
||||||
|
#include "SamplesModel.h"
|
||||||
#include <AK/HashTable.h>
|
#include <AK/HashTable.h>
|
||||||
#include <AK/LexicalPath.h>
|
#include <AK/LexicalPath.h>
|
||||||
#include <AK/MappedFile.h>
|
#include <AK/MappedFile.h>
|
||||||
|
@ -55,6 +56,7 @@ Profile::Profile(String executable_path, Vector<Event> events, NonnullOwnPtr<Lib
|
||||||
m_last_timestamp = m_events.last().timestamp;
|
m_last_timestamp = m_events.last().timestamp;
|
||||||
|
|
||||||
m_model = ProfileModel::create(*this);
|
m_model = ProfileModel::create(*this);
|
||||||
|
m_samples_model = SamplesModel::create(*this);
|
||||||
|
|
||||||
for (auto& event : m_events) {
|
for (auto& event : m_events) {
|
||||||
m_deepest_stack_depth = max((u32)event.frames.size(), m_deepest_stack_depth);
|
m_deepest_stack_depth = max((u32)event.frames.size(), m_deepest_stack_depth);
|
||||||
|
@ -72,6 +74,11 @@ GUI::Model& Profile::model()
|
||||||
return *m_model;
|
return *m_model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GUI::Model& Profile::samples_model()
|
||||||
|
{
|
||||||
|
return *m_samples_model;
|
||||||
|
}
|
||||||
|
|
||||||
void Profile::rebuild_tree()
|
void Profile::rebuild_tree()
|
||||||
{
|
{
|
||||||
u32 filtered_event_count = 0;
|
u32 filtered_event_count = 0;
|
||||||
|
@ -91,18 +98,14 @@ void Profile::rebuild_tree()
|
||||||
|
|
||||||
HashTable<FlatPtr> live_allocations;
|
HashTable<FlatPtr> live_allocations;
|
||||||
|
|
||||||
for (auto& event : m_events) {
|
for_each_event_in_filter_range([&](auto& event) {
|
||||||
if (has_timestamp_filter_range()) {
|
|
||||||
auto timestamp = event.timestamp;
|
|
||||||
if (timestamp < m_timestamp_filter_range_start || timestamp > m_timestamp_filter_range_end)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type == "malloc")
|
if (event.type == "malloc")
|
||||||
live_allocations.set(event.ptr);
|
live_allocations.set(event.ptr);
|
||||||
else if (event.type == "free")
|
else if (event.type == "free")
|
||||||
live_allocations.remove(event.ptr);
|
live_allocations.remove(event.ptr);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
Optional<size_t> first_filtered_event_index;
|
||||||
|
|
||||||
for (size_t event_index = 0; event_index < m_events.size(); ++event_index) {
|
for (size_t event_index = 0; event_index < m_events.size(); ++event_index) {
|
||||||
auto& event = m_events.at(event_index);
|
auto& event = m_events.at(event_index);
|
||||||
|
@ -112,6 +115,9 @@ void Profile::rebuild_tree()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!first_filtered_event_index.has_value())
|
||||||
|
first_filtered_event_index = event_index;
|
||||||
|
|
||||||
if (event.type == "malloc" && !live_allocations.contains(event.ptr))
|
if (event.type == "malloc" && !live_allocations.contains(event.ptr))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -197,6 +203,7 @@ void Profile::rebuild_tree()
|
||||||
sort_profile_nodes(roots);
|
sort_profile_nodes(roots);
|
||||||
|
|
||||||
m_filtered_event_count = filtered_event_count;
|
m_filtered_event_count = filtered_event_count;
|
||||||
|
m_first_filtered_event_index = first_filtered_event_index.value_or(0);
|
||||||
m_roots = move(roots);
|
m_roots = move(roots);
|
||||||
m_model->update();
|
m_model->update();
|
||||||
}
|
}
|
||||||
|
@ -307,6 +314,7 @@ void Profile::set_timestamp_filter_range(u64 start, u64 end)
|
||||||
m_timestamp_filter_range_end = max(start, end);
|
m_timestamp_filter_range_end = max(start, end);
|
||||||
|
|
||||||
rebuild_tree();
|
rebuild_tree();
|
||||||
|
m_samples_model->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Profile::clear_timestamp_filter_range()
|
void Profile::clear_timestamp_filter_range()
|
||||||
|
@ -315,6 +323,7 @@ void Profile::clear_timestamp_filter_range()
|
||||||
return;
|
return;
|
||||||
m_has_timestamp_filter_range = false;
|
m_has_timestamp_filter_range = false;
|
||||||
rebuild_tree();
|
rebuild_tree();
|
||||||
|
m_samples_model->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Profile::set_inverted(bool inverted)
|
void Profile::set_inverted(bool inverted)
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
|
|
||||||
class ProfileModel;
|
class ProfileModel;
|
||||||
class DisassemblyModel;
|
class DisassemblyModel;
|
||||||
|
class SamplesModel;
|
||||||
|
|
||||||
class ProfileNode : public RefCounted<ProfileNode> {
|
class ProfileNode : public RefCounted<ProfileNode> {
|
||||||
public:
|
public:
|
||||||
|
@ -132,6 +133,7 @@ public:
|
||||||
~Profile();
|
~Profile();
|
||||||
|
|
||||||
GUI::Model& model();
|
GUI::Model& model();
|
||||||
|
GUI::Model& samples_model();
|
||||||
GUI::Model* disassembly_model();
|
GUI::Model* disassembly_model();
|
||||||
|
|
||||||
void set_disassembly_index(const GUI::ModelIndex&);
|
void set_disassembly_index(const GUI::ModelIndex&);
|
||||||
|
@ -154,6 +156,7 @@ public:
|
||||||
Vector<Frame> frames;
|
Vector<Frame> frames;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
u32 first_filtered_event_index() const { return m_first_filtered_event_index; }
|
||||||
u32 filtered_event_count() const { return m_filtered_event_count; }
|
u32 filtered_event_count() const { return m_filtered_event_count; }
|
||||||
|
|
||||||
const Vector<Event>& events() const { return m_events; }
|
const Vector<Event>& events() const { return m_events; }
|
||||||
|
@ -200,6 +203,19 @@ public:
|
||||||
|
|
||||||
const LibraryMetadata& libraries() const { return *m_library_metadata; }
|
const LibraryMetadata& libraries() const { return *m_library_metadata; }
|
||||||
|
|
||||||
|
template<typename Callback>
|
||||||
|
void for_each_event_in_filter_range(Callback callback)
|
||||||
|
{
|
||||||
|
for (auto& event : m_events) {
|
||||||
|
if (has_timestamp_filter_range()) {
|
||||||
|
auto timestamp = event.timestamp;
|
||||||
|
if (timestamp < m_timestamp_filter_range_start || timestamp > m_timestamp_filter_range_end)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
callback(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Profile(String executable_path, Vector<Event>, NonnullOwnPtr<LibraryMetadata>);
|
Profile(String executable_path, Vector<Event>, NonnullOwnPtr<LibraryMetadata>);
|
||||||
|
|
||||||
|
@ -208,12 +224,14 @@ private:
|
||||||
String m_executable_path;
|
String m_executable_path;
|
||||||
|
|
||||||
RefPtr<ProfileModel> m_model;
|
RefPtr<ProfileModel> m_model;
|
||||||
|
RefPtr<SamplesModel> m_samples_model;
|
||||||
RefPtr<DisassemblyModel> m_disassembly_model;
|
RefPtr<DisassemblyModel> m_disassembly_model;
|
||||||
|
|
||||||
GUI::ModelIndex m_disassembly_index;
|
GUI::ModelIndex m_disassembly_index;
|
||||||
|
|
||||||
Vector<NonnullRefPtr<ProfileNode>> m_roots;
|
Vector<NonnullRefPtr<ProfileNode>> m_roots;
|
||||||
u32 m_filtered_event_count { 0 };
|
u32 m_filtered_event_count { 0 };
|
||||||
|
size_t m_first_filtered_event_index { 0 };
|
||||||
u64 m_first_timestamp { 0 };
|
u64 m_first_timestamp { 0 };
|
||||||
u64 m_last_timestamp { 0 };
|
u64 m_last_timestamp { 0 };
|
||||||
|
|
||||||
|
|
95
Userland/DevTools/Profiler/SamplesModel.cpp
Normal file
95
Userland/DevTools/Profiler/SamplesModel.cpp
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "SamplesModel.h"
|
||||||
|
#include "Profile.h"
|
||||||
|
#include <AK/StringBuilder.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
SamplesModel::SamplesModel(Profile& profile)
|
||||||
|
: m_profile(profile)
|
||||||
|
{
|
||||||
|
m_user_frame_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/inspector-object.png"));
|
||||||
|
m_kernel_frame_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/inspector-object-red.png"));
|
||||||
|
}
|
||||||
|
|
||||||
|
SamplesModel::~SamplesModel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int SamplesModel::row_count(const GUI::ModelIndex&) const
|
||||||
|
{
|
||||||
|
return m_profile.filtered_event_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
int SamplesModel::column_count(const GUI::ModelIndex&) const
|
||||||
|
{
|
||||||
|
return Column::__Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
String SamplesModel::column_name(int column) const
|
||||||
|
{
|
||||||
|
switch (column) {
|
||||||
|
case Column::SampleIndex:
|
||||||
|
return "#";
|
||||||
|
case Column::Timestamp:
|
||||||
|
return "Timestamp";
|
||||||
|
case Column::InnermostStackFrame:
|
||||||
|
return "Innermost Frame";
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI::Variant SamplesModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
|
||||||
|
{
|
||||||
|
u32 event_index = m_profile.first_filtered_event_index() + index.row();
|
||||||
|
auto& event = m_profile.events().at(event_index);
|
||||||
|
|
||||||
|
if (role == GUI::ModelRole::Custom) {
|
||||||
|
return event_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == GUI::ModelRole::Display) {
|
||||||
|
if (index.column() == Column::SampleIndex)
|
||||||
|
return event_index;
|
||||||
|
|
||||||
|
if (index.column() == Column::Timestamp) {
|
||||||
|
return (u32)event.timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index.column() == Column::InnermostStackFrame) {
|
||||||
|
return event.frames.last().symbol;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void SamplesModel::update()
|
||||||
|
{
|
||||||
|
did_update(Model::InvalidateAllIndexes);
|
||||||
|
}
|
62
Userland/DevTools/Profiler/SamplesModel.h
Normal file
62
Userland/DevTools/Profiler/SamplesModel.h
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibGUI/Model.h>
|
||||||
|
|
||||||
|
class Profile;
|
||||||
|
|
||||||
|
class SamplesModel final : public GUI::Model {
|
||||||
|
public:
|
||||||
|
static NonnullRefPtr<SamplesModel> create(Profile& profile)
|
||||||
|
{
|
||||||
|
return adopt(*new SamplesModel(profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Column {
|
||||||
|
SampleIndex,
|
||||||
|
Timestamp,
|
||||||
|
InnermostStackFrame,
|
||||||
|
__Count
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual ~SamplesModel() override;
|
||||||
|
|
||||||
|
virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
|
||||||
|
virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
|
||||||
|
virtual String column_name(int) const override;
|
||||||
|
virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
|
||||||
|
virtual void update() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit SamplesModel(Profile&);
|
||||||
|
|
||||||
|
Profile& m_profile;
|
||||||
|
|
||||||
|
GUI::Icon m_user_frame_icon;
|
||||||
|
GUI::Icon m_kernel_frame_icon;
|
||||||
|
};
|
|
@ -24,6 +24,7 @@
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "IndividualSampleModel.h"
|
||||||
#include "Profile.h"
|
#include "Profile.h"
|
||||||
#include "ProfileTimelineWidget.h"
|
#include "ProfileTimelineWidget.h"
|
||||||
#include <LibCore/ArgsParser.h>
|
#include <LibCore/ArgsParser.h>
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
#include <LibGUI/Model.h>
|
#include <LibGUI/Model.h>
|
||||||
#include <LibGUI/ProcessChooser.h>
|
#include <LibGUI/ProcessChooser.h>
|
||||||
#include <LibGUI/Splitter.h>
|
#include <LibGUI/Splitter.h>
|
||||||
|
#include <LibGUI/TabWidget.h>
|
||||||
#include <LibGUI/TableView.h>
|
#include <LibGUI/TableView.h>
|
||||||
#include <LibGUI/TreeView.h>
|
#include <LibGUI/TreeView.h>
|
||||||
#include <LibGUI/Window.h>
|
#include <LibGUI/Window.h>
|
||||||
|
@ -100,7 +102,12 @@ int main(int argc, char** argv)
|
||||||
|
|
||||||
main_widget.add<ProfileTimelineWidget>(*profile);
|
main_widget.add<ProfileTimelineWidget>(*profile);
|
||||||
|
|
||||||
auto& bottom_splitter = main_widget.add<GUI::VerticalSplitter>();
|
auto& tab_widget = main_widget.add<GUI::TabWidget>();
|
||||||
|
|
||||||
|
auto& tree_tab = tab_widget.add_tab<GUI::Widget>("Call Tree");
|
||||||
|
tree_tab.set_layout<GUI::VerticalBoxLayout>();
|
||||||
|
tree_tab.layout()->set_margins({ 4, 4, 4, 4 });
|
||||||
|
auto& bottom_splitter = tree_tab.add<GUI::VerticalSplitter>();
|
||||||
|
|
||||||
auto& tree_view = bottom_splitter.add<GUI::TreeView>();
|
auto& tree_view = bottom_splitter.add<GUI::TreeView>();
|
||||||
tree_view.set_should_fill_selected_rows(true);
|
tree_view.set_should_fill_selected_rows(true);
|
||||||
|
@ -114,6 +121,20 @@ int main(int argc, char** argv)
|
||||||
disassembly_view.set_model(profile->disassembly_model());
|
disassembly_view.set_model(profile->disassembly_model());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto& samples_tab = tab_widget.add_tab<GUI::Widget>("Samples");
|
||||||
|
samples_tab.set_layout<GUI::VerticalBoxLayout>();
|
||||||
|
samples_tab.layout()->set_margins({ 4, 4, 4, 4 });
|
||||||
|
|
||||||
|
auto& samples_splitter = samples_tab.add<GUI::HorizontalSplitter>();
|
||||||
|
auto& samples_table_view = samples_splitter.add<GUI::TableView>();
|
||||||
|
samples_table_view.set_model(profile->samples_model());
|
||||||
|
|
||||||
|
auto& individual_sample_view = samples_splitter.add<GUI::TableView>();
|
||||||
|
samples_table_view.on_selection = [&](const GUI::ModelIndex& index) {
|
||||||
|
auto model = IndividualSampleModel::create(*profile, index.data(GUI::ModelRole::Custom).to_integer<size_t>());
|
||||||
|
individual_sample_view.set_model(move(model));
|
||||||
|
};
|
||||||
|
|
||||||
auto menubar = GUI::MenuBar::construct();
|
auto menubar = GUI::MenuBar::construct();
|
||||||
auto& app_menu = menubar->add_menu("Profiler");
|
auto& app_menu = menubar->add_menu("Profiler");
|
||||||
app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(); }));
|
app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(); }));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue