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:
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"));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue