Skip to content

Commit

Permalink
Profiler: Add Tracy backend
Browse files Browse the repository at this point in the history
This differs from the existing GPUVis backend in a number of ways:
* Tracy is optimized for minimal overhead and nanosecond-resolution profiling
* Tracy supports live tracing (in addition to capture-based operation)
* Tracy has a richer feature set and a more polished UI (notably, statistics and histograms are generated out-of-the-box)
* GPUVis supports tracing multiple processes, whereas Tracy is single-process only

To use this backend, one of the environment variables FEX_PROFILE_TARGET_NAME
or FEX_PROFILE_TARGET_PATH must be defined to select the application under
profile by name or by path suffix.

Additionally, FEX_PROFILE_WAIT_FOR_FORK=1 may be needed for games that fork on startup.
  • Loading branch information
neobrain committed Feb 12, 2025
1 parent f147f11 commit db5d56d
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 21 deletions.
19 changes: 18 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -61,6 +61,19 @@ 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)
else()
message(FATAL_ERROR "Unknown FEXCore profiler backend ${FEXCORE_PROFILER_BACKEND}")
endif()
Expand Down Expand Up @@ -270,6 +283,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!")
Expand Down
4 changes: 4 additions & 0 deletions FEXCore/Source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,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})

Expand Down
1 change: 1 addition & 0 deletions FEXCore/Source/Interface/Core/Core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
111 changes: 99 additions & 12 deletions FEXCore/Source/Utils/Profiler.cpp
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
// SPDX-License-Identifier: MIT
#include <array>
#include <cstdint>
#include <fcntl.h>
#include <limits.h>
#ifndef _WIN32
#include <linux/magic.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <time.h>
#endif

#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/Utils/Profiler.h>
#include <FEXCore/fextl/fmt.h>
#include <FEXCore/fextl/string.h>

#define BACKEND_OFF 0
#define BACKEND_GPUVIS 1

#ifdef ENABLE_FEXCORE_PROFILER
#if FEXCORE_PROFILER_BACKEND == FEXCORE_PROFILER_BACKEND_GPUVIS
#include <array>
#include <limits.h>
#include <time.h>
#ifndef _WIN32
static inline uint64_t GetTime() {
// We want the time in the least amount of overhead possible
Expand Down Expand Up @@ -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()}
Expand Down Expand Up @@ -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=<n> 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
27 changes: 22 additions & 5 deletions FEXCore/include/FEXCore/Utils/Profiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,31 @@

#include <FEXCore/Utils/CompilerDefs.h>

#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 {
#ifdef ENABLE_FEXCORE_PROFILER

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);

// 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:
Expand All @@ -28,15 +45,15 @@ class ProfilerBlock final {
#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

#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]]
Expand Down
3 changes: 2 additions & 1 deletion Source/Tools/FEXLoader/FEXLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())) {
Expand Down Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion Source/Windows/ARM64EC/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,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);

Expand Down
2 changes: 1 addition & 1 deletion Source/Windows/WOW64/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,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);

Expand Down

0 comments on commit db5d56d

Please sign in to comment.