diff --git a/.gitmodules b/.gitmodules index c957c702d7..b8bca39e2b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -47,3 +47,6 @@ [submodule "External/jemalloc_glibc"] path = External/jemalloc_glibc url = https://github.com/FEX-Emu/jemalloc.git +[submodule "External/tracy"] + path = External/tracy + url = https://github.com/wolfpld/tracy diff --git a/CMakeLists.txt b/CMakeLists.txt index 2dcc44f688..ed190fe12b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ option(ENABLE_VIXL_SIMULATOR "Enable use of VIXL simulator for emulation (only u option(ENABLE_VIXL_DISASSEMBLER "Enables debug disassembler output with VIXL" FALSE) option(USE_LEGACY_BINFMTMISC "Uses legacy method of setting up binfmt_misc" FALSE) option(ENABLE_FEXCORE_PROFILER "Enables use of the FEXCore timeline profiling capabilities" FALSE) -set (FEXCORE_PROFILER_BACKEND "gpuvis" CACHE STRING "Set which backend you want to use for the FEXCore profiler") +set (FEXCORE_PROFILER_BACKEND "gpuvis" CACHE STRING "Set which backend to use for the FEXCore profiler (gpuvis, tracy)") option(ENABLE_GLIBC_ALLOCATOR_HOOK_FAULT "Enables glibc memory allocation hooking with fault for CI testing") option(USE_PDB_DEBUGINFO "Builds debug info in PDB format" FALSE) @@ -61,6 +61,22 @@ if (ENABLE_FEXCORE_PROFILER) if (FEXCORE_PROFILER_BACKEND STREQUAL "GPUVIS") add_definitions(-DFEXCORE_PROFILER_BACKEND=1) + elseif (FEXCORE_PROFILER_BACKEND STREQUAL "TRACY") + add_definitions(-DFEXCORE_PROFILER_BACKEND=2) + add_definitions(-DTRACY_ENABLE=1) + # Required so that Tracy will only start in the selected guest application + add_definitions(-DTRACY_MANUAL_LIFETIME=1) + add_definitions(-DTRACY_DELAYED_INIT=1) + # This interferes with FEX's signal handling + add_definitions(-DTRACY_NO_CRASH_HANDLER=1) + # Tracy can gather call stack samples in regular intervals, but this + # isn't useful for us since it would usually sample opaque JIT code + add_definitions(-DTRACY_NO_SAMPLING=1) + # This pulls in libbacktrace which allocators in global constructors (before FEX can set up its allocator hooks) + add_definitions(-DTRACY_NO_CALLSTACK=1) + if (MINGW_BUILD) + message(FATAL_ERROR "Tracy profiler not supported") + endif() else() message(FATAL_ERROR "Unknown FEXCore profiler backend ${FEXCORE_PROFILER_BACKEND}") endif() @@ -270,6 +286,10 @@ if (BUILD_TESTS OR ENABLE_VIXL_DISASSEMBLER OR ENABLE_VIXL_SIMULATOR) include_directories(SYSTEM External/vixl/src/) endif() +if (ENABLE_FEXCORE_PROFILER AND FEXCORE_PROFILER_BACKEND STREQUAL "TRACY") + add_subdirectory(External/tracy) +endif() + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # This means we were attempted to get compiled with GCC message(FATAL_ERROR "FEX doesn't support getting compiled with GCC!") diff --git a/External/tracy b/External/tracy new file mode 160000 index 0000000000..5d542dc09f --- /dev/null +++ b/External/tracy @@ -0,0 +1 @@ +Subproject commit 5d542dc09f3d9378d005092a4ad446bd405f819a diff --git a/FEXCore/Source/CMakeLists.txt b/FEXCore/Source/CMakeLists.txt index a609e22e38..6e5d8fa929 100644 --- a/FEXCore/Source/CMakeLists.txt +++ b/FEXCore/Source/CMakeLists.txt @@ -338,6 +338,10 @@ add_library(FEXCore_Base STATIC ${FEXCORE_BASE_SRCS}) target_link_libraries(FEXCore_Base ${LIBS}) AddDefaultOptionsToTarget(FEXCore_Base) +if (ENABLE_FEXCORE_PROFILER AND FEXCORE_PROFILER_BACKEND STREQUAL "TRACY") + target_link_libraries(FEXCore_Base TracyClient) +endif() + function(AddObject Name Type) add_library(${Name} ${Type} ${SRCS}) diff --git a/FEXCore/Source/Interface/Core/Core.cpp b/FEXCore/Source/Interface/Core/Core.cpp index 4f832f200c..b09266f32d 100644 --- a/FEXCore/Source/Interface/Core/Core.cpp +++ b/FEXCore/Source/Interface/Core/Core.cpp @@ -473,6 +473,7 @@ void ContextImpl::DestroyThread(FEXCore::Core::InternalThreadState* Thread) { void ContextImpl::UnlockAfterFork(FEXCore::Core::InternalThreadState* LiveThread, bool Child) { Allocator::UnlockAfterFork(LiveThread, Child); + Profiler::PostForkAction(Child); if (Child) { CodeInvalidationMutex.StealAndDropActiveLocks(); if (Config.StrictInProcessSplitLocks) { diff --git a/FEXCore/Source/Utils/Profiler.cpp b/FEXCore/Source/Utils/Profiler.cpp index 6df103f007..bf6e1f8eb8 100644 --- a/FEXCore/Source/Utils/Profiler.cpp +++ b/FEXCore/Source/Utils/Profiler.cpp @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT -#include #include #include -#include #ifndef _WIN32 #include #include #include -#include #endif #include @@ -15,10 +12,11 @@ #include #include -#define BACKEND_OFF 0 -#define BACKEND_GPUVIS 1 - #ifdef ENABLE_FEXCORE_PROFILER +#if FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_GPUVIS +#include +#include +#include #ifndef _WIN32 static inline uint64_t GetTime() { // We want the time in the least amount of overhead possible @@ -49,7 +47,6 @@ static inline uint64_t GetTime() { #endif -#if FEXCORE_PROFILER_BACKEND == BACKEND_GPUVIS namespace FEXCore::Profiler { ProfilerBlock::ProfilerBlock(std::string_view const Format) : DurationBegin {GetTime()} @@ -114,35 +111,125 @@ void TraceObject(std::string_view const Format) { } } } // namespace GPUVis +#elif FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_TRACY +#include "tracy/Tracy.hpp" +namespace Tracy { +static int EnableAfterFork = 0; +static bool Enable = false; + +void Init(std::string_view ProgramName, std::string_view ProgramPath) { + const char* ProfileTargetName = getenv("FEX_PROFILE_TARGET_NAME"); // Match by application name + const char* ProfileTargetPath = getenv("FEX_PROFILE_TARGET_PATH"); // Match by path suffix + const char* WaitForFork = getenv("FEX_PROFILE_WAIT_FOR_FORK"); // Don't enable profiling until the process forks N times + bool Matched = (ProfileTargetName && ProgramName == ProfileTargetName) || (ProfileTargetPath && ProgramPath.ends_with(ProfileTargetPath)); + if (Matched && WaitForFork) { + EnableAfterFork = std::atoi(WaitForFork); + } + Enable = Matched && !EnableAfterFork; + if (Enable) { + tracy::StartupProfiler(); + LogMan::Msg::IFmt("Tracy profiling started"); + } else if (EnableAfterFork) { + LogMan::Msg::IFmt("Tracy profiling will start after fork"); + } +} + +void PostForkAction(bool IsChild) { + if (Enable) { + // Tracy does not support multiprocess profiling + LogMan::Msg::EFmt("Warning: Profiling a process with forks is not supported. Set the environment variable " + "FEX_PROFILE_WAIT_FOR_FORK= to start profiling after the n-th fork."); + } + + if (IsChild) { + Enable = false; + return; + } + + if (EnableAfterFork > 1) { + --EnableAfterFork; + LogMan::Msg::IFmt("Tracy profiling will start after {} forks", EnableAfterFork); + } else if (EnableAfterFork == 1) { + Enable = true; + EnableAfterFork = 0; + tracy::StartupProfiler(); + LogMan::Msg::IFmt("Tracy profiling started"); + } +} + +void Shutdown() { + if (Tracy::Enable) { + LogMan::Msg::IFmt("Stopping Tracy profiling"); + tracy::ShutdownProfiler(); + } +} + +void TraceObject(std::string_view const Format, uint64_t Duration) {} + +void TraceObject(std::string_view const Format) { + if (Tracy::Enable) { + TracyMessage(Format.data(), Format.size()); + } +} +} // namespace Tracy #else #error Unknown profiler backend #endif #endif namespace FEXCore::Profiler { + #ifdef ENABLE_FEXCORE_PROFILER -void Init() { -#if FEXCORE_PROFILER_BACKEND == BACKEND_GPUVIS +void Init(std::string_view ProgramName, std::string_view ProgramPath) { +#if FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_GPUVIS GPUVis::Init(); +#elif FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_TRACY + Tracy::Init(ProgramName, ProgramPath); +#endif +} + +void PostForkAction(bool IsChild) { +#if FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_TRACY + Tracy::PostForkAction(IsChild); +#endif +} + +bool IsActive() { +#if FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_GPUVIS + // Always active + return true; +#elif FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_TRACY + // Active if previously enabled + if (Tracy::Enable) { + LogMan::Msg::EFmt("PROFILE ENABLED"); + } + return Tracy::Enable; #endif } void Shutdown() { -#if FEXCORE_PROFILER_BACKEND == BACKEND_GPUVIS +#if FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_GPUVIS GPUVis::Shutdown(); +#elif FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_TRACY + Tracy::Shutdown(); #endif } void TraceObject(std::string_view const Format, uint64_t Duration) { -#if FEXCORE_PROFILER_BACKEND == BACKEND_GPUVIS +#if FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_GPUVIS GPUVis::TraceObject(Format, Duration); +#elif FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_TRACY + Tracy::TraceObject(Format, Duration); #endif } void TraceObject(std::string_view const Format) { -#if FEXCORE_PROFILER_BACKEND == BACKEND_GPUVIS +#if FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_GPUVIS GPUVis::TraceObject(Format); +#elif FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_TRACY + Tracy::TraceObject(Format); #endif } + #endif } // namespace FEXCore::Profiler diff --git a/FEXCore/include/FEXCore/Utils/Profiler.h b/FEXCore/include/FEXCore/Utils/Profiler.h index d886adcc75..b240ce439d 100644 --- a/FEXCore/include/FEXCore/Utils/Profiler.h +++ b/FEXCore/include/FEXCore/Utils/Profiler.h @@ -10,6 +10,14 @@ #include +#define FEXCORE_PROFILER_BACKEND_OFF 0 +#define FEXCORE_PROFILER_BACKEND_GPUVIS 1 +#define FEXCORE_PROFILER_BACKEND_TRACY 2 + +#if defined(ENABLE_FEXCORE_PROFILER) && FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_TRACY +#include "tracy/Tracy.hpp" +#endif + namespace FEXCore::Profiler { // FEXCore live-stats constexpr uint8_t STATS_VERSION = 2; @@ -69,11 +77,23 @@ static inline uint64_t GetCycleCounter() { } #endif -FEX_DEFAULT_VISIBILITY void Init(); +FEX_DEFAULT_VISIBILITY void Init(std::string_view ProgramName, std::string_view ProgramPath); +FEX_DEFAULT_VISIBILITY void PostForkAction(bool IsChild); +FEX_DEFAULT_VISIBILITY bool IsActive(); FEX_DEFAULT_VISIBILITY void Shutdown(); FEX_DEFAULT_VISIBILITY void TraceObject(std::string_view const Format); FEX_DEFAULT_VISIBILITY void TraceObject(std::string_view const Format, uint64_t Duration); +#define UniqueScopeName2(name, line) name##line +#define UniqueScopeName(name, line) UniqueScopeName2(name, line) + +// Declare an instantaneous profiler event. +#define FEXCORE_PROFILE_INSTANT(name) FEXCore::Profiler::TraceObject(name) + +#if FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_TRACY +// Declare a scoped profile block variable with a fixed name. +#define FEXCORE_PROFILE_SCOPED(name) ZoneNamedN(___tracy_scoped_zone, name, ::FEXCore::Profiler::IsActive()) +#else // A class that follows scoping rules to generate a profile duration block class ProfilerBlock final { public: @@ -86,14 +106,9 @@ class ProfilerBlock final { std::string_view const Format; }; -#define UniqueScopeName2(name, line) name##line -#define UniqueScopeName(name, line) UniqueScopeName2(name, line) - -// Declare an instantaneous profiler event. -#define FEXCORE_PROFILE_INSTANT(name) FEXCore::Profiler::TraceObject(name) - // Declare a scoped profile block variable with a fixed name. #define FEXCORE_PROFILE_SCOPED(name) FEXCore::Profiler::ProfilerBlock UniqueScopeName(ScopedBlock_, __LINE__)(name) +#endif template class AccumulationBlock final { @@ -127,7 +142,9 @@ class AccumulationBlock final { #else [[maybe_unused]] -static void Init() {} +static void Init(std::string_view ProgramName, std::string_view ProgramPath) {} +[[maybe_unused]] +static void PostForkAction(bool IsChild) {} [[maybe_unused]] static void Shutdown() {} [[maybe_unused]] diff --git a/Source/Tools/FEXLoader/FEXLoader.cpp b/Source/Tools/FEXLoader/FEXLoader.cpp index de6140228d..3d40f66acf 100644 --- a/Source/Tools/FEXLoader/FEXLoader.cpp +++ b/Source/Tools/FEXLoader/FEXLoader.cpp @@ -389,7 +389,6 @@ int main(int argc, char** argv, char** const envp) { std::this_thread::sleep_for(std::chrono::seconds(StartupSleep())); } - FEXCore::Profiler::Init(); FEXCore::Telemetry::Initialize(); if (!LDPath().empty() && Program.ProgramPath.starts_with(LDPath())) { @@ -493,6 +492,8 @@ int main(int argc, char** argv, char** const envp) { free(data); } + FEXCore::Profiler::Init(Program.ProgramName, Program.ProgramPath); + // System allocator is now system allocator or FEX FEXCore::Context::InitializeStaticTables(Loader.Is64BitMode() ? FEXCore::Context::MODE_64BIT : FEXCore::Context::MODE_32BIT); diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index aa3104c288..8db65561bf 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -539,7 +539,7 @@ NTSTATUS ProcessInit() { // Not applicable to Windows FEXCore::Config::EraseSet(FEXCore::Config::ConfigOption::CONFIG_TSOAUTOMIGRATION, "0"); - FEXCore::Profiler::Init(); + FEXCore::Profiler::Init("", ""); FEXCore::Context::InitializeStaticTables(FEXCore::Context::MODE_64BIT); diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index 422e10ecd2..6213cbd816 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -461,7 +461,7 @@ void BTCpuProcessInit() { // Not applicable to Windows FEXCore::Config::EraseSet(FEXCore::Config::ConfigOption::CONFIG_TSOAUTOMIGRATION, "0"); - FEXCore::Profiler::Init(); + FEXCore::Profiler::Init("", ""); FEXCore::Context::InitializeStaticTables(FEXCore::Context::MODE_32BIT);