Serenity Operating System
at master 206 lines 6.3 kB view raw
1/* 2 * Copyright (c) 2021, Stephan Unverwerth <s.unverwerth@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include "SourceModel.h" 8#include "Gradient.h" 9#include "Profile.h" 10#include <LibDebug/DebugInfo.h> 11#include <LibGfx/Font/FontDatabase.h> 12#include <LibSymbolication/Symbolication.h> 13#include <stdio.h> 14 15namespace Profiler { 16 17class SourceFile final { 18public: 19 struct Line { 20 DeprecatedString content; 21 size_t num_samples { 0 }; 22 }; 23 24 static constexpr StringView source_root_path = "/usr/src/serenity/"sv; 25 26 SourceFile(StringView filename) 27 { 28 DeprecatedString source_file_name = filename.replace("../../"sv, source_root_path, ReplaceMode::FirstOnly); 29 30 auto try_read_lines = [&]() -> ErrorOr<void> { 31 auto unbuffered_file = TRY(Core::File::open(source_file_name, Core::File::OpenMode::Read)); 32 auto file = TRY(Core::BufferedFile::create(move(unbuffered_file))); 33 34 Array<u8, 1024> buffer; 35 while (!file->is_eof()) 36 m_lines.append({ TRY(file->read_line(buffer)), 0 }); 37 38 return {}; 39 }; 40 41 auto maybe_error = try_read_lines(); 42 if (maybe_error.is_error()) { 43 dbgln("Could not map source file \"{}\". Tried {}. {} (errno={})", filename, source_file_name, maybe_error.error().string_literal(), maybe_error.error().code()); 44 return; 45 } 46 } 47 48 void try_add_samples(size_t line, size_t samples) 49 { 50 if (line < 1 || line - 1 >= m_lines.size()) 51 return; 52 53 m_lines[line - 1].num_samples += samples; 54 } 55 56 Vector<Line> const& lines() const { return m_lines; } 57 58private: 59 Vector<Line> m_lines; 60}; 61 62SourceModel::SourceModel(Profile& profile, ProfileNode& node) 63 : m_profile(profile) 64 , m_node(node) 65{ 66 FlatPtr base_address = 0; 67 Debug::DebugInfo const* debug_info; 68 if (auto maybe_kernel_base = Symbolication::kernel_base(); maybe_kernel_base.has_value() && m_node.address() >= *maybe_kernel_base) { 69 if (!g_kernel_debuginfo_object.has_value()) 70 return; 71 base_address = maybe_kernel_base.release_value(); 72 if (g_kernel_debug_info == nullptr) 73 g_kernel_debug_info = make<Debug::DebugInfo>(g_kernel_debuginfo_object->elf, DeprecatedString::empty(), base_address); 74 debug_info = g_kernel_debug_info.ptr(); 75 } else { 76 auto const& process = node.process(); 77 auto const* library_data = process.library_metadata.library_containing(node.address()); 78 if (!library_data) { 79 dbgln("no library data for address {:p}", node.address()); 80 return; 81 } 82 base_address = library_data->base; 83 debug_info = &library_data->load_debug_info(base_address); 84 } 85 86 VERIFY(debug_info != nullptr); 87 88 // Try to read all source files contributing to the selected function and aggregate the samples by line. 89 HashMap<DeprecatedString, SourceFile> source_files; 90 for (auto const& pair : node.events_per_address()) { 91 auto position = debug_info->get_source_position(pair.key - base_address); 92 if (position.has_value()) { 93 auto it = source_files.find(position.value().file_path); 94 if (it == source_files.end()) { 95 source_files.set(position.value().file_path, SourceFile(position.value().file_path)); 96 it = source_files.find(position.value().file_path); 97 } 98 99 it->value.try_add_samples(position.value().line_number, pair.value); 100 } 101 } 102 103 // Process source file map and turn content into view model 104 for (auto const& file_iterator : source_files) { 105 u32 line_number = 0; 106 for (auto const& line_iterator : file_iterator.value.lines()) { 107 line_number++; 108 109 m_source_lines.append({ 110 (u32)line_iterator.num_samples, 111 line_iterator.num_samples * 100.0f / node.event_count(), 112 file_iterator.key, 113 line_number, 114 line_iterator.content, 115 }); 116 } 117 } 118} 119 120int SourceModel::row_count(GUI::ModelIndex const&) const 121{ 122 return m_source_lines.size(); 123} 124 125DeprecatedString SourceModel::column_name(int column) const 126{ 127 switch (column) { 128 case Column::SampleCount: 129 return m_profile.show_percentages() ? "% Samples" : "# Samples"; 130 case Column::SourceCode: 131 return "Source Code"; 132 case Column::Location: 133 return "Location"; 134 case Column::LineNumber: 135 return "Line"; 136 default: 137 VERIFY_NOT_REACHED(); 138 return {}; 139 } 140} 141 142struct ColorPair { 143 Color background; 144 Color foreground; 145}; 146 147static Optional<ColorPair> color_pair_for(SourceLineData const& line) 148{ 149 if (line.percent == 0) 150 return {}; 151 152 Color background = color_for_percent(line.percent); 153 Color foreground; 154 if (line.percent > 50) 155 foreground = Color::White; 156 else 157 foreground = Color::Black; 158 return ColorPair { background, foreground }; 159} 160 161GUI::Variant SourceModel::data(GUI::ModelIndex const& index, GUI::ModelRole role) const 162{ 163 auto const& line = m_source_lines[index.row()]; 164 165 if (role == GUI::ModelRole::BackgroundColor) { 166 auto colors = color_pair_for(line); 167 if (!colors.has_value()) 168 return {}; 169 return colors.value().background; 170 } 171 172 if (role == GUI::ModelRole::ForegroundColor) { 173 auto colors = color_pair_for(line); 174 if (!colors.has_value()) 175 return {}; 176 return colors.value().foreground; 177 } 178 179 if (role == GUI::ModelRole::Font) { 180 if (index.column() == Column::SourceCode) 181 return Gfx::FontDatabase::default_fixed_width_font(); 182 return {}; 183 } 184 185 if (role == GUI::ModelRole::Display) { 186 if (index.column() == Column::SampleCount) { 187 if (m_profile.show_percentages()) 188 return ((float)line.event_count / (float)m_node.event_count()) * 100.0f; 189 return line.event_count; 190 } 191 192 if (index.column() == Column::Location) 193 return line.location; 194 195 if (index.column() == Column::LineNumber) 196 return line.line_number; 197 198 if (index.column() == Column::SourceCode) 199 return line.source_code; 200 201 return {}; 202 } 203 return {}; 204} 205 206}