1
Fork 0
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:
Andreas Kling 2021-01-11 09:52:18 +01:00
parent f259d96871
commit 5dafb72370
20 changed files with 195 additions and 310 deletions

View file

@ -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);

View file

@ -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 "??";
}

View file

@ -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 };

View file

@ -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"));

View file

@ -96,7 +96,6 @@ set(KERNEL_SOURCES
PerformanceEventBuffer.cpp
Process.cpp
ProcessGroup.cpp
Profiling.cpp
Ptrace.cpp
RTC.cpp
Random.cpp

View file

@ -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 };
}

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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);
}
}
}
}

View file

@ -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&)>);
}
}

View file

@ -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))

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -63,6 +63,7 @@ enum {
_SC_OPEN_MAX
};
#define PERF_EVENT_SAMPLE 0
#define PERF_EVENT_MALLOC 1
#define PERF_EVENT_FREE 2

View file

@ -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

View file

@ -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;