diff --git a/Kernel/PerformanceEventBuffer.cpp b/Kernel/PerformanceEventBuffer.cpp index 8f8023e510..b4c3767abd 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 2fe0964ab9..c6c589bedb 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 259f931d11..46cd4afc49 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 a388af94bc..34e9d3bf8a 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 c1e6f50f4a..4c90294ecc 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 eb2b6a59d1..76b9bd4ecf 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 d70175d755..1f60ae9094 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 0caf62df5c..af6c265f30 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 e34b08a538..2ceae850ad 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 0043826b5a..cc0c91bf6c 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 e7ac46e478..1c956faef0 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 cdc0d7ed0d..08f8d05899 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 11f6962a24..27a01e128f 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 251e695f70..57a5e0abe3 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 475b4c3e44..1bc7a2bd9f 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 0000000000..ca6179d08e --- /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 0000000000..f4a765ecd5 --- /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 0000000000..ad78b1c989 --- /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 0000000000..85d8733a16 --- /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 2cb75fae4c..101f5ec598 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 659d0b6d12..661afd5c4b 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 39904d5aca..3e9aa67b15 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 b916061a1b..e46866e956 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 9ebf18c049..e2a643f2ff 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 03e7218cf4..814c3e9489 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 b75a97c8cf..846e4ab6a3 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);