mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 09:38:11 +00:00
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.
This commit is contained in:
parent
f57c57966b
commit
eb798d5538
26 changed files with 658 additions and 292 deletions
|
@ -20,20 +20,20 @@ PerformanceEventBuffer::PerformanceEventBuffer(NonnullOwnPtr<KBuffer> 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<FlatPtr, PerformanceEvent::max_stack_frame_count> raw_backtrace(FlatPtr ebp, FlatPtr eip)
|
||||
{
|
||||
Vector<FlatPtr, PerformanceEvent::max_stack_frame_count> 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<FlatPtr, PerformanceEvent::max_stack_frame_count> 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<size_t>(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<u64>(event.data.free.ptr));
|
||||
break;
|
||||
case PERF_EVENT_MMAP:
|
||||
event_object.add("type", "mmap");
|
||||
event_object.add("ptr", static_cast<u64>(event.data.mmap.ptr));
|
||||
event_object.add("size", static_cast<u64>(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<u64>(event.data.munmap.ptr));
|
||||
event_object.add("size", static_cast<u64>(event.data.munmap.size));
|
||||
break;
|
||||
case PERF_EVENT_PROCESS_CREATE:
|
||||
event_object.add("type", "process_create");
|
||||
event_object.add("parent_pid", static_cast<u64>(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<u64>(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> 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue