diff --git a/client/TracyProfiler.cpp b/client/TracyProfiler.cpp index c4bca249..aef00366 100644 --- a/client/TracyProfiler.cpp +++ b/client/TracyProfiler.cpp @@ -1810,6 +1810,22 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token ) MemWrite( &item->gpuTime.gpuTime, dt ); break; } + case QueueType::SysCallEnter: + { + int64_t t = MemRead( &item->sysCallEnter.time ); + int64_t dt = t - m_refTimeCtx; + m_refTimeCtx = t; + MemWrite( &item->sysCallEnter.time, dt ); + break; + } + case QueueType::SysCallExit: + { + int64_t t = MemRead( &item->sysCallExit.time ); + int64_t dt = t - m_refTimeCtx; + m_refTimeCtx = t; + MemWrite( &item->sysCallExit.time, dt ); + break; + } default: assert( false ); break; diff --git a/client/TracySysTrace.cpp b/client/TracySysTrace.cpp index cf88575b..5a67f6ad 100644 --- a/client/TracySysTrace.cpp +++ b/client/TracySysTrace.cpp @@ -54,8 +54,47 @@ struct ReadyThread int8_t reserverd; }; +#ifdef TRACY_SYSCALLS +struct SysCallEnter +{ + // Official MSDN documentation is bullshitting here + // This is corroborated by tracerpt displaying address as 64-bit +#if defined __x86_64__ || defined _M_X64 + uint64_t sysCallAddress; +#else + uint32_t sysCallAddress; +#endif +}; + +struct TraceThread +{ + uint32_t ProcessId; + uint32_t TThreadId; + uint32_t StackBase; + uint32_t StackLimit; + uint32_t UserStackBase; + uint32_t UserStackLimit; + uint32_t Affinity; + uint32_t Win32StartAddr; + uint32_t TebBase; + uint32_t SubProcessTag; + uint8_t BasePriority; + uint8_t PagePriority; + uint8_t IoPriority; + uint8_t ThreadFlags; +}; + uint64_t coreThread[256] = {}; +// We need to know which thread ids are in our process. A hash map would be the best solution here, +// but there is no solution ready to use right now. Instead, let's store a thread bit map. Note that +// this is only an approximation, there are no guarantees about thread ids returned by the kernel, as +// Raymond Chen said on multiple occasions. In the false positive case, we will be reporting system +// calls belonging to some other process, and that's no big deal. +enum { TidCapacity = 8192 }; +uint64_t processThreads[TidCapacity] = {}; +#endif + void WINAPI EventRecordCallback( PEVENT_RECORD record ) { #ifdef TRACY_ON_DEMAND @@ -83,7 +122,9 @@ void WINAPI EventRecordCallback( PEVENT_RECORD record ) MemWrite( &item->contextSwitch.state, cswitch->oldThreadState ); tail.store( magic + 1, std::memory_order_release ); +#ifdef TRACY_SYSCALLS coreThread[cpu] = cswitch->newThreadId; +#endif } else if( hdr.EventDescriptor.Opcode == 50 ) { @@ -99,6 +140,70 @@ void WINAPI EventRecordCallback( PEVENT_RECORD record ) memset( ((char*)&item->threadWakeup.thread)+4, 0, 4 ); tail.store( magic + 1, std::memory_order_release ); } +#ifdef TRACY_SYSCALLS + else if( hdr.EventDescriptor.Opcode == 51 ) + { + const auto cpu = record->BufferContext.ProcessorNumber; + const auto thread = coreThread[cpu]; + + if( thread != 0 ) + { + const auto tid = thread / 4; + const auto entry = ( tid / 64 ) % TidCapacity; + const auto bit = tid % 64; + if( processThreads[entry] & ( 1ull << bit ) ) + { + const auto syscall = (const SysCallEnter*)record->UserData; + uint64_t addr = syscall->sysCallAddress; + + Magic magic; + auto token = GetToken(); + auto& tail = token->get_tail_index(); + auto item = token->enqueue_begin( magic ); + MemWrite( &item->hdr.type, QueueType::SysCallEnter ); + MemWrite( &item->sysCallEnter.time, hdr.TimeStamp.QuadPart ); + MemWrite( &item->sysCallEnter.thread, thread ); + MemWrite( &item->sysCallEnter.address, addr ); + tail.store( magic + 1, std::memory_order_release ); + } + } + } + else if( hdr.EventDescriptor.Opcode == 52 ) + { + const auto cpu = record->BufferContext.ProcessorNumber; + const auto thread = coreThread[cpu]; + + if( thread != 0 ) + { + const auto tid = thread / 4; + const auto entry = ( tid / 64 ) % TidCapacity; + const auto bit = tid % 64; + if( processThreads[entry] & ( 1ull << bit ) ) + { + Magic magic; + auto token = GetToken(); + auto& tail = token->get_tail_index(); + auto item = token->enqueue_begin( magic ); + MemWrite( &item->hdr.type, QueueType::SysCallExit ); + MemWrite( &item->sysCallExit.time, hdr.TimeStamp.QuadPart ); + MemWrite( &item->sysCallExit.thread, thread ); + tail.store( magic + 1, std::memory_order_release ); + } + } + } + else if( hdr.EventDescriptor.Opcode == 1 || hdr.EventDescriptor.Opcode == 3 ) + { + const auto tt = (const TraceThread*)record->UserData; + if( tt->ProcessId == GetProfiler().Pid() && tt->TThreadId != GetProfiler().GetProfilerTid() && tt->TThreadId != GetCurrentThreadId() ) + { + // Thread ids are (currently) multiples of 4, but its not a part of the contract. + const uint32_t tid = tt->TThreadId / 4; + const auto entry = ( tid / 64 ) % TidCapacity; + const auto bit = tid % 64; + processThreads[entry] |= ( 1ull << bit ); + } + } +#endif } bool SysTraceStart() @@ -119,7 +224,11 @@ bool SysTraceStart() const auto psz = sizeof( EVENT_TRACE_PROPERTIES ) + sizeof( KERNEL_LOGGER_NAME ); s_prop = (EVENT_TRACE_PROPERTIES*)tracy_malloc( psz ); memset( s_prop, 0, sizeof( EVENT_TRACE_PROPERTIES ) ); +#ifdef TRACY_SYSCALLS + s_prop->EnableFlags = EVENT_TRACE_FLAG_CSWITCH | EVENT_TRACE_FLAG_DISPATCHER | EVENT_TRACE_FLAG_SYSTEMCALL | EVENT_TRACE_FLAG_THREAD; +#else s_prop->EnableFlags = EVENT_TRACE_FLAG_CSWITCH | EVENT_TRACE_FLAG_DISPATCHER; +#endif s_prop->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; s_prop->Wnode.BufferSize = psz; s_prop->Wnode.Flags = WNODE_FLAG_TRACED_GUID; diff --git a/common/TracyProtocol.hpp b/common/TracyProtocol.hpp index d2742df9..f41ecb2e 100644 --- a/common/TracyProtocol.hpp +++ b/common/TracyProtocol.hpp @@ -9,7 +9,7 @@ namespace tracy { -enum : uint32_t { ProtocolVersion = 21 }; +enum : uint32_t { ProtocolVersion = 22 }; enum : uint32_t { BroadcastVersion = 0 }; using lz4sz_t = uint32_t; diff --git a/common/TracyQueue.hpp b/common/TracyQueue.hpp index c8ab0f99..12e42063 100644 --- a/common/TracyQueue.hpp +++ b/common/TracyQueue.hpp @@ -42,6 +42,8 @@ enum class QueueType : uint8_t ContextSwitch, ThreadWakeup, GpuTime, + SysCallEnter, + SysCallExit, Terminate, KeepAlive, ThreadContext, @@ -330,6 +332,19 @@ struct QueueTidToPid uint64_t pid; }; +struct QueueSysCallEnter +{ + int64_t time; + uint64_t thread; + uint64_t address; +}; + +struct QueueSysCallExit +{ + int64_t time; + uint64_t thread; +}; + struct QueueHeader { union @@ -378,6 +393,8 @@ struct QueueItem QueueContextSwitch contextSwitch; QueueThreadWakeup threadWakeup; QueueTidToPid tidToPid; + QueueSysCallEnter sysCallEnter; + QueueSysCallExit sysCallExit; }; }; #pragma pack() @@ -420,6 +437,8 @@ static const size_t QueueDataSize[] = { sizeof( QueueHeader ) + sizeof( QueueContextSwitch ), sizeof( QueueHeader ) + sizeof( QueueThreadWakeup ), sizeof( QueueHeader ) + sizeof( QueueGpuTime ), + sizeof( QueueHeader ) + sizeof( QueueSysCallEnter ), + sizeof( QueueHeader ) + sizeof( QueueSysCallExit ), // above items must be first sizeof( QueueHeader ), // terminate sizeof( QueueHeader ), // keep alive