diff options
author | Andreas Kling <kling@serenityos.org> | 2020-04-11 18:46:11 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-04-11 18:46:11 +0200 |
commit | 69583f07e0d6b17671d8ee7809811220eef6a24b (patch) | |
tree | dec55b4336afb4fc3c01b2266062f968608992a0 /DevTools | |
parent | 5b91d848a7f4bdb0a197acb32e16c6cdf428db83 (diff) | |
download | serenity-69583f07e0d6b17671d8ee7809811220eef6a24b.zip |
ProfileViewer: Add an instruction-level sample viewer
When you select a function in the profile tree, we will now display
a per-instruction breakdown of aggregated samples in that function.
This allows us to look much closer at what our code is doing! :^)
Diffstat (limited to 'DevTools')
-rw-r--r-- | DevTools/ProfileViewer/DisassemblyModel.cpp | 131 | ||||
-rw-r--r-- | DevTools/ProfileViewer/DisassemblyModel.h | 75 | ||||
-rw-r--r-- | DevTools/ProfileViewer/Makefile | 3 | ||||
-rw-r--r-- | DevTools/ProfileViewer/Profile.cpp | 23 | ||||
-rw-r--r-- | DevTools/ProfileViewer/Profile.h | 26 | ||||
-rw-r--r-- | DevTools/ProfileViewer/ProfileModel.h | 1 | ||||
-rw-r--r-- | DevTools/ProfileViewer/main.cpp | 14 |
7 files changed, 267 insertions, 6 deletions
diff --git a/DevTools/ProfileViewer/DisassemblyModel.cpp b/DevTools/ProfileViewer/DisassemblyModel.cpp new file mode 100644 index 0000000000..594c237018 --- /dev/null +++ b/DevTools/ProfileViewer/DisassemblyModel.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2020, 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 "DisassemblyModel.h" +#include "Profile.h" +#include <AK/MappedFile.h> +#include <LibELF/ELFLoader.h> +#include <LibX86/Disassembler.h> +#include <ctype.h> +#include <stdio.h> + +DisassemblyModel::DisassemblyModel(Profile& profile, ProfileNode& node) + : m_profile(profile) + , m_node(node) +{ + m_file = make<MappedFile>(profile.executable_path()); + auto elf_loader = make<ELFLoader>((const u8*)m_file->data(), m_file->size()); + + auto symbol = elf_loader->find_symbol(node.address()); + ASSERT(symbol.has_value()); + + auto view = symbol.value().raw_data(); + X86::SimpleInstructionStream stream((const u8*)view.characters_without_null_termination(), view.length()); + X86::Disassembler disassembler(stream); + + dbg() << "Disassembly for " << node.symbol() << " @ " << (const void*)node.address(); + + size_t offset_into_symbol = 0; + for (;;) { + auto insn = disassembler.next(); + if (!insn.has_value()) + break; + FlatPtr address_in_profiled_program = symbol.value().value() + offset_into_symbol; + + auto disassembly = insn.value().to_string(address_in_profiled_program); + dbg() << disassembly; + + StringView instruction_bytes = view.substring_view(offset_into_symbol, insn.value().length()); + size_t samples_at_this_instruction = m_node.events_per_address().get(address_in_profiled_program).value_or(0); + + m_instructions.append({ insn.value(), disassembly, instruction_bytes, address_in_profiled_program, samples_at_this_instruction }); + + offset_into_symbol += insn.value().length(); + } +} + +DisassemblyModel::~DisassemblyModel() +{ +} + +int DisassemblyModel::row_count(const GUI::ModelIndex&) const +{ + return m_instructions.size(); +} + +String DisassemblyModel::column_name(int column) const +{ + switch (column) { + case Column::SampleCount: + return m_profile.show_percentages() ? "% Samples" : "# Samples"; + case Column::Address: + return "Address"; + case Column::InstructionBytes: + return "Insn Bytes"; + case Column::Disassembly: + return "Disassembly"; + default: + ASSERT_NOT_REACHED(); + return {}; + } +} + +GUI::Model::ColumnMetadata DisassemblyModel::column_metadata(int column) const +{ + if (column == Column::SampleCount) + return ColumnMetadata { 0, Gfx::TextAlignment::CenterRight }; + return {}; +} + +GUI::Variant DisassemblyModel::data(const GUI::ModelIndex& index, Role role) const +{ + auto& insn = m_instructions[index.row()]; + if (role == Role::Display) { + if (index.column() == Column::SampleCount) { + if (m_profile.show_percentages()) + return ((float)insn.event_count / (float)m_profile.filtered_event_count()) * 100.0f; + return insn.event_count; + } + if (index.column() == Column::Address) + return String::format("%#08x", insn.address); + if (index.column() == Column::InstructionBytes) { + StringBuilder builder; + for (auto ch : insn.bytes) { + builder.appendf("%02x ", (u8)ch); + } + return builder.to_string(); + } + if (index.column() == Column::Disassembly) + return insn.disassembly; + return {}; + } + return {}; +} + +void DisassemblyModel::update() +{ + did_update(); +} diff --git a/DevTools/ProfileViewer/DisassemblyModel.h b/DevTools/ProfileViewer/DisassemblyModel.h new file mode 100644 index 0000000000..2414e27fed --- /dev/null +++ b/DevTools/ProfileViewer/DisassemblyModel.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020, 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> +#include <LibX86/Instruction.h> + +class Profile; +class ProfileNode; + +struct InstructionData { + X86::Instruction insn; + String disassembly; + StringView bytes; + FlatPtr address { 0 }; + u32 event_count { 0 }; +}; + +class DisassemblyModel final : public GUI::Model { +public: + static NonnullRefPtr<DisassemblyModel> create(Profile& profile, ProfileNode& node) + { + return adopt(*new DisassemblyModel(profile, node)); + } + + enum Column { + Address, + SampleCount, + InstructionBytes, + Disassembly, + __Count + }; + + virtual ~DisassemblyModel() override; + + virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override; + virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return Column::__Count; } + virtual String column_name(int) const override; + virtual ColumnMetadata column_metadata(int) const override; + virtual GUI::Variant data(const GUI::ModelIndex&, Role = Role::Display) const override; + virtual void update() override; + +private: + DisassemblyModel(Profile&, ProfileNode&); + + Profile& m_profile; + ProfileNode& m_node; + OwnPtr<MappedFile> m_file; + + Vector<InstructionData> m_instructions; +}; diff --git a/DevTools/ProfileViewer/Makefile b/DevTools/ProfileViewer/Makefile index 745fa429dd..4df2dc0236 100644 --- a/DevTools/ProfileViewer/Makefile +++ b/DevTools/ProfileViewer/Makefile @@ -1,4 +1,5 @@ OBJS = \ + DisassemblyModel.o \ Profile.o \ ProfileModel.o \ ProfileTimelineWidget.o \ @@ -6,6 +7,6 @@ OBJS = \ PROGRAM = ProfileViewer -LIB_DEPS = GUI Gfx IPC Core +LIB_DEPS = GUI Gfx IPC Core X86 include ../../Makefile.common diff --git a/DevTools/ProfileViewer/Profile.cpp b/DevTools/ProfileViewer/Profile.cpp index e6a6075f7a..93bddfa16c 100644 --- a/DevTools/ProfileViewer/Profile.cpp +++ b/DevTools/ProfileViewer/Profile.cpp @@ -25,6 +25,7 @@ */ #include "Profile.h" +#include "DisassemblyModel.h" #include "ProfileModel.h" #include <AK/HashTable.h> #include <AK/MappedFile.h> @@ -43,8 +44,9 @@ static void sort_profile_nodes(Vector<NonnullRefPtr<ProfileNode>>& nodes) child->sort_children(); } -Profile::Profile(Vector<Event> events) - : m_events(move(events)) +Profile::Profile(String executable_path, Vector<Event> events) + : m_executable_path(move(executable_path)) + , m_events(move(events)) { m_first_timestamp = m_events.first().timestamp; m_last_timestamp = m_events.last().timestamp; @@ -142,6 +144,7 @@ void Profile::rebuild_tree() else node = &node->find_or_create_child(symbol, address, offset, event.timestamp); + node->add_event_address(address); node->increment_event_count(); if (is_innermost_frame) node->increment_self_count(); @@ -242,7 +245,7 @@ OwnPtr<Profile> Profile::load_from_perfcore_file(const StringView& path) events.append(move(event)); } - return NonnullOwnPtr<Profile>(NonnullOwnPtr<Profile>::Adopt, *new Profile(move(events))); + return NonnullOwnPtr<Profile>(NonnullOwnPtr<Profile>::Adopt, *new Profile(executable_path, move(events))); } void ProfileNode::sort_children() @@ -284,3 +287,17 @@ void Profile::set_show_percentages(bool show_percentages) return; m_show_percentages = show_percentages; } + +void Profile::set_disassembly_index(const GUI::ModelIndex& index) +{ + if (m_disassembly_index == index) + return; + m_disassembly_index = index; + auto* node = static_cast<ProfileNode*>(index.internal_data()); + m_disassembly_model = DisassemblyModel::create(*this, *node); +} + +GUI::Model* Profile::disassembly_model() +{ + return m_disassembly_model; +} diff --git a/DevTools/ProfileViewer/Profile.h b/DevTools/ProfileViewer/Profile.h index 04c96d9c56..45a02ddfe7 100644 --- a/DevTools/ProfileViewer/Profile.h +++ b/DevTools/ProfileViewer/Profile.h @@ -32,8 +32,10 @@ #include <AK/NonnullRefPtrVector.h> #include <AK/OwnPtr.h> #include <LibGUI/Forward.h> +#include <LibGUI/ModelIndex.h> class ProfileModel; +class DisassemblyModel; class ProfileNode : public RefCounted<ProfileNode> { public: @@ -83,6 +85,16 @@ public: void sort_children(); + const HashMap<FlatPtr, size_t>& events_per_address() const { return m_events_per_address; } + void add_event_address(FlatPtr address) + { + auto it = m_events_per_address.find(address); + if (it == m_events_per_address.end()) + m_events_per_address.set(address, 1); + else + m_events_per_address.set(address, it->value + 1); + } + private: explicit ProfileNode(const String& symbol, u32 address, u32 offset, u64 timestamp) : m_symbol(symbol) @@ -100,6 +112,7 @@ private: u32 m_self_count { 0 }; u64 m_timestamp { 0 }; Vector<NonnullRefPtr<ProfileNode>> m_children; + HashMap<FlatPtr, size_t> m_events_per_address; }; class Profile { @@ -108,6 +121,9 @@ public: ~Profile(); GUI::Model& model(); + GUI::Model* disassembly_model(); + + void set_disassembly_index(const GUI::ModelIndex&); const Vector<NonnullRefPtr<ProfileNode>>& roots() const { return m_roots; } @@ -145,12 +161,20 @@ public: bool show_percentages() const { return m_show_percentages; } void set_show_percentages(bool); + const String& executable_path() const { return m_executable_path; } + private: - explicit Profile(Vector<Event>); + Profile(String executable_path, Vector<Event>); void rebuild_tree(); + String m_executable_path; + RefPtr<ProfileModel> m_model; + RefPtr<DisassemblyModel> m_disassembly_model; + + GUI::ModelIndex m_disassembly_index; + Vector<NonnullRefPtr<ProfileNode>> m_roots; u32 m_filtered_event_count { 0 }; u64 m_first_timestamp { 0 }; diff --git a/DevTools/ProfileViewer/ProfileModel.h b/DevTools/ProfileViewer/ProfileModel.h index 8ab3934b31..9e816ec2ea 100644 --- a/DevTools/ProfileViewer/ProfileModel.h +++ b/DevTools/ProfileViewer/ProfileModel.h @@ -28,6 +28,7 @@ #include <LibGUI/Model.h> + class Profile; class ProfileModel final : public GUI::Model { diff --git a/DevTools/ProfileViewer/main.cpp b/DevTools/ProfileViewer/main.cpp index 4c21561240..a8625ba886 100644 --- a/DevTools/ProfileViewer/main.cpp +++ b/DevTools/ProfileViewer/main.cpp @@ -32,6 +32,7 @@ #include <LibGUI/Menu.h> #include <LibGUI/MenuBar.h> #include <LibGUI/Model.h> +#include <LibGUI/TableView.h> #include <LibGUI/TreeView.h> #include <LibGUI/Window.h> #include <stdio.h> @@ -63,11 +64,22 @@ int main(int argc, char** argv) main_widget.add<ProfileTimelineWidget>(*profile); - auto& tree_view = main_widget.add<GUI::TreeView>(); + auto& bottom_container = main_widget.add<GUI::Widget>(); + bottom_container.set_layout<GUI::VerticalBoxLayout>(); + + auto& tree_view = bottom_container.add<GUI::TreeView>(); tree_view.set_headers_visible(true); tree_view.set_size_columns_to_fit_content(true); tree_view.set_model(profile->model()); + auto& disassembly_view = bottom_container.add<GUI::TableView>(); + disassembly_view.set_size_columns_to_fit_content(true); + + tree_view.on_selection = [&](auto& index) { + profile->set_disassembly_index(index); + disassembly_view.set_model(profile->disassembly_model()); + }; + auto menubar = make<GUI::MenuBar>(); auto& app_menu = menubar->add_menu("ProfileViewer"); app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app.quit(); })); |