diff --git a/Userland/DevTools/Profiler/CMakeLists.txt b/Userland/DevTools/Profiler/CMakeLists.txt index ef0413de0a..251e695f70 100644 --- a/Userland/DevTools/Profiler/CMakeLists.txt +++ b/Userland/DevTools/Profiler/CMakeLists.txt @@ -1,9 +1,11 @@ set(SOURCES DisassemblyModel.cpp main.cpp - Profile.cpp + IndividualSampleModel.cpp + Profile.cpp ProfileModel.cpp - ProfileTimelineWidget.cpp + ProfileTimelineWidget.cpp + SamplesModel.cpp ) serenity_app(Profiler ICON app-profiler) diff --git a/Userland/DevTools/Profiler/IndividualSampleModel.cpp b/Userland/DevTools/Profiler/IndividualSampleModel.cpp new file mode 100644 index 0000000000..b64feabb1e --- /dev/null +++ b/Userland/DevTools/Profiler/IndividualSampleModel.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018-2021, Andreas Kling + * 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 +#include + +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); +} diff --git a/Userland/DevTools/Profiler/IndividualSampleModel.h b/Userland/DevTools/Profiler/IndividualSampleModel.h new file mode 100644 index 0000000000..5a5943e3a0 --- /dev/null +++ b/Userland/DevTools/Profiler/IndividualSampleModel.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018-2021, Andreas Kling + * 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 + +class Profile; + +class IndividualSampleModel final : public GUI::Model { +public: + static NonnullRefPtr 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 }; +}; diff --git a/Userland/DevTools/Profiler/Profile.cpp b/Userland/DevTools/Profiler/Profile.cpp index bbed5cefcc..a486fe683f 100644 --- a/Userland/DevTools/Profiler/Profile.cpp +++ b/Userland/DevTools/Profiler/Profile.cpp @@ -27,6 +27,7 @@ #include "Profile.h" #include "DisassemblyModel.h" #include "ProfileModel.h" +#include "SamplesModel.h" #include #include #include @@ -55,6 +56,7 @@ Profile::Profile(String executable_path, Vector events, NonnullOwnPtr live_allocations; - 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; - } - + for_each_event_in_filter_range([&](auto& event) { if (event.type == "malloc") live_allocations.set(event.ptr); else if (event.type == "free") live_allocations.remove(event.ptr); - } + }); + + Optional first_filtered_event_index; for (size_t event_index = 0; event_index < m_events.size(); ++event_index) { auto& event = m_events.at(event_index); @@ -112,6 +115,9 @@ void Profile::rebuild_tree() continue; } + if (!first_filtered_event_index.has_value()) + first_filtered_event_index = event_index; + if (event.type == "malloc" && !live_allocations.contains(event.ptr)) continue; @@ -197,6 +203,7 @@ void Profile::rebuild_tree() sort_profile_nodes(roots); m_filtered_event_count = filtered_event_count; + m_first_filtered_event_index = first_filtered_event_index.value_or(0); m_roots = move(roots); 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); rebuild_tree(); + m_samples_model->update(); } void Profile::clear_timestamp_filter_range() @@ -315,6 +323,7 @@ void Profile::clear_timestamp_filter_range() return; m_has_timestamp_filter_range = false; rebuild_tree(); + m_samples_model->update(); } void Profile::set_inverted(bool inverted) diff --git a/Userland/DevTools/Profiler/Profile.h b/Userland/DevTools/Profiler/Profile.h index 65ad5fb608..e45cb08e07 100644 --- a/Userland/DevTools/Profiler/Profile.h +++ b/Userland/DevTools/Profiler/Profile.h @@ -41,6 +41,7 @@ class ProfileModel; class DisassemblyModel; +class SamplesModel; class ProfileNode : public RefCounted { public: @@ -132,6 +133,7 @@ public: ~Profile(); GUI::Model& model(); + GUI::Model& samples_model(); GUI::Model* disassembly_model(); void set_disassembly_index(const GUI::ModelIndex&); @@ -154,6 +156,7 @@ public: Vector frames; }; + u32 first_filtered_event_index() const { return m_first_filtered_event_index; } u32 filtered_event_count() const { return m_filtered_event_count; } const Vector& events() const { return m_events; } @@ -200,6 +203,19 @@ public: const LibraryMetadata& libraries() const { return *m_library_metadata; } + template + 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: Profile(String executable_path, Vector, NonnullOwnPtr); @@ -208,12 +224,14 @@ private: String m_executable_path; RefPtr m_model; + RefPtr m_samples_model; RefPtr m_disassembly_model; GUI::ModelIndex m_disassembly_index; Vector> m_roots; u32 m_filtered_event_count { 0 }; + size_t m_first_filtered_event_index { 0 }; u64 m_first_timestamp { 0 }; u64 m_last_timestamp { 0 }; diff --git a/Userland/DevTools/Profiler/SamplesModel.cpp b/Userland/DevTools/Profiler/SamplesModel.cpp new file mode 100644 index 0000000000..79159ddc3a --- /dev/null +++ b/Userland/DevTools/Profiler/SamplesModel.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018-2021, Andreas Kling + * 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 +#include + +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); +} diff --git a/Userland/DevTools/Profiler/SamplesModel.h b/Userland/DevTools/Profiler/SamplesModel.h new file mode 100644 index 0000000000..f75b375af2 --- /dev/null +++ b/Userland/DevTools/Profiler/SamplesModel.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018-2021, Andreas Kling + * 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 + +class Profile; + +class SamplesModel final : public GUI::Model { +public: + static NonnullRefPtr 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; +}; diff --git a/Userland/DevTools/Profiler/main.cpp b/Userland/DevTools/Profiler/main.cpp index 4c5722200a..f6c2fcd420 100644 --- a/Userland/DevTools/Profiler/main.cpp +++ b/Userland/DevTools/Profiler/main.cpp @@ -24,6 +24,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "IndividualSampleModel.h" #include "Profile.h" #include "ProfileTimelineWidget.h" #include @@ -44,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -100,7 +102,12 @@ int main(int argc, char** argv) main_widget.add(*profile); - auto& bottom_splitter = main_widget.add(); + auto& tab_widget = main_widget.add(); + + auto& tree_tab = tab_widget.add_tab("Call Tree"); + tree_tab.set_layout(); + tree_tab.layout()->set_margins({ 4, 4, 4, 4 }); + auto& bottom_splitter = tree_tab.add(); auto& tree_view = bottom_splitter.add(); 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()); }; + auto& samples_tab = tab_widget.add_tab("Samples"); + samples_tab.set_layout(); + samples_tab.layout()->set_margins({ 4, 4, 4, 4 }); + + auto& samples_splitter = samples_tab.add(); + auto& samples_table_view = samples_splitter.add(); + samples_table_view.set_model(profile->samples_model()); + + auto& individual_sample_view = samples_splitter.add(); + samples_table_view.on_selection = [&](const GUI::ModelIndex& index) { + auto model = IndividualSampleModel::create(*profile, index.data(GUI::ModelRole::Custom).to_integer()); + individual_sample_view.set_model(move(model)); + }; + auto menubar = GUI::MenuBar::construct(); auto& app_menu = menubar->add_menu("Profiler"); app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(); }));