tracy/client/TracyProfiler.cpp

496 lines
14 KiB
C++
Raw Normal View History

#ifdef TRACY_ENABLE
2017-09-14 17:25:16 +00:00
#ifdef _MSC_VER
# include <winsock2.h>
2017-10-03 21:17:16 +00:00
# include <windows.h>
2017-09-14 17:25:16 +00:00
#else
# include <sys/time.h>
#endif
2017-10-03 21:17:16 +00:00
#ifdef _GNU_SOURCE
# include <errno.h>
#endif
2017-09-23 19:33:05 +00:00
#include <atomic>
2017-09-10 15:43:56 +00:00
#include <assert.h>
2017-09-23 19:09:46 +00:00
#include <chrono>
2017-09-14 17:25:16 +00:00
#include <limits>
2017-09-21 22:36:36 +00:00
#include <memory>
2017-09-19 00:19:20 +00:00
#include <string.h>
2017-09-10 15:43:56 +00:00
#include "../common/TracyProtocol.hpp"
2017-09-11 20:51:47 +00:00
#include "../common/TracySocket.hpp"
#include "../common/TracySystem.hpp"
2017-10-14 14:59:43 +00:00
#include "tracy_rpmalloc.hpp"
2017-09-24 14:02:09 +00:00
#include "TracyScoped.hpp"
2017-09-10 15:43:56 +00:00
#include "TracyProfiler.hpp"
#include "TracyThread.hpp"
2017-09-10 15:43:56 +00:00
#ifdef __GNUC__
#define init_order( val ) __attribute__ ((init_priority(val)))
#else
#define init_order(x)
#endif
2017-09-10 15:43:56 +00:00
namespace tracy
{
2017-10-14 14:59:43 +00:00
struct RPMallocInit
{
RPMallocInit() { rpmalloc_initialize(); }
};
struct RPMallocThreadInit
{
RPMallocThreadInit() { rpmalloc_thread_initialize(); }
};
2017-10-16 23:07:34 +00:00
struct InitTimeWrapper
{
int64_t val;
};
2017-10-03 21:17:16 +00:00
static const char* GetProcessName()
{
#if defined _MSC_VER
2017-10-03 21:17:16 +00:00
static char buf[_MAX_PATH];
GetModuleFileNameA( nullptr, buf, _MAX_PATH );
const char* ptr = buf;
while( *ptr != '\0' ) ptr++;
while( ptr > buf && *ptr != '\\' && *ptr != '/' ) ptr--;
if( ptr > buf ) ptr++;
return ptr;
#elif defined _GNU_SOURCE
return program_invocation_short_name;
#else
return "unknown";
#endif
}
2017-09-24 13:59:53 +00:00
enum { QueuePrealloc = 256 * 1024 };
2017-10-16 22:36:15 +00:00
// MSVC static initialization order solution. gcc/clang uses init_order() to avoid all this.
2017-09-10 15:43:56 +00:00
static Profiler* s_instance = nullptr;
static Thread* s_thread = nullptr;
2017-10-16 23:07:34 +00:00
static unsigned int __dontcare_cpu;
2017-09-10 15:43:56 +00:00
2017-10-16 22:36:15 +00:00
// 1a. But s_queue is needed for initialization of variables in point 2.
extern moodycamel::ConcurrentQueue<QueueItem> s_queue;
2017-10-16 23:07:34 +00:00
static thread_local RPMallocThreadInit init_order(105) s_rpmalloc_thread_init;
2017-10-16 22:36:15 +00:00
// 2. If these variables would be in the .CRT$XCB section, they would be initialized only in main thread.
2017-10-16 23:07:34 +00:00
static thread_local moodycamel::ProducerToken init_order(106) s_token_detail( s_queue );
thread_local ProducerWrapper init_order(107) s_token { s_queue.get_explicit_producer( s_token_detail ) };
2017-10-16 22:36:15 +00:00
#ifdef _MSC_VER
// 1. Initialize these static variables before all other variables.
# pragma warning( disable : 4075 )
# pragma init_seg( ".CRT$XCB" )
#endif
2017-10-16 23:07:34 +00:00
static InitTimeWrapper init_order(101) s_initTime { Profiler::GetTime( __dontcare_cpu ) };
static RPMallocInit init_order(102) s_rpmalloc_init;
moodycamel::ConcurrentQueue<QueueItem> init_order(103) s_queue( QueuePrealloc );
static Profiler init_order(104) s_profiler;
2017-10-16 22:36:15 +00:00
2017-09-10 15:43:56 +00:00
Profiler::Profiler()
: m_timeBegin( 0 )
, m_mainThread( GetThreadHandle() )
, m_epoch( std::chrono::duration_cast<std::chrono::seconds>( std::chrono::system_clock::now().time_since_epoch() ).count() )
2017-09-10 18:14:16 +00:00
, m_shutdown( false )
2017-10-18 17:49:17 +00:00
, m_sock( nullptr )
, m_stream( LZ4_createStream() )
, m_buffer( (char*)tracy_malloc( TargetFrameSize*3 ) )
, m_bufferOffset( 0 )
2017-09-10 15:43:56 +00:00
{
assert( !s_instance );
s_instance = this;
2017-10-16 22:36:15 +00:00
#ifdef _MSC_VER
// 3. But these variables need to be initialized in main thread within the .CRT$XCB section. Do it here.
s_token_detail = moodycamel::ProducerToken( s_queue );
s_token = ProducerWrapper { s_queue.get_explicit_producer( s_token_detail ) };
#endif
2017-09-23 19:33:05 +00:00
CalibrateTimer();
2017-09-24 14:02:09 +00:00
CalibrateDelay();
2017-09-23 19:33:05 +00:00
s_thread = (Thread*)tracy_malloc( sizeof( Thread ) );
new(s_thread) Thread( LaunchWorker, this );
SetThreadName( s_thread->Handle(), "Tracy Profiler" );
uint32_t cpu;
m_timeBegin.store( GetTime( cpu ), std::memory_order_relaxed );
2017-09-10 15:43:56 +00:00
}
Profiler::~Profiler()
{
m_shutdown.store( true, std::memory_order_relaxed );
s_thread->~Thread();
tracy_free( s_thread );
tracy_free( m_buffer );
LZ4_freeStream( m_stream );
2017-10-18 17:49:17 +00:00
if( m_sock )
{
m_sock->~Socket();
tracy_free( m_sock );
}
2017-09-10 15:43:56 +00:00
assert( s_instance );
s_instance = nullptr;
}
bool Profiler::ShouldExit()
{
return s_instance->m_shutdown.load( std::memory_order_relaxed );
}
2017-10-18 16:48:51 +00:00
enum { BulkSize = TargetFrameSize / QueueItemSize };
2017-09-10 15:43:56 +00:00
void Profiler::Worker()
{
const auto procname = GetProcessName();
const auto pnsz = std::min<size_t>( strlen( procname ), WelcomeMessageProgramNameSize - 1 );
while( m_timeBegin.load( std::memory_order_relaxed ) == 0 ) std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
WelcomeMessage welcome;
#ifdef TRACY_DISABLE_LZ4
2017-10-18 16:50:42 +00:00
// notify client that lz4 compression is disabled
welcome.lz4 = 0;
#else
welcome.lz4 = 1;
#endif
welcome.timerMul = m_timerMul;
welcome.initBegin = s_initTime.val;
welcome.initEnd = m_timeBegin.load( std::memory_order_relaxed );
welcome.delay = m_delay;
welcome.resolution = m_resolution;
welcome.epoch = m_epoch;
memcpy( welcome.programName, procname, pnsz );
memset( welcome.programName + pnsz, 0, WelcomeMessageProgramNameSize - pnsz );
moodycamel::ConsumerToken token( s_queue );
2017-09-11 20:51:47 +00:00
ListenSocket listen;
listen.Listen( "8086", 8 );
2017-09-10 15:43:56 +00:00
for(;;)
{
2017-09-11 20:51:47 +00:00
for(;;)
{
2017-10-18 18:01:12 +00:00
#ifndef TRACY_NO_EXIT
if( ShouldExit() ) return;
2017-10-18 18:01:12 +00:00
#endif
m_sock = listen.Accept();
if( m_sock ) break;
}
2017-09-11 20:51:47 +00:00
m_sock->Send( &welcome, sizeof( welcome ) );
LZ4_resetStream( m_stream );
2017-09-11 20:51:47 +00:00
for(;;)
{
2017-10-18 16:48:51 +00:00
const auto status = Dequeue( token );
if( status == ConnectionLost )
2017-09-11 20:51:47 +00:00
{
2017-10-18 16:48:51 +00:00
break;
2017-09-11 20:51:47 +00:00
}
2017-10-18 16:48:51 +00:00
else if( status == QueueEmpty )
2017-09-11 20:51:47 +00:00
{
2017-10-18 16:48:51 +00:00
if( ShouldExit() ) break;
2017-09-11 20:51:47 +00:00
std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
}
2017-09-14 17:25:16 +00:00
while( m_sock->HasData() )
{
if( !HandleServerQuery() ) break;
2017-09-14 17:25:16 +00:00
}
}
2017-10-18 16:48:51 +00:00
if( ShouldExit() ) break;
}
QueueItem terminate;
terminate.hdr.type = QueueType::Terminate;
if( !SendData( (const char*)&terminate, 1 ) ) return;
for(;;)
{
if( m_sock->HasData() )
{
while( m_sock->HasData() ) if( !HandleServerQuery() ) return;
while( Dequeue( token ) == Success ) {}
}
else
{
std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
}
}
}
Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token )
{
QueueItem item[BulkSize];
const auto sz = s_queue.try_dequeue_bulk( token, item, BulkSize );
if( sz > 0 )
{
auto buf = m_buffer + m_bufferOffset;
auto ptr = buf;
for( size_t i=0; i<sz; i++ )
{
const auto dsz = QueueDataSize[item[i].hdr.idx];
memcpy( ptr, item+i, dsz );
ptr += dsz;
}
if( !SendData( buf, ptr - buf ) ) return ConnectionLost;
m_bufferOffset += int( ptr - buf );
if( m_bufferOffset > TargetFrameSize * 2 ) m_bufferOffset = 0;
}
else
{
return QueueEmpty;
2017-09-10 15:43:56 +00:00
}
2017-10-18 16:48:51 +00:00
return Success;
2017-09-10 15:43:56 +00:00
}
bool Profiler::SendData( const char* data, size_t len )
{
#ifdef TRACY_DISABLE_LZ4
2017-10-16 18:42:53 +00:00
if( m_sock->Send( data, (int)len ) == -1 ) return false;
#else
char lz4[LZ4Size + sizeof( lz4sz_t )];
2017-10-16 22:25:32 +00:00
const lz4sz_t lz4sz = LZ4_compress_fast_continue( m_stream, data, lz4 + sizeof( lz4sz_t ), (int)len, LZ4Size, 1 );
memcpy( lz4, &lz4sz, sizeof( lz4sz ) );
if( m_sock->Send( lz4, lz4sz + sizeof( lz4sz_t ) ) == -1 ) return false;
#endif
return true;
}
bool Profiler::SendString( uint64_t str, const char* ptr, QueueType type )
{
2017-10-14 12:28:04 +00:00
assert( type == QueueType::StringData || type == QueueType::ThreadName || type == QueueType::CustomStringData || type == QueueType::PlotName || type == QueueType::MessageData );
2017-10-03 14:41:32 +00:00
QueueItem item;
item.hdr.type = type;
item.stringTransfer.ptr = str;
const auto isz = QueueDataSize[item.hdr.idx];
auto buf = m_buffer + m_bufferOffset;
2017-10-03 14:41:32 +00:00
memcpy( buf, &item, isz );
auto len = strlen( ptr );
2017-10-03 14:41:32 +00:00
assert( len < TargetFrameSize - isz - sizeof( uint16_t ) );
assert( len <= std::numeric_limits<uint16_t>::max() );
2017-10-16 18:42:53 +00:00
auto l16 = uint16_t( len );
2017-10-03 14:41:32 +00:00
memcpy( buf + isz, &l16, sizeof( l16 ) );
memcpy( buf + isz + sizeof( l16 ), ptr, l16 );
2017-10-16 18:42:53 +00:00
m_bufferOffset += int( isz + sizeof( l16 ) + l16 );
if( m_bufferOffset > TargetFrameSize * 2 ) m_bufferOffset = 0;
2017-10-03 14:41:32 +00:00
return SendData( buf, isz + sizeof( l16 ) + l16 );
}
void Profiler::SendSourceLocation( uint64_t ptr )
{
auto srcloc = (const SourceLocation*)ptr;
QueueItem item;
item.hdr.type = QueueType::SourceLocation;
2017-10-03 14:41:32 +00:00
item.srcloc.ptr = ptr;
item.srcloc.file = (uint64_t)srcloc->file;
item.srcloc.function = (uint64_t)srcloc->function;
item.srcloc.line = srcloc->line;
item.srcloc.r = ( srcloc->color ) & 0xFF;
item.srcloc.g = ( srcloc->color >> 8 ) & 0xFF;
item.srcloc.b = ( srcloc->color >> 16 ) & 0xFF;
2017-10-10 23:44:35 +00:00
s_token.ptr->enqueue<moodycamel::CanAlloc>( std::move( item ) );
}
2017-10-17 20:02:47 +00:00
static bool DontExit() { return false; }
bool Profiler::HandleServerQuery()
{
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 10000;
uint8_t type;
2017-10-17 20:02:47 +00:00
if( !m_sock->Read( &type, sizeof( type ), &tv, DontExit ) ) return false;
uint64_t ptr;
2017-10-17 20:02:47 +00:00
if( !m_sock->Read( &ptr, sizeof( ptr ), &tv, DontExit ) ) return false;
switch( type )
{
case ServerQueryString:
SendString( ptr, (const char*)ptr, QueueType::StringData );
break;
2017-09-21 23:55:02 +00:00
case ServerQueryThreadString:
2017-09-22 23:38:26 +00:00
if( ptr == m_mainThread )
{
SendString( ptr, "Main thread", QueueType::ThreadName );
}
else
{
SendString( ptr, GetThreadName( ptr ), QueueType::ThreadName );
}
2017-09-21 23:55:02 +00:00
break;
2017-09-27 00:18:17 +00:00
case ServerQueryCustomString:
SendString( ptr, (const char*)ptr, QueueType::CustomStringData );
tracy_free( (void*)ptr );
2017-09-27 00:18:17 +00:00
break;
case ServerQuerySourceLocation:
SendSourceLocation( ptr );
break;
2017-10-13 01:36:59 +00:00
case ServerQueryPlotName:
SendString( ptr, (const char*)ptr, QueueType::PlotName );
break;
2017-10-14 12:28:04 +00:00
case ServerQueryMessage:
SendString( ptr, (const char*)ptr, QueueType::MessageData );
tracy_free( (void*)ptr );
2017-10-14 12:28:04 +00:00
break;
2017-10-15 11:06:49 +00:00
case ServerQueryMessageLiteral:
SendString( ptr, (const char*)ptr, QueueType::MessageData );
break;
2017-10-18 16:48:51 +00:00
case ServerQueryTerminate:
return false;
default:
assert( false );
break;
}
return true;
}
2017-09-23 19:33:05 +00:00
void Profiler::CalibrateTimer()
{
2017-10-03 13:35:43 +00:00
#ifdef TRACY_RDTSCP_SUPPORTED
uint32_t cpu;
2017-09-23 19:33:05 +00:00
std::atomic_signal_fence( std::memory_order_acq_rel );
const auto t0 = std::chrono::high_resolution_clock::now();
2017-10-03 13:35:43 +00:00
const auto r0 = tracy_rdtscp( cpu );
2017-09-23 19:33:05 +00:00
std::atomic_signal_fence( std::memory_order_acq_rel );
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
std::atomic_signal_fence( std::memory_order_acq_rel );
const auto t1 = std::chrono::high_resolution_clock::now();
2017-10-03 13:35:43 +00:00
const auto r1 = tracy_rdtscp( cpu );
2017-09-23 19:33:05 +00:00
std::atomic_signal_fence( std::memory_order_acq_rel );
const auto dt = std::chrono::duration_cast<std::chrono::nanoseconds>( t1 - t0 ).count();
const auto dr = r1 - r0;
m_timerMul = double( dt ) / double( dr );
#else
m_timerMul = 1.;
2017-09-23 19:33:05 +00:00
#endif
}
2017-09-24 14:02:09 +00:00
class FakeZone
{
public:
2017-10-01 15:42:22 +00:00
FakeZone( const SourceLocation* srcloc ) : m_id( (uint64_t)srcloc ) {}
2017-09-24 14:02:09 +00:00
~FakeZone() {}
private:
2017-10-01 15:42:22 +00:00
volatile uint64_t m_id;
2017-09-24 14:02:09 +00:00
};
void Profiler::CalibrateDelay()
{
enum { Iterations = 50000 };
enum { Events = Iterations * 2 }; // start + end
static_assert( Events * 2 < QueuePrealloc, "Delay calibration loop will allocate memory in queue" );
uint32_t cpu;
2017-10-10 23:04:21 +00:00
moodycamel::ProducerToken ptoken_detail( s_queue );
moodycamel::ConcurrentQueue<QueueItem>::ExplicitProducer* ptoken = s_queue.get_explicit_producer( ptoken_detail );
2017-09-24 14:02:09 +00:00
for( int i=0; i<Iterations; i++ )
{
static const tracy::SourceLocation __tracy_source_location { __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 };
{
2017-10-03 12:50:55 +00:00
Magic magic;
2017-10-10 23:27:22 +00:00
auto& tail = ptoken->get_tail_index();
2017-10-10 23:04:21 +00:00
auto item = ptoken->enqueue_begin<moodycamel::CanAlloc>( magic );
item->hdr.type = QueueType::ZoneBegin;
2017-10-03 14:41:32 +00:00
item->zoneBegin.thread = GetThreadHandle();
2017-10-01 17:11:01 +00:00
item->zoneBegin.time = GetTime( item->zoneBegin.cpu );
item->zoneBegin.srcloc = (uint64_t)&__tracy_source_location;
2017-10-10 23:27:22 +00:00
tail.store( magic + 1, std::memory_order_release );
}
{
2017-10-03 12:50:55 +00:00
Magic magic;
2017-10-10 23:27:22 +00:00
auto& tail = ptoken->get_tail_index();
2017-10-10 23:04:21 +00:00
auto item = ptoken->enqueue_begin<moodycamel::CanAlloc>( magic );
item->hdr.type = QueueType::ZoneEnd;
2017-10-03 14:41:32 +00:00
item->zoneEnd.thread = 0;
2017-10-01 17:11:01 +00:00
item->zoneEnd.time = GetTime( item->zoneEnd.cpu );
2017-10-10 23:27:22 +00:00
tail.store( magic + 1, std::memory_order_release );
}
2017-09-24 14:02:09 +00:00
}
2017-10-01 17:11:01 +00:00
const auto f0 = GetTime( cpu );
2017-09-24 14:02:09 +00:00
for( int i=0; i<Iterations; i++ )
{
2017-10-16 18:42:53 +00:00
static const tracy::SourceLocation __tracy_source_location { __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 };
FakeZone ___tracy_scoped_zone( &__tracy_source_location );
2017-09-24 14:02:09 +00:00
}
2017-10-01 17:11:01 +00:00
const auto t0 = GetTime( cpu );
2017-09-24 14:02:09 +00:00
for( int i=0; i<Iterations; i++ )
{
static const tracy::SourceLocation __tracy_source_location { __FUNCTION__, __FILE__, (uint32_t)__LINE__, 0 };
{
2017-10-03 12:50:55 +00:00
Magic magic;
2017-10-10 23:27:22 +00:00
auto& tail = ptoken->get_tail_index();
2017-10-10 23:04:21 +00:00
auto item = ptoken->enqueue_begin<moodycamel::CanAlloc>( magic );
item->hdr.type = QueueType::ZoneBegin;
2017-10-03 14:41:32 +00:00
item->zoneBegin.thread = GetThreadHandle();
2017-10-01 17:11:01 +00:00
item->zoneBegin.time = GetTime( item->zoneBegin.cpu );
item->zoneBegin.srcloc = (uint64_t)&__tracy_source_location;
2017-10-10 23:27:22 +00:00
tail.store( magic + 1, std::memory_order_release );
}
{
2017-10-03 12:50:55 +00:00
Magic magic;
2017-10-10 23:27:22 +00:00
auto& tail = ptoken->get_tail_index();
2017-10-10 23:04:21 +00:00
auto item = ptoken->enqueue_begin<moodycamel::CanAlloc>( magic );
item->hdr.type = QueueType::ZoneEnd;
2017-10-03 14:41:32 +00:00
item->zoneEnd.thread = 0;
2017-10-01 17:11:01 +00:00
item->zoneEnd.time = GetTime( item->zoneEnd.cpu );
2017-10-10 23:27:22 +00:00
tail.store( magic + 1, std::memory_order_release );
}
2017-09-24 14:02:09 +00:00
}
2017-10-01 17:11:01 +00:00
const auto t1 = GetTime( cpu );
2017-09-24 14:02:09 +00:00
const auto dt = t1 - t0;
const auto df = t0 - f0;
m_delay = ( dt - df ) / Events;
2017-10-16 18:42:53 +00:00
auto mindiff = std::numeric_limits<int64_t>::max();
2017-09-29 16:29:39 +00:00
for( int i=0; i<Iterations * 10; i++ )
{
2017-10-01 17:11:01 +00:00
const auto t0 = GetTime( cpu );
const auto t1 = GetTime( cpu );
2017-09-29 16:29:39 +00:00
const auto dt = t1 - t0;
if( dt > 0 && dt < mindiff ) mindiff = dt;
}
m_resolution = mindiff;
2017-09-24 14:02:09 +00:00
enum { Bulk = 1000 };
moodycamel::ConsumerToken token( s_queue );
int left = Events * 2;
QueueItem item[Bulk];
while( left != 0 )
{
const auto sz = s_queue.try_dequeue_bulk( token, item, std::min( left, (int)Bulk ) );
assert( sz > 0 );
2017-10-16 18:42:53 +00:00
left -= (int)sz;
2017-09-24 14:02:09 +00:00
}
}
2017-09-10 15:43:56 +00:00
}
#endif