// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team // SPDX-License-Identifier: GPL-3.0+ #include "common/Perf.h" #include "common/Pcsx2Defs.h" #include "common/Assertions.h" #include "common/StringUtil.h" #ifdef ENABLE_VTUNE #include "jitprofiling.h" #endif #include #include #include #ifdef __linux__ #include #include #include #include #include #include #include #include #endif // Set by CMake options USE_PERF_MAP / USE_PERF_JITDUMP (see cmake/BuildParameters.cmake). // Or define manually below for ad-hoc builds. #if defined(ENABLE_PERF_MAP) && !defined(ProfileWithPerf) #define ProfileWithPerf #endif #if defined(ENABLE_PERF_JITDUMP) && !defined(ProfileWithPerfJitDump) #define ProfileWithPerfJitDump #endif #if defined(ENABLE_VTUNE) && defined(_WIN32) #pragma comment(lib, "jitprofiling.lib") #endif namespace Perf { Group any(""); Group ee("EE"); Group iop("IOP"); Group vu0("VU0"); Group vu1("VU1"); Group vif("VIF"); // Perf is only supported on linux #if defined(__linux__) && defined(ProfileWithPerf) static std::FILE* s_map_file = nullptr; static bool s_map_file_opened = false; static std::mutex s_mutex; static void RegisterMethod(const void* ptr, size_t size, const char* symbol) { std::unique_lock lock(s_mutex); if (!s_map_file) { if (s_map_file_opened) return; char file[256]; snprintf(file, std::size(file), "/tmp/perf-%d.map", getpid()); s_map_file = std::fopen(file, "wb"); s_map_file_opened = true; if (!s_map_file) return; } std::fprintf(s_map_file, "%" PRIx64 " %zx %s\n", static_cast(reinterpret_cast(ptr)), size, symbol); std::fflush(s_map_file); } #elif defined(__linux__) && defined(ProfileWithPerfJitDump) enum : u32 { JIT_CODE_LOAD = 0, JIT_CODE_MOVE = 1, JIT_CODE_DEBUG_INFO = 2, JIT_CODE_CLOSE = 3, JIT_CODE_UNWINDING_INFO = 4 }; #pragma pack(push, 1) struct JITDUMP_HEADER { u32 magic = 0x4A695444; // JiTD u32 version = 1; u32 header_size = sizeof(JITDUMP_HEADER); u32 elf_mach; u32 pad1 = 0; u32 pid; u64 timestamp; u64 flags = 0; }; struct JITDUMP_RECORD_HEADER { u32 id; u32 total_size; u64 timestamp; }; struct JITDUMP_CODE_LOAD { JITDUMP_RECORD_HEADER header; u32 pid; u32 tid; u64 vma; u64 code_addr; u64 code_size; u64 code_index; // name }; #pragma pack(pop) static u64 JitDumpTimestamp() { struct timespec ts = {}; clock_gettime(CLOCK_MONOTONIC, &ts); return (static_cast(ts.tv_sec) * 1000000000ULL) + static_cast(ts.tv_nsec); } static FILE* s_jitdump_file = nullptr; static bool s_jitdump_file_opened = false; static std::mutex s_jitdump_mutex; static u32 s_jitdump_record_id; // Default to /tmp for ad-hoc launches; pcsx2 startup overrides this with // EmuFolders::Cache so the dump (which can be hundreds of MB) doesn't fill // up small /tmp mounts on memory-constrained systems. static std::string s_jitdump_dir = "/tmp"; // Default OFF — VMManager flips this from EmuConfig.Profiler.EnablePerfDump. // USE_PERF_JITDUMP build with the flag disabled does no file I/O at all. static std::atomic s_jitdump_enabled{false}; static void RegisterMethod(const void* ptr, size_t size, const char* symbol) { // Gate every JIT registration on the runtime flag — when off we // neither open the dump file nor write any records, so a USE_PERF_JITDUMP // build with EnablePerfDump=false costs nothing per dispatch. if (!s_jitdump_enabled.load(std::memory_order_relaxed)) return; const u32 namelen = std::strlen(symbol) + 1; std::unique_lock lock(s_jitdump_mutex); if (!s_jitdump_file) { if (!s_jitdump_file_opened) { // Write the JIT dump (and the synthesized jitted--*.so // files that perf inject -j later places alongside it) into // a per-pid subdir under s_jitdump_dir (default /tmp; pcsx2 // overrides to EmuFolders::Cache). Per-pid dir keeps multiple // concurrent runs separated and avoids polluting cwd. char dir[512]; snprintf(dir, std::size(dir), "%s/pcsx2-perf-%d", s_jitdump_dir.c_str(), getpid()); mkdir(dir, 0700); // ignore EEXIST char file[576]; snprintf(file, std::size(file), "%s/jit-%d.dump", dir, getpid()); s_jitdump_file = fopen(file, "w+b"); s_jitdump_file_opened = true; if (!s_jitdump_file) return; } void* perf_marker = mmap(nullptr, 4096, PROT_READ | PROT_EXEC, MAP_PRIVATE, fileno(s_jitdump_file), 0); pxAssertRel(perf_marker != MAP_FAILED, "Map perf marker"); JITDUMP_HEADER jh = {}; #if defined(ARCH_X86) jh.elf_mach = EM_X86_64; #elif defined(ARCH_ARM64) jh.elf_mach = EM_AARCH64; #else #error Unhandled architecture. #endif jh.pid = getpid(); jh.timestamp = JitDumpTimestamp(); std::fwrite(&jh, sizeof(jh), 1, s_jitdump_file); } JITDUMP_CODE_LOAD cl = {}; cl.header.id = JIT_CODE_LOAD; cl.header.total_size = sizeof(cl) + namelen + static_cast(size); cl.header.timestamp = JitDumpTimestamp(); cl.pid = getpid(); cl.tid = syscall(SYS_gettid); cl.vma = 0; cl.code_addr = static_cast(reinterpret_cast(ptr)); cl.code_size = static_cast(size); cl.code_index = s_jitdump_record_id++; std::fwrite(&cl, sizeof(cl), 1, s_jitdump_file); std::fwrite(symbol, namelen, 1, s_jitdump_file); std::fwrite(ptr, size, 1, s_jitdump_file); std::fflush(s_jitdump_file); } #elif defined(ENABLE_VTUNE) static void RegisterMethod(const void* ptr, size_t size, const char* symbol) { iJIT_Method_Load_V2 ml = {}; ml.method_id = iJIT_GetNewMethodID(); ml.method_name = const_cast(symbol); ml.method_load_address = const_cast(ptr); ml.method_size = static_cast(size); iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED_V2, &ml); } #endif #if (defined(__linux__) && (defined(ProfileWithPerf) || defined(ProfileWithPerfJitDump))) || defined(ENABLE_VTUNE) void Group::Register(const void* ptr, size_t size, const char* symbol) { char full_symbol[128]; if (HasPrefix()) std::snprintf(full_symbol, std::size(full_symbol), "%s_%s", m_prefix, symbol); else StringUtil::Strlcpy(full_symbol, symbol, std::size(full_symbol)); RegisterMethod(ptr, size, full_symbol); } void Group::RegisterPC(const void* ptr, size_t size, u32 pc) { char full_symbol[128]; if (HasPrefix()) std::snprintf(full_symbol, std::size(full_symbol), "%s_%08X", m_prefix, pc); else std::snprintf(full_symbol, std::size(full_symbol), "%08X", pc); RegisterMethod(ptr, size, full_symbol); } void Group::RegisterKey(const void* ptr, size_t size, const char* prefix, u64 key) { char full_symbol[128]; if (HasPrefix()) std::snprintf(full_symbol, std::size(full_symbol), "%s_%s%016" PRIX64, m_prefix, prefix, key); else std::snprintf(full_symbol, std::size(full_symbol), "%s%016" PRIX64, prefix, key); RegisterMethod(ptr, size, full_symbol); } #else void Group::Register(const void* ptr, size_t size, const char* symbol) {} void Group::RegisterPC(const void* ptr, size_t size, u32 pc) {} void Group::RegisterKey(const void* ptr, size_t size, const char* prefix, u64 key) {} #endif void SetJitDumpDir(std::string dir) { #if defined(__linux__) && defined(ProfileWithPerfJitDump) std::unique_lock lock(s_jitdump_mutex); // Caller must invoke before the first JIT block compiles; once the // file is opened we don't redirect mid-run. if (!s_jitdump_file_opened && !dir.empty()) s_jitdump_dir = std::move(dir); #else (void)dir; #endif } void SetJitDumpEnabled(bool enabled) { #if defined(__linux__) && defined(ProfileWithPerfJitDump) s_jitdump_enabled.store(enabled, std::memory_order_relaxed); #else (void)enabled; #endif } } // namespace Perf