mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 12:28:12 +00:00
Kernel+Profiler: Make profiling per-process and without core dumps
This patch merges the profiling functionality in the kernel with the performance events mechanism. A profiler sample is now just another perf event, rather than a dedicated thing. Since perf events were already per-process, this now makes profiling per-process as well. Processes with perf events would already write out a perfcore.PID file to the current directory on death, but since we may want to profile a process and then let it continue running, recorded perf events can now be accessed at any time via /proc/PID/perf_events. This patch also adds information about process memory regions to the perfcore JSON format. This removes the need to supply a core dump to the Profiler app for symbolication, and so the "profiler coredump" mechanism is removed entirely. There's still a hard limit of 4MB worth of perf events per process, so this is by no means a perfect final design, but it's a nice step forward for both simplicity and stability. Fixes #4848 Fixes #4849
This commit is contained in:
parent
f259d96871
commit
5dafb72370
20 changed files with 195 additions and 310 deletions
|
@ -68,13 +68,13 @@ 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.coredump().library_containing(node.address());
|
||||
auto library_data = profile.libraries().library_containing(node.address());
|
||||
if (!library_data) {
|
||||
dbgln("no library data");
|
||||
return;
|
||||
}
|
||||
elf = &library_data->lib_elf;
|
||||
base_address = library_data->base_address;
|
||||
elf = &library_data->elf;
|
||||
base_address = library_data->base;
|
||||
}
|
||||
|
||||
ASSERT(elf != nullptr);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -46,20 +46,10 @@ static void sort_profile_nodes(Vector<NonnullRefPtr<ProfileNode>>& nodes)
|
|||
child->sort_children();
|
||||
}
|
||||
|
||||
static String symbolicate_from_coredump(CoreDump::Reader& coredump, u32 ptr, [[maybe_unused]] u32& offset)
|
||||
{
|
||||
auto library_data = coredump.library_containing(ptr);
|
||||
if (!library_data) {
|
||||
dbgln("could not symbolicate: {:p}", ptr);
|
||||
return "??";
|
||||
}
|
||||
return String::formatted("[{}] {}", library_data->name, library_data->lib_elf.symbolicate(ptr - library_data->base_address, &offset));
|
||||
}
|
||||
|
||||
Profile::Profile(String executable_path, NonnullOwnPtr<CoreDump::Reader>&& coredump, Vector<Event> events)
|
||||
Profile::Profile(String executable_path, Vector<Event> events, NonnullOwnPtr<LibraryMetadata> library_metadata)
|
||||
: m_executable_path(move(executable_path))
|
||||
, m_coredump(move(coredump))
|
||||
, m_events(move(events))
|
||||
, m_library_metadata(move(library_metadata))
|
||||
{
|
||||
m_first_timestamp = m_events.first().timestamp;
|
||||
m_last_timestamp = m_events.last().timestamp;
|
||||
|
@ -226,10 +216,6 @@ Result<NonnullOwnPtr<Profile>, String> Profile::load_from_perfcore_file(const St
|
|||
if (!pid.is_u32())
|
||||
return String { "Invalid perfcore format (no process ID)" };
|
||||
|
||||
auto coredump = CoreDump::Reader::create(String::formatted("/tmp/profiler_coredumps/{}", pid.as_u32()));
|
||||
if (!coredump)
|
||||
return String { "Could not open coredump" };
|
||||
|
||||
auto file_or_error = MappedFile::map("/boot/Kernel");
|
||||
OwnPtr<ELF::Image> kernel_elf;
|
||||
if (!file_or_error.is_error())
|
||||
|
@ -239,10 +225,16 @@ Result<NonnullOwnPtr<Profile>, String> Profile::load_from_perfcore_file(const St
|
|||
if (!events_value.is_array())
|
||||
return String { "Malformed profile (events is not an array)" };
|
||||
|
||||
auto regions_value = object.get("regions");
|
||||
if (!regions_value.is_array() || regions_value.as_array().is_empty())
|
||||
return String { "Malformed profile (regions is not an array, or it is empty)" };
|
||||
|
||||
auto& perf_events = events_value.as_array();
|
||||
if (perf_events.is_empty())
|
||||
return String { "No events captured (targeted process was never on CPU)" };
|
||||
|
||||
auto library_metadata = make<LibraryMetadata>(regions_value.as_array());
|
||||
|
||||
Vector<Event> events;
|
||||
|
||||
for (auto& perf_event_value : perf_events.values()) {
|
||||
|
@ -274,7 +266,7 @@ Result<NonnullOwnPtr<Profile>, String> Profile::load_from_perfcore_file(const St
|
|||
symbol = "??";
|
||||
}
|
||||
} else {
|
||||
symbol = symbolicate_from_coredump(*coredump, ptr, offset);
|
||||
symbol = library_metadata->symbolicate(ptr, offset);
|
||||
}
|
||||
|
||||
event.frames.append({ symbol, ptr, offset });
|
||||
|
@ -289,7 +281,7 @@ Result<NonnullOwnPtr<Profile>, String> Profile::load_from_perfcore_file(const St
|
|||
events.append(move(event));
|
||||
}
|
||||
|
||||
return adopt_own(*new Profile(executable_path, coredump.release_nonnull(), move(events)));
|
||||
return adopt_own(*new Profile(executable_path, move(events), move(library_metadata)));
|
||||
}
|
||||
|
||||
void ProfileNode::sort_children()
|
||||
|
@ -353,3 +345,55 @@ GUI::Model* Profile::disassembly_model()
|
|||
{
|
||||
return m_disassembly_model;
|
||||
}
|
||||
|
||||
Profile::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 file_or_error = MappedFile::map(path);
|
||||
if (file_or_error.is_error()) {
|
||||
m_libraries.set(name, nullptr);
|
||||
continue;
|
||||
}
|
||||
auto elf = ELF::Image(file_or_error.value()->bytes());
|
||||
if (!elf.is_valid())
|
||||
continue;
|
||||
auto library = make<Library>(base, size, name, file_or_error.release_value(), move(elf));
|
||||
m_libraries.set(name, move(library));
|
||||
}
|
||||
}
|
||||
|
||||
const Profile::LibraryMetadata::Library* Profile::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;
|
||||
}
|
||||
|
||||
String Profile::LibraryMetadata::symbolicate(FlatPtr ptr, u32& offset) const
|
||||
{
|
||||
if (auto* library = library_containing(ptr))
|
||||
return String::formatted("[{}] {}", library->name, library->elf.symbolicate(ptr - library->base, &offset));
|
||||
return "??";
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -30,10 +30,11 @@
|
|||
#include <AK/JsonArray.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/JsonValue.h>
|
||||
#include <AK/MappedFile.h>
|
||||
#include <AK/NonnullRefPtrVector.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/Result.h>
|
||||
#include <LibCoreDump/Reader.h>
|
||||
#include <LibELF/Image.h>
|
||||
#include <LibGUI/Forward.h>
|
||||
#include <LibGUI/ModelIndex.h>
|
||||
|
||||
|
@ -177,15 +178,36 @@ public:
|
|||
void set_show_percentages(bool);
|
||||
|
||||
const String& executable_path() const { return m_executable_path; }
|
||||
const CoreDump::Reader& coredump() const { return *m_coredump; }
|
||||
|
||||
class LibraryMetadata {
|
||||
public:
|
||||
LibraryMetadata(JsonArray regions);
|
||||
|
||||
String symbolicate(FlatPtr ptr, u32& offset) const;
|
||||
|
||||
struct Library {
|
||||
FlatPtr base;
|
||||
size_t size;
|
||||
String name;
|
||||
NonnullRefPtr<MappedFile> file;
|
||||
ELF::Image elf;
|
||||
};
|
||||
|
||||
const Library* library_containing(FlatPtr) const;
|
||||
|
||||
private:
|
||||
mutable HashMap<String, OwnPtr<Library>> m_libraries;
|
||||
JsonArray m_regions;
|
||||
};
|
||||
|
||||
const LibraryMetadata& libraries() const { return *m_library_metadata; }
|
||||
|
||||
private:
|
||||
Profile(String executable_path, NonnullOwnPtr<CoreDump::Reader>&&, Vector<Event>);
|
||||
Profile(String executable_path, Vector<Event>, NonnullOwnPtr<LibraryMetadata>);
|
||||
|
||||
void rebuild_tree();
|
||||
|
||||
String m_executable_path;
|
||||
NonnullOwnPtr<CoreDump::Reader> m_coredump;
|
||||
|
||||
RefPtr<ProfileModel> m_model;
|
||||
RefPtr<DisassemblyModel> m_disassembly_model;
|
||||
|
@ -199,6 +221,8 @@ private:
|
|||
|
||||
Vector<Event> m_events;
|
||||
|
||||
NonnullOwnPtr<LibraryMetadata> m_library_metadata;
|
||||
|
||||
bool m_has_timestamp_filter_range { false };
|
||||
u64 m_timestamp_filter_range_start { 0 };
|
||||
u64 m_timestamp_filter_range_end { 0 };
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -50,7 +50,7 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static bool generate_profile(pid_t specified_pid);
|
||||
static bool generate_profile(pid_t& pid);
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
|
@ -62,11 +62,11 @@ int main(int argc, char** argv)
|
|||
auto app = GUI::Application::construct(argc, argv);
|
||||
auto app_icon = GUI::Icon::default_icon("app-profiler");
|
||||
|
||||
const char* path = nullptr;
|
||||
String path;
|
||||
if (argc != 2) {
|
||||
if (!generate_profile(pid))
|
||||
return 0;
|
||||
path = "/proc/profile";
|
||||
path = String::formatted("/proc/{}/perf_events", pid);
|
||||
} else {
|
||||
path = argv[1];
|
||||
}
|
||||
|
@ -182,7 +182,7 @@ static bool prompt_to_stop_profiling(pid_t pid, const String& process_name)
|
|||
return GUI::Application::the()->exec() == 0;
|
||||
}
|
||||
|
||||
bool generate_profile(pid_t pid)
|
||||
bool generate_profile(pid_t& pid)
|
||||
{
|
||||
if (!pid) {
|
||||
auto process_chooser = GUI::ProcessChooser::construct("Profiler", "Profile", Gfx::Bitmap::load_from_file("/res/icons/16x16/app-profiler.png"));
|
||||
|
|
|
@ -96,7 +96,6 @@ set(KERNEL_SOURCES
|
|||
PerformanceEventBuffer.cpp
|
||||
Process.cpp
|
||||
ProcessGroup.cpp
|
||||
Profiling.cpp
|
||||
Ptrace.cpp
|
||||
RTC.cpp
|
||||
Random.cpp
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -51,8 +51,8 @@
|
|||
#include <Kernel/Net/TCPSocket.h>
|
||||
#include <Kernel/Net/UDPSocket.h>
|
||||
#include <Kernel/PCI/Access.h>
|
||||
#include <Kernel/PerformanceEventBuffer.h>
|
||||
#include <Kernel/Process.h>
|
||||
#include <Kernel/Profiling.h>
|
||||
#include <Kernel/Scheduler.h>
|
||||
#include <Kernel/StdLib.h>
|
||||
#include <Kernel/TTY/TTY.h>
|
||||
|
@ -113,6 +113,7 @@ enum ProcFileType {
|
|||
FI_PID,
|
||||
|
||||
__FI_PID_Start,
|
||||
FI_PID_perf_events,
|
||||
FI_PID_vm,
|
||||
FI_PID_vmobjects,
|
||||
FI_PID_stacks, // directory
|
||||
|
@ -460,35 +461,21 @@ static bool procfs$modules(InodeIdentifier, KBufferBuilder& builder)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool procfs$profile(InodeIdentifier, KBufferBuilder& builder)
|
||||
static bool procfs$pid_perf_events(InodeIdentifier identifier, KBufferBuilder& builder)
|
||||
{
|
||||
auto process = Process::from_pid(to_pid(identifier));
|
||||
if (!process)
|
||||
return false;
|
||||
|
||||
InterruptDisabler disabler;
|
||||
|
||||
JsonObjectSerializer object(builder);
|
||||
object.add("pid", Profiling::pid().value());
|
||||
object.add("executable", Profiling::executable_path());
|
||||
if (!process->executable())
|
||||
return false;
|
||||
|
||||
auto array = object.add_array("events");
|
||||
bool mask_kernel_addresses = !Process::current()->is_superuser();
|
||||
Profiling::for_each_sample([&](auto& sample) {
|
||||
auto object = array.add_object();
|
||||
object.add("type", "sample");
|
||||
object.add("tid", sample.tid.value());
|
||||
object.add("timestamp", sample.timestamp);
|
||||
auto frames_array = object.add_array("stack");
|
||||
for (size_t i = 0; i < Profiling::max_stack_frame_count; ++i) {
|
||||
if (sample.frames[i] == 0)
|
||||
break;
|
||||
u32 address = (u32)sample.frames[i];
|
||||
if (mask_kernel_addresses && !is_user_address(VirtualAddress(address)))
|
||||
address = 0xdeadc0de;
|
||||
frames_array.add(address);
|
||||
}
|
||||
frames_array.finish();
|
||||
});
|
||||
array.finish();
|
||||
object.finish();
|
||||
return true;
|
||||
if (!process->perf_events())
|
||||
return false;
|
||||
|
||||
return process->perf_events()->to_json(builder, process->pid(), process->executable()->absolute_path());
|
||||
}
|
||||
|
||||
static bool procfs$net_adapters(InodeIdentifier, KBufferBuilder& builder)
|
||||
|
@ -1752,7 +1739,6 @@ 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, false, procfs$profile };
|
||||
m_entries[FI_Root_sys] = { "sys", FI_Root_sys, true };
|
||||
m_entries[FI_Root_net] = { "net", FI_Root_net, false };
|
||||
|
||||
|
@ -1770,6 +1756,7 @@ ProcFS::ProcFS()
|
|||
m_entries[FI_PID_cwd] = { "cwd", FI_PID_cwd, false, procfs$pid_cwd };
|
||||
m_entries[FI_PID_unveil] = { "unveil", FI_PID_unveil, false, procfs$pid_unveil };
|
||||
m_entries[FI_PID_root] = { "root", FI_PID_root, false, procfs$pid_root };
|
||||
m_entries[FI_PID_perf_events] = { "perf_events", FI_PID_perf_events, false, procfs$pid_perf_events };
|
||||
m_entries[FI_PID_fd] = { "fd", FI_PID_fd, false };
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -29,11 +29,12 @@
|
|||
#include <AK/JsonObjectSerializer.h>
|
||||
#include <Kernel/KBufferBuilder.h>
|
||||
#include <Kernel/PerformanceEventBuffer.h>
|
||||
#include <Kernel/Process.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
PerformanceEventBuffer::PerformanceEventBuffer()
|
||||
: m_buffer(KBuffer::try_create_with_size(4 * MiB))
|
||||
: m_buffer(KBuffer::try_create_with_size(4 * MiB, Region::Access::Read | Region::Access::Write, "Performance events", AllocationStrategy::AllocateNow))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -46,18 +47,14 @@ KResult PerformanceEventBuffer::append(int type, FlatPtr arg1, FlatPtr arg2)
|
|||
event.type = type;
|
||||
|
||||
switch (type) {
|
||||
case PERF_EVENT_SAMPLE:
|
||||
break;
|
||||
case PERF_EVENT_MALLOC:
|
||||
event.data.malloc.size = arg1;
|
||||
event.data.malloc.ptr = arg2;
|
||||
#ifdef VERY_DEBUG
|
||||
dbg() << "PERF_EVENT_MALLOC: " << (void*)event.data.malloc.ptr << " (" << event.data.malloc.size << ")";
|
||||
#endif
|
||||
break;
|
||||
case PERF_EVENT_FREE:
|
||||
event.data.free.ptr = arg1;
|
||||
#ifdef VERY_DEBUG
|
||||
dbg() << "PERF_EVENT_FREE: " << (void*)event.data.free.ptr;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
return KResult(-EINVAL);
|
||||
|
@ -76,11 +73,6 @@ KResult PerformanceEventBuffer::append(int type, FlatPtr arg1, FlatPtr arg2)
|
|||
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));
|
||||
|
||||
#ifdef VERY_DEBUG
|
||||
for (size_t i = 0; i < event.stack_size; ++i)
|
||||
dbg() << " " << (void*)event.stack[i];
|
||||
#endif
|
||||
|
||||
event.timestamp = TimeManagement::the().uptime_ms();
|
||||
at(m_count++) = event;
|
||||
return KSuccess;
|
||||
|
@ -96,16 +88,40 @@ PerformanceEvent& PerformanceEventBuffer::at(size_t index)
|
|||
OwnPtr<KBuffer> PerformanceEventBuffer::to_json(ProcessID pid, const String& executable_path) const
|
||||
{
|
||||
KBufferBuilder builder;
|
||||
if (!to_json(builder, pid, executable_path))
|
||||
return nullptr;
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
bool PerformanceEventBuffer::to_json(KBufferBuilder& builder, ProcessID pid, const String& executable_path) const
|
||||
{
|
||||
auto process = Process::from_pid(pid);
|
||||
ASSERT(process);
|
||||
ScopedSpinLock locker(process->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->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);
|
||||
auto event_object = array.add_object();
|
||||
switch (event.type) {
|
||||
case PERF_EVENT_SAMPLE:
|
||||
event_object.add("type", "sample");
|
||||
break;
|
||||
case PERF_EVENT_MALLOC:
|
||||
event_object.add("type", "malloc");
|
||||
event_object.add("ptr", static_cast<u64>(event.data.malloc.ptr));
|
||||
|
@ -116,6 +132,7 @@ OwnPtr<KBuffer> PerformanceEventBuffer::to_json(ProcessID pid, const String& exe
|
|||
event_object.add("ptr", static_cast<u64>(event.data.free.ptr));
|
||||
break;
|
||||
}
|
||||
event_object.add("tid", event.tid);
|
||||
event_object.add("timestamp", event.timestamp);
|
||||
auto stack_array = event_object.add_array("stack");
|
||||
for (size_t j = 0; j < event.stack_size; ++j) {
|
||||
|
@ -126,7 +143,7 @@ OwnPtr<KBuffer> PerformanceEventBuffer::to_json(ProcessID pid, const String& exe
|
|||
}
|
||||
array.finish();
|
||||
object.finish();
|
||||
return builder.build();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
|
||||
namespace Kernel {
|
||||
|
||||
class KBufferBuilder;
|
||||
|
||||
struct [[gnu::packed]] MallocPerformanceEvent {
|
||||
size_t size;
|
||||
FlatPtr ptr;
|
||||
|
@ -44,12 +46,14 @@ struct [[gnu::packed]] FreePerformanceEvent {
|
|||
struct [[gnu::packed]] PerformanceEvent {
|
||||
u8 type { 0 };
|
||||
u8 stack_size { 0 };
|
||||
u32 tid { 0 };
|
||||
u64 timestamp;
|
||||
union {
|
||||
MallocPerformanceEvent malloc;
|
||||
FreePerformanceEvent free;
|
||||
} data;
|
||||
FlatPtr stack[32];
|
||||
static constexpr size_t max_stack_frame_count = 32;
|
||||
FlatPtr stack[max_stack_frame_count];
|
||||
};
|
||||
|
||||
class PerformanceEventBuffer {
|
||||
|
@ -58,6 +62,11 @@ public:
|
|||
|
||||
KResult append(int type, FlatPtr arg1, FlatPtr arg2);
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_count = 0;
|
||||
}
|
||||
|
||||
size_t capacity() const
|
||||
{
|
||||
if (!m_buffer)
|
||||
|
@ -71,6 +80,7 @@ public:
|
|||
}
|
||||
|
||||
OwnPtr<KBuffer> to_json(ProcessID, const String& executable_path) const;
|
||||
bool to_json(KBufferBuilder&, ProcessID, const String& executable_path) const;
|
||||
|
||||
private:
|
||||
PerformanceEvent& at(size_t index);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -597,16 +597,6 @@ void Process::finalize()
|
|||
dbg() << "Finalizing process " << *this;
|
||||
#endif
|
||||
|
||||
if (is_profiling()) {
|
||||
auto coredump = CoreDump::create(*this, String::formatted("/tmp/profiler_coredumps/{}", pid().value()));
|
||||
if (coredump) {
|
||||
auto result = coredump->write();
|
||||
if (result.is_error())
|
||||
dbgln("Core dump generation failed: {}", result.error());
|
||||
} else {
|
||||
dbgln("Could not create coredump");
|
||||
}
|
||||
}
|
||||
if (m_should_dump_core) {
|
||||
dbgln("Generating coredump for pid: {}", m_pid.value());
|
||||
|
||||
|
@ -622,7 +612,7 @@ void Process::finalize()
|
|||
}
|
||||
|
||||
if (m_perf_event_buffer) {
|
||||
auto description_or_error = VFS::the().open(String::format("perfcore.%d", m_pid), O_CREAT | O_EXCL, 0400, current_directory(), UidAndGid { m_uid, m_gid });
|
||||
auto description_or_error = VFS::the().open(String::formatted("perfcore.{}", m_pid), O_CREAT | O_EXCL, 0400, current_directory(), UidAndGid { m_uid, m_gid });
|
||||
if (!description_or_error.is_error()) {
|
||||
auto& description = description_or_error.value();
|
||||
auto json = m_perf_event_buffer->to_json(m_pid, m_executable ? m_executable->absolute_path() : "");
|
||||
|
@ -922,4 +912,11 @@ void Process::tracer_trap(Thread& thread, const RegisterState& regs)
|
|||
thread.send_urgent_signal_to_self(SIGTRAP);
|
||||
}
|
||||
|
||||
PerformanceEventBuffer& Process::ensure_perf_events()
|
||||
{
|
||||
if (!m_perf_event_buffer)
|
||||
m_perf_event_buffer = make<PerformanceEventBuffer>();
|
||||
return *m_perf_event_buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -509,11 +509,15 @@ public:
|
|||
|
||||
const HashMap<String, String>& coredump_metadata() const { return m_coredump_metadata; }
|
||||
|
||||
PerformanceEventBuffer* perf_events() { return m_perf_event_buffer; }
|
||||
|
||||
private:
|
||||
friend class MemoryManager;
|
||||
friend class Scheduler;
|
||||
friend class Region;
|
||||
|
||||
PerformanceEventBuffer& ensure_perf_events();
|
||||
|
||||
Process(RefPtr<Thread>& first_thread, const String& name, uid_t, gid_t, ProcessID ppid, bool is_kernel_process, RefPtr<Custody> cwd = nullptr, RefPtr<Custody> executable = nullptr, TTY* = nullptr, Process* fork_parent = nullptr);
|
||||
static ProcessID allocate_pid();
|
||||
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <AK/Demangle.h>
|
||||
#include <AK/Singleton.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <Kernel/FileSystem/Custody.h>
|
||||
#include <Kernel/KBuffer.h>
|
||||
#include <Kernel/KSyms.h>
|
||||
#include <Kernel/Process.h>
|
||||
#include <Kernel/Profiling.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
namespace Profiling {
|
||||
|
||||
static size_t s_slot_count;
|
||||
static AK::Singleton<KBuffer, []() -> KBuffer* {
|
||||
auto buffer = KBuffer::try_create_with_size(8 * MiB, Region::Access::Read | Region::Access::Write, "Profiling Buffer", AllocationStrategy::AllocateNow);
|
||||
s_slot_count = buffer->size() / sizeof(Sample);
|
||||
return buffer.leak_ptr();
|
||||
}>
|
||||
s_profiling_buffer;
|
||||
static size_t s_next_slot_index;
|
||||
static ProcessID s_pid { -1 };
|
||||
|
||||
String& executable_path()
|
||||
{
|
||||
static String* path;
|
||||
if (!path)
|
||||
path = new String;
|
||||
return *path;
|
||||
}
|
||||
|
||||
ProcessID pid()
|
||||
{
|
||||
return s_pid;
|
||||
}
|
||||
|
||||
void start(Process& process)
|
||||
{
|
||||
if (process.executable())
|
||||
executable_path() = process.executable()->absolute_path().impl();
|
||||
else
|
||||
executable_path() = {};
|
||||
s_pid = process.pid();
|
||||
|
||||
s_profiling_buffer.ensure_instance();
|
||||
|
||||
s_next_slot_index = 0;
|
||||
}
|
||||
|
||||
static Sample& sample_slot(size_t index)
|
||||
{
|
||||
return ((Sample*)s_profiling_buffer->data())[index];
|
||||
}
|
||||
|
||||
Sample& next_sample_slot()
|
||||
{
|
||||
auto& slot = sample_slot(s_next_slot_index++);
|
||||
if (s_next_slot_index >= s_slot_count)
|
||||
s_next_slot_index = 0;
|
||||
return slot;
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
// FIXME: This probably shouldn't be empty.
|
||||
}
|
||||
|
||||
void did_exec(const String& new_executable_path)
|
||||
{
|
||||
executable_path() = new_executable_path;
|
||||
s_next_slot_index = 0;
|
||||
}
|
||||
|
||||
void for_each_sample(Function<void(Sample&)> callback)
|
||||
{
|
||||
for (size_t i = 0; i < s_next_slot_index; ++i) {
|
||||
auto& sample = sample_slot(i);
|
||||
callback(sample);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Types.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class Process;
|
||||
|
||||
namespace Profiling {
|
||||
|
||||
constexpr size_t max_stack_frame_count = 50;
|
||||
|
||||
struct Sample {
|
||||
ProcessID pid;
|
||||
ThreadID tid;
|
||||
u64 timestamp;
|
||||
u32 frames[max_stack_frame_count];
|
||||
};
|
||||
|
||||
extern ProcessID pid();
|
||||
extern String& executable_path();
|
||||
|
||||
Sample& next_sample_slot();
|
||||
void start(Process&);
|
||||
void stop();
|
||||
void did_exec(const String& new_executable_path);
|
||||
void for_each_sample(Function<void(Sample&)>);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -28,8 +28,8 @@
|
|||
#include <AK/ScopeGuard.h>
|
||||
#include <AK/TemporaryChange.h>
|
||||
#include <AK/Time.h>
|
||||
#include <Kernel/PerformanceEventBuffer.h>
|
||||
#include <Kernel/Process.h>
|
||||
#include <Kernel/Profiling.h>
|
||||
#include <Kernel/RTC.h>
|
||||
#include <Kernel/Scheduler.h>
|
||||
#include <Kernel/Time/TimeManagement.h>
|
||||
|
@ -477,15 +477,9 @@ void Scheduler::timer_tick(const RegisterState& regs)
|
|||
if (!is_bsp)
|
||||
return; // TODO: This prevents scheduling on other CPUs!
|
||||
if (current_thread->process().is_profiling()) {
|
||||
SmapDisabler disabler;
|
||||
auto backtrace = current_thread->raw_backtrace(regs.ebp, regs.eip);
|
||||
auto& sample = Profiling::next_sample_slot();
|
||||
sample.pid = current_thread->process().pid();
|
||||
sample.tid = current_thread->tid();
|
||||
sample.timestamp = TimeManagement::the().uptime_ms();
|
||||
for (size_t i = 0; i < min(backtrace.size(), Profiling::max_stack_frame_count); ++i) {
|
||||
sample.frames[i] = backtrace[i];
|
||||
}
|
||||
ASSERT(current_thread->process().perf_events());
|
||||
auto& perf_events = *current_thread->process().perf_events();
|
||||
[[maybe_unused]] auto rc = perf_events.append(PERF_EVENT_SAMPLE, 0, 0);
|
||||
}
|
||||
|
||||
if (current_thread->tick((regs.cs & 3) == 0))
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -29,8 +29,8 @@
|
|||
#include <AK/TemporaryChange.h>
|
||||
#include <Kernel/FileSystem/Custody.h>
|
||||
#include <Kernel/FileSystem/FileDescription.h>
|
||||
#include <Kernel/PerformanceEventBuffer.h>
|
||||
#include <Kernel/Process.h>
|
||||
#include <Kernel/Profiling.h>
|
||||
#include <Kernel/Random.h>
|
||||
#include <Kernel/Time/TimeManagement.h>
|
||||
#include <Kernel/VM/AllocationStrategy.h>
|
||||
|
@ -446,7 +446,6 @@ int Process::do_exec(NonnullRefPtr<FileDescription> main_program_description, Ve
|
|||
return -ENOENT;
|
||||
|
||||
// Disable profiling temporarily in case it's running on this process.
|
||||
bool was_profiling = is_profiling();
|
||||
TemporaryChange profiling_disabler(m_profiling, false);
|
||||
|
||||
// Mark this thread as the current thread that does exec
|
||||
|
@ -589,8 +588,9 @@ int Process::do_exec(NonnullRefPtr<FileDescription> main_program_description, Ve
|
|||
tss.cr3 = m_page_directory->cr3();
|
||||
tss.ss2 = m_pid.value();
|
||||
|
||||
if (was_profiling)
|
||||
Profiling::did_exec(path);
|
||||
// Throw away any recorded performance events in this process.
|
||||
if (m_perf_event_buffer)
|
||||
m_perf_event_buffer->clear();
|
||||
|
||||
{
|
||||
ScopedSpinLock lock(g_scheduler_lock);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -31,9 +31,7 @@ namespace Kernel {
|
|||
|
||||
int Process::sys$perf_event(int type, FlatPtr arg1, FlatPtr arg2)
|
||||
{
|
||||
if (!m_perf_event_buffer)
|
||||
m_perf_event_buffer = make<PerformanceEventBuffer>();
|
||||
return m_perf_event_buffer->append(type, arg1, arg2);
|
||||
return ensure_perf_events().append(type, arg1, arg2);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -25,8 +25,10 @@
|
|||
*/
|
||||
|
||||
#include <Kernel/CoreDump.h>
|
||||
#include <Kernel/FileSystem/FileDescription.h>
|
||||
#include <Kernel/FileSystem/VirtualFileSystem.h>
|
||||
#include <Kernel/PerformanceEventBuffer.h>
|
||||
#include <Kernel/Process.h>
|
||||
#include <Kernel/Profiling.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
|
@ -41,7 +43,7 @@ int Process::sys$profiling_enable(pid_t pid)
|
|||
return -ESRCH;
|
||||
if (!is_superuser() && process->uid() != m_uid)
|
||||
return -EPERM;
|
||||
Profiling::start(*process);
|
||||
process->ensure_perf_events();
|
||||
process->set_profiling(true);
|
||||
return 0;
|
||||
}
|
||||
|
@ -54,20 +56,9 @@ int Process::sys$profiling_disable(pid_t pid)
|
|||
return -ESRCH;
|
||||
if (!is_superuser() && process->uid() != m_uid)
|
||||
return -EPERM;
|
||||
if (!process->is_profiling())
|
||||
return -EINVAL;
|
||||
process->set_profiling(false);
|
||||
Profiling::stop();
|
||||
|
||||
// We explicitly unlock here because we can't hold the lock when writing the coredump VFS
|
||||
lock.unlock();
|
||||
|
||||
if (auto coredump = CoreDump::create(*process, String::formatted("/tmp/profiler_coredumps/{}", pid))) {
|
||||
auto result = coredump->write();
|
||||
if (result.is_error())
|
||||
return result.error();
|
||||
} else {
|
||||
// FIXME: Return an error maybe?
|
||||
dbgln("Unable to create profiler coredump for PID {}", pid);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,8 +31,8 @@
|
|||
#include <Kernel/Arch/i386/CPU.h>
|
||||
#include <Kernel/FileSystem/FileDescription.h>
|
||||
#include <Kernel/KSyms.h>
|
||||
#include <Kernel/PerformanceEventBuffer.h>
|
||||
#include <Kernel/Process.h>
|
||||
#include <Kernel/Profiling.h>
|
||||
#include <Kernel/Scheduler.h>
|
||||
#include <Kernel/Thread.h>
|
||||
#include <Kernel/ThreadTracer.h>
|
||||
|
@ -1036,7 +1036,7 @@ Vector<FlatPtr> Thread::raw_backtrace(FlatPtr ebp, FlatPtr eip) const
|
|||
InterruptDisabler disabler;
|
||||
auto& process = const_cast<Process&>(this->process());
|
||||
ProcessPagingScope paging_scope(process);
|
||||
Vector<FlatPtr, Profiling::max_stack_frame_count> backtrace;
|
||||
Vector<FlatPtr, PerformanceEvent::max_stack_frame_count> backtrace;
|
||||
backtrace.append(eip);
|
||||
FlatPtr stack_ptr_copy;
|
||||
FlatPtr stack_ptr = (FlatPtr)ebp;
|
||||
|
@ -1048,7 +1048,7 @@ Vector<FlatPtr> Thread::raw_backtrace(FlatPtr ebp, FlatPtr eip) const
|
|||
if (!safe_memcpy(&retaddr, (void*)(stack_ptr + sizeof(FlatPtr)), sizeof(FlatPtr), fault_at))
|
||||
break;
|
||||
backtrace.append(retaddr);
|
||||
if (backtrace.size() == Profiling::max_stack_frame_count)
|
||||
if (backtrace.size() == PerformanceEvent::max_stack_frame_count)
|
||||
break;
|
||||
stack_ptr = stack_ptr_copy;
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ enum {
|
|||
_SC_OPEN_MAX
|
||||
};
|
||||
|
||||
#define PERF_EVENT_SAMPLE 0
|
||||
#define PERF_EVENT_MALLOC 1
|
||||
#define PERF_EVENT_FREE 2
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@ int futex(int32_t* userspace_address, int futex_op, int32_t value, const struct
|
|||
|
||||
int purge(int mode);
|
||||
|
||||
#define PERF_EVENT_SAMPLE 0
|
||||
#define PERF_EVENT_MALLOC 1
|
||||
#define PERF_EVENT_FREE 2
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -205,18 +205,6 @@ static void create_tmp_coredump_directory()
|
|||
umask(old_umask);
|
||||
}
|
||||
|
||||
static void create_tmp_profiler_coredumps_directory()
|
||||
{
|
||||
dbgln("Creating /tmp/profiler_coredumps directory");
|
||||
auto old_umask = umask(0);
|
||||
auto rc = mkdir("/tmp/profiler_coredumps", 0755);
|
||||
if (rc < 0) {
|
||||
perror("mkdir(/tmp/profiler_coredumps)");
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
umask(old_umask);
|
||||
}
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
prepare_devfs();
|
||||
|
@ -229,7 +217,6 @@ int main(int, char**)
|
|||
mount_all_filesystems();
|
||||
create_tmp_rpc_directory();
|
||||
create_tmp_coredump_directory();
|
||||
create_tmp_profiler_coredumps_directory();
|
||||
parse_boot_mode();
|
||||
|
||||
Core::EventLoop event_loop;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue