diff options
author | Andreas Kling <kling@serenityos.org> | 2021-01-11 09:52:18 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-01-11 11:36:00 +0100 |
commit | 5dafb723700c798bc54515a33a3572e01a092812 (patch) | |
tree | 80692b22f17a44a171282c7c1647ce4e580b99d7 | |
parent | f259d96871d66a4e764a43613ab0e9866a22e182 (diff) | |
download | serenity-5dafb723700c798bc54515a33a3572e01a092812.zip |
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
-rw-r--r-- | DevTools/Profiler/DisassemblyModel.cpp | 6 | ||||
-rw-r--r-- | DevTools/Profiler/Profile.cpp | 82 | ||||
-rw-r--r-- | DevTools/Profiler/Profile.h | 34 | ||||
-rw-r--r-- | DevTools/Profiler/main.cpp | 10 | ||||
-rw-r--r-- | Kernel/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Kernel/FileSystem/ProcFS.cpp | 45 | ||||
-rw-r--r-- | Kernel/PerformanceEventBuffer.cpp | 45 | ||||
-rw-r--r-- | Kernel/PerformanceEventBuffer.h | 12 | ||||
-rw-r--r-- | Kernel/Process.cpp | 21 | ||||
-rw-r--r-- | Kernel/Process.h | 4 | ||||
-rw-r--r-- | Kernel/Profiling.cpp | 110 | ||||
-rw-r--r-- | Kernel/Profiling.h | 59 | ||||
-rw-r--r-- | Kernel/Scheduler.cpp | 16 | ||||
-rw-r--r-- | Kernel/Syscalls/execve.cpp | 10 | ||||
-rw-r--r-- | Kernel/Syscalls/perf_event.cpp | 6 | ||||
-rw-r--r-- | Kernel/Syscalls/profiling.cpp | 23 | ||||
-rw-r--r-- | Kernel/Thread.cpp | 6 | ||||
-rw-r--r-- | Kernel/UnixTypes.h | 1 | ||||
-rw-r--r-- | Libraries/LibC/serenity.h | 1 | ||||
-rw-r--r-- | Services/SystemServer/main.cpp | 15 |
20 files changed, 196 insertions, 311 deletions
diff --git a/DevTools/Profiler/DisassemblyModel.cpp b/DevTools/Profiler/DisassemblyModel.cpp index 77f74585bd..4195f103cd 100644 --- a/DevTools/Profiler/DisassemblyModel.cpp +++ b/DevTools/Profiler/DisassemblyModel.cpp @@ -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); diff --git a/DevTools/Profiler/Profile.cpp b/DevTools/Profiler/Profile.cpp index bf2caa7f08..308fdcc7ac 100644 --- a/DevTools/Profiler/Profile.cpp +++ b/DevTools/Profiler/Profile.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 @@ -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 "??"; +} diff --git a/DevTools/Profiler/Profile.h b/DevTools/Profiler/Profile.h index 79c618a306..0acf3e917f 100644 --- a/DevTools/Profiler/Profile.h +++ b/DevTools/Profiler/Profile.h @@ -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 }; diff --git a/DevTools/Profiler/main.cpp b/DevTools/Profiler/main.cpp index fcef34ffbc..5f801936c0 100644 --- a/DevTools/Profiler/main.cpp +++ b/DevTools/Profiler/main.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 @@ -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")); diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index 5b1c8b11f3..c3268d5a67 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -96,7 +96,6 @@ set(KERNEL_SOURCES PerformanceEventBuffer.cpp Process.cpp ProcessGroup.cpp - Profiling.cpp Ptrace.cpp RTC.cpp Random.cpp diff --git a/Kernel/FileSystem/ProcFS.cpp b/Kernel/FileSystem/ProcFS.cpp index 4d4b566b80..87ec817a34 100644 --- a/Kernel/FileSystem/ProcFS.cpp +++ b/Kernel/FileSystem/ProcFS.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()); - - 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->executable()) + return false; + + 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 }; } diff --git a/Kernel/PerformanceEventBuffer.cpp b/Kernel/PerformanceEventBuffer.cpp index 51c35c051e..b564c2ee35 100644 --- a/Kernel/PerformanceEventBuffer.cpp +++ b/Kernel/PerformanceEventBuffer.cpp @@ -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; } } diff --git a/Kernel/PerformanceEventBuffer.h b/Kernel/PerformanceEventBuffer.h index 5e78489c12..19844b7103 100644 --- a/Kernel/PerformanceEventBuffer.h +++ b/Kernel/PerformanceEventBuffer.h @@ -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); diff --git a/Kernel/Process.cpp b/Kernel/Process.cpp index a270b8c198..2e5b981692 100644 --- a/Kernel/Process.cpp +++ b/Kernel/Process.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 @@ -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; +} + } diff --git a/Kernel/Process.h b/Kernel/Process.h index e5f7fbdf77..b8462908e8 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -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(); diff --git a/Kernel/Profiling.cpp b/Kernel/Profiling.cpp deleted file mode 100644 index 08278a5c01..0000000000 --- a/Kernel/Profiling.cpp +++ /dev/null @@ -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); - } -} - -} - -} diff --git a/Kernel/Profiling.h b/Kernel/Profiling.h deleted file mode 100644 index fa7d682b68..0000000000 --- a/Kernel/Profiling.h +++ /dev/null @@ -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&)>); - -} - -} diff --git a/Kernel/Scheduler.cpp b/Kernel/Scheduler.cpp index 7e7aac64b3..ac5e573399 100644 --- a/Kernel/Scheduler.cpp +++ b/Kernel/Scheduler.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 @@ -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)) diff --git a/Kernel/Syscalls/execve.cpp b/Kernel/Syscalls/execve.cpp index b02c45bd77..7a42d2366f 100644 --- a/Kernel/Syscalls/execve.cpp +++ b/Kernel/Syscalls/execve.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 @@ -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); diff --git a/Kernel/Syscalls/perf_event.cpp b/Kernel/Syscalls/perf_event.cpp index f1b50c722e..d7f9ea584a 100644 --- a/Kernel/Syscalls/perf_event.cpp +++ b/Kernel/Syscalls/perf_event.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 @@ -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); } } diff --git a/Kernel/Syscalls/profiling.cpp b/Kernel/Syscalls/profiling.cpp index 9e9ba0c56b..7d69f6ba22 100644 --- a/Kernel/Syscalls/profiling.cpp +++ b/Kernel/Syscalls/profiling.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 @@ -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; } diff --git a/Kernel/Thread.cpp b/Kernel/Thread.cpp index 8bb6664ff8..71283101f0 100644 --- a/Kernel/Thread.cpp +++ b/Kernel/Thread.cpp @@ -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; } diff --git a/Kernel/UnixTypes.h b/Kernel/UnixTypes.h index 0665d464f6..cc5c9fe9dc 100644 --- a/Kernel/UnixTypes.h +++ b/Kernel/UnixTypes.h @@ -63,6 +63,7 @@ enum { _SC_OPEN_MAX }; +#define PERF_EVENT_SAMPLE 0 #define PERF_EVENT_MALLOC 1 #define PERF_EVENT_FREE 2 diff --git a/Libraries/LibC/serenity.h b/Libraries/LibC/serenity.h index e68b391d31..4654476469 100644 --- a/Libraries/LibC/serenity.h +++ b/Libraries/LibC/serenity.h @@ -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 diff --git a/Services/SystemServer/main.cpp b/Services/SystemServer/main.cpp index e0f11ac4b6..f243032b95 100644 --- a/Services/SystemServer/main.cpp +++ b/Services/SystemServer/main.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 @@ -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; |