Skip to content

Commit

Permalink
Kernel+Profiler: Capture metadata about all profiled processes
Browse files Browse the repository at this point in the history
The perfcore file format was previously limited to a single process
since the pid/executable/regions data was top-level in the JSON.

This patch moves the process-specific data into a top-level array
named "processes" and we now add entries for each process that has
been sampled during the profile run.

This makes it possible to see samples from multiple threads when
viewing a perfcore file with Profiler. This is extremely cool! :^)
  • Loading branch information
awesomekling committed Mar 2, 2021
1 parent ea500dd commit 5e7abea
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 103 deletions.
8 changes: 1 addition & 7 deletions Kernel/FileSystem/ProcFS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -488,16 +488,10 @@ static bool procfs$pid_perf_events(InodeIdentifier identifier, KBufferBuilder& b
auto process = Process::from_pid(to_pid(identifier));
if (!process)
return false;

InterruptDisabler disabler;

if (!process->executable())
return false;

if (!process->perf_events())
return false;

return process->perf_events()->to_json(builder, process->pid(), process->executable()->absolute_path());
return process->perf_events()->to_json(builder);
}

static bool procfs$net_adapters(InodeIdentifier, KBufferBuilder& builder)
Expand Down
77 changes: 48 additions & 29 deletions Kernel/PerformanceEventBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <AK/JsonObject.h>
#include <AK/JsonObjectSerializer.h>
#include <Kernel/Arch/x86/SmapDisabler.h>
#include <Kernel/FileSystem/Custody.h>
#include <Kernel/KBufferBuilder.h>
#include <Kernel/PerformanceEventBuffer.h>
#include <Kernel/Process.h>
Expand Down Expand Up @@ -111,14 +112,6 @@ PerformanceEvent& PerformanceEventBuffer::at(size_t index)
return events[index];
}

OwnPtr<KBuffer> PerformanceEventBuffer::to_json(ProcessID pid, const String& executable_path) const
{
KBufferBuilder builder;
if (!to_json(builder, pid, executable_path))
return {};
return builder.build();
}

template<typename Serializer>
bool PerformanceEventBuffer::to_json_impl(Serializer& object) const
{
Expand Down Expand Up @@ -154,33 +147,28 @@ bool PerformanceEventBuffer::to_json_impl(Serializer& object) const
return true;
}

bool PerformanceEventBuffer::to_json(KBufferBuilder& builder)
bool PerformanceEventBuffer::to_json(KBufferBuilder& builder) const
{
JsonObjectSerializer object(builder);
return to_json_impl(object);
}

bool PerformanceEventBuffer::to_json(KBufferBuilder& builder, ProcessID pid, const String& executable_path) const
{
auto process = Process::from_pid(pid);
VERIFY(process);
ScopedSpinLock locker(process->space().get_lock());

JsonObjectSerializer object(builder);
object.add("pid", pid.value());
object.add("executable", executable_path);

{
auto region_array = object.add_array("regions");
for (const auto& region : process->space().regions()) {
auto region_object = region_array.add_object();
region_object.add("base", region.vaddr().get());
region_object.add("size", region.size());
region_object.add("name", region.name());
auto processes_array = object.add_array("processes");
for (auto& it : m_processes) {
auto& process = *it.value;
auto process_object = processes_array.add_object();
process_object.add("pid", process.pid.value());
process_object.add("executable", process.executable);

auto regions_array = process_object.add_array("regions");
for (auto& region : process.regions) {
auto region_object = regions_array.add_object();
region_object.add("name", region.name);
region_object.add("base", region.range.base().get());
region_object.add("size", region.range.size());
}
region_array.finish();
}

processes_array.finish();

return to_json_impl(object);
}

Expand All @@ -192,4 +180,35 @@ OwnPtr<PerformanceEventBuffer> PerformanceEventBuffer::try_create_with_size(size
return adopt_own(*new PerformanceEventBuffer(buffer.release_nonnull()));
}

void PerformanceEventBuffer::add_process(const Process& process)
{
// FIXME: What about threads that have died?

ScopedSpinLock locker(process.space().get_lock());

String executable;
if (process.executable())
executable = process.executable()->absolute_path();

auto sampled_process = adopt_own(*new SampledProcess {
.pid = process.pid().value(),
.executable = executable,
.threads = {},
.regions = {},
});
process.for_each_thread([&](auto& thread) {
sampled_process->threads.set(thread.tid());
return IterationDecision::Continue;
});

for (auto& region : process.space().regions()) {
sampled_process->regions.append(SampledProcess::Region {
.name = region.name(),
.range = region.range(),
});
}

m_processes.set(process.pid(), move(sampled_process));
}

}
20 changes: 16 additions & 4 deletions Kernel/PerformanceEventBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,22 +75,34 @@ class PerformanceEventBuffer {
return const_cast<PerformanceEventBuffer&>(*this).at(index);
}

OwnPtr<KBuffer> to_json(ProcessID, const String& executable_path) const;
bool to_json(KBufferBuilder&, ProcessID, const String& executable_path) const;
bool to_json(KBufferBuilder&) const;

// Used by full-system profile (/proc/profile)
bool to_json(KBufferBuilder&);
void add_process(const Process&);

private:
explicit PerformanceEventBuffer(NonnullOwnPtr<KBuffer>);

struct SampledProcess {
ProcessID pid;
String executable;
HashTable<ThreadID> threads;

struct Region {
String name;
Range range;
};
Vector<Region> regions;
};

template<typename Serializer>
bool to_json_impl(Serializer&) const;

PerformanceEvent& at(size_t index);

size_t m_count { 0 };
NonnullOwnPtr<KBuffer> m_buffer;

HashMap<ProcessID, NonnullOwnPtr<SampledProcess>> m_processes;
};

}
8 changes: 6 additions & 2 deletions Kernel/Process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -448,10 +448,13 @@ bool Process::dump_perfcore()
if (description_or_error.is_error())
return false;
auto& description = description_or_error.value();
auto json = m_perf_event_buffer->to_json(m_pid, m_executable ? m_executable->absolute_path() : "");
if (!json)
KBufferBuilder builder;
if (!m_perf_event_buffer->to_json(builder))
return false;

auto json = builder.build();
if (!json)
return false;
auto json_buffer = UserOrKernelBuffer::for_kernel_buffer(json->data());
return !description->write(json_buffer, json->size()).is_error();
}
Expand Down Expand Up @@ -671,6 +674,7 @@ bool Process::create_perf_events_buffer_if_needed()
{
if (!m_perf_event_buffer) {
m_perf_event_buffer = PerformanceEventBuffer::try_create_with_size(4 * MiB);
m_perf_event_buffer->add_process(*this);
}
return !!m_perf_event_buffer;
}
Expand Down
9 changes: 8 additions & 1 deletion Kernel/Scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -552,8 +552,15 @@ void Scheduler::timer_tick(const RegisterState& regs)
VERIFY(g_global_perf_events);
// FIXME: We currently don't collect samples while idle.
// That will be an interesting mode to add in the future. :^)
if (current_thread != Processor::current().idle_thread())
if (current_thread != Processor::current().idle_thread()) {
perf_events = g_global_perf_events;
if (current_thread->process().space().enforces_syscall_regions()) {
// FIXME: This is very nasty! We dump the current process's address
// space layout *every time* it's sampled. We should figure out
// a way to do this less often.
perf_events->add_process(current_thread->process());
}
}
} else if (current_thread->process().is_profiling()) {
VERIFY(current_thread->process().perf_events());
perf_events = current_thread->process().perf_events();
Expand Down
1 change: 1 addition & 0 deletions Kernel/Syscalls/mmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include <AK/WeakPtr.h>
#include <Kernel/FileSystem/FileDescription.h>
#include <Kernel/PerformanceEventBuffer.h>
#include <Kernel/Process.h>
#include <Kernel/VM/MemoryManager.h>
#include <Kernel/VM/PageDirectory.h>
Expand Down
3 changes: 2 additions & 1 deletion Userland/DevTools/Profiler/DisassemblyModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ DisassemblyModel::DisassemblyModel(Profile& profile, ProfileNode& node)
kernel_elf = make<ELF::Image>((const u8*)m_kernel_file->data(), m_kernel_file->size());
elf = kernel_elf.ptr();
} else {
auto library_data = profile.libraries().library_containing(node.address());
// FIXME: This is kinda rickety looking with all the -> -> ->
auto library_data = node.process(profile)->library_metadata->library_containing(node.address());
if (!library_data) {
dbgln("no library data");
return;
Expand Down
Loading

0 comments on commit 5e7abea

Please sign in to comment.