Add TRACY_PLATFORM_HEADER hook for unsupported platforms.

Extension point so private/unsupported platforms can plug in their own implementations of the kernel/libc primitives Tracy depends on, without patching the `#if`/`#elif` chains.

Projects supply a platform header via `-DTRACY_PLATFORM_HEADER="\"my_platform.h\""` at build time. Tracy includes it in any TU that needs the hooks. The header toggles per-category `TRACY_HAS_CUSTOM_*` macros and declares matching `tracy::Platform*` functions.

Available hooks:

- `TRACY_HAS_CUSTOM_THREAD_ID` → `PlatformGetThreadId`
- `TRACY_HAS_CUSTOM_USER_INFO` → `PlatformGetHostname`, `PlatformGetUserLogin`, `PlatformGetUserFullName`
- `TRACY_HAS_CUSTOM_SAFE_COPY` → `PlatformSafeMemcpy`
- `TRACY_HAS_CUSTOM_ALLOCATOR` → `PlatformMalloc`, `PlatformFree`, `PlatformRealloc`, `PlatformAllocatorInit`, `PlatformAllocatorThreadInit`, `PlatformAllocatorFinalize`, `PlatformAllocatorThreadFinalize`

Each hook is wired as the first arm of its respective `#if`/`#elif` chain, so existing supported platforms are unaffected.

Template files in `examples/CustomPlatform/` and a new subsection in `manual/tracy.tex` document the mechanism.
This commit is contained in:
Clément Grégoire
2026-05-24 15:41:52 +02:00
parent 84570487bf
commit f93d17a96f
14 changed files with 253 additions and 20 deletions

View File

@@ -111,6 +111,7 @@ include(cmake/options.cmake)
tracy_set_option(TRACY_ENABLE "Enable profiling" OFF TracyClient)
tracy_set_option(TRACY_ON_DEMAND "On-demand profiling" OFF TracyClient)
tracy_set_option_value(TRACY_CALLSTACK "Override the callstack collection depth for tracy zones" "" TracyClient)
tracy_set_option_value_as_string(TRACY_PLATFORM_HEADER "Path to a header providing TRACY_HAS_CUSTOM_* hooks for an unsupported platform" "" TracyClient)
tracy_set_option(TRACY_NO_CALLSTACK "Disable all callstack related functionality" OFF TracyClient)
tracy_set_option(TRACY_NO_CALLSTACK_INLINES "Disables the inline functions in callstacks" OFF TracyClient)
tracy_set_option(TRACY_ONLY_LOCALHOST "Only listen on the localhost interface" OFF TracyClient)

View File

@@ -0,0 +1,57 @@
// Template implementations of the tracy::Platform* hooks. Pair with the
// platform header (see CustomPlatform.h) and link this into your final
// binary.
#include <stdlib.h>
#include <string.h>
#include "CustomPlatform.h"
namespace tracy
{
uint32_t PlatformGetThreadId()
{
return 0;
}
void PlatformGetHostname( char* buf, size_t size )
{
const char* placeholder = "(?)";
if( size == 0 ) return;
const size_t n = strlen( placeholder );
const size_t copy = n < size - 1 ? n : size - 1;
memcpy( buf, placeholder, copy );
buf[copy] = '\0';
}
const char* PlatformGetUserLogin()
{
return "(?)";
}
const char* PlatformGetUserFullName()
{
return nullptr;
}
bool PlatformSafeMemcpy( void* dst, const void* src, size_t size )
{
// Stub: report failure so Tracy skips the snapshot. Real impls use SEH
// on Win32, pipe(2) on POSIX, or an equivalent probe-and-copy primitive.
(void)dst; (void)src; (void)size;
return false;
}
// Stubs forward to the C runtime. Swap in the allocator you actually want.
void* PlatformMalloc( size_t size ) { return malloc( size ); }
void PlatformFree( void* ptr ) { free( ptr ); }
void* PlatformRealloc( void* ptr, size_t size ) { return realloc( ptr, size ); }
void PlatformAllocatorInit() {}
void PlatformAllocatorThreadInit() {}
void PlatformAllocatorFinalize() {}
void PlatformAllocatorThreadFinalize(){}
}

View File

@@ -0,0 +1,73 @@
// Template platform header for unsupported targets.
//
// Copy into your project, fill in the sections you need, and point Tracy at
// it via -DTRACY_PLATFORM_HEADER="\"my_platform.h\"". Provide the
// implementations in any TU linked into your final binary (see
// CustomPlatform.cpp).
//
// Use this only for the TRACY_HAS_CUSTOM_* hooks and matching Platform*
// declarations — don't set unrelated TRACY_* options here. Some are checked
// before this header is included, so the result would depend on which TU
// consulted them; set those at the build system level instead.
//
// For platform-specific features without a custom hook (call stacks,
// context switches, crash handling, system tracing, etc.), disable them at
// the build system level with the matching TRACY_NO_* macro.
#ifndef __MY_TRACY_PLATFORM_H__
#define __MY_TRACY_PLATFORM_H__
#include <stddef.h>
#include <stdint.h>
namespace tracy
{
// --- Thread id --------------------------------------------------------------
//
// Required if defaults in TracySystem.cpp do not matches your platform.
// Note pthread_self() is NOT suitable, it returns a library handle, not a kernel id.
//#define TRACY_HAS_CUSTOM_THREAD_ID
uint32_t PlatformGetThreadId();
// --- User info --------------------------------------------------------------
//
// Identifies the machine and user in the trace header. Return placeholder
// strings (e.g. "(?)") from any of these if your platform has no equivalent
// notion.
//#define TRACY_HAS_CUSTOM_USER_INFO
void PlatformGetHostname( char* buf, size_t size );
const char* PlatformGetUserLogin();
const char* PlatformGetUserFullName();
// --- Safe memory copy -------------------------------------------------------
//
// Tracy uses this to snapshot potentially-unmapped memory during sampling.
// Must not crash on unreadable input — return false instead. Plain memcpy()
// is NOT a valid implementation.
//#define TRACY_HAS_CUSTOM_SAFE_COPY
bool PlatformSafeMemcpy( void* dst, const void* src, size_t size );
// --- Allocator --------------------------------------------------------------
//
// Replaces Tracy's internal allocator. Drop in the system allocator, an
// in-house one, or any third-party allocator you like. Malloc/Free/Realloc
// must be thread-safe; ThreadInit is an optional prime, not a precondition.
// Finalize must also tear down the calling thread's per-thread state, the
// way rpmalloc_finalize() does — Tracy does not call ThreadFinalize for the
// shutdown thread before Finalize.
//#define TRACY_HAS_CUSTOM_ALLOCATOR
void* PlatformMalloc( size_t size );
void PlatformFree( void* ptr );
void* PlatformRealloc( void* ptr, size_t size );
void PlatformAllocatorInit();
void PlatformAllocatorThreadInit();
void PlatformAllocatorFinalize();
void PlatformAllocatorThreadFinalize();
}
#endif

View File

@@ -669,6 +669,40 @@ Although the basic features will work without them, you'll have to grant elevate
\item \texttt{-{}-pid=host}
\end{itemize}
\subsubsection{Porting to unsupported platforms}
\label{customplatform}
When Tracy is built for a platform that is not among the supported set, some of the platform-specific code paths it relies on may fail to compile or have undesired behavior. Rather than patching the \texttt{\#if} chains in Tracy itself, you can point Tracy at a \emph{platform header} that provides your own implementations of these primitives.
Define \texttt{TRACY\_PLATFORM\_HEADER} at build time to the path of a header that Tracy will include from its internal translation units:
\begin{lstlisting}
-DTRACY_PLATFORM_HEADER="\"my_platform.h\""
\end{lstlisting}
Inside that header, enable any subset of the hooks you need by defining the corresponding \texttt{TRACY\_HAS\_CUSTOM\_*} macro and declaring the matching \texttt{tracy::Platform*} function. Provide the implementations in a separate translation unit that is linked into your final binary.
The available hooks are:
\begin{itemize}
\item \texttt{TRACY\_HAS\_CUSTOM\_THREAD\_ID} $\rightarrow$ \texttt{tracy::PlatformGetThreadId()}. Required.
\item \texttt{TRACY\_HAS\_CUSTOM\_USER\_INFO} $\rightarrow$ \texttt{tracy::PlatformGetHostname()}, \texttt{tracy::PlatformGetUserLogin()}, \texttt{tracy::PlatformGetUserFullName()}.
\item \texttt{TRACY\_HAS\_CUSTOM\_SAFE\_COPY} $\rightarrow$ \texttt{tracy::PlatformSafeMemcpy()}.
\item \texttt{TRACY\_HAS\_CUSTOM\_ALLOCATOR} $\rightarrow$ \texttt{tracy::PlatformMalloc()}, \texttt{tracy::PlatformFree()}, \texttt{tracy::PlatformRealloc()}, \texttt{tracy::PlatformAllocatorInit()}, \texttt{tracy::PlatformAllocatorThreadInit()}, \texttt{tracy::PlatformAllocatorFinalize()}, \texttt{tracy::PlatformAllocatorThreadFinalize()}.
\end{itemize}
Template files are provided in the repository ( \texttt{examples/CustomPlatform/CustomPlatform(.h|.cpp)} ). See \texttt{CustomPlatform.h} for the contract each \texttt{Platform*} function must satisfy (return values, threading guarantees, and footguns to avoid). Copy these files into your project, fill in the bodies for the hooks you enable, and point Tracy at the header.
These are the only categories currently exposed through the custom-platform mechanism. Other platform-specific subsystems (call stack collection, context switch capture, crash handling, system tracing, and so on) are not pluggable this way. If your platform cannot support one of them, disable it at build time using the corresponding \texttt{TRACY\_NO\_*} macros rather than trying to stub it out via the platform header.
\begin{bclogo}[
noborder=true,
couleur=black!5,
logo=\bcbombe
]{Important}
The platform header is intended only for the \texttt{TRACY\_HAS\_CUSTOM\_*} hooks and their matching function declarations. Do not use it to set unrelated \texttt{TRACY\_*} options (such as \texttt{TRACY\_ENABLE} or \texttt{TRACY\_ON\_DEMAND}). Some of those are checked in \texttt{Tracy.hpp} before the platform header is included, so the results would be inconsistent depending on which translation unit consults them. Set those options at the build system level instead, as described in section~\ref{initialsetup}.
\end{bclogo}
\subsubsection{Troubleshooting}
By default, Tracy's diagnostics will be sent as Message logs (section~\ref{messagelog}) to the server.

View File

@@ -21,6 +21,10 @@ if get_option('callstack') > 0
tracy_common_args += ['-DTRACY_CALLSTACK='+get_option('callstack').to_string()]
endif
if get_option('platform_header') != ''
tracy_common_args += ['-DTRACY_PLATFORM_HEADER="'+get_option('platform_header')+'"']
endif
if get_option('no_callstack')
tracy_common_args += ['-DTRACY_NO_CALLSTACK']
endif

View File

@@ -1,6 +1,7 @@
option('tracy_enable', type : 'boolean', value : false, description : 'Enable profiling', yield: true)
option('on_demand', type : 'boolean', value : false, description : 'On-demand profiling')
option('callstack', type : 'integer', value : 0, description : 'Enforce callstack collection for tracy zones x frames deep')
option('platform_header', type : 'string', value : '', description : 'Path to a header providing TRACY_HAS_CUSTOM_* hooks for an unsupported platform')
option('no_callstack', type : 'boolean', value : false, description : 'Disable all callstack related functionality')
option('no_callstack_inlines', type : 'boolean', value : false, description : 'Disables the inline functions in callstacks')
option('only_localhost', type : 'boolean', value : false, description : 'Only listen on the localhost interface')

View File

@@ -26,7 +26,9 @@
#include "client/TracySysTime.cpp"
#include "client/TracySysTrace.cpp"
#include "common/TracySocket.cpp"
#ifndef TRACY_HAS_CUSTOM_ALLOCATOR
#include "client/tracy_rpmalloc.cpp"
#endif
#include "client/TracyDxt1.cpp"
#include "client/TracyAlloc.cpp"
#include "client/TracyOverride.cpp"

View File

@@ -1,6 +1,6 @@
#include "../common/TracyAlloc.hpp"
#ifdef TRACY_USE_RPMALLOC
#if defined TRACY_USE_RPMALLOC || defined TRACY_HAS_CUSTOM_ALLOCATOR
#include <atomic>
@@ -24,12 +24,20 @@ tracy_no_inline static void InitAllocatorPlumbing()
const auto done = RpInitDone.load( std::memory_order_acquire );
if( !done )
{
#if defined TRACY_HAS_CUSTOM_ALLOCATOR
PlatformAllocatorInit();
#else
rpmalloc_initialize();
#endif
RpInitDone.store( 1, std::memory_order_release );
}
RpInitLock.store( 0, std::memory_order_release );
}
#if defined TRACY_HAS_CUSTOM_ALLOCATOR
PlatformAllocatorThreadInit();
#else
rpmalloc_thread_initialize();
#endif
RpThreadInitDone = true;
}

View File

@@ -43,8 +43,14 @@
#define TRACY_TIMER_FALLBACK_MANGLE _TF0
#endif
#ifdef TRACY_PLATFORM_HEADER
#define TRACY_PLATFORM_HEADER_MANGLE _PH1
#else
#define TRACY_PLATFORM_HEADER_MANGLE _PH0
#endif
#define MANGLED_NAME_BASED_ON_CONFIG(base) \
TracyConcat(TracyConcat(TracyConcat(TracyConcat(TracyConcat(TracyConcat(TracyConcat( \
TracyConcat(TracyConcat(TracyConcat(TracyConcat(TracyConcat(TracyConcat(TracyConcat(TracyConcat( \
base##_CFG, \
TRACY_ENABLE_MANGLE), \
TRACY_ON_DEMAND_MANGLE), \
@@ -52,6 +58,7 @@
TRACY_MANUAL_LIFETIME_MANGLE), \
TRACY_FIBERS_MANGLE), \
TRACY_DISALLOW_HW_TIMER_MANGLE), \
TRACY_TIMER_FALLBACK_MANGLE)
TRACY_TIMER_FALLBACK_MANGLE), \
TRACY_PLATFORM_HEADER_MANGLE)
#endif // __TRACYMANGLE_HPP__

View File

@@ -71,7 +71,6 @@
#include "../common/TracySystem.hpp"
#include "../common/TracyYield.hpp"
#include "../common/tracy_lz4.hpp"
#include "tracy_rpmalloc.hpp"
#include "TracyCallstack.hpp"
#include "TracyDebug.hpp"
#include "TracyDxt1.hpp"
@@ -605,7 +604,11 @@ static const char* GetHostInfo()
const char* user = GetUserLogin();
char hostname[512] = {};
#if defined TRACY_HAS_CUSTOM_USER_INFO
PlatformGetHostname( hostname, sizeof( hostname ) );
#else
gethostname( hostname, sizeof( hostname ) );
#endif
ptr += sprintf( ptr, "User: %s@%s", user, hostname );
const char* fullName = GetUserFullName();
@@ -1284,7 +1287,11 @@ TRACY_API void ShutdownProfiler()
s_profilerData->~ProfilerData();
tracy_free( s_profilerData );
s_profilerData = nullptr;
#if defined TRACY_HAS_CUSTOM_ALLOCATOR
PlatformAllocatorFinalize();
#elif defined TRACY_USE_RPMALLOC
rpmalloc_finalize();
#endif
RpThreadInitDone = false;
RpInitDone.store( 0, std::memory_order_release );
}
@@ -1531,7 +1538,7 @@ Profiler::Profiler()
m_safeSendBuffer = (char*)tracy_malloc( SafeSendBufferSize );
#ifndef _WIN32
#if !defined _WIN32 && !defined TRACY_HAS_CUSTOM_SAFE_COPY
pipe(m_pipe);
# if defined __APPLE__ || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined __DragonFly__
// FreeBSD/XNU don't have F_SETPIPE_SZ, so use the default
@@ -1675,7 +1682,7 @@ Profiler::~Profiler()
tracy_free( m_kcore );
#endif
#ifndef _WIN32
#if !defined _WIN32 && !defined TRACY_HAS_CUSTOM_SAFE_COPY
close( m_pipe[0] );
close( m_pipe[1] );
#endif
@@ -3349,7 +3356,9 @@ char* Profiler::SafeCopyProlog( const char* data, size_t size )
if( size > SafeSendBufferSize ) buf = (char*)tracy_malloc( size );
#ifdef _WIN32
#if defined TRACY_HAS_CUSTOM_SAFE_COPY
success = PlatformSafeMemcpy( buf, data, size );
#elif defined _WIN32
# ifdef _MSC_VER
__try
{

View File

@@ -22,6 +22,10 @@
#include "../common/TracyMutex.hpp"
#include "../common/TracyProtocol.hpp"
#ifdef TRACY_PLATFORM_HEADER
# include TRACY_PLATFORM_HEADER
#endif
#if defined _WIN32
# include <intrin.h>
#endif
@@ -1122,7 +1126,7 @@ private:
#if defined _WIN32
void* m_prevHandler;
#else
#elif !defined TRACY_HAS_CUSTOM_SAFE_COPY
int m_pipe[2];
int m_pipeBufSize;
#endif

View File

@@ -8,7 +8,7 @@
#endif
#ifdef TRACY_MANUAL_LIFETIME
# include "tracy_rpmalloc.hpp"
# include "../common/TracyAlloc.hpp"
#endif
namespace tracy
@@ -24,7 +24,11 @@ public:
~ThreadExitHandler()
{
#ifdef TRACY_MANUAL_LIFETIME
# if defined TRACY_HAS_CUSTOM_ALLOCATOR
PlatformAllocatorThreadFinalize();
# elif defined TRACY_USE_RPMALLOC
rpmalloc_thread_finalize( 1 );
# endif
RpThreadInitDone = false;
#endif
}

View File

@@ -3,17 +3,23 @@
#include <stdlib.h>
#ifdef TRACY_PLATFORM_HEADER
# include TRACY_PLATFORM_HEADER
#endif
#if defined TRACY_ENABLE && !defined __EMSCRIPTEN__
# include "TracyApi.h"
# include "TracyForceInline.hpp"
# include "../client/tracy_rpmalloc.hpp"
# define TRACY_USE_RPMALLOC
# if !defined TRACY_HAS_CUSTOM_ALLOCATOR
# include "../client/tracy_rpmalloc.hpp"
# define TRACY_USE_RPMALLOC
# endif
#endif
namespace tracy
{
#ifdef TRACY_USE_RPMALLOC
#if defined TRACY_USE_RPMALLOC || defined TRACY_HAS_CUSTOM_ALLOCATOR
TRACY_API void InitAllocator();
#else
static inline void InitAllocator() {}
@@ -21,7 +27,10 @@ static inline void InitAllocator() {}
static inline void* tracy_malloc( size_t size )
{
#ifdef TRACY_USE_RPMALLOC
#if defined TRACY_HAS_CUSTOM_ALLOCATOR
InitAllocator();
return PlatformMalloc( size );
#elif defined TRACY_USE_RPMALLOC
InitAllocator();
return rpmalloc( size );
#else
@@ -31,7 +40,9 @@ static inline void* tracy_malloc( size_t size )
static inline void* tracy_malloc_fast( size_t size )
{
#ifdef TRACY_USE_RPMALLOC
#if defined TRACY_HAS_CUSTOM_ALLOCATOR
return PlatformMalloc( size );
#elif defined TRACY_USE_RPMALLOC
return rpmalloc( size );
#else
return malloc( size );
@@ -40,7 +51,10 @@ static inline void* tracy_malloc_fast( size_t size )
static inline void tracy_free( void* ptr )
{
#ifdef TRACY_USE_RPMALLOC
#if defined TRACY_HAS_CUSTOM_ALLOCATOR
InitAllocator();
PlatformFree( ptr );
#elif defined TRACY_USE_RPMALLOC
InitAllocator();
rpfree( ptr );
#else
@@ -50,7 +64,9 @@ static inline void tracy_free( void* ptr )
static inline void tracy_free_fast( void* ptr )
{
#ifdef TRACY_USE_RPMALLOC
#if defined TRACY_HAS_CUSTOM_ALLOCATOR
PlatformFree( ptr );
#elif defined TRACY_USE_RPMALLOC
rpfree( ptr );
#else
free( ptr );
@@ -59,7 +75,10 @@ static inline void tracy_free_fast( void* ptr )
static inline void* tracy_realloc( void* ptr, size_t size )
{
#ifdef TRACY_USE_RPMALLOC
#if defined TRACY_HAS_CUSTOM_ALLOCATOR
InitAllocator();
return PlatformRealloc( ptr, size );
#elif defined TRACY_USE_RPMALLOC
InitAllocator();
return rprealloc( ptr, size );
#else

View File

@@ -51,6 +51,10 @@
#include "TracySystem.hpp"
#ifdef TRACY_PLATFORM_HEADER
# include TRACY_PLATFORM_HEADER
#endif
#if defined _WIN32
extern "C" typedef HRESULT (WINAPI *t_SetThreadDescription)( HANDLE, PCWSTR );
extern "C" typedef HRESULT (WINAPI *t_GetThreadDescription)( HANDLE, PWSTR* );
@@ -69,7 +73,9 @@ namespace detail
TRACY_API uint32_t GetThreadHandleImpl()
{
#if defined _WIN32
#if defined TRACY_HAS_CUSTOM_THREAD_ID
return PlatformGetThreadId();
#elif defined _WIN32
static_assert( sizeof( decltype( GetCurrentThreadId() ) ) <= sizeof( uint32_t ), "Thread handle too big to fit in protocol" );
return uint32_t( GetCurrentThreadId() );
#elif defined __APPLE__
@@ -341,7 +347,9 @@ TRACY_API const char* GetEnvVar( const char* name )
TRACY_API const char* GetUserLogin()
{
#if defined _WIN32
#if defined TRACY_HAS_CUSTOM_USER_INFO
return PlatformGetUserLogin();
#elif defined _WIN32
# if defined TRACY_WIN32_NO_DESKTOP
return "(?)";
# else
@@ -363,7 +371,9 @@ TRACY_API const char* GetUserLogin()
TRACY_API const char* GetUserFullName()
{
#if defined _WIN32
#if defined TRACY_HAS_CUSTOM_USER_INFO
return PlatformGetUserFullName();
#elif defined _WIN32
static char buf[1024];
ULONG size = sizeof( buf );
if( GetUserNameExA( NameDisplay, buf, &size ) ) return buf;