mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 20:42:43 +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
	
	 Andreas Kling
						Andreas Kling