1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 15:37:46 +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"));