diff options
Diffstat (limited to 'DevTools/ProfileViewer')
-rw-r--r-- | DevTools/ProfileViewer/CMakeLists.txt | 10 | ||||
-rw-r--r-- | DevTools/ProfileViewer/DisassemblyModel.cpp | 203 | ||||
-rw-r--r-- | DevTools/ProfileViewer/DisassemblyModel.h | 75 | ||||
-rw-r--r-- | DevTools/ProfileViewer/Profile.cpp | 306 | ||||
-rw-r--r-- | DevTools/ProfileViewer/Profile.h | 192 | ||||
-rw-r--r-- | DevTools/ProfileViewer/ProfileModel.cpp | 147 | ||||
-rw-r--r-- | DevTools/ProfileViewer/ProfileModel.h | 66 | ||||
-rw-r--r-- | DevTools/ProfileViewer/ProfileTimelineWidget.cpp | 112 | ||||
-rw-r--r-- | DevTools/ProfileViewer/ProfileTimelineWidget.h | 53 | ||||
-rw-r--r-- | DevTools/ProfileViewer/main.cpp | 104 |
10 files changed, 0 insertions, 1268 deletions
diff --git a/DevTools/ProfileViewer/CMakeLists.txt b/DevTools/ProfileViewer/CMakeLists.txt deleted file mode 100644 index c1adfa7ecf..0000000000 --- a/DevTools/ProfileViewer/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -set(SOURCES - DisassemblyModel.cpp - main.cpp - Profile.cpp - ProfileModel.cpp - ProfileTimelineWidget.cpp -) - -serenity_bin(ProfileViewer) -target_link_libraries(ProfileViewer LibGUI LibX86) diff --git a/DevTools/ProfileViewer/DisassemblyModel.cpp b/DevTools/ProfileViewer/DisassemblyModel.cpp deleted file mode 100644 index 3386ba946d..0000000000 --- a/DevTools/ProfileViewer/DisassemblyModel.cpp +++ /dev/null @@ -1,203 +0,0 @@ -/* - * 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/Loader.h> -#include <LibGUI/Painter.h> -#include <LibX86/Disassembler.h> -#include <ctype.h> -#include <stdio.h> - -static const Gfx::Bitmap& heat_gradient() -{ - static RefPtr<Gfx::Bitmap> bitmap; - if (!bitmap) { - bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, { 100, 1 }); - GUI::Painter painter(*bitmap); - painter.fill_rect_with_gradient(Orientation::Horizontal, bitmap->rect(), Color::from_rgb(0xffc080), Color::from_rgb(0xff3000)); - } - return *bitmap; -} - -static Color color_for_percent(int percent) -{ - ASSERT(percent >= 0 && percent <= 100); - return heat_gradient().get_pixel(percent, 0); -} - -class ELFSymbolProvider final : public X86::SymbolProvider { -public: - ELFSymbolProvider(ELF::Loader& loader) - : m_loader(loader) - { - } - - virtual String symbolicate(FlatPtr address, u32* offset = nullptr) const - { - return m_loader.symbolicate(address, offset); - } - -private: - ELF::Loader& m_loader; -}; - -DisassemblyModel::DisassemblyModel(Profile& profile, ProfileNode& node) - : m_profile(profile) - , m_node(node) -{ - String path; - if (m_node.address() >= 0xc0000000) - path = "/boot/Kernel"; - else - path = profile.executable_path(); - m_file = make<MappedFile>(path); - - if (!m_file->is_valid()) - return; - - auto elf_loader = ELF::Loader::create((const u8*)m_file->data(), m_file->size()); - - auto symbol = elf_loader->find_symbol(node.address()); - if (!symbol.has_value()) - return; - ASSERT(symbol.has_value()); - - auto view = symbol.value().raw_data(); - - ELFSymbolProvider symbol_provider(*elf_loader); - X86::SimpleInstructionStream stream((const u8*)view.characters_without_null_termination(), view.length()); - X86::Disassembler disassembler(stream); - - 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, &symbol_provider); - - 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); - float percent = ((float)samples_at_this_instruction / (float)m_node.event_count()) * 100.0f; - - m_instructions.append({ insn.value(), disassembly, instruction_bytes, address_in_profiled_program, samples_at_this_instruction, percent }); - - 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 {}; - } -} - -struct ColorPair { - Color background; - Color foreground; -}; - -static Optional<ColorPair> color_pair_for(const InstructionData& insn) -{ - if (insn.percent == 0) - return {}; - - Color background = color_for_percent(insn.percent); - Color foreground; - if (insn.percent > 50) - foreground = Color::White; - else - foreground = Color::Black; - return ColorPair { background, foreground }; -} - -GUI::Variant DisassemblyModel::data(const GUI::ModelIndex& index, Role role) const -{ - auto& insn = m_instructions[index.row()]; - - if (role == Role::BackgroundColor) { - auto colors = color_pair_for(insn); - if (!colors.has_value()) - return {}; - return colors.value().background; - } - - if (role == Role::ForegroundColor) { - auto colors = color_pair_for(insn); - if (!colors.has_value()) - return {}; - return colors.value().foreground; - } - - if (role == Role::Display) { - if (index.column() == Column::SampleCount) { - if (m_profile.show_percentages()) - return ((float)insn.event_count / (float)m_node.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 deleted file mode 100644 index 499a980997..0000000000 --- a/DevTools/ProfileViewer/DisassemblyModel.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 }; - float percent { 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 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/Profile.cpp b/DevTools/ProfileViewer/Profile.cpp deleted file mode 100644 index 32c966bbb5..0000000000 --- a/DevTools/ProfileViewer/Profile.cpp +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (c) 2018-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 "Profile.h" -#include "DisassemblyModel.h" -#include "ProfileModel.h" -#include <AK/HashTable.h> -#include <AK/MappedFile.h> -#include <AK/QuickSort.h> -#include <AK/RefPtr.h> -#include <LibCore/File.h> -#include <LibELF/Loader.h> -#include <stdio.h> - -static void sort_profile_nodes(Vector<NonnullRefPtr<ProfileNode>>& nodes) -{ - quick_sort(nodes.begin(), nodes.end(), [](auto& a, auto& b) { - return a->event_count() >= b->event_count(); - }); - - for (auto& child : nodes) - child->sort_children(); -} - -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; - - m_model = ProfileModel::create(*this); - - for (auto& event : m_events) { - m_deepest_stack_depth = max((u32)event.frames.size(), m_deepest_stack_depth); - } - - rebuild_tree(); -} - -Profile::~Profile() -{ -} - -GUI::Model& Profile::model() -{ - return *m_model; -} - -void Profile::rebuild_tree() -{ - u32 filtered_event_count = 0; - Vector<NonnullRefPtr<ProfileNode>> roots; - - auto find_or_create_root = [&roots](const String& symbol, u32 address, u32 offset, u64 timestamp) -> ProfileNode& { - for (size_t i = 0; i < roots.size(); ++i) { - auto& root = roots[i]; - if (root->symbol() == symbol) { - return root; - } - } - auto new_root = ProfileNode::create(symbol, address, offset, timestamp); - roots.append(new_root); - return new_root; - }; - - HashTable<FlatPtr> 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; - } - - if (event.type == "malloc") - live_allocations.set(event.ptr); - else if (event.type == "free") - live_allocations.remove(event.ptr); - } - - 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; - } - - if (event.type == "malloc" && !live_allocations.contains(event.ptr)) - continue; - - if (event.type == "free") - continue; - - ProfileNode* node = nullptr; - - auto for_each_frame = [&]<typename Callback>(Callback callback) - { - if (!m_inverted) { - for (size_t i = 0; i < event.frames.size(); ++i) { - if (callback(event.frames.at(i), i == event.frames.size() - 1) == IterationDecision::Break) - break; - } - } else { - for (ssize_t i = event.frames.size() - 1; i >= 0; --i) { - if (callback(event.frames.at(i), static_cast<size_t>(i) == event.frames.size() - 1) == IterationDecision::Break) - break; - } - } - }; - - for_each_frame([&](const Frame& frame, bool is_innermost_frame) { - auto& symbol = frame.symbol; - auto& address = frame.address; - auto& offset = frame.offset; - - if (symbol.is_empty()) - return IterationDecision::Break; - - if (!node) - node = &find_or_create_root(symbol, address, offset, event.timestamp); - else - node = &node->find_or_create_child(symbol, address, offset, event.timestamp); - - node->increment_event_count(); - if (is_innermost_frame) { - node->add_event_address(address); - node->increment_self_count(); - } - return IterationDecision::Continue; - }); - - ++filtered_event_count; - } - - sort_profile_nodes(roots); - - m_filtered_event_count = filtered_event_count; - m_roots = move(roots); - m_model->update(); -} - -OwnPtr<Profile> Profile::load_from_perfcore_file(const StringView& path) -{ - auto file = Core::File::construct(path); - if (!file->open(Core::IODevice::ReadOnly)) { - fprintf(stderr, "Unable to open %s, error: %s\n", path.to_string().characters(), file->error_string()); - return nullptr; - } - - auto json = JsonValue::from_string(file->read_all()); - ASSERT(json.has_value()); - if (!json.value().is_object()) { - fprintf(stderr, "Invalid perfcore format (not a JSON object)\n"); - return nullptr; - } - - auto& object = json.value().as_object(); - auto executable_path = object.get("executable").to_string(); - - MappedFile elf_file(executable_path); - if (!elf_file.is_valid()) { - fprintf(stderr, "Unable to open executable '%s' for symbolication.\n", executable_path.characters()); - return nullptr; - } - - auto elf_loader = ELF::Loader::create(static_cast<const u8*>(elf_file.data()), elf_file.size()); - - MappedFile kernel_elf_file("/boot/Kernel"); - RefPtr<ELF::Loader> kernel_elf_loader; - if (kernel_elf_file.is_valid()) - kernel_elf_loader = ELF::Loader::create(static_cast<const u8*>(kernel_elf_file.data()), kernel_elf_file.size()); - - auto events_value = object.get("events"); - if (!events_value.is_array()) - return nullptr; - - auto& perf_events = events_value.as_array(); - if (perf_events.is_empty()) - return nullptr; - - Vector<Event> events; - - for (auto& perf_event_value : perf_events.values()) { - auto& perf_event = perf_event_value.as_object(); - - Event event; - - event.timestamp = perf_event.get("timestamp").to_number<u64>(); - event.type = perf_event.get("type").to_string(); - - if (event.type == "malloc") { - event.ptr = perf_event.get("ptr").to_number<FlatPtr>(); - event.size = perf_event.get("size").to_number<size_t>(); - } else if (event.type == "free") { - event.ptr = perf_event.get("ptr").to_number<FlatPtr>(); - } - - auto stack_array = perf_event.get("stack").as_array(); - for (ssize_t i = stack_array.values().size() - 1; i >= 0; --i) { - auto& frame = stack_array.at(i); - auto ptr = frame.to_number<u32>(); - u32 offset = 0; - String symbol; - - if (ptr >= 0xc0000000) { - if (kernel_elf_loader) { - symbol = kernel_elf_loader->symbolicate(ptr, &offset); - } else { - symbol = "??"; - } - } else { - symbol = elf_loader->symbolicate(ptr, &offset); - } - - event.frames.append({ symbol, ptr, offset }); - } - - if (event.frames.size() < 2) - continue; - - FlatPtr innermost_frame_address = event.frames.at(1).address; - event.in_kernel = innermost_frame_address >= 0xc0000000; - - events.append(move(event)); - } - - return NonnullOwnPtr<Profile>(NonnullOwnPtr<Profile>::Adopt, *new Profile(executable_path, move(events))); -} - -void ProfileNode::sort_children() -{ - sort_profile_nodes(m_children); -} - -void Profile::set_timestamp_filter_range(u64 start, u64 end) -{ - if (m_has_timestamp_filter_range && m_timestamp_filter_range_start == start && m_timestamp_filter_range_end == end) - return; - m_has_timestamp_filter_range = true; - - m_timestamp_filter_range_start = min(start, end); - m_timestamp_filter_range_end = max(start, end); - - rebuild_tree(); -} - -void Profile::clear_timestamp_filter_range() -{ - if (!m_has_timestamp_filter_range) - return; - m_has_timestamp_filter_range = false; - rebuild_tree(); -} - -void Profile::set_inverted(bool inverted) -{ - if (m_inverted == inverted) - return; - m_inverted = inverted; - rebuild_tree(); -} - -void Profile::set_show_percentages(bool show_percentages) -{ - if (m_show_percentages == 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 deleted file mode 100644 index 45a02ddfe7..0000000000 --- a/DevTools/ProfileViewer/Profile.h +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (c) 2018-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 <AK/JsonArray.h> -#include <AK/JsonObject.h> -#include <AK/JsonValue.h> -#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: - static NonnullRefPtr<ProfileNode> create(const String& symbol, u32 address, u32 offset, u64 timestamp) - { - return adopt(*new ProfileNode(symbol, address, offset, timestamp)); - } - - const String& symbol() const { return m_symbol; } - u32 address() const { return m_address; } - u32 offset() const { return m_offset; } - u64 timestamp() const { return m_timestamp; } - - u32 event_count() const { return m_event_count; } - u32 self_count() const { return m_self_count; } - - int child_count() const { return m_children.size(); } - const Vector<NonnullRefPtr<ProfileNode>>& children() const { return m_children; } - - void add_child(ProfileNode& child) - { - if (child.m_parent == this) - return; - ASSERT(!child.m_parent); - child.m_parent = this; - m_children.append(child); - } - - ProfileNode& find_or_create_child(const String& symbol, u32 address, u32 offset, u64 timestamp) - { - for (size_t i = 0; i < m_children.size(); ++i) { - auto& child = m_children[i]; - if (child->symbol() == symbol) { - return child; - } - } - auto new_child = ProfileNode::create(symbol, address, offset, timestamp); - add_child(new_child); - return new_child; - }; - - ProfileNode* parent() { return m_parent; } - const ProfileNode* parent() const { return m_parent; } - - void increment_event_count() { ++m_event_count; } - void increment_self_count() { ++m_self_count; } - - 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) - , m_address(address) - , m_offset(offset) - , m_timestamp(timestamp) - { - } - - ProfileNode* m_parent { nullptr }; - String m_symbol; - u32 m_address { 0 }; - u32 m_offset { 0 }; - u32 m_event_count { 0 }; - u32 m_self_count { 0 }; - u64 m_timestamp { 0 }; - Vector<NonnullRefPtr<ProfileNode>> m_children; - HashMap<FlatPtr, size_t> m_events_per_address; -}; - -class Profile { -public: - static OwnPtr<Profile> load_from_perfcore_file(const StringView& path); - ~Profile(); - - GUI::Model& model(); - GUI::Model* disassembly_model(); - - void set_disassembly_index(const GUI::ModelIndex&); - - const Vector<NonnullRefPtr<ProfileNode>>& roots() const { return m_roots; } - - struct Frame { - String symbol; - u32 address { 0 }; - u32 offset { 0 }; - }; - - struct Event { - u64 timestamp { 0 }; - String type; - FlatPtr ptr { 0 }; - size_t size { 0 }; - bool in_kernel { false }; - Vector<Frame> frames; - }; - - u32 filtered_event_count() const { return m_filtered_event_count; } - - const Vector<Event>& events() const { return m_events; } - - 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_last_timestamp; } - u32 deepest_stack_depth() const { return m_deepest_stack_depth; } - - void set_timestamp_filter_range(u64 start, u64 end); - void clear_timestamp_filter_range(); - bool has_timestamp_filter_range() const { return m_has_timestamp_filter_range; } - - bool is_inverted() const { return m_inverted; } - void set_inverted(bool); - - bool show_percentages() const { return m_show_percentages; } - void set_show_percentages(bool); - - const String& executable_path() const { return m_executable_path; } - -private: - 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 }; - u64 m_last_timestamp { 0 }; - - Vector<Event> m_events; - - bool m_has_timestamp_filter_range { false }; - u64 m_timestamp_filter_range_start { 0 }; - u64 m_timestamp_filter_range_end { 0 }; - - u32 m_deepest_stack_depth { 0 }; - bool m_inverted { false }; - bool m_show_percentages { false }; -}; diff --git a/DevTools/ProfileViewer/ProfileModel.cpp b/DevTools/ProfileViewer/ProfileModel.cpp deleted file mode 100644 index 1d628e66c0..0000000000 --- a/DevTools/ProfileViewer/ProfileModel.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2018-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 "ProfileModel.h" -#include "Profile.h" -#include <AK/StringBuilder.h> -#include <ctype.h> -#include <stdio.h> - -ProfileModel::ProfileModel(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")); -} - -ProfileModel::~ProfileModel() -{ -} - -GUI::ModelIndex ProfileModel::index(int row, int column, const GUI::ModelIndex& parent) const -{ - if (!parent.is_valid()) { - if (m_profile.roots().is_empty()) - return {}; - return create_index(row, column, m_profile.roots().at(row).ptr()); - } - auto& remote_parent = *static_cast<ProfileNode*>(parent.internal_data()); - return create_index(row, column, remote_parent.children().at(row).ptr()); -} - -GUI::ModelIndex ProfileModel::parent_index(const GUI::ModelIndex& index) const -{ - if (!index.is_valid()) - return {}; - auto& node = *static_cast<ProfileNode*>(index.internal_data()); - if (!node.parent()) - return {}; - - // NOTE: If the parent has no parent, it's a root, so we have to look among the roots. - if (!node.parent()->parent()) { - for (size_t row = 0; row < m_profile.roots().size(); ++row) { - if (m_profile.roots()[row].ptr() == node.parent()) { - return create_index(row, index.column(), node.parent()); - } - } - ASSERT_NOT_REACHED(); - return {}; - } - - for (size_t row = 0; row < node.parent()->parent()->children().size(); ++row) { - if (node.parent()->parent()->children()[row].ptr() == node.parent()) - return create_index(row, index.column(), node.parent()); - } - - ASSERT_NOT_REACHED(); - return {}; -} - -int ProfileModel::row_count(const GUI::ModelIndex& index) const -{ - if (!index.is_valid()) - return m_profile.roots().size(); - auto& node = *static_cast<ProfileNode*>(index.internal_data()); - return node.children().size(); -} - -int ProfileModel::column_count(const GUI::ModelIndex&) const -{ - return Column::__Count; -} - -String ProfileModel::column_name(int column) const -{ - switch (column) { - case Column::SampleCount: - return m_profile.show_percentages() ? "% Samples" : "# Samples"; - case Column::SelfCount: - return m_profile.show_percentages() ? "% Self" : "# Self"; - case Column::StackFrame: - return "Stack Frame"; - default: - ASSERT_NOT_REACHED(); - return {}; - } -} - -GUI::Variant ProfileModel::data(const GUI::ModelIndex& index, Role role) const -{ - auto* node = static_cast<ProfileNode*>(index.internal_data()); - if (role == Role::TextAlignment) { - if (index.column() == Column::SampleCount || index.column() == Column::SelfCount) - return Gfx::TextAlignment::CenterRight; - } - if (role == Role::Icon) { - if (index.column() == Column::StackFrame) { - if (node->address() >= 0xc0000000) - return m_kernel_frame_icon; - return m_user_frame_icon; - } - return {}; - } - if (role == Role::Display) { - if (index.column() == Column::SampleCount) { - if (m_profile.show_percentages()) - return ((float)node->event_count() / (float)m_profile.filtered_event_count()) * 100.0f; - return node->event_count(); - } - if (index.column() == Column::SelfCount) { - if (m_profile.show_percentages()) - return ((float)node->self_count() / (float)m_profile.filtered_event_count()) * 100.0f; - return node->self_count(); - } - if (index.column() == Column::StackFrame) - return node->symbol(); - return {}; - } - return {}; -} - -void ProfileModel::update() -{ - did_update(Model::InvalidateAllIndexes); -} diff --git a/DevTools/ProfileViewer/ProfileModel.h b/DevTools/ProfileViewer/ProfileModel.h deleted file mode 100644 index a25520ec3c..0000000000 --- a/DevTools/ProfileViewer/ProfileModel.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2018-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> - - -class Profile; - -class ProfileModel final : public GUI::Model { -public: - static NonnullRefPtr<ProfileModel> create(Profile& profile) - { - return adopt(*new ProfileModel(profile)); - } - - enum Column { - SampleCount, - SelfCount, - StackFrame, - __Count - }; - - virtual ~ProfileModel() 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&, Role = Role::Display) const override; - virtual GUI::ModelIndex index(int row, int column, const GUI::ModelIndex& parent = GUI::ModelIndex()) const override; - virtual GUI::ModelIndex parent_index(const GUI::ModelIndex&) const override; - virtual void update() override; - virtual int tree_column() const override { return Column::StackFrame; } - -private: - explicit ProfileModel(Profile&); - - Profile& m_profile; - - GUI::Icon m_user_frame_icon; - GUI::Icon m_kernel_frame_icon; -}; diff --git a/DevTools/ProfileViewer/ProfileTimelineWidget.cpp b/DevTools/ProfileViewer/ProfileTimelineWidget.cpp deleted file mode 100644 index 025af2c5f9..0000000000 --- a/DevTools/ProfileViewer/ProfileTimelineWidget.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2018-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 "ProfileTimelineWidget.h" -#include "Profile.h" -#include <LibGUI/Painter.h> - -ProfileTimelineWidget::ProfileTimelineWidget(Profile& profile) - : m_profile(profile) -{ - set_background_color(Color::White); - set_fill_with_background_color(true); - set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); - set_preferred_size(0, 80); -} - -ProfileTimelineWidget::~ProfileTimelineWidget() -{ -} - -void ProfileTimelineWidget::paint_event(GUI::PaintEvent& event) -{ - GUI::Frame::paint_event(event); - - GUI::Painter painter(*this); - painter.add_clip_rect(event.rect()); - - float column_width = (float)frame_inner_rect().width() / (float)m_profile.length_in_ms(); - float frame_height = (float)frame_inner_rect().height() / (float)m_profile.deepest_stack_depth(); - - for (auto& event : m_profile.events()) { - u64 t = event.timestamp - m_profile.first_timestamp(); - int x = (int)((float)t * column_width); - int cw = max(1, (int)column_width); - - int column_height = frame_inner_rect().height() - (int)((float)event.frames.size() * frame_height); - - bool in_kernel = event.in_kernel; - 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() + column_height }, { x + i, height() - frame_thickness() * 2 }, color); - } - - u64 normalized_start_time = min(m_select_start_time, m_select_end_time); - u64 normalized_end_time = max(m_select_start_time, m_select_end_time); - - int select_start_x = (int)((float)(normalized_start_time - m_profile.first_timestamp()) * column_width); - int select_end_x = (int)((float)(normalized_end_time - m_profile.first_timestamp()) * column_width); - painter.fill_rect({ select_start_x, frame_thickness(), select_end_x - select_start_x, height() - frame_thickness() * 2 }, Color(0, 0, 0, 60)); -} - -u64 ProfileTimelineWidget::timestamp_at_x(int x) const -{ - float column_width = (float)frame_inner_rect().width() / (float)m_profile.length_in_ms(); - float ms_into_profile = (float)x / column_width; - return m_profile.first_timestamp() + (u64)ms_into_profile; -} - -void ProfileTimelineWidget::mousedown_event(GUI::MouseEvent& event) -{ - if (event.button() != GUI::MouseButton::Left) - return; - - m_selecting = true; - m_select_start_time = timestamp_at_x(event.x()); - m_select_end_time = m_select_start_time; - m_profile.set_timestamp_filter_range(m_select_start_time, m_select_end_time); - update(); -} - -void ProfileTimelineWidget::mousemove_event(GUI::MouseEvent& event) -{ - if (!m_selecting) - return; - - m_select_end_time = timestamp_at_x(event.x()); - m_profile.set_timestamp_filter_range(m_select_start_time, m_select_end_time); - update(); -} - -void ProfileTimelineWidget::mouseup_event(GUI::MouseEvent& event) -{ - if (event.button() != GUI::MouseButton::Left) - return; - - m_selecting = false; - if (m_select_start_time == m_select_end_time) - m_profile.clear_timestamp_filter_range(); -} diff --git a/DevTools/ProfileViewer/ProfileTimelineWidget.h b/DevTools/ProfileViewer/ProfileTimelineWidget.h deleted file mode 100644 index 056e07769e..0000000000 --- a/DevTools/ProfileViewer/ProfileTimelineWidget.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2018-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/Frame.h> - -class Profile; - -class ProfileTimelineWidget final : public GUI::Frame { - C_OBJECT(ProfileTimelineWidget) -public: - virtual ~ProfileTimelineWidget() override; - -private: - virtual void paint_event(GUI::PaintEvent&) override; - virtual void mousedown_event(GUI::MouseEvent&) override; - virtual void mousemove_event(GUI::MouseEvent&) override; - virtual void mouseup_event(GUI::MouseEvent&) override; - - explicit ProfileTimelineWidget(Profile&); - - u64 timestamp_at_x(int x) const; - - Profile& m_profile; - - bool m_selecting { false }; - u64 m_select_start_time { 0 }; - u64 m_select_end_time { 0 }; -}; diff --git a/DevTools/ProfileViewer/main.cpp b/DevTools/ProfileViewer/main.cpp deleted file mode 100644 index 0cd47c37a5..0000000000 --- a/DevTools/ProfileViewer/main.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2018-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 "Profile.h" -#include "ProfileTimelineWidget.h" -#include <LibGUI/Action.h> -#include <LibGUI/Application.h> -#include <LibGUI/BoxLayout.h> -#include <LibGUI/Menu.h> -#include <LibGUI/MenuBar.h> -#include <LibGUI/Model.h> -#include <LibGUI/Splitter.h> -#include <LibGUI/TableView.h> -#include <LibGUI/TreeView.h> -#include <LibGUI/Window.h> -#include <stdio.h> - -int main(int argc, char** argv) -{ - if (argc != 2) { - printf("usage: %s <profile-file>\n", argv[0]); - return 0; - } - - const char* path = argv[1]; - auto profile = Profile::load_from_perfcore_file(path); - - if (!profile) { - fprintf(stderr, "Unable to load profile '%s'\n", path); - return 1; - } - - GUI::Application app(argc, argv); - - auto window = GUI::Window::construct(); - window->set_title("ProfileViewer"); - window->set_rect(100, 100, 800, 600); - - auto& main_widget = window->set_main_widget<GUI::Widget>(); - main_widget.set_fill_with_background_color(true); - main_widget.set_layout<GUI::VerticalBoxLayout>(); - - main_widget.add<ProfileTimelineWidget>(*profile); - - auto& bottom_splitter = main_widget.add<GUI::VerticalSplitter>(); - - auto& tree_view = bottom_splitter.add<GUI::TreeView>(); - tree_view.set_headers_visible(true); - tree_view.set_model(profile->model()); - - auto& disassembly_view = bottom_splitter.add<GUI::TableView>(); - - tree_view.on_selection = [&](auto& index) { - profile->set_disassembly_index(index); - disassembly_view.set_model(profile->disassembly_model()); - }; - - auto menubar = GUI::MenuBar::construct(); - auto& app_menu = menubar->add_menu("ProfileViewer"); - app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app.quit(); })); - - auto& view_menu = menubar->add_menu("View"); - auto invert_action = GUI::Action::create_checkable("Invert tree", { Mod_Ctrl, Key_I }, [&](auto& action) { - profile->set_inverted(action.is_checked()); - }); - invert_action->set_checked(false); - view_menu.add_action(invert_action); - - auto percent_action = GUI::Action::create_checkable("Show percentages", { Mod_Ctrl, Key_P }, [&](auto& action) { - profile->set_show_percentages(action.is_checked()); - tree_view.update(); - disassembly_view.update(); - }); - percent_action->set_checked(false); - view_menu.add_action(percent_action); - - app.set_menubar(move(menubar)); - - window->show(); - return app.exec(); -} |