Kernel: Start work on full system profiling :^)

The superuser can now call sys$profiling_enable() with PID -1 to enable
profiling of all running threads in the system. The perf events are
collected in a global PerformanceEventBuffer (currently 32 MiB in size.)

The events can be accessed via /proc/profile
This commit is contained in:
Andreas Kling 2021-03-02 17:19:35 +01:00
parent b425c2602c
commit ea500dd3e3
Notes: sideshowbarker 2024-07-18 21:46:01 +09:00
5 changed files with 91 additions and 23 deletions

View file

@ -474,6 +474,15 @@ static bool procfs$modules(InodeIdentifier, KBufferBuilder& builder)
return true;
}
static bool procfs$profile(InodeIdentifier, KBufferBuilder& builder)
{
extern PerformanceEventBuffer* g_global_perf_events;
if (!g_global_perf_events)
return false;
return g_global_perf_events->to_json(builder);
}
static bool procfs$pid_perf_events(InodeIdentifier identifier, KBufferBuilder& builder)
{
auto process = Process::from_pid(to_pid(identifier));
@ -1703,6 +1712,7 @@ ProcFS::ProcFS()
m_entries[FI_Root_uptime] = { "uptime", FI_Root_uptime, false, procfs$uptime };
m_entries[FI_Root_cmdline] = { "cmdline", FI_Root_cmdline, true, procfs$cmdline };
m_entries[FI_Root_modules] = { "modules", FI_Root_modules, true, procfs$modules };
m_entries[FI_Root_profile] = { "profile", FI_Root_profile, true, procfs$profile };
m_entries[FI_Root_sys] = { "sys", FI_Root_sys, true };
m_entries[FI_Root_net] = { "net", FI_Root_net, false };

View file

@ -98,6 +98,7 @@ 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.timestamp = TimeManagement::the().uptime_ms();
at(m_count++) = event;
return KSuccess;
@ -118,27 +119,9 @@ OwnPtr<KBuffer> PerformanceEventBuffer::to_json(ProcessID pid, const String& exe
return builder.build();
}
bool PerformanceEventBuffer::to_json(KBufferBuilder& builder, ProcessID pid, const String& executable_path) const
template<typename Serializer>
bool PerformanceEventBuffer::to_json_impl(Serializer& object) const
{
auto process = Process::from_pid(pid);
VERIFY(process);
ScopedSpinLock locker(process->space().get_lock());
JsonObjectSerializer object(builder);
object.add("pid", pid.value());
object.add("executable", executable_path);
{
auto region_array = object.add_array("regions");
for (const auto& region : process->space().regions()) {
auto region_object = region_array.add_object();
region_object.add("base", region.vaddr().get());
region_object.add("size", region.size());
region_object.add("name", region.name());
}
region_array.finish();
}
auto array = object.add_array("events");
for (size_t i = 0; i < m_count; ++i) {
auto& event = at(i);
@ -171,6 +154,36 @@ bool PerformanceEventBuffer::to_json(KBufferBuilder& builder, ProcessID pid, con
return true;
}
bool PerformanceEventBuffer::to_json(KBufferBuilder& builder)
{
JsonObjectSerializer object(builder);
return to_json_impl(object);
}
bool PerformanceEventBuffer::to_json(KBufferBuilder& builder, ProcessID pid, const String& executable_path) const
{
auto process = Process::from_pid(pid);
VERIFY(process);
ScopedSpinLock locker(process->space().get_lock());
JsonObjectSerializer object(builder);
object.add("pid", pid.value());
object.add("executable", executable_path);
{
auto region_array = object.add_array("regions");
for (const auto& region : process->space().regions()) {
auto region_object = region_array.add_object();
region_object.add("base", region.vaddr().get());
region_object.add("size", region.size());
region_object.add("name", region.name());
}
region_array.finish();
}
return to_json_impl(object);
}
OwnPtr<PerformanceEventBuffer> PerformanceEventBuffer::try_create_with_size(size_t buffer_size)
{
auto buffer = KBuffer::try_create_with_size(buffer_size, Region::Access::Read | Region::Access::Write, "Performance events", AllocationStrategy::AllocateNow);

View file

@ -78,9 +78,15 @@ public:
OwnPtr<KBuffer> to_json(ProcessID, const String& executable_path) const;
bool to_json(KBufferBuilder&, ProcessID, const String& executable_path) const;
// Used by full-system profile (/proc/profile)
bool to_json(KBufferBuilder&);
private:
explicit PerformanceEventBuffer(NonnullOwnPtr<KBuffer>);
template<typename Serializer>
bool to_json_impl(Serializer&) const;
PerformanceEvent& at(size_t index);
size_t m_count { 0 };

View file

@ -42,6 +42,9 @@
namespace Kernel {
extern bool g_profiling_all_threads;
extern PerformanceEventBuffer* g_global_perf_events;
class SchedulerPerProcessorData {
AK_MAKE_NONCOPYABLE(SchedulerPerProcessorData);
AK_MAKE_NONMOVABLE(SchedulerPerProcessorData);
@ -542,10 +545,22 @@ void Scheduler::timer_tick(const RegisterState& regs)
if (!is_bsp)
return; // TODO: This prevents scheduling on other CPUs!
#endif
if (current_thread->process().is_profiling()) {
PerformanceEventBuffer* perf_events = nullptr;
if (g_profiling_all_threads) {
VERIFY(g_global_perf_events);
// FIXME: We currently don't collect samples while idle.
// That will be an interesting mode to add in the future. :^)
if (current_thread != Processor::current().idle_thread())
perf_events = g_global_perf_events;
} else if (current_thread->process().is_profiling()) {
VERIFY(current_thread->process().perf_events());
auto& perf_events = *current_thread->process().perf_events();
[[maybe_unused]] auto rc = perf_events.append_with_eip_and_ebp(regs.eip, regs.ebp, PERF_EVENT_SAMPLE, 0, 0);
perf_events = current_thread->process().perf_events();
}
if (perf_events) {
[[maybe_unused]] auto rc = perf_events->append_with_eip_and_ebp(regs.eip, regs.ebp, PERF_EVENT_SAMPLE, 0, 0);
}
if (current_thread->tick())

View file

@ -32,9 +32,25 @@
namespace Kernel {
PerformanceEventBuffer* g_global_perf_events;
bool g_profiling_all_threads;
KResultOr<int> Process::sys$profiling_enable(pid_t pid)
{
REQUIRE_NO_PROMISES;
if (pid == -1) {
if (!is_superuser())
return EPERM;
ScopedCritical critical;
if (g_global_perf_events)
g_global_perf_events->clear();
else
g_global_perf_events = PerformanceEventBuffer::try_create_with_size(32 * MiB).leak_ptr();
g_profiling_all_threads = true;
return 0;
}
ScopedSpinLock lock(g_processes_lock);
auto process = Process::from_pid(pid);
if (!process)
@ -51,6 +67,14 @@ KResultOr<int> Process::sys$profiling_enable(pid_t pid)
KResultOr<int> Process::sys$profiling_disable(pid_t pid)
{
if (pid == -1) {
if (!is_superuser())
return EPERM;
ScopedCritical critical;
g_profiling_all_threads = false;
return 0;
}
ScopedSpinLock lock(g_processes_lock);
auto process = Process::from_pid(pid);
if (!process)