diff options
-rw-r--r-- | Kernel/FileSystem/ProcFS.cpp | 8 | ||||
-rw-r--r-- | Kernel/PerformanceEventBuffer.cpp | 77 | ||||
-rw-r--r-- | Kernel/PerformanceEventBuffer.h | 20 | ||||
-rw-r--r-- | Kernel/Process.cpp | 8 | ||||
-rw-r--r-- | Kernel/Scheduler.cpp | 9 | ||||
-rw-r--r-- | Kernel/Syscalls/mmap.cpp | 1 | ||||
-rw-r--r-- | Userland/DevTools/Profiler/DisassemblyModel.cpp | 3 | ||||
-rw-r--r-- | Userland/DevTools/Profiler/Profile.cpp | 94 | ||||
-rw-r--r-- | Userland/DevTools/Profiler/Profile.h | 91 | ||||
-rw-r--r-- | Userland/DevTools/Profiler/SamplesModel.cpp | 14 | ||||
-rw-r--r-- | Userland/DevTools/Profiler/SamplesModel.h | 2 |
11 files changed, 224 insertions, 103 deletions
diff --git a/Kernel/FileSystem/ProcFS.cpp b/Kernel/FileSystem/ProcFS.cpp index 0a41328964..df7deae852 100644 --- a/Kernel/FileSystem/ProcFS.cpp +++ b/Kernel/FileSystem/ProcFS.cpp @@ -488,16 +488,10 @@ static bool procfs$pid_perf_events(InodeIdentifier identifier, KBufferBuilder& b auto process = Process::from_pid(to_pid(identifier)); if (!process) return false; - InterruptDisabler disabler; - - if (!process->executable()) - return false; - if (!process->perf_events()) return false; - - return process->perf_events()->to_json(builder, process->pid(), process->executable()->absolute_path()); + return process->perf_events()->to_json(builder); } static bool procfs$net_adapters(InodeIdentifier, KBufferBuilder& builder) diff --git a/Kernel/PerformanceEventBuffer.cpp b/Kernel/PerformanceEventBuffer.cpp index 990ddfc5c4..0b3058696b 100644 --- a/Kernel/PerformanceEventBuffer.cpp +++ b/Kernel/PerformanceEventBuffer.cpp @@ -28,6 +28,7 @@ #include <AK/JsonObject.h> #include <AK/JsonObjectSerializer.h> #include <Kernel/Arch/x86/SmapDisabler.h> +#include <Kernel/FileSystem/Custody.h> #include <Kernel/KBufferBuilder.h> #include <Kernel/PerformanceEventBuffer.h> #include <Kernel/Process.h> @@ -111,14 +112,6 @@ PerformanceEvent& PerformanceEventBuffer::at(size_t index) return events[index]; } -OwnPtr<KBuffer> PerformanceEventBuffer::to_json(ProcessID pid, const String& executable_path) const -{ - KBufferBuilder builder; - if (!to_json(builder, pid, executable_path)) - return {}; - return builder.build(); -} - template<typename Serializer> bool PerformanceEventBuffer::to_json_impl(Serializer& object) const { @@ -154,33 +147,28 @@ bool PerformanceEventBuffer::to_json_impl(Serializer& object) const return true; } -bool PerformanceEventBuffer::to_json(KBufferBuilder& builder) +bool PerformanceEventBuffer::to_json(KBufferBuilder& builder) const { JsonObjectSerializer object(builder); - return to_json_impl(object); -} - -bool PerformanceEventBuffer::to_json(KBufferBuilder& builder, ProcessID pid, const String& executable_path) const -{ - auto process = Process::from_pid(pid); - VERIFY(process); - ScopedSpinLock locker(process->space().get_lock()); - JsonObjectSerializer object(builder); - object.add("pid", pid.value()); - object.add("executable", executable_path); - - { - auto region_array = object.add_array("regions"); - for (const auto& region : process->space().regions()) { - auto region_object = region_array.add_object(); - region_object.add("base", region.vaddr().get()); - region_object.add("size", region.size()); - region_object.add("name", region.name()); + auto processes_array = object.add_array("processes"); + for (auto& it : m_processes) { + auto& process = *it.value; + auto process_object = processes_array.add_object(); + process_object.add("pid", process.pid.value()); + process_object.add("executable", process.executable); + + auto regions_array = process_object.add_array("regions"); + for (auto& region : process.regions) { + auto region_object = regions_array.add_object(); + region_object.add("name", region.name); + region_object.add("base", region.range.base().get()); + region_object.add("size", region.range.size()); } - region_array.finish(); } + processes_array.finish(); + return to_json_impl(object); } @@ -192,4 +180,35 @@ OwnPtr<PerformanceEventBuffer> PerformanceEventBuffer::try_create_with_size(size return adopt_own(*new PerformanceEventBuffer(buffer.release_nonnull())); } +void PerformanceEventBuffer::add_process(const Process& process) +{ + // FIXME: What about threads that have died? + + ScopedSpinLock locker(process.space().get_lock()); + + String executable; + if (process.executable()) + executable = process.executable()->absolute_path(); + + auto sampled_process = adopt_own(*new SampledProcess { + .pid = process.pid().value(), + .executable = executable, + .threads = {}, + .regions = {}, + }); + process.for_each_thread([&](auto& thread) { + sampled_process->threads.set(thread.tid()); + return IterationDecision::Continue; + }); + + for (auto& region : process.space().regions()) { + sampled_process->regions.append(SampledProcess::Region { + .name = region.name(), + .range = region.range(), + }); + } + + m_processes.set(process.pid(), move(sampled_process)); +} + } diff --git a/Kernel/PerformanceEventBuffer.h b/Kernel/PerformanceEventBuffer.h index b85b60f7c8..18c1a1d087 100644 --- a/Kernel/PerformanceEventBuffer.h +++ b/Kernel/PerformanceEventBuffer.h @@ -75,15 +75,25 @@ public: return const_cast<PerformanceEventBuffer&>(*this).at(index); } - OwnPtr<KBuffer> to_json(ProcessID, const String& executable_path) const; - bool to_json(KBufferBuilder&, ProcessID, const String& executable_path) const; + bool to_json(KBufferBuilder&) const; - // Used by full-system profile (/proc/profile) - bool to_json(KBufferBuilder&); + void add_process(const Process&); private: explicit PerformanceEventBuffer(NonnullOwnPtr<KBuffer>); + struct SampledProcess { + ProcessID pid; + String executable; + HashTable<ThreadID> threads; + + struct Region { + String name; + Range range; + }; + Vector<Region> regions; + }; + template<typename Serializer> bool to_json_impl(Serializer&) const; @@ -91,6 +101,8 @@ private: size_t m_count { 0 }; NonnullOwnPtr<KBuffer> m_buffer; + + HashMap<ProcessID, NonnullOwnPtr<SampledProcess>> m_processes; }; } diff --git a/Kernel/Process.cpp b/Kernel/Process.cpp index 4cba181984..f4075153f4 100644 --- a/Kernel/Process.cpp +++ b/Kernel/Process.cpp @@ -448,10 +448,13 @@ bool Process::dump_perfcore() if (description_or_error.is_error()) return false; auto& description = description_or_error.value(); - auto json = m_perf_event_buffer->to_json(m_pid, m_executable ? m_executable->absolute_path() : ""); - if (!json) + KBufferBuilder builder; + if (!m_perf_event_buffer->to_json(builder)) return false; + auto json = builder.build(); + if (!json) + return false; auto json_buffer = UserOrKernelBuffer::for_kernel_buffer(json->data()); return !description->write(json_buffer, json->size()).is_error(); } @@ -671,6 +674,7 @@ bool Process::create_perf_events_buffer_if_needed() { if (!m_perf_event_buffer) { m_perf_event_buffer = PerformanceEventBuffer::try_create_with_size(4 * MiB); + m_perf_event_buffer->add_process(*this); } return !!m_perf_event_buffer; } diff --git a/Kernel/Scheduler.cpp b/Kernel/Scheduler.cpp index fb71b6af28..f6c85fabb0 100644 --- a/Kernel/Scheduler.cpp +++ b/Kernel/Scheduler.cpp @@ -552,8 +552,15 @@ void Scheduler::timer_tick(const RegisterState& regs) VERIFY(g_global_perf_events); // FIXME: We currently don't collect samples while idle. // That will be an interesting mode to add in the future. :^) - if (current_thread != Processor::current().idle_thread()) + if (current_thread != Processor::current().idle_thread()) { perf_events = g_global_perf_events; + if (current_thread->process().space().enforces_syscall_regions()) { + // FIXME: This is very nasty! We dump the current process's address + // space layout *every time* it's sampled. We should figure out + // a way to do this less often. + perf_events->add_process(current_thread->process()); + } + } } else if (current_thread->process().is_profiling()) { VERIFY(current_thread->process().perf_events()); perf_events = current_thread->process().perf_events(); diff --git a/Kernel/Syscalls/mmap.cpp b/Kernel/Syscalls/mmap.cpp index 7f25a6fa08..45693ff76c 100644 --- a/Kernel/Syscalls/mmap.cpp +++ b/Kernel/Syscalls/mmap.cpp @@ -26,6 +26,7 @@ #include <AK/WeakPtr.h> #include <Kernel/FileSystem/FileDescription.h> +#include <Kernel/PerformanceEventBuffer.h> #include <Kernel/Process.h> #include <Kernel/VM/MemoryManager.h> #include <Kernel/VM/PageDirectory.h> diff --git a/Userland/DevTools/Profiler/DisassemblyModel.cpp b/Userland/DevTools/Profiler/DisassemblyModel.cpp index 26e057302c..b058df6152 100644 --- a/Userland/DevTools/Profiler/DisassemblyModel.cpp +++ b/Userland/DevTools/Profiler/DisassemblyModel.cpp @@ -68,7 +68,8 @@ DisassemblyModel::DisassemblyModel(Profile& profile, ProfileNode& node) kernel_elf = make<ELF::Image>((const u8*)m_kernel_file->data(), m_kernel_file->size()); elf = kernel_elf.ptr(); } else { - auto library_data = profile.libraries().library_containing(node.address()); + // FIXME: This is kinda rickety looking with all the -> -> -> + auto library_data = node.process(profile)->library_metadata->library_containing(node.address()); if (!library_data) { dbgln("no library data"); return; diff --git a/Userland/DevTools/Profiler/Profile.cpp b/Userland/DevTools/Profiler/Profile.cpp index a486fe683f..893f02a0e5 100644 --- a/Userland/DevTools/Profiler/Profile.cpp +++ b/Userland/DevTools/Profiler/Profile.cpp @@ -47,10 +47,9 @@ static void sort_profile_nodes(Vector<NonnullRefPtr<ProfileNode>>& nodes) child->sort_children(); } -Profile::Profile(String executable_path, Vector<Event> events, NonnullOwnPtr<LibraryMetadata> library_metadata) - : m_executable_path(move(executable_path)) +Profile::Profile(Vector<Process> processes, Vector<Event> events) + : m_processes(move(processes)) , m_events(move(events)) - , m_library_metadata(move(library_metadata)) { m_first_timestamp = m_events.first().timestamp; m_last_timestamp = m_events.last().timestamp; @@ -84,14 +83,14 @@ void Profile::rebuild_tree() u32 filtered_event_count = 0; Vector<NonnullRefPtr<ProfileNode>> roots; - auto find_or_create_root = [&roots](FlyString object_name, String symbol, u32 address, u32 offset, u64 timestamp) -> ProfileNode& { + auto find_or_create_root = [&roots](FlyString object_name, String symbol, u32 address, u32 offset, u64 timestamp, pid_t pid) -> 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(move(object_name), move(symbol), address, offset, timestamp); + auto new_root = ProfileNode::create(move(object_name), move(symbol), address, offset, timestamp, pid); roots.append(new_root); return new_root; }; @@ -149,10 +148,11 @@ void Profile::rebuild_tree() if (symbol.is_empty()) return IterationDecision::Break; + // FIXME: More cheating with intentional mixing of TID/PID here: if (!node) - node = &find_or_create_root(object_name, symbol, address, offset, event.timestamp); + node = &find_or_create_root(object_name, symbol, address, offset, event.timestamp, event.tid); else - node = &node->find_or_create_child(object_name, symbol, address, offset, event.timestamp); + node = &node->find_or_create_child(object_name, symbol, address, offset, event.timestamp, event.tid); node->increment_event_count(); if (is_innermost_frame) { @@ -174,12 +174,13 @@ void Profile::rebuild_tree() if (symbol.is_empty()) break; + // FIXME: More PID/TID mixing cheats here: if (!node) { - node = &find_or_create_root(object_name, symbol, address, offset, event.timestamp); + node = &find_or_create_root(object_name, symbol, address, offset, event.timestamp, event.tid); root = node; root->will_track_seen_events(m_events.size()); } else { - node = &node->find_or_create_child(object_name, symbol, address, offset, event.timestamp); + node = &node->find_or_create_child(object_name, symbol, address, offset, event.timestamp, event.tid); } if (!root->has_seen_event(event_index)) { @@ -219,11 +220,45 @@ Result<NonnullOwnPtr<Profile>, String> Profile::load_from_perfcore_file(const St return String { "Invalid perfcore format (not a JSON object)" }; auto& object = json.value().as_object(); - auto executable_path = object.get("executable").to_string(); - auto pid = object.get("pid"); - if (!pid.is_u32()) - return String { "Invalid perfcore format (no process ID)" }; + auto processes_value = object.get("processes"); + if (processes_value.is_null()) + return String { "Invalid perfcore format (no processes)" }; + + if (!processes_value.is_array()) + return String { "Invalid perfcore format (processes is not an array)" }; + + Vector<Process> sampled_processes; + + for (auto& process_value : processes_value.as_array().values()) { + if (!process_value.is_object()) + return String { "Invalid perfcore format (process value is not an object)" }; + auto& process = process_value.as_object(); + auto regions_value = process.get("regions"); + if (!regions_value.is_array()) + return String { "Invalid perfcore format (regions is not an array)" }; + + Process sampled_process { + .pid = (pid_t)process.get("pid").to_i32(), + .executable = process.get("executable").to_string(), + .threads = {}, + .regions = {}, + .library_metadata = make<LibraryMetadata>(regions_value.as_array()), + }; + + for (auto& region_value : regions_value.as_array().values()) { + if (!region_value.is_object()) + return String { "Invalid perfcore format (region is not an object)" }; + auto& region = region_value.as_object(); + sampled_process.regions.append(Process::Region { + .name = region.get("name").to_string(), + .base = region.get("base").to_u32(), + .size = region.get("size").to_u32(), + }); + } + + sampled_processes.append(move(sampled_process)); + } auto file_or_error = MappedFile::map("/boot/Kernel"); OwnPtr<ELF::Image> kernel_elf; @@ -234,16 +269,10 @@ Result<NonnullOwnPtr<Profile>, String> Profile::load_from_perfcore_file(const St if (!events_value.is_array()) return String { "Malformed profile (events is not an array)" }; - auto regions_value = object.get("regions"); - if (!regions_value.is_array() || regions_value.as_array().is_empty()) - return String { "Malformed profile (regions is not an array, or it is empty)" }; - auto& perf_events = events_value.as_array(); if (perf_events.is_empty()) return String { "No events captured (targeted process was never on CPU)" }; - auto library_metadata = make<LibraryMetadata>(regions_value.as_array()); - Vector<Event> events; for (auto& perf_event_value : perf_events.values()) { @@ -253,6 +282,7 @@ Result<NonnullOwnPtr<Profile>, String> Profile::load_from_perfcore_file(const St event.timestamp = perf_event.get("timestamp").to_number<u64>(); event.type = perf_event.get("type").to_string(); + event.tid = perf_event.get("tid").to_i32(); if (event.type == "malloc") { event.ptr = perf_event.get("ptr").to_number<FlatPtr>(); @@ -276,7 +306,15 @@ Result<NonnullOwnPtr<Profile>, String> Profile::load_from_perfcore_file(const St symbol = "??"; } } else { - if (auto* library = library_metadata->library_containing(ptr)) { + auto it = sampled_processes.find_if([&](auto& entry) { + // FIXME: This doesn't support multi-threaded programs! + return entry.pid == event.tid; + }); + // FIXME: This logic is kinda gnarly, find a way to clean it up. + LibraryMetadata* library_metadata {}; + if (!it.is_end()) + library_metadata = it->library_metadata.ptr(); + if (auto* library = library_metadata ? library_metadata->library_containing(ptr) : nullptr) { object_name = library->name; symbol = library->elf.symbolicate(ptr - library->base, &offset); } else { @@ -296,7 +334,7 @@ Result<NonnullOwnPtr<Profile>, String> Profile::load_from_perfcore_file(const St events.append(move(event)); } - return adopt_own(*new Profile(executable_path, move(events), move(library_metadata))); + return adopt_own(*new Profile(move(sampled_processes), move(events))); } void ProfileNode::sort_children() @@ -363,7 +401,7 @@ GUI::Model* Profile::disassembly_model() return m_disassembly_model; } -Profile::LibraryMetadata::LibraryMetadata(JsonArray regions) +LibraryMetadata::LibraryMetadata(JsonArray regions) : m_regions(move(regions)) { for (auto& region_value : m_regions.values()) { @@ -391,12 +429,12 @@ Profile::LibraryMetadata::LibraryMetadata(JsonArray regions) auto elf = ELF::Image(file_or_error.value()->bytes()); if (!elf.is_valid()) continue; - auto library = make<Library>(base, size, name, file_or_error.release_value(), move(elf)); + auto library = adopt_own(*new Library { base, size, name, file_or_error.release_value(), move(elf) }); m_libraries.set(name, move(library)); } } -const Profile::LibraryMetadata::Library* Profile::LibraryMetadata::library_containing(FlatPtr ptr) const +const LibraryMetadata::Library* LibraryMetadata::library_containing(FlatPtr ptr) const { for (auto& it : m_libraries) { if (!it.value) @@ -408,8 +446,9 @@ const Profile::LibraryMetadata::Library* Profile::LibraryMetadata::library_conta return nullptr; } -ProfileNode::ProfileNode(const String& object_name, String symbol, u32 address, u32 offset, u64 timestamp) +ProfileNode::ProfileNode(const String& object_name, String symbol, u32 address, u32 offset, u64 timestamp, pid_t pid) : m_symbol(move(symbol)) + , m_pid(pid) , m_address(address) , m_offset(offset) , m_timestamp(timestamp) @@ -422,3 +461,8 @@ ProfileNode::ProfileNode(const String& object_name, String symbol, u32 address, } m_object_name = LexicalPath(object).basename(); } + +const Process* ProfileNode::process(Profile& profile) const +{ + return profile.find_process(m_pid); +} diff --git a/Userland/DevTools/Profiler/Profile.h b/Userland/DevTools/Profiler/Profile.h index b8d69ca1ee..efacf40c2f 100644 --- a/Userland/DevTools/Profiler/Profile.h +++ b/Userland/DevTools/Profiler/Profile.h @@ -39,15 +39,50 @@ #include <LibGUI/Forward.h> #include <LibGUI/ModelIndex.h> -class ProfileModel; class DisassemblyModel; +class Profile; +class ProfileModel; class SamplesModel; +class LibraryMetadata { +public: + explicit LibraryMetadata(JsonArray regions); + + struct Library { + FlatPtr base; + size_t size; + String name; + NonnullRefPtr<MappedFile> file; + ELF::Image elf; + }; + + const Library* library_containing(FlatPtr) const; + +private: + mutable HashMap<String, OwnPtr<Library>> m_libraries; + JsonArray m_regions; +}; + +struct Process { + pid_t pid {}; + String executable; + HashTable<int> threads; + + struct Region { + String name; + FlatPtr base {}; + size_t size {}; + }; + Vector<Region> regions; + + NonnullOwnPtr<LibraryMetadata> library_metadata; +}; + class ProfileNode : public RefCounted<ProfileNode> { public: - static NonnullRefPtr<ProfileNode> create(FlyString object_name, String symbol, u32 address, u32 offset, u64 timestamp) + static NonnullRefPtr<ProfileNode> create(FlyString object_name, String symbol, u32 address, u32 offset, u64 timestamp, pid_t pid) { - return adopt(*new ProfileNode(move(object_name), move(symbol), address, offset, timestamp)); + return adopt(*new ProfileNode(move(object_name), move(symbol), address, offset, timestamp, pid)); } // These functions are only relevant for root nodes @@ -80,7 +115,7 @@ public: m_children.append(child); } - ProfileNode& find_or_create_child(FlyString object_name, String symbol, u32 address, u32 offset, u64 timestamp) + ProfileNode& find_or_create_child(FlyString object_name, String symbol, u32 address, u32 offset, u64 timestamp, pid_t pid) { for (size_t i = 0; i < m_children.size(); ++i) { auto& child = m_children[i]; @@ -88,7 +123,7 @@ public: return child; } } - auto new_child = ProfileNode::create(move(object_name), move(symbol), address, offset, timestamp); + auto new_child = ProfileNode::create(move(object_name), move(symbol), address, offset, timestamp, pid); add_child(new_child); return new_child; }; @@ -111,12 +146,17 @@ public: m_events_per_address.set(address, it->value + 1); } + pid_t pid() const { return m_pid; } + + const Process* process(Profile&) const; + private: - explicit ProfileNode(const String& object_name, String symbol, u32 address, u32 offset, u64 timestamp); + explicit ProfileNode(const String& object_name, String symbol, u32 address, u32 offset, u64 timestamp, pid_t); ProfileNode* m_parent { nullptr }; FlyString m_object_name; String m_symbol; + pid_t m_pid { 0 }; u32 m_address { 0 }; u32 m_offset { 0 }; u32 m_event_count { 0 }; @@ -136,6 +176,14 @@ public: GUI::Model& samples_model(); GUI::Model* disassembly_model(); + const Process* find_process(pid_t pid) const + { + auto it = m_processes.find_if([&](auto& entry) { + return entry.pid == pid; + }); + return it.is_end() ? nullptr : &(*it); + } + void set_disassembly_index(const GUI::ModelIndex&); const Vector<NonnullRefPtr<ProfileNode>>& roots() const { return m_roots; } @@ -152,6 +200,7 @@ public: String type; FlatPtr ptr { 0 }; size_t size { 0 }; + int tid { 0 }; bool in_kernel { false }; Vector<Frame> frames; }; @@ -178,29 +227,6 @@ public: bool show_percentages() const { return m_show_percentages; } void set_show_percentages(bool); - const String& executable_path() const { return m_executable_path; } - - class LibraryMetadata { - public: - LibraryMetadata(JsonArray regions); - - struct Library { - FlatPtr base; - size_t size; - String name; - NonnullRefPtr<MappedFile> file; - ELF::Image elf; - }; - - const Library* library_containing(FlatPtr) const; - - private: - mutable HashMap<String, OwnPtr<Library>> m_libraries; - JsonArray m_regions; - }; - - const LibraryMetadata& libraries() const { return *m_library_metadata; } - template<typename Callback> void for_each_event_in_filter_range(Callback callback) { @@ -215,12 +241,10 @@ public: } private: - Profile(String executable_path, Vector<Event>, NonnullOwnPtr<LibraryMetadata>); + Profile(Vector<Process>, Vector<Event>); void rebuild_tree(); - String m_executable_path; - RefPtr<ProfileModel> m_model; RefPtr<SamplesModel> m_samples_model; RefPtr<DisassemblyModel> m_disassembly_model; @@ -233,10 +257,9 @@ private: u64 m_first_timestamp { 0 }; u64 m_last_timestamp { 0 }; + Vector<Process> m_processes; Vector<Event> m_events; - NonnullOwnPtr<LibraryMetadata> m_library_metadata; - bool m_has_timestamp_filter_range { false }; u64 m_timestamp_filter_range_start { 0 }; u64 m_timestamp_filter_range_end { 0 }; diff --git a/Userland/DevTools/Profiler/SamplesModel.cpp b/Userland/DevTools/Profiler/SamplesModel.cpp index 79159ddc3a..3daac16bd1 100644 --- a/Userland/DevTools/Profiler/SamplesModel.cpp +++ b/Userland/DevTools/Profiler/SamplesModel.cpp @@ -57,6 +57,10 @@ String SamplesModel::column_name(int column) const return "#"; case Column::Timestamp: return "Timestamp"; + case Column::ThreadID: + return "TID"; + case Column::ExecutableName: + return "Executable"; case Column::InnermostStackFrame: return "Innermost Frame"; default: @@ -77,6 +81,16 @@ GUI::Variant SamplesModel::data(const GUI::ModelIndex& index, GUI::ModelRole rol if (index.column() == Column::SampleIndex) return event_index; + if (index.column() == Column::ThreadID) + return event.tid; + + if (index.column() == Column::ExecutableName) { + // FIXME: More abuse of the PID/TID relationship: + if (auto* process = m_profile.find_process(event.tid)) + return process->executable; + return ""; + } + if (index.column() == Column::Timestamp) { return (u32)event.timestamp; } diff --git a/Userland/DevTools/Profiler/SamplesModel.h b/Userland/DevTools/Profiler/SamplesModel.h index f75b375af2..ee91b7e221 100644 --- a/Userland/DevTools/Profiler/SamplesModel.h +++ b/Userland/DevTools/Profiler/SamplesModel.h @@ -40,6 +40,8 @@ public: enum Column { SampleIndex, Timestamp, + ThreadID, + ExecutableName, InnermostStackFrame, __Count }; |