From eb798d5538ea3c978034d8d8364ecfd7fbc6a8ee Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Sun, 25 Apr 2021 23:42:36 +0200 Subject: [PATCH] Kernel+Profiler: Improve profiling subsystem This turns the perfcore format into more a log than it was before, which lets us properly log process, thread and region creation/destruction. This also makes it unnecessary to dump the process' regions every time it is scheduled like we did before. Incidentally this also fixes 'profile -c' because we previously ended up incorrectly dumping the parent's region map into the profile data. Log-based mmap support enables profiling shared libraries which are loaded at runtime, e.g. via dlopen(). This enables profiling both the parent and child process for programs which use execve(). Previously we'd discard the profiling data for the old process. The Profiler tool has been updated to not treat thread IDs as process IDs anymore. This enables support for processes with more than one thread. Also, there's a new widget to filter which process should be displayed. --- Kernel/PerformanceEventBuffer.cpp | 124 +++++--- Kernel/PerformanceEventBuffer.h | 59 ++-- Kernel/Process.cpp | 10 +- Kernel/Process.h | 6 + Kernel/Scheduler.cpp | 13 +- Kernel/Syscalls/execve.cpp | 6 +- Kernel/Syscalls/exit.cpp | 6 + Kernel/Syscalls/fork.cpp | 6 + Kernel/Syscalls/mmap.cpp | 23 ++ Kernel/Syscalls/perf_event.cpp | 2 +- Kernel/Syscalls/profiling.cpp | 7 +- Kernel/Syscalls/thread.cpp | 9 + Kernel/UnixTypes.h | 15 +- Userland/DevTools/Profiler/CMakeLists.txt | 10 +- .../DevTools/Profiler/DisassemblyModel.cpp | 10 +- Userland/DevTools/Profiler/Process.cpp | 119 ++++++++ Userland/DevTools/Profiler/Process.h | 68 +++++ .../DevTools/Profiler/ProcessPickerWidget.cpp | 48 ++++ .../DevTools/Profiler/ProcessPickerWidget.h | 31 ++ Userland/DevTools/Profiler/Profile.cpp | 271 ++++++++---------- Userland/DevTools/Profiler/Profile.h | 73 ++--- Userland/DevTools/Profiler/ProfileModel.cpp | 4 +- Userland/DevTools/Profiler/SamplesModel.cpp | 12 +- Userland/DevTools/Profiler/SamplesModel.h | 1 + Userland/DevTools/Profiler/main.cpp | 2 + Userland/Libraries/LibC/serenity.h | 15 +- 26 files changed, 658 insertions(+), 292 deletions(-) create mode 100644 Userland/DevTools/Profiler/Process.cpp create mode 100644 Userland/DevTools/Profiler/Process.h create mode 100644 Userland/DevTools/Profiler/ProcessPickerWidget.cpp create mode 100644 Userland/DevTools/Profiler/ProcessPickerWidget.h diff --git a/Kernel/PerformanceEventBuffer.cpp b/Kernel/PerformanceEventBuffer.cpp index 8f8023e5106..b4c3767abd4 100644 --- a/Kernel/PerformanceEventBuffer.cpp +++ b/Kernel/PerformanceEventBuffer.cpp @@ -20,20 +20,20 @@ PerformanceEventBuffer::PerformanceEventBuffer(NonnullOwnPtr buffer) { } -KResult PerformanceEventBuffer::append(int type, FlatPtr arg1, FlatPtr arg2) +NEVER_INLINE KResult PerformanceEventBuffer::append(int type, FlatPtr arg1, FlatPtr arg2, const StringView& arg3) { FlatPtr ebp; asm volatile("movl %%ebp, %%eax" : "=a"(ebp)); auto current_thread = Thread::current(); - auto eip = current_thread->get_register_dump_from_stack().eip; - return append_with_eip_and_ebp(eip, ebp, type, arg1, arg2); + return append_with_eip_and_ebp(current_thread->pid(), current_thread->tid(), 0, ebp, type, arg1, arg2, arg3); } static Vector raw_backtrace(FlatPtr ebp, FlatPtr eip) { Vector backtrace; - backtrace.append(eip); + if (eip != 0) + backtrace.append(eip); FlatPtr stack_ptr_copy; FlatPtr stack_ptr = (FlatPtr)ebp; // FIXME: Figure out how to remove this SmapDisabler without breaking profile stacks. @@ -55,7 +55,8 @@ static Vector raw_backtrace(Fl return backtrace; } -KResult PerformanceEventBuffer::append_with_eip_and_ebp(u32 eip, u32 ebp, int type, FlatPtr arg1, FlatPtr arg2) +KResult PerformanceEventBuffer::append_with_eip_and_ebp(ProcessID pid, ThreadID tid, + u32 eip, u32 ebp, int type, FlatPtr arg1, FlatPtr arg2, const StringView& arg3) { if (count() >= capacity()) return ENOBUFS; @@ -73,6 +74,39 @@ KResult PerformanceEventBuffer::append_with_eip_and_ebp(u32 eip, u32 ebp, int ty case PERF_EVENT_FREE: event.data.free.ptr = arg1; break; + case PERF_EVENT_MMAP: + event.data.mmap.ptr = arg1; + event.data.mmap.size = arg2; + memset(event.data.mmap.name, 0, sizeof(event.data.mmap.name)); + if (!arg3.is_empty()) + memcpy(event.data.mmap.name, arg3.characters_without_null_termination(), min(arg3.length(), sizeof(event.data.mmap.name) - 1)); + break; + case PERF_EVENT_MUNMAP: + event.data.mmap.ptr = arg1; + event.data.mmap.size = arg2; + break; + case PERF_EVENT_PROCESS_CREATE: + event.data.process_create.parent_pid = arg1; + memset(event.data.process_create.executable, 0, sizeof(event.data.process_create.executable)); + if (!arg3.is_empty()) { + memcpy(event.data.process_create.executable, arg3.characters_without_null_termination(), + min(arg3.length(), sizeof(event.data.process_create.executable) - 1)); + } + break; + case PERF_EVENT_PROCESS_EXEC: + memset(event.data.process_exec.executable, 0, sizeof(event.data.process_exec.executable)); + if (!arg3.is_empty()) { + memcpy(event.data.process_exec.executable, arg3.characters_without_null_termination(), + min(arg3.length(), sizeof(event.data.process_exec.executable) - 1)); + } + break; + case PERF_EVENT_PROCESS_EXIT: + break; + case PERF_EVENT_THREAD_CREATE: + event.data.thread_create.parent_tid = arg1; + break; + case PERF_EVENT_THREAD_EXIT: + break; default: return EINVAL; } @@ -81,7 +115,8 @@ KResult PerformanceEventBuffer::append_with_eip_and_ebp(u32 eip, u32 ebp, int ty event.stack_size = min(sizeof(event.stack) / sizeof(FlatPtr), static_cast(backtrace.size())); memcpy(event.stack, backtrace.data(), event.stack_size * sizeof(FlatPtr)); - event.tid = Thread::current()->tid().value(); + event.pid = pid.value(); + event.tid = tid.value(); event.timestamp = TimeManagement::the().uptime_ms(); at(m_count++) = event; return KSuccess; @@ -114,7 +149,38 @@ bool PerformanceEventBuffer::to_json_impl(Serializer& object) const event_object.add("type", "free"); event_object.add("ptr", static_cast(event.data.free.ptr)); break; + case PERF_EVENT_MMAP: + event_object.add("type", "mmap"); + event_object.add("ptr", static_cast(event.data.mmap.ptr)); + event_object.add("size", static_cast(event.data.mmap.size)); + event_object.add("name", event.data.mmap.name); + break; + case PERF_EVENT_MUNMAP: + event_object.add("type", "munmap"); + event_object.add("ptr", static_cast(event.data.munmap.ptr)); + event_object.add("size", static_cast(event.data.munmap.size)); + break; + case PERF_EVENT_PROCESS_CREATE: + event_object.add("type", "process_create"); + event_object.add("parent_pid", static_cast(event.data.process_create.parent_pid)); + event_object.add("executable", event.data.process_create.executable); + break; + case PERF_EVENT_PROCESS_EXEC: + event_object.add("type", "process_exec"); + event_object.add("executable", event.data.process_exec.executable); + break; + case PERF_EVENT_PROCESS_EXIT: + event_object.add("type", "process_exit"); + break; + case PERF_EVENT_THREAD_CREATE: + event_object.add("type", "thread_create"); + event_object.add("parent_tid", static_cast(event.data.thread_create.parent_tid)); + break; + case PERF_EVENT_THREAD_EXIT: + event_object.add("type", "thread_exit"); + break; } + event_object.add("pid", event.pid); event_object.add("tid", event.tid); event_object.add("timestamp", event.timestamp); auto stack_array = event_object.add_array("stack"); @@ -132,25 +198,6 @@ bool PerformanceEventBuffer::to_json_impl(Serializer& object) const bool PerformanceEventBuffer::to_json(KBufferBuilder& builder) const { JsonObjectSerializer object(builder); - - 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()); - } - } - - processes_array.finish(); - return to_json_impl(object); } @@ -162,35 +209,30 @@ OwnPtr PerformanceEventBuffer::try_create_with_size(size return adopt_own(*new PerformanceEventBuffer(buffer.release_nonnull())); } -void PerformanceEventBuffer::add_process(const Process& process) +void PerformanceEventBuffer::add_process(const Process& process, ProcessEventType event_type) { - // FIXME: What about threads that have died? - ScopedSpinLock locker(process.space().get_lock()); String executable; if (process.executable()) executable = process.executable()->absolute_path(); + else + executable = String::formatted("<{}>", process.name()); + + [[maybe_unused]] auto rc = append_with_eip_and_ebp(process.pid(), 0, 0, 0, + event_type == ProcessEventType::Create ? PERF_EVENT_PROCESS_CREATE : PERF_EVENT_PROCESS_EXEC, + process.pid().value(), 0, executable.characters()); - 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()); + [[maybe_unused]] auto rc = append_with_eip_and_ebp(process.pid(), thread.tid().value(), + 0, 0, PERF_EVENT_THREAD_CREATE, 0, 0, nullptr); return IterationDecision::Continue; }); for (auto& region : process.space().regions()) { - sampled_process->regions.append(SampledProcess::Region { - .name = region->name(), - .range = region->range(), - }); + [[maybe_unused]] auto rc = append_with_eip_and_ebp(process.pid(), 0, + 0, 0, PERF_EVENT_MMAP, region->range().base().get(), region->range().size(), region->name().characters()); } - - m_processes.set(process.pid(), move(sampled_process)); } } diff --git a/Kernel/PerformanceEventBuffer.h b/Kernel/PerformanceEventBuffer.h index 2fe0964ab91..c6c589bedb8 100644 --- a/Kernel/PerformanceEventBuffer.h +++ b/Kernel/PerformanceEventBuffer.h @@ -23,25 +23,61 @@ struct [[gnu::packed]] FreePerformanceEvent { FlatPtr ptr; }; +struct [[gnu::packed]] MmapPerformanceEvent { + size_t size; + FlatPtr ptr; + char name[64]; +}; + +struct [[gnu::packed]] MunmapPerformanceEvent { + size_t size; + FlatPtr ptr; +}; + +struct [[gnu::packed]] ProcessCreatePerformanceEvent { + pid_t parent_pid; + char executable[64]; +}; + +struct [[gnu::packed]] ProcessExecPerformanceEvent { + char executable[64]; +}; + +struct [[gnu::packed]] ThreadCreatePerformanceEvent { + pid_t parent_tid; +}; + struct [[gnu::packed]] PerformanceEvent { u8 type { 0 }; u8 stack_size { 0 }; + u32 pid { 0 }; u32 tid { 0 }; u64 timestamp; union { MallocPerformanceEvent malloc; FreePerformanceEvent free; + MmapPerformanceEvent mmap; + MunmapPerformanceEvent munmap; + ProcessCreatePerformanceEvent process_create; + ProcessExecPerformanceEvent process_exec; + ThreadCreatePerformanceEvent thread_create; } data; static constexpr size_t max_stack_frame_count = 64; FlatPtr stack[max_stack_frame_count]; }; +enum class ProcessEventType { + Create, + Exec +}; + class PerformanceEventBuffer { public: static OwnPtr try_create_with_size(size_t buffer_size); - KResult append(int type, FlatPtr arg1, FlatPtr arg2); - KResult append_with_eip_and_ebp(u32 eip, u32 ebp, int type, FlatPtr arg1, FlatPtr arg2); + KResult append(int type, FlatPtr arg1, FlatPtr arg2, const StringView& arg3); + KResult append_with_eip_and_ebp(ProcessID pid, ThreadID tid, u32 eip, u32 ebp, + int type, FlatPtr arg1, FlatPtr arg2, const StringView& arg3); void clear() { @@ -57,23 +93,11 @@ public: bool to_json(KBufferBuilder&) const; - void add_process(const Process&); + void add_process(const Process&, ProcessEventType event_type); private: explicit PerformanceEventBuffer(NonnullOwnPtr); - struct SampledProcess { - ProcessID pid; - String executable; - HashTable threads; - - struct Region { - String name; - Range range; - }; - Vector regions; - }; - template bool to_json_impl(Serializer&) const; @@ -81,8 +105,9 @@ private: size_t m_count { 0 }; NonnullOwnPtr m_buffer; - - HashMap> m_processes; }; +extern bool g_profiling_all_threads; +extern PerformanceEventBuffer* g_global_perf_events; + } diff --git a/Kernel/Process.cpp b/Kernel/Process.cpp index 259f931d111..46cd4afc492 100644 --- a/Kernel/Process.cpp +++ b/Kernel/Process.cpp @@ -242,8 +242,14 @@ Process::~Process() VERIFY(thread_count() == 0); // all threads should have been finalized VERIFY(!m_alarm_timer); + if (g_profiling_all_threads) { + VERIFY(g_global_perf_events); + [[maybe_unused]] auto rc = g_global_perf_events->append_with_eip_and_ebp( + pid(), 0, 0, 0, PERF_EVENT_PROCESS_EXIT, 0, 0, nullptr); + } + { - ScopedSpinLock processses_lock(g_processes_lock); + ScopedSpinLock processes_lock(g_processes_lock); if (prev() || next()) g_processes->remove(this); } @@ -675,7 +681,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); + m_perf_event_buffer->add_process(*this, ProcessEventType::Create); } return !!m_perf_event_buffer; } diff --git a/Kernel/Process.h b/Kernel/Process.h index a388af94bcd..34e9d3bf8a9 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -528,6 +529,11 @@ private: void clear_futex_queues_on_exec(); + inline PerformanceEventBuffer* current_perf_events_buffer() + { + return g_profiling_all_threads ? g_global_perf_events : m_perf_event_buffer.ptr(); + } + Process* m_prev { nullptr }; Process* m_next { nullptr }; diff --git a/Kernel/Scheduler.cpp b/Kernel/Scheduler.cpp index c1e6f50f4ab..4c90294eccc 100644 --- a/Kernel/Scheduler.cpp +++ b/Kernel/Scheduler.cpp @@ -22,9 +22,6 @@ namespace Kernel { -extern bool g_profiling_all_threads; -extern PerformanceEventBuffer* g_global_perf_events; - class SchedulerPerProcessorData { AK_MAKE_NONCOPYABLE(SchedulerPerProcessorData); AK_MAKE_NONMOVABLE(SchedulerPerProcessorData); @@ -513,12 +510,6 @@ void Scheduler::timer_tick(const RegisterState& regs) // That will be an interesting mode to add in the future. :^) 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()); @@ -526,7 +517,9 @@ void Scheduler::timer_tick(const RegisterState& regs) } if (perf_events) { - [[maybe_unused]] auto rc = perf_events->append_with_eip_and_ebp(regs.eip, regs.ebp, PERF_EVENT_SAMPLE, 0, 0); + [[maybe_unused]] auto rc = perf_events->append_with_eip_and_ebp( + current_thread->pid(), current_thread->tid(), + regs.eip, regs.ebp, PERF_EVENT_SAMPLE, 0, 0, nullptr); } if (current_thread->tick()) diff --git a/Kernel/Syscalls/execve.cpp b/Kernel/Syscalls/execve.cpp index eb2b6a59d19..76b9bd4ecfd 100644 --- a/Kernel/Syscalls/execve.cpp +++ b/Kernel/Syscalls/execve.cpp @@ -628,9 +628,9 @@ KResult Process::do_exec(NonnullRefPtr main_program_description tss.cr3 = space().page_directory().cr3(); tss.ss2 = pid().value(); - // Throw away any recorded performance events in this process. - if (m_perf_event_buffer) - m_perf_event_buffer->clear(); + if (auto* event_buffer = current_perf_events_buffer()) { + event_buffer->add_process(*this, ProcessEventType::Exec); + } { ScopedSpinLock lock(g_scheduler_lock); diff --git a/Kernel/Syscalls/exit.cpp b/Kernel/Syscalls/exit.cpp index d70175d7557..1f60ae90944 100644 --- a/Kernel/Syscalls/exit.cpp +++ b/Kernel/Syscalls/exit.cpp @@ -5,6 +5,7 @@ */ #include +#include #include namespace Kernel { @@ -16,6 +17,11 @@ void Process::sys$exit(int status) m_termination_status = status; m_termination_signal = 0; } + + if (auto* event_buffer = current_perf_events_buffer()) { + [[maybe_unused]] auto rc = event_buffer->append(PERF_EVENT_THREAD_EXIT, Thread::current()->tid().value(), 0, nullptr); + } + die(); Thread::current()->die_if_needed(); VERIFY_NOT_REACHED(); diff --git a/Kernel/Syscalls/fork.cpp b/Kernel/Syscalls/fork.cpp index 0caf62df5cd..af6c265f308 100644 --- a/Kernel/Syscalls/fork.cpp +++ b/Kernel/Syscalls/fork.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -84,6 +85,11 @@ KResultOr Process::sys$fork(RegisterState& regs) g_processes->prepend(child); } + if (g_profiling_all_threads) { + VERIFY(g_global_perf_events); + g_global_perf_events->add_process(*child, ProcessEventType::Create); + } + ScopedSpinLock lock(g_scheduler_lock); child_first_thread->set_affinity(Thread::current()->affinity()); child_first_thread->set_state(Thread::State::Runnable); diff --git a/Kernel/Syscalls/mmap.cpp b/Kernel/Syscalls/mmap.cpp index e34b08a5380..2ceae850ad4 100644 --- a/Kernel/Syscalls/mmap.cpp +++ b/Kernel/Syscalls/mmap.cpp @@ -246,6 +246,12 @@ KResultOr Process::sys$mmap(Userspace u if (!region) return ENOMEM; + + if (auto* event_buffer = current_perf_events_buffer()) { + [[maybe_unused]] auto res = event_buffer->append(PERF_EVENT_MMAP, region->vaddr().get(), + region->size(), name.is_null() ? region->name().characters() : name.characters()); + } + region->set_mmap(true); if (map_shared) region->set_shared(true); @@ -430,6 +436,9 @@ KResultOr Process::sys$set_mmap_name(Userspaceis_mmap()) return EPERM; + if (auto* event_buffer = current_perf_events_buffer()) { + [[maybe_unused]] auto res = event_buffer->append(PERF_EVENT_MMAP, region->vaddr().get(), region->size(), name.characters()); + } region->set_name(move(name)); return 0; } @@ -453,8 +462,13 @@ KResultOr Process::sys$munmap(Userspace addr, size_t size) if (auto* whole_region = space().find_region_from_range(range_to_unmap)) { if (!whole_region->is_mmap()) return EPERM; + auto base = whole_region->vaddr(); + auto size = whole_region->size(); bool success = space().deallocate_region(*whole_region); VERIFY(success); + if (auto* event_buffer = current_perf_events_buffer()) { + [[maybe_unused]] auto res = event_buffer->append(PERF_EVENT_MUNMAP, base.get(), size, nullptr); + } return 0; } @@ -479,6 +493,11 @@ KResultOr Process::sys$munmap(Userspace addr, size_t size) for (auto* new_region : new_regions) { new_region->map(space().page_directory()); } + + if (auto* event_buffer = current_perf_events_buffer()) { + [[maybe_unused]] auto res = event_buffer->append(PERF_EVENT_MUNMAP, range_to_unmap.base().get(), range_to_unmap.size(), nullptr); + } + return 0; } @@ -521,6 +540,10 @@ KResultOr Process::sys$munmap(Userspace addr, size_t size) new_region->map(space().page_directory()); } + if (auto* event_buffer = current_perf_events_buffer()) { + [[maybe_unused]] auto res = event_buffer->append(PERF_EVENT_MUNMAP, range_to_unmap.base().get(), range_to_unmap.size(), nullptr); + } + return 0; } diff --git a/Kernel/Syscalls/perf_event.cpp b/Kernel/Syscalls/perf_event.cpp index 0043826b5a6..cc0c91bf6c3 100644 --- a/Kernel/Syscalls/perf_event.cpp +++ b/Kernel/Syscalls/perf_event.cpp @@ -13,7 +13,7 @@ KResultOr Process::sys$perf_event(int type, FlatPtr arg1, FlatPtr arg2) { if (!create_perf_events_buffer_if_needed()) return ENOMEM; - return perf_events()->append(type, arg1, arg2); + return perf_events()->append(type, arg1, arg2, nullptr); } } diff --git a/Kernel/Syscalls/profiling.cpp b/Kernel/Syscalls/profiling.cpp index e7ac46e478e..1c956faef09 100644 --- a/Kernel/Syscalls/profiling.cpp +++ b/Kernel/Syscalls/profiling.cpp @@ -12,8 +12,8 @@ namespace Kernel { -PerformanceEventBuffer* g_global_perf_events; bool g_profiling_all_threads; +PerformanceEventBuffer* g_global_perf_events; KResultOr Process::sys$profiling_enable(pid_t pid) { @@ -27,6 +27,11 @@ KResultOr Process::sys$profiling_enable(pid_t pid) g_global_perf_events->clear(); else g_global_perf_events = PerformanceEventBuffer::try_create_with_size(32 * MiB).leak_ptr(); + ScopedSpinLock lock(g_processes_lock); + Process::for_each([](auto& process) { + g_global_perf_events->add_process(process, ProcessEventType::Create); + return IterationDecision::Continue; + }); g_profiling_all_threads = true; return 0; } diff --git a/Kernel/Syscalls/thread.cpp b/Kernel/Syscalls/thread.cpp index cdc0d7ed0d8..08f8d058998 100644 --- a/Kernel/Syscalls/thread.cpp +++ b/Kernel/Syscalls/thread.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +70,10 @@ KResultOr Process::sys$create_thread(void* (*entry)(void*), Userspaceappend(PERF_EVENT_THREAD_CREATE, thread->tid().value(), 0, nullptr); + } + ScopedSpinLock lock(g_scheduler_lock); thread->set_priority(requested_thread_priority); thread->set_state(Thread::State::Runnable); @@ -84,6 +89,10 @@ void Process::sys$exit_thread(Userspace exit_value) this->sys$exit(0); } + if (m_perf_event_buffer) { + [[maybe_unused]] auto rc = m_perf_event_buffer->append(PERF_EVENT_THREAD_EXIT, Thread::current()->tid().value(), 0, nullptr); + } + Thread::current()->exit(reinterpret_cast(exit_value.ptr())); VERIFY_NOT_REACHED(); } diff --git a/Kernel/UnixTypes.h b/Kernel/UnixTypes.h index 11f6962a24c..27a01e128f9 100644 --- a/Kernel/UnixTypes.h +++ b/Kernel/UnixTypes.h @@ -46,9 +46,18 @@ enum { _SC_CLK_TCK, }; -#define PERF_EVENT_SAMPLE 0 -#define PERF_EVENT_MALLOC 1 -#define PERF_EVENT_FREE 2 +enum { + PERF_EVENT_SAMPLE, + PERF_EVENT_MALLOC, + PERF_EVENT_FREE, + PERF_EVENT_MMAP, + PERF_EVENT_MUNMAP, + PERF_EVENT_PROCESS_CREATE, + PERF_EVENT_PROCESS_EXEC, + PERF_EVENT_PROCESS_EXIT, + PERF_EVENT_THREAD_CREATE, + PERF_EVENT_THREAD_EXIT +}; #define WNOHANG 1 #define WUNTRACED 2 diff --git a/Userland/DevTools/Profiler/CMakeLists.txt b/Userland/DevTools/Profiler/CMakeLists.txt index 251e695f702..57a5e0abe35 100644 --- a/Userland/DevTools/Profiler/CMakeLists.txt +++ b/Userland/DevTools/Profiler/CMakeLists.txt @@ -1,11 +1,13 @@ set(SOURCES DisassemblyModel.cpp main.cpp - IndividualSampleModel.cpp - Profile.cpp + IndividualSampleModel.cpp + Process.cpp + ProcessPickerWidget.cpp + Profile.cpp ProfileModel.cpp - ProfileTimelineWidget.cpp - SamplesModel.cpp + ProfileTimelineWidget.cpp + SamplesModel.cpp ) serenity_app(Profiler ICON app-profiler) diff --git a/Userland/DevTools/Profiler/DisassemblyModel.cpp b/Userland/DevTools/Profiler/DisassemblyModel.cpp index 475b4c3e44a..1bc7a2bd9f0 100644 --- a/Userland/DevTools/Profiler/DisassemblyModel.cpp +++ b/Userland/DevTools/Profiler/DisassemblyModel.cpp @@ -48,10 +48,14 @@ DisassemblyModel::DisassemblyModel(Profile& profile, ProfileNode& node) kernel_elf = make((const u8*)m_kernel_file->data(), m_kernel_file->size()); elf = kernel_elf.ptr(); } else { - // FIXME: This is kinda rickety looking with all the -> -> -> - auto library_data = node.process(profile)->library_metadata->library_containing(node.address()); + auto process = node.process(profile, node.timestamp()); + if (!process) { + dbgln("no process for address {}", node.address()); + return; + } + auto library_data = process->library_metadata.library_containing(node.address()); if (!library_data) { - dbgln("no library data"); + dbgln("no library data for address {}", node.address()); return; } elf = &library_data->object->elf; diff --git a/Userland/DevTools/Profiler/Process.cpp b/Userland/DevTools/Profiler/Process.cpp new file mode 100644 index 00000000000..ca6179d08e3 --- /dev/null +++ b/Userland/DevTools/Profiler/Process.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2018-2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "Process.h" + +Thread* Process::find_thread(pid_t tid, u64 timestamp) +{ + auto it = threads.find(tid); + if (it == threads.end()) + return nullptr; + for (auto& thread : it->value) { + if (thread.start_valid < timestamp && (thread.end_valid == 0 || thread.end_valid > timestamp)) + return &thread; + } + return nullptr; +} + +void Process::handle_thread_create(pid_t tid, u64 timestamp) +{ + auto it = threads.find(tid); + if (it == threads.end()) { + threads.set(tid, {}); + it = threads.find(tid); + } + + auto thread = Thread { tid, timestamp, 0 }; + it->value.append(move(thread)); +} + +void Process::handle_thread_exit(pid_t tid, u64 timestamp) +{ + auto* thread = find_thread(tid, timestamp); + if (!thread) + return; + thread->end_valid = timestamp; +} + +HashMap> g_mapped_object_cache; + +static MappedObject* get_or_create_mapped_object(const String& path) +{ + if (auto it = g_mapped_object_cache.find(path); it != g_mapped_object_cache.end()) + return it->value.ptr(); + + auto file_or_error = MappedFile::map(path); + if (file_or_error.is_error()) { + g_mapped_object_cache.set(path, {}); + return nullptr; + } + auto elf = ELF::Image(file_or_error.value()->bytes()); + if (!elf.is_valid()) { + g_mapped_object_cache.set(path, {}); + return nullptr; + } + auto new_mapped_object = adopt_own(*new MappedObject { + .file = file_or_error.release_value(), + .elf = elf, + }); + auto* ptr = new_mapped_object.ptr(); + g_mapped_object_cache.set(path, move(new_mapped_object)); + return ptr; +} + +void LibraryMetadata::handle_mmap(FlatPtr base, size_t size, const String& name) +{ + String path; + if (name.contains("Loader.so")) + path = "Loader.so"; + else if (!name.contains(":")) + return; + else + path = name.substring(0, name.view().find_first_of(":").value()); + + String full_path; + if (name.contains(".so")) + full_path = String::formatted("/usr/lib/{}", path); + else + full_path = path; + + auto* mapped_object = get_or_create_mapped_object(full_path); + if (!mapped_object) { + full_path = String::formatted("/usr/local/lib/{}", path); + mapped_object = get_or_create_mapped_object(full_path); + if (!mapped_object) + return; + } + + FlatPtr text_base {}; + mapped_object->elf.for_each_program_header([&](const ELF::Image::ProgramHeader& ph) { + if (ph.is_executable()) + text_base = ph.vaddr().get(); + return IterationDecision::Continue; + }); + + m_libraries.set(name, adopt_own(*new Library { base, size, name, text_base, mapped_object })); +} + +String LibraryMetadata::Library::symbolicate(FlatPtr ptr, u32* offset) const +{ + if (!object) + return String::formatted("?? <{:p}>", ptr); + + return object->elf.symbolicate(ptr - base + text_base, offset); +} + +const LibraryMetadata::Library* LibraryMetadata::library_containing(FlatPtr ptr) const +{ + for (auto& it : m_libraries) { + if (!it.value) + continue; + auto& library = *it.value; + if (ptr >= library.base && ptr < (library.base + library.size)) + return &library; + } + return nullptr; +} diff --git a/Userland/DevTools/Profiler/Process.h b/Userland/DevTools/Profiler/Process.h new file mode 100644 index 00000000000..f4a765ecd52 --- /dev/null +++ b/Userland/DevTools/Profiler/Process.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018-2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +struct MappedObject { + NonnullRefPtr file; + ELF::Image elf; +}; + +extern HashMap> g_mapped_object_cache; + +class LibraryMetadata { +public: + struct Library { + FlatPtr base; + size_t size; + String name; + FlatPtr text_base; + MappedObject* object { nullptr }; + + String symbolicate(FlatPtr, u32* offset) const; + }; + + void handle_mmap(FlatPtr base, size_t size, const String& name); + const Library* library_containing(FlatPtr) const; + +private: + mutable HashMap> m_libraries; +}; + +struct Thread { + pid_t tid; + u64 start_valid; + u64 end_valid { 0 }; + + bool valid_at(u64 timestamp) const + { + return timestamp >= start_valid && (end_valid == 0 || timestamp <= end_valid); + } +}; + +struct Process { + pid_t pid {}; + String executable; + HashMap> threads {}; + LibraryMetadata library_metadata {}; + u64 start_valid; + u64 end_valid { 0 }; + + Thread* find_thread(pid_t tid, u64 timestamp); + void handle_thread_create(pid_t tid, u64 timestamp); + void handle_thread_exit(pid_t tid, u64 timestamp); + + bool valid_at(u64 timestamp) const + { + return timestamp >= start_valid && (end_valid == 0 || timestamp <= end_valid); + } +}; diff --git a/Userland/DevTools/Profiler/ProcessPickerWidget.cpp b/Userland/DevTools/Profiler/ProcessPickerWidget.cpp new file mode 100644 index 00000000000..ad78b1c9894 --- /dev/null +++ b/Userland/DevTools/Profiler/ProcessPickerWidget.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021, Gunnar Beutner + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "ProcessPickerWidget.h" +#include "Profile.h" +#include +#include +#include + +ProcessPickerWidget::ProcessPickerWidget(Profile& profile) + : m_profile(profile) +{ + set_layout(); + set_fixed_height(30); + + set_frame_shape(Gfx::FrameShape::NoFrame); + + auto& label = add("Process:"); + label.set_fixed_width(50); + label.set_text_alignment(Gfx::TextAlignment::CenterRight); + + m_process_combo = add(); + m_process_combo->set_only_allow_values_from_model(true); + + m_processes.append("All processes"); + + for (auto& process : m_profile.processes()) + m_processes.append(String::formatted("{}: {}", process.pid, process.executable)); + + m_process_combo->set_model(*GUI::ItemListModel::create(m_processes)); + m_process_combo->set_selected_index(0); + m_process_combo->on_change = [this](auto&, const GUI::ModelIndex& index) { + if (index.row() == 0) { + m_profile.clear_process_filter(); + } else { + auto& process = m_profile.processes()[index.row() - 1]; + auto end_valid = process.end_valid == 0 ? m_profile.last_timestamp() : process.end_valid; + m_profile.set_process_filter(process.pid, process.start_valid, end_valid); + } + }; +} + +ProcessPickerWidget::~ProcessPickerWidget() +{ +} diff --git a/Userland/DevTools/Profiler/ProcessPickerWidget.h b/Userland/DevTools/Profiler/ProcessPickerWidget.h new file mode 100644 index 00000000000..85d8733a161 --- /dev/null +++ b/Userland/DevTools/Profiler/ProcessPickerWidget.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021, Gunnar Beutner + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +class Profile; + +class ProcessPickerWidget final : public GUI::Frame { + C_OBJECT(ProcessPickerWidget) +public: + virtual ~ProcessPickerWidget() override; + + struct Process { + pid_t pid; + String name; + }; + +private: + explicit ProcessPickerWidget(Profile&); + + Profile& m_profile; + Vector m_processes; + + RefPtr m_process_combo; +}; diff --git a/Userland/DevTools/Profiler/Profile.cpp b/Userland/DevTools/Profiler/Profile.cpp index 2cb75fae4c9..101f5ec598a 100644 --- a/Userland/DevTools/Profiler/Profile.cpp +++ b/Userland/DevTools/Profiler/Profile.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -60,12 +61,10 @@ GUI::Model& Profile::samples_model() void Profile::rebuild_tree() { - u32 filtered_event_count = 0; Vector> roots; 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]; + for (auto root : roots) { if (root->symbol() == symbol) { return root; } @@ -84,18 +83,23 @@ void Profile::rebuild_tree() live_allocations.remove(event.ptr); }); - Optional first_filtered_event_index; + m_filtered_event_indices.clear(); for (size_t event_index = 0; event_index < m_events.size(); ++event_index) { auto& event = m_events.at(event_index); + if (has_timestamp_filter_range()) { auto timestamp = event.timestamp; if (timestamp < m_timestamp_filter_range_start || timestamp > m_timestamp_filter_range_end) continue; } - if (!first_filtered_event_index.has_value()) - first_filtered_event_index = event_index; + if (has_process_filter()) { + if (event.pid != m_process_filter_pid || event.timestamp < m_process_filter_start_valid || event.timestamp > m_process_filter_end_valid) + continue; + } + + m_filtered_event_indices.append(event_index); if (event.type == "malloc" && !live_allocations.contains(event.ptr)) continue; @@ -130,9 +134,9 @@ void Profile::rebuild_tree() // FIXME: More cheating with intentional mixing of TID/PID here: if (!node) - node = &find_or_create_root(object_name, symbol, address, offset, event.timestamp, event.tid); + node = &find_or_create_root(object_name, symbol, address, offset, event.timestamp, event.pid); else - node = &node->find_or_create_child(object_name, symbol, address, offset, event.timestamp, event.tid); + node = &node->find_or_create_child(object_name, symbol, address, offset, event.timestamp, event.pid); node->increment_event_count(); if (is_innermost_frame) { @@ -156,11 +160,11 @@ void Profile::rebuild_tree() // FIXME: More PID/TID mixing cheats here: if (!node) { - node = &find_or_create_root(object_name, symbol, address, offset, event.timestamp, event.tid); + node = &find_or_create_root(object_name, symbol, address, offset, event.timestamp, event.pid); root = node; root->will_track_seen_events(m_events.size()); } else { - node = &node->find_or_create_child(object_name, symbol, address, offset, event.timestamp, event.tid); + node = &node->find_or_create_child(object_name, symbol, address, offset, event.timestamp, event.pid); } if (!root->has_seen_event(event_index)) { @@ -177,14 +181,10 @@ void Profile::rebuild_tree() } } } - - ++filtered_event_count; } sort_profile_nodes(roots); - m_filtered_event_count = filtered_event_count; - m_first_filtered_event_index = first_filtered_event_index.value_or(0); m_roots = move(roots); m_model->update(); } @@ -201,45 +201,6 @@ Result, String> Profile::load_from_perfcore_file(const St auto& object = json.value().as_object(); - 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 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(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 kernel_elf; if (!file_or_error.is_error()) @@ -250,9 +211,9 @@ Result, String> Profile::load_from_perfcore_file(const St return String { "Malformed profile (events is not an array)" }; auto& perf_events = events_value.as_array(); - if (perf_events.is_empty()) - return String { "No events captured (targeted process was never on CPU)" }; + NonnullOwnPtrVector all_processes; + HashMap current_processes; Vector events; for (auto& perf_event_value : perf_events.values()) { @@ -262,6 +223,7 @@ Result, String> Profile::load_from_perfcore_file(const St event.timestamp = perf_event.get("timestamp").to_number(); event.type = perf_event.get("type").to_string(); + event.pid = perf_event.get("pid").to_i32(); event.tid = perf_event.get("tid").to_i32(); if (event.type == "malloc") { @@ -269,6 +231,65 @@ Result, String> Profile::load_from_perfcore_file(const St event.size = perf_event.get("size").to_number(); } else if (event.type == "free") { event.ptr = perf_event.get("ptr").to_number(); + } else if (event.type == "mmap") { + event.ptr = perf_event.get("ptr").to_number(); + event.size = perf_event.get("size").to_number(); + event.name = perf_event.get("name").to_string(); + + auto it = current_processes.find(event.pid); + if (it != current_processes.end()) + it->value->library_metadata.handle_mmap(event.ptr, event.size, event.name); + continue; + } else if (event.type == "munmap") { + event.ptr = perf_event.get("ptr").to_number(); + event.size = perf_event.get("size").to_number(); + continue; + } else if (event.type == "process_create") { + event.parent_pid = perf_event.get("parent_pid").to_number(); + event.executable = perf_event.get("executable").to_string(); + + auto sampled_process = adopt_own(*new Process { + .pid = event.pid, + .executable = event.executable, + .start_valid = event.timestamp, + }); + + current_processes.set(sampled_process->pid, sampled_process); + all_processes.append(move(sampled_process)); + continue; + } else if (event.type == "process_exec") { + event.executable = perf_event.get("executable").to_string(); + + auto old_process = current_processes.get(event.pid).value(); + old_process->end_valid = event.timestamp - 1; + + current_processes.remove(event.pid); + + auto sampled_process = adopt_own(*new Process { + .pid = event.pid, + .executable = event.executable, + .start_valid = event.timestamp }); + + current_processes.set(sampled_process->pid, sampled_process); + all_processes.append(move(sampled_process)); + continue; + } else if (event.type == "process_exit") { + auto old_process = current_processes.get(event.pid).value(); + old_process->end_valid = event.timestamp - 1; + + current_processes.remove(event.pid); + continue; + } else if (event.type == "thread_create") { + event.parent_tid = perf_event.get("parent_tid").to_i32(); + auto it = current_processes.find(event.pid); + if (it != current_processes.end()) + it->value->handle_thread_create(event.tid, event.timestamp); + continue; + } else if (event.type == "thread_exit") { + auto it = current_processes.find(event.pid); + if (it != current_processes.end()) + it->value->handle_thread_exit(event.tid, event.timestamp); + continue; } auto stack_array = perf_event.get("stack").as_array(); @@ -283,22 +304,19 @@ Result, String> Profile::load_from_perfcore_file(const St if (kernel_elf) { symbol = kernel_elf->symbolicate(ptr, &offset); } else { - symbol = "??"; + symbol = String::formatted("?? <{:p}>", ptr); } } else { - auto it = sampled_processes.find_if([&](auto& entry) { - // FIXME: This doesn't support multi-threaded programs! - return entry.pid == event.tid; - }); + auto it = current_processes.find(event.pid); // 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 (it != current_processes.end()) + library_metadata = &it->value->library_metadata; if (auto* library = library_metadata ? library_metadata->library_containing(ptr) : nullptr) { object_name = library->name; symbol = library->symbolicate(ptr, &offset); } else { - symbol = "??"; + symbol = String::formatted("?? <{:p}>", ptr); } } @@ -314,7 +332,21 @@ Result, String> Profile::load_from_perfcore_file(const St events.append(move(event)); } - return adopt_own(*new Profile(move(sampled_processes), move(events))); + if (events.is_empty()) + return String { "No events captured (targeted process was never on CPU)" }; + + quick_sort(all_processes, [](auto& a, auto& b) { + if (a.pid == b.pid) + return a.start_valid < b.start_valid; + else + return a.pid < b.pid; + }); + + Vector processes; + for (auto& it : all_processes) + processes.append(move(it)); + + return adopt_own(*new Profile(move(processes), move(events))); } void ProfileNode::sort_children() @@ -344,6 +376,32 @@ void Profile::clear_timestamp_filter_range() m_samples_model->update(); } +void Profile::set_process_filter(pid_t pid, u64 start_valid, u64 end_valid) +{ + if (m_has_process_filter && m_process_filter_pid == pid && m_process_filter_start_valid == start_valid && m_process_filter_end_valid == end_valid) + return; + m_has_process_filter = true; + + m_process_filter_pid = pid; + m_process_filter_start_valid = start_valid; + m_process_filter_end_valid = end_valid; + + rebuild_tree(); + if (m_disassembly_model) + m_disassembly_model->update(); + m_samples_model->update(); +} +void Profile::clear_process_filter() +{ + if (!m_has_process_filter) + return; + m_has_process_filter = false; + rebuild_tree(); + if (m_disassembly_model) + m_disassembly_model->update(); + m_samples_model->update(); +} + void Profile::set_inverted(bool inverted) { if (m_inverted == inverted) @@ -381,87 +439,6 @@ GUI::Model* Profile::disassembly_model() return m_disassembly_model; } -HashMap> g_mapped_object_cache; - -static MappedObject* get_or_create_mapped_object(const String& path) -{ - if (auto it = g_mapped_object_cache.find(path); it != g_mapped_object_cache.end()) - return it->value.ptr(); - - auto file_or_error = MappedFile::map(path); - if (file_or_error.is_error()) { - g_mapped_object_cache.set(path, {}); - return nullptr; - } - auto elf = ELF::Image(file_or_error.value()->bytes()); - if (!elf.is_valid()) { - g_mapped_object_cache.set(path, {}); - return nullptr; - } - auto new_mapped_object = adopt_own(*new MappedObject { - .file = file_or_error.release_value(), - .elf = move(elf), - }); - auto* ptr = new_mapped_object.ptr(); - g_mapped_object_cache.set(path, move(new_mapped_object)); - return ptr; -} - -LibraryMetadata::LibraryMetadata(JsonArray regions) - : m_regions(move(regions)) -{ - for (auto& region_value : m_regions.values()) { - auto& region = region_value.as_object(); - auto base = region.get("base").as_u32(); - auto size = region.get("size").as_u32(); - auto name = region.get("name").as_string(); - - String path; - if (name.contains("Loader.so")) - path = "Loader.so"; - else if (!name.contains(":")) - continue; - else - path = name.substring(0, name.view().find_first_of(":").value()); - - if (name.contains(".so")) - path = String::formatted("/usr/lib/{}", path); - - auto* mapped_object = get_or_create_mapped_object(path); - if (!mapped_object) - continue; - - FlatPtr text_base {}; - mapped_object->elf.for_each_program_header([&](const ELF::Image::ProgramHeader& ph) { - if (ph.is_executable()) - text_base = ph.vaddr().get(); - return IterationDecision::Continue; - }); - - m_libraries.set(name, adopt_own(*new Library { base, size, name, text_base, mapped_object })); - } -} - -String LibraryMetadata::Library::symbolicate(FlatPtr ptr, u32* offset) const -{ - if (!object) - return "??"sv; - - return object->elf.symbolicate(ptr - base + text_base, offset); -} - -const LibraryMetadata::Library* LibraryMetadata::library_containing(FlatPtr ptr) const -{ - for (auto& it : m_libraries) { - if (!it.value) - continue; - auto& library = *it.value; - if (ptr >= library.base && ptr < (library.base + library.size)) - return &library; - } - return nullptr; -} - ProfileNode::ProfileNode(const String& object_name, String symbol, u32 address, u32 offset, u64 timestamp, pid_t pid) : m_symbol(move(symbol)) , m_pid(pid) @@ -478,7 +455,7 @@ ProfileNode::ProfileNode(const String& object_name, String symbol, u32 address, m_object_name = LexicalPath(object).basename(); } -const Process* ProfileNode::process(Profile& profile) const +const Process* ProfileNode::process(Profile& profile, u64 timestamp) const { - return profile.find_process(m_pid); + return profile.find_process(m_pid, timestamp); } diff --git a/Userland/DevTools/Profiler/Profile.h b/Userland/DevTools/Profiler/Profile.h index 659d0b6d126..661afd5c4bb 100644 --- a/Userland/DevTools/Profiler/Profile.h +++ b/Userland/DevTools/Profiler/Profile.h @@ -6,6 +6,7 @@ #pragma once +#include "Process.h" #include #include #include @@ -24,49 +25,6 @@ class Profile; class ProfileModel; class SamplesModel; -struct MappedObject { - NonnullRefPtr file; - ELF::Image elf; -}; - -extern HashMap> g_mapped_object_cache; - -class LibraryMetadata { -public: - explicit LibraryMetadata(JsonArray regions); - - struct Library { - FlatPtr base; - size_t size; - String name; - FlatPtr text_base; - MappedObject* object { nullptr }; - - String symbolicate(FlatPtr, u32* offset) const; - }; - - const Library* library_containing(FlatPtr) const; - -private: - mutable HashMap> m_libraries; - JsonArray m_regions; -}; - -struct Process { - pid_t pid {}; - String executable; - HashTable threads; - - struct Region { - String name; - FlatPtr base {}; - size_t size {}; - }; - Vector regions; - - NonnullOwnPtr library_metadata; -}; - class ProfileNode : public RefCounted { public: static NonnullRefPtr create(FlyString object_name, String symbol, u32 address, u32 offset, u64 timestamp, pid_t pid) @@ -137,7 +95,7 @@ public: pid_t pid() const { return m_pid; } - const Process* process(Profile&) const; + const Process* process(Profile&, u64 timestamp) const; private: explicit ProfileNode(const String& object_name, String symbol, u32 address, u32 offset, u64 timestamp, pid_t); @@ -165,10 +123,10 @@ public: GUI::Model& samples_model(); GUI::Model* disassembly_model(); - const Process* find_process(pid_t pid) const + const Process* find_process(pid_t pid, u64 timestamp) const { auto it = m_processes.find_if([&](auto& entry) { - return entry.pid == pid; + return entry.pid == pid && entry.valid_at(timestamp); }); return it.is_end() ? nullptr : &(*it); } @@ -189,15 +147,18 @@ public: String type; FlatPtr ptr { 0 }; size_t size { 0 }; + String name; + int parent_pid { 0 }; + int parent_tid { 0 }; + String executable; + int pid { 0 }; int tid { 0 }; bool in_kernel { false }; Vector frames; }; - u32 first_filtered_event_index() const { return m_first_filtered_event_index; } - u32 filtered_event_count() const { return m_filtered_event_count; } - const Vector& events() const { return m_events; } + const Vector& filtered_event_indices() const { return m_filtered_event_indices; } u64 length_in_ms() const { return m_last_timestamp - m_first_timestamp; } u64 first_timestamp() const { return m_first_timestamp; } @@ -208,6 +169,10 @@ public: void clear_timestamp_filter_range(); bool has_timestamp_filter_range() const { return m_has_timestamp_filter_range; } + void set_process_filter(pid_t pid, u64 start_valid, u64 end_valid); + void clear_process_filter(); + bool has_process_filter() const { return m_has_process_filter; } + bool is_inverted() const { return m_inverted; } void set_inverted(bool); @@ -216,6 +181,8 @@ public: bool show_percentages() const { return m_show_percentages; } void set_show_percentages(bool); + const Vector& processes() const { return m_processes; } + template void for_each_event_in_filter_range(Callback callback) { @@ -241,8 +208,7 @@ private: GUI::ModelIndex m_disassembly_index; Vector> m_roots; - u32 m_filtered_event_count { 0 }; - size_t m_first_filtered_event_index { 0 }; + Vector m_filtered_event_indices; u64 m_first_timestamp { 0 }; u64 m_last_timestamp { 0 }; @@ -253,6 +219,11 @@ private: u64 m_timestamp_filter_range_start { 0 }; u64 m_timestamp_filter_range_end { 0 }; + bool m_has_process_filter { false }; + pid_t m_process_filter_pid { 0 }; + u64 m_process_filter_start_valid { 0 }; + u64 m_process_filter_end_valid { 0 }; + u32 m_deepest_stack_depth { 0 }; bool m_inverted { false }; bool m_show_top_functions { false }; diff --git a/Userland/DevTools/Profiler/ProfileModel.cpp b/Userland/DevTools/Profiler/ProfileModel.cpp index 39904d5acad..3e9aa67b15b 100644 --- a/Userland/DevTools/Profiler/ProfileModel.cpp +++ b/Userland/DevTools/Profiler/ProfileModel.cpp @@ -108,12 +108,12 @@ GUI::Variant ProfileModel::data(const GUI::ModelIndex& index, GUI::ModelRole rol if (role == GUI::ModelRole::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 ((float)node->event_count() / (float)m_profile.filtered_event_indices().size()) * 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 ((float)node->self_count() / (float)m_profile.filtered_event_indices().size()) * 100.0f; return node->self_count(); } if (index.column() == Column::ObjectName) diff --git a/Userland/DevTools/Profiler/SamplesModel.cpp b/Userland/DevTools/Profiler/SamplesModel.cpp index b916061a1bc..e46866e9562 100644 --- a/Userland/DevTools/Profiler/SamplesModel.cpp +++ b/Userland/DevTools/Profiler/SamplesModel.cpp @@ -22,7 +22,7 @@ SamplesModel::~SamplesModel() int SamplesModel::row_count(const GUI::ModelIndex&) const { - return m_profile.filtered_event_count(); + return m_profile.filtered_event_indices().size(); } int SamplesModel::column_count(const GUI::ModelIndex&) const @@ -37,6 +37,8 @@ String SamplesModel::column_name(int column) const return "#"; case Column::Timestamp: return "Timestamp"; + case Column::ProcessID: + return "PID"; case Column::ThreadID: return "TID"; case Column::ExecutableName: @@ -50,7 +52,7 @@ String SamplesModel::column_name(int column) const GUI::Variant SamplesModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const { - u32 event_index = m_profile.first_filtered_event_index() + index.row(); + u32 event_index = m_profile.filtered_event_indices()[index.row()]; auto& event = m_profile.events().at(event_index); if (role == GUI::ModelRole::Custom) { @@ -61,12 +63,14 @@ GUI::Variant SamplesModel::data(const GUI::ModelIndex& index, GUI::ModelRole rol if (index.column() == Column::SampleIndex) return event_index; + if (index.column() == Column::ProcessID) + return event.pid; + 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)) + if (auto* process = m_profile.find_process(event.pid, event.timestamp)) return process->executable; return ""; } diff --git a/Userland/DevTools/Profiler/SamplesModel.h b/Userland/DevTools/Profiler/SamplesModel.h index 9ebf18c049d..e2a643f2ffe 100644 --- a/Userland/DevTools/Profiler/SamplesModel.h +++ b/Userland/DevTools/Profiler/SamplesModel.h @@ -20,6 +20,7 @@ public: enum Column { SampleIndex, Timestamp, + ProcessID, ThreadID, ExecutableName, InnermostStackFrame, diff --git a/Userland/DevTools/Profiler/main.cpp b/Userland/DevTools/Profiler/main.cpp index 03e7218cf40..814c3e94893 100644 --- a/Userland/DevTools/Profiler/main.cpp +++ b/Userland/DevTools/Profiler/main.cpp @@ -5,6 +5,7 @@ */ #include "IndividualSampleModel.h" +#include "ProcessPickerWidget.h" #include "Profile.h" #include "ProfileTimelineWidget.h" #include @@ -86,6 +87,7 @@ int main(int argc, char** argv) main_widget.set_layout(); main_widget.add(*profile); + main_widget.add(*profile); auto& tab_widget = main_widget.add(); diff --git a/Userland/Libraries/LibC/serenity.h b/Userland/Libraries/LibC/serenity.h index b75a97c8cfa..846e4ab6a3f 100644 --- a/Userland/Libraries/LibC/serenity.h +++ b/Userland/Libraries/LibC/serenity.h @@ -75,9 +75,18 @@ int futex(uint32_t* userspace_address, int futex_op, uint32_t value, const struc int purge(int mode); -#define PERF_EVENT_SAMPLE 0 -#define PERF_EVENT_MALLOC 1 -#define PERF_EVENT_FREE 2 +enum { + PERF_EVENT_SAMPLE, + PERF_EVENT_MALLOC, + PERF_EVENT_FREE, + PERF_EVENT_MMAP, + PERF_EVENT_MUNMAP, + PERF_EVENT_PROCESS_CREATE, + PERF_EVENT_PROCESS_EXEC, + PERF_EVENT_PROCESS_EXIT, + PERF_EVENT_THREAD_CREATE, + PERF_EVENT_THREAD_EXIT +}; int perf_event(int type, uintptr_t arg1, uintptr_t arg2);