mirror of
https://github.com/wolfpld/tracy.git
synced 2026-10-02 15:05:26 +00:00
initial prototype of system tracing for Apple devices
This commit is contained in:
@@ -10,6 +10,8 @@
|
||||
# define TRACY_SAMPLING_HZ 8000
|
||||
# elif defined __linux__
|
||||
# define TRACY_SAMPLING_HZ 10000
|
||||
# elif defined __APPLE__
|
||||
# define TRACY_SAMPLING_HZ 1000
|
||||
# endif
|
||||
#endif
|
||||
|
||||
@@ -1581,6 +1583,10 @@ void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const ch
|
||||
|
||||
}
|
||||
|
||||
# elif defined __APPLE__
|
||||
|
||||
# include "apple/TracyMach.cpp"
|
||||
|
||||
# endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#ifndef __TRACYSYSTRACE_HPP__
|
||||
#define __TRACYSYSTRACE_HPP__
|
||||
|
||||
#if !defined TRACY_NO_SYSTEM_TRACING && ( defined _WIN32 || defined __linux__ )
|
||||
# include "../common/TracyWinFamily.hpp"
|
||||
# if !defined TRACY_WIN32_NO_DESKTOP
|
||||
#if !defined TRACY_NO_SYSTEM_TRACING
|
||||
# if defined _WIN32 || defined __linux__
|
||||
# include "../common/TracyWinFamily.hpp"
|
||||
# if !defined TRACY_WIN32_NO_DESKTOP
|
||||
# define TRACY_HAS_SYSTEM_TRACING
|
||||
# endif
|
||||
# elif defined __APPLE__
|
||||
# define TRACY_HAS_SYSTEM_TRACING
|
||||
# endif
|
||||
#endif
|
||||
|
||||
253
public/client/apple/TracyMach.cpp
Normal file
253
public/client/apple/TracyMach.cpp
Normal file
@@ -0,0 +1,253 @@
|
||||
// Included by TracySysTrace.cpp
|
||||
|
||||
#include <atomic>
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#include "../TracyProfiler.hpp"
|
||||
#include "../TracyStringHelpers.hpp"
|
||||
#include "../TracyThread.hpp"
|
||||
|
||||
#include "../../tracy/Tracy.hpp"
|
||||
|
||||
namespace tracy
|
||||
{
|
||||
|
||||
static std::atomic<bool> s_traceActive { false };
|
||||
static mach_timebase_info_data_t s_timebase;
|
||||
static int s_samplingHz;
|
||||
static pthread_t s_watchdogThread;
|
||||
|
||||
static void SysTraceEmitCallstackSample( uint32_t threadId, int64_t timestamp, const uint64_t* frames, int depth )
|
||||
{
|
||||
#ifdef TRACY_ON_DEMAND
|
||||
if( !GetProfiler().IsConnected() ) return;
|
||||
#endif
|
||||
|
||||
auto trace = (uint64_t*)tracy_malloc( ( 1 + depth ) * sizeof( uint64_t ) );
|
||||
trace[0] = (uint64_t)depth;
|
||||
memcpy( trace + 1, frames, depth * sizeof( uint64_t ) );
|
||||
|
||||
TracyLfqPrepare( QueueType::CallstackSample );
|
||||
MemWrite( &item->callstackSampleFat.time, timestamp );
|
||||
MemWrite( &item->callstackSampleFat.thread, threadId );
|
||||
MemWrite( &item->callstackSampleFat.ptr, (uint64_t)trace );
|
||||
TracyLfqCommit;
|
||||
}
|
||||
|
||||
static int SysTraceBacktrace( uint64_t* frames, int maxDepth, uint64_t pc, uint64_t fp )
|
||||
{
|
||||
int depth = 0;
|
||||
frames[depth++] = pc;
|
||||
|
||||
// NOTE: frame-pointer walk for now... It should be fine since the ABI
|
||||
// mandates it on ARM64 (and on x64 Apple clang preserves it by default)
|
||||
// ALSO: on ARM64 with PAC, return addresses on the stack may be signed!
|
||||
// (can strip auth bits via arm_thread_state64_get_lr() when needed)
|
||||
auto framePtr = (const uint64_t*)fp;
|
||||
while( framePtr && depth < maxDepth )
|
||||
{
|
||||
if( (uintptr_t)framePtr & (sizeof(uint64_t) - 1) ) break; // misaligned — stop walk
|
||||
// [framePtr + 0] = saved frame pointer (previous frame)
|
||||
// [framePtr + 1] = return address (PC of caller)
|
||||
frames[depth++] = framePtr[1];
|
||||
framePtr = (const uint64_t*)framePtr[0];
|
||||
}
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
static void SysTraceSampleThread( mach_port_t tid )
|
||||
{
|
||||
const int64_t t0 = Profiler::GetTime();
|
||||
if( thread_suspend( tid ) != KERN_SUCCESS ) return;
|
||||
const int64_t t1 = Profiler::GetTime();
|
||||
const int64_t timestamp = t0 + ( t1 - t0 ) / 2;
|
||||
|
||||
#if defined(__aarch64__)
|
||||
arm_thread_state64_t state;
|
||||
mach_msg_type_number_t stateCount = ARM_THREAD_STATE64_COUNT;
|
||||
const kern_return_t kr = thread_get_state( tid, ARM_THREAD_STATE64, (thread_state_t)&state, &stateCount );
|
||||
#elif defined(__x86_64__)
|
||||
x86_thread_state64_t state;
|
||||
mach_msg_type_number_t stateCount = x86_THREAD_STATE64_COUNT;
|
||||
const kern_return_t kr = thread_get_state( tid, x86_THREAD_STATE64, (thread_state_t)&state, &stateCount );
|
||||
#else
|
||||
#error "unsupported architecture"
|
||||
#endif
|
||||
|
||||
if( kr != KERN_SUCCESS )
|
||||
{
|
||||
thread_resume( tid );
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int MaxDepth = 192;
|
||||
uint64_t frames [MaxDepth];
|
||||
|
||||
#if defined(__aarch64__)
|
||||
const int depth = SysTraceBacktrace( frames, MaxDepth, state.__pc, state.__fp );
|
||||
#elif defined(__x86_64__)
|
||||
const int depth = SysTraceBacktrace( frames, MaxDepth, state.__rip, state.__rbp );
|
||||
#endif
|
||||
|
||||
thread_resume( tid );
|
||||
|
||||
SysTraceEmitCallstackSample( (uint32_t)tid, timestamp, frames, depth );
|
||||
}
|
||||
|
||||
static void SysTraceWait( uint64_t deadline )
|
||||
{
|
||||
ZoneScopedC(tracy::Color::DimGray);
|
||||
mach_wait_until( deadline );
|
||||
}
|
||||
|
||||
static uint64_t SysTraceRngInit()
|
||||
{
|
||||
uint64_t seed = mach_absolute_time();
|
||||
seed ^= (uint64_t)(uintptr_t)&seed;
|
||||
return seed;
|
||||
}
|
||||
|
||||
static uint32_t SysTraceRngNext( uint64_t& rng, uint32_t range )
|
||||
{
|
||||
rng ^= rng << 13; rng ^= rng >> 7; rng ^= rng << 17;
|
||||
return (uint32_t)( rng % range );
|
||||
}
|
||||
|
||||
static void SysTraceWatch( mach_port_t selfThread )
|
||||
{
|
||||
const uint64_t periodNs = 1'000'000'000ULL / s_samplingHz;
|
||||
const uint64_t periodMach = periodNs * s_timebase.denom / s_timebase.numer;
|
||||
|
||||
std::vector<mach_port_t> runningThreads;
|
||||
std::vector<mach_port_t> waitingThreads;
|
||||
|
||||
uint64_t rng = SysTraceRngInit();
|
||||
|
||||
uint64_t deadline = mach_absolute_time();
|
||||
while( s_traceActive.load( std::memory_order_relaxed ) )
|
||||
{
|
||||
SysTraceWait(deadline);
|
||||
deadline = mach_absolute_time() + periodMach;
|
||||
|
||||
ZoneScoped;
|
||||
|
||||
#ifdef TRACY_ON_DEMAND
|
||||
if( !GetProfiler().IsConnected() ) continue;
|
||||
#endif
|
||||
|
||||
thread_act_array_t threads;
|
||||
mach_msg_type_number_t threadCount;
|
||||
if( task_threads( mach_task_self(), &threads, &threadCount ) != KERN_SUCCESS ) continue;
|
||||
|
||||
runningThreads.clear();
|
||||
waitingThreads.clear();
|
||||
|
||||
for( mach_msg_type_number_t i = 0; i < threadCount; i++ )
|
||||
{
|
||||
const mach_port_t tid = threads[i];
|
||||
if( tid == selfThread ) continue;
|
||||
|
||||
thread_basic_info_data_t info;
|
||||
mach_msg_type_number_t infoCount = THREAD_BASIC_INFO_COUNT;
|
||||
if( thread_info( tid, THREAD_BASIC_INFO, (thread_info_t)&info, &infoCount ) != KERN_SUCCESS ) continue;
|
||||
if( info.flags & TH_FLAGS_IDLE ) continue; // kernel idle thread, not user code
|
||||
|
||||
if( info.run_state == TH_STATE_RUNNING )
|
||||
runningThreads.push_back( tid );
|
||||
else
|
||||
waitingThreads.push_back( tid );
|
||||
}
|
||||
|
||||
for( const mach_port_t tid : runningThreads )
|
||||
SysTraceSampleThread( tid );
|
||||
|
||||
while( !waitingThreads.empty() )
|
||||
{
|
||||
if( mach_absolute_time() >= deadline ) break;
|
||||
const uint32_t idx = SysTraceRngNext( rng, (uint32_t)waitingThreads.size() );
|
||||
SysTraceSampleThread( waitingThreads[idx] );
|
||||
std::swap( waitingThreads[idx], waitingThreads.back() );
|
||||
waitingThreads.pop_back();
|
||||
}
|
||||
|
||||
for( mach_msg_type_number_t i = 0; i < threadCount; i++ )
|
||||
mach_port_deallocate( mach_task_self(), threads[i] );
|
||||
vm_deallocate( mach_task_self(), (vm_address_t)threads, sizeof(thread_t) * threadCount );
|
||||
}
|
||||
|
||||
ZoneScopedNC("OUT", tracy::Color::Crimson);
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds(100) );
|
||||
}
|
||||
|
||||
void SysTraceWorker( void* )
|
||||
{
|
||||
ThreadExitHandler threadExitHandler;
|
||||
SetThreadName( "Tracy Mach Watchdog" );
|
||||
InitRpmalloc();
|
||||
const mach_port_t selfThread = mach_thread_self();
|
||||
SysTraceWatch( selfThread );
|
||||
mach_port_deallocate( mach_task_self(), selfThread );
|
||||
}
|
||||
|
||||
bool SysTraceStart( int64_t& samplingPeriod )
|
||||
{
|
||||
bool expected = false;
|
||||
if( !s_traceActive.compare_exchange_strong( expected, true, std::memory_order_relaxed ) )
|
||||
return false;
|
||||
|
||||
mach_timebase_info( &s_timebase );
|
||||
s_samplingHz = GetSamplingFrequency();
|
||||
samplingPeriod = SamplingFrequencyToPeriodNs( s_samplingHz );
|
||||
auto trampoline = []( void* ) -> void* { SysTraceWorker( nullptr ); return nullptr; };
|
||||
if( pthread_create( &s_watchdogThread, nullptr, trampoline, nullptr ) != 0 )
|
||||
{
|
||||
s_traceActive.store( false, std::memory_order_relaxed );
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SysTraceStop()
|
||||
{
|
||||
bool expected = true;
|
||||
if( !s_traceActive.compare_exchange_strong( expected, false, std::memory_order_relaxed ) )
|
||||
return;
|
||||
pthread_join( s_watchdogThread, nullptr );
|
||||
}
|
||||
|
||||
void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const char*& name )
|
||||
{
|
||||
// Resolve pthread handle from the Mach port so we can query the thread name.
|
||||
const mach_port_t mach_tid = (mach_port_t)thread;
|
||||
thread_identifier_info_data_t idInfo;
|
||||
mach_msg_type_number_t idInfoCount = THREAD_IDENTIFIER_INFO_COUNT;
|
||||
if( thread_info( mach_tid, THREAD_IDENTIFIER_INFO, (thread_info_t)&idInfo, &idInfoCount ) == KERN_SUCCESS )
|
||||
{
|
||||
char buf[64] = {};
|
||||
const pthread_t pt = (pthread_t)(uintptr_t)idInfo.thread_handle;
|
||||
if( pt && pthread_getname_np( pt, buf, sizeof( buf ) ) == 0 && buf[0] != '\0' )
|
||||
threadName = CopyString( buf );
|
||||
else
|
||||
threadName = CopyString( "???", 3 );
|
||||
|
||||
TracyLfqPrepare( QueueType::TidToPid );
|
||||
MemWrite( &item->tidToPid.tid, thread );
|
||||
MemWrite( &item->tidToPid.pid, (uint64_t)getpid() );
|
||||
TracyLfqCommit;
|
||||
}
|
||||
else
|
||||
{
|
||||
threadName = CopyString( "???", 3 );
|
||||
}
|
||||
|
||||
name = CopyStringFast( getprogname() );
|
||||
}
|
||||
|
||||
} // namespace tracy
|
||||
Reference in New Issue
Block a user