diff --git a/public/client/TracySysTrace.cpp b/public/client/TracySysTrace.cpp index 1dd94774..cb1974e3 100644 --- a/public/client/TracySysTrace.cpp +++ b/public/client/TracySysTrace.cpp @@ -52,8 +52,6 @@ static int GetSamplingPeriod() # include # include # include -# include -# include # include # include @@ -61,25 +59,13 @@ static int GetSamplingPeriod() # include "../common/TracySystem.hpp" # include "TracyProfiler.hpp" # include "TracyThread.hpp" +# include "windows/TracyETW.cpp" namespace tracy { -static const GUID PerfInfoGuid = { 0xce1dbfb4, 0x137e, 0x4da6, { 0x87, 0xb0, 0x3f, 0x59, 0xaa, 0x10, 0x2c, 0xbc } }; -static const GUID DxgKrnlGuid = { 0x802ec45a, 0x1e99, 0x4b83, { 0x99, 0x20, 0x87, 0xc9, 0x82, 0x77, 0xba, 0x9d } }; -static const GUID ThreadV2Guid = { 0x3d6fa8d1, 0xfe05, 0x11d0, { 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c } }; - - -static TRACEHANDLE s_traceHandle; -static TRACEHANDLE s_traceHandle2; -static EVENT_TRACE_PROPERTIES* s_prop; static DWORD s_pid; -static EVENT_TRACE_PROPERTIES* s_propVsync; -static TRACEHANDLE s_traceHandleVsync; -static TRACEHANDLE s_traceHandleVsync2; -Thread* s_threadVsync = nullptr; - struct CSwitch { uint32_t newThreadId; @@ -163,7 +149,7 @@ void WINAPI EventRecordCallback( PEVENT_RECORD record ) const auto& hdr = record->EventHeader; switch( hdr.ProviderId.Data1 ) { - case 0x3d6fa8d1: // Thread Guid + case etw::ThreadGuid.Data1: if( hdr.EventDescriptor.Opcode == 36 ) { const auto cswitch = (const CSwitch*)record->UserData; @@ -205,7 +191,7 @@ void WINAPI EventRecordCallback( PEVENT_RECORD record ) TracyLfqCommit; } break; - case 0xdef2fe46: // StackWalk Guid + case etw::StackWalkGuid.Data1: if( hdr.EventDescriptor.Opcode == 32 ) { const auto sw = (const StackWalkEvent*)record->UserData; @@ -226,246 +212,53 @@ void WINAPI EventRecordCallback( PEVENT_RECORD record ) } } break; + case etw::DxgKrnlGuid.Data1: + assert( hdr.EventDescriptor.Id == 0x0011 ); + { + const auto vs = (const VSyncInfo*)record->UserData; + TracyLfqPrepare( QueueType::FrameVsync ); + MemWrite( &item->frameVsync.time, hdr.TimeStamp.QuadPart ); + MemWrite( &item->frameVsync.id, vs->vidPnTargetId ); + TracyLfqCommit; + } + break; default: break; } } -void WINAPI EventRecordCallbackVsync( PEVENT_RECORD record ) -{ -#ifdef TRACY_ON_DEMAND - if( !GetProfiler().IsConnected() ) return; -#endif - - const auto& hdr = record->EventHeader; - - // Check for Lost_Event (6a399ae0-4bc6-4de9-870b-3657f8947e7e) - if( hdr.ProviderId.Data1 == 0x6A399AE0 ) return; - - assert( hdr.ProviderId.Data1 == 0x802EC45A ); - assert( hdr.EventDescriptor.Id == 0x0011 ); - - const auto vs = (const VSyncInfo*)record->UserData; - - TracyLfqPrepare( QueueType::FrameVsync ); - MemWrite( &item->frameVsync.time, hdr.TimeStamp.QuadPart ); - MemWrite( &item->frameVsync.id, vs->vidPnTargetId ); - TracyLfqCommit; -} - -static void SetupVsync() -{ -#if _WIN32_WINNT >= _WIN32_WINNT_WINBLUE && !defined(__MINGW32__) - const auto psz = sizeof( EVENT_TRACE_PROPERTIES ) + MAX_PATH; - s_propVsync = (EVENT_TRACE_PROPERTIES*)tracy_malloc( psz ); - memset( s_propVsync, 0, sizeof( EVENT_TRACE_PROPERTIES ) ); - s_propVsync->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; - s_propVsync->Wnode.BufferSize = psz; -#ifdef TRACY_TIMER_QPC - s_propVsync->Wnode.ClientContext = 1; -#else - s_propVsync->Wnode.ClientContext = 3; -#endif - s_propVsync->LoggerNameOffset = sizeof( EVENT_TRACE_PROPERTIES ); - strcpy( ((char*)s_propVsync) + sizeof( EVENT_TRACE_PROPERTIES ), "TracyVsync" ); - - auto backup = tracy_malloc( psz ); - memcpy( backup, s_propVsync, psz ); - - const auto controlStatus = ControlTraceA( 0, "TracyVsync", s_propVsync, EVENT_TRACE_CONTROL_STOP ); - if( controlStatus != ERROR_SUCCESS && controlStatus != ERROR_WMI_INSTANCE_NOT_FOUND ) - { - tracy_free( backup ); - tracy_free( s_propVsync ); - return; - } - - memcpy( s_propVsync, backup, psz ); - tracy_free( backup ); - - const auto startStatus = StartTraceA( &s_traceHandleVsync, "TracyVsync", s_propVsync ); - if( startStatus != ERROR_SUCCESS ) - { - tracy_free( s_propVsync ); - return; - } - - EVENT_FILTER_EVENT_ID fe = {}; - fe.FilterIn = TRUE; - fe.Count = 1; - fe.Events[0] = 0x0011; // VSyncDPC_Info - - EVENT_FILTER_DESCRIPTOR desc = {}; - desc.Ptr = (ULONGLONG)&fe; - desc.Size = sizeof( fe ); - desc.Type = EVENT_FILTER_TYPE_EVENT_ID; - - ENABLE_TRACE_PARAMETERS params = {}; - params.Version = ENABLE_TRACE_PARAMETERS_VERSION_2; - params.EnableProperty = EVENT_ENABLE_PROPERTY_IGNORE_KEYWORD_0; - params.SourceId = s_propVsync->Wnode.Guid; - params.EnableFilterDesc = &desc; - params.FilterDescCount = 1; - - uint64_t mask = 0x4000000000000001; // Microsoft_Windows_DxgKrnl_Performance | Base - if( EnableTraceEx2( s_traceHandleVsync, &DxgKrnlGuid, EVENT_CONTROL_CODE_ENABLE_PROVIDER, TRACE_LEVEL_INFORMATION, mask, mask, 0, ¶ms ) != ERROR_SUCCESS ) - { - tracy_free( s_propVsync ); - return; - } - - char loggerName[MAX_PATH]; - strcpy( loggerName, "TracyVsync" ); - - EVENT_TRACE_LOGFILEA log = {}; - log.LoggerName = loggerName; - log.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_RAW_TIMESTAMP; - log.EventRecordCallback = EventRecordCallbackVsync; - - s_traceHandleVsync2 = OpenTraceA( &log ); - if( s_traceHandleVsync2 == (TRACEHANDLE)INVALID_HANDLE_VALUE ) - { - CloseTrace( s_traceHandleVsync ); - tracy_free( s_propVsync ); - return; - } - - s_threadVsync = (Thread*)tracy_malloc( sizeof( Thread ) ); - new(s_threadVsync) Thread( [] (void*) { - ThreadExitHandler threadExitHandler; - SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL ); - SetThreadName( "Tracy Vsync" ); - ProcessTrace( &s_traceHandleVsync2, 1, nullptr, nullptr ); - }, nullptr ); -#endif -} - static int GetSamplingInterval() { return GetSamplingPeriod() / 100; } +static etw::Session session = {}; + bool SysTraceStart( int64_t& samplingPeriod ) { if( !_GetThreadDescription ) _GetThreadDescription = (t_GetThreadDescription)GetProcAddress( GetModuleHandleA( "kernel32.dll" ), "GetThreadDescription" ); s_pid = GetCurrentProcessId(); -#if defined _WIN64 - constexpr bool isOs64Bit = true; -#else - BOOL _iswow64; - IsWow64Process( GetCurrentProcess(), &_iswow64 ); - const bool isOs64Bit = _iswow64; -#endif + session = etw::StartPrivateKernelSession(TEXT("TracySysTrace")); + if (session.handle == 0) + return false; - TOKEN_PRIVILEGES priv = {}; - priv.PrivilegeCount = 1; - priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - if( LookupPrivilegeValue( nullptr, SE_SYSTEM_PROFILE_NAME, &priv.Privileges[0].Luid ) == 0 ) return false; - - HANDLE pt; - if( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &pt ) == 0 ) return false; - const auto adjust = AdjustTokenPrivileges( pt, FALSE, &priv, 0, nullptr, nullptr ); - CloseHandle( pt ); - if( adjust == 0 ) return false; - const auto status = GetLastError(); - if( status != ERROR_SUCCESS ) return false; - - if( isOs64Bit ) - { - TRACE_PROFILE_INTERVAL interval = {}; - interval.Interval = GetSamplingInterval(); - const auto intervalStatus = TraceSetInformation( 0, TraceSampledProfileIntervalInfo, &interval, sizeof( interval ) ); - if( intervalStatus != ERROR_SUCCESS ) return false; - samplingPeriod = GetSamplingPeriod(); - } - - 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 ) ); - ULONG flags = 0; #ifndef TRACY_NO_CONTEXT_SWITCH - flags = EVENT_TRACE_FLAG_CSWITCH | EVENT_TRACE_FLAG_DISPATCHER | EVENT_TRACE_FLAG_THREAD; + if (etw::EnableContextSwitchMonitoring(session) != ERROR_SUCCESS) + return etw::StopSession(session), false; #endif -#ifndef TRACY_NO_SAMPLING - if( isOs64Bit ) flags |= EVENT_TRACE_FLAG_PROFILE; -#endif - s_prop->EnableFlags = flags; - s_prop->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; - s_prop->Wnode.BufferSize = psz; - s_prop->Wnode.Flags = WNODE_FLAG_TRACED_GUID; -#ifdef TRACY_TIMER_QPC - s_prop->Wnode.ClientContext = 1; -#else - s_prop->Wnode.ClientContext = 3; -#endif - s_prop->Wnode.Guid = SystemTraceControlGuid; - s_prop->BufferSize = 1024; - s_prop->MinimumBuffers = std::thread::hardware_concurrency() * 4; - s_prop->MaximumBuffers = std::thread::hardware_concurrency() * 6; - s_prop->LoggerNameOffset = sizeof( EVENT_TRACE_PROPERTIES ); - memcpy( ((char*)s_prop) + sizeof( EVENT_TRACE_PROPERTIES ), KERNEL_LOGGER_NAME, sizeof( KERNEL_LOGGER_NAME ) ); - - auto backup = tracy_malloc( psz ); - memcpy( backup, s_prop, psz ); - - const auto controlStatus = ControlTrace( 0, KERNEL_LOGGER_NAME, s_prop, EVENT_TRACE_CONTROL_STOP ); - if( controlStatus != ERROR_SUCCESS && controlStatus != ERROR_WMI_INSTANCE_NOT_FOUND ) - { - tracy_free( backup ); - tracy_free( s_prop ); - return false; - } - - memcpy( s_prop, backup, psz ); - tracy_free( backup ); - - const auto startStatus = StartTrace( &s_traceHandle, KERNEL_LOGGER_NAME, s_prop ); - if( startStatus != ERROR_SUCCESS ) - { - tracy_free( s_prop ); - return false; - } #ifndef TRACY_NO_SAMPLING - if( isOs64Bit ) - { - CLASSIC_EVENT_ID stackId[2] = {}; - stackId[0].EventGuid = PerfInfoGuid; - stackId[0].Type = 46; - stackId[1].EventGuid = ThreadV2Guid; - stackId[1].Type = 36; - const auto stackStatus = TraceSetInformation( s_traceHandle, TraceStackTracingInfo, &stackId, sizeof( stackId ) ); - if( stackStatus != ERROR_SUCCESS ) - { - tracy_free( s_prop ); - return false; - } - } + int microseconds = GetSamplingInterval() / 10; + if (etw::EnableCPUProfiling(session, microseconds) != ERROR_SUCCESS) + return etw::StopSession(session), false; + samplingPeriod = GetSamplingPeriod(); #endif -#ifdef UNICODE - WCHAR KernelLoggerName[sizeof( KERNEL_LOGGER_NAME )]; -#else - char KernelLoggerName[sizeof( KERNEL_LOGGER_NAME )]; -#endif - memcpy( KernelLoggerName, KERNEL_LOGGER_NAME, sizeof( KERNEL_LOGGER_NAME ) ); - EVENT_TRACE_LOGFILE log = {}; - log.LoggerName = KernelLoggerName; - log.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_RAW_TIMESTAMP; - log.EventRecordCallback = EventRecordCallback; - - s_traceHandle2 = OpenTrace( &log ); - if( s_traceHandle2 == (TRACEHANDLE)INVALID_HANDLE_VALUE ) - { - CloseTrace( s_traceHandle ); - tracy_free( s_prop ); - return false; - } - #ifndef TRACY_NO_VSYNC_CAPTURE - SetupVsync(); + if (etw::EnableVSyncMonitoring(session) != ERROR_SUCCESS) + return etw::StopSession(session), false; #endif return true; @@ -473,26 +266,15 @@ bool SysTraceStart( int64_t& samplingPeriod ) void SysTraceStop() { - if( s_threadVsync ) - { - CloseTrace( s_traceHandleVsync2 ); - CloseTrace( s_traceHandleVsync ); - s_threadVsync->~Thread(); - tracy_free( s_threadVsync ); - } - - CloseTrace( s_traceHandle2 ); - CloseTrace( s_traceHandle ); + etw::StopSession(session); } void SysTraceWorker( void* ptr ) { ThreadExitHandler threadExitHandler; SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL ); - SetThreadName( "Tracy SysTrace" ); - ProcessTrace( &s_traceHandle2, 1, 0, 0 ); - ControlTrace( 0, KERNEL_LOGGER_NAME, s_prop, EVENT_TRACE_CONTROL_STOP ); - tracy_free( s_prop ); + SetThreadName( "Tracy SysTrace (ETW)" ); + etw::EventConsumerLoop(session, EventRecordCallback); } void SysTraceGetExternalName( uint64_t thread, const char*& threadName, const char*& name ) diff --git a/public/client/windows/TracyETW.cpp b/public/client/windows/TracyETW.cpp new file mode 100644 index 00000000..81790021 --- /dev/null +++ b/public/client/windows/TracyETW.cpp @@ -0,0 +1,248 @@ +#include +#include +#include + +#include +#include + +namespace tracy { namespace etw { + +constexpr GUID NullGuid = {}; +constexpr GUID ThreadGuid = { 0x3D6FA8D1, 0xFE05, 0x11D0, { 0x9D, 0xDA, 0x00, 0xC0, 0x4F, 0xD7, 0xBA, 0x7C } }; +constexpr GUID PerfInfoGuid = { 0xCE1DBFB4, 0x137E, 0x4DA6, { 0x87, 0xB0, 0x3F, 0x59, 0xAA, 0x10, 0x2C, 0xBC } }; +constexpr GUID StackWalkGuid = { 0xDEF2FE46, 0x7BD6, 0x4B80, { 0xBD, 0x94, 0xF5, 0x7F, 0xE2, 0x0D, 0x0C, 0xE3 } }; +constexpr GUID DxgKrnlGuid = { 0x802EC45A, 0x1E99, 0x4B83, { 0x99, 0x20, 0x87, 0xC9, 0x82, 0x77, 0xBA, 0x9D } }; +constexpr GUID LostEventGuid = { 0x6A399AE0, 0x4BC6, 0x4DE9, { 0x87, 0x0B, 0x36, 0x57, 0xF8, 0x94, 0x7E, 0x7E } }; + +struct Session { + EVENT_TRACE_PROPERTIES properties = {}; + TCHAR name[64] = {}; + CONTROLTRACE_ID handle = 0; +}; + +static ULONG ETWError(ULONG result) { + if (result == ERROR_SUCCESS) + return result; + ZoneScopedC(tracy::Color::Red4); + char message [128] = {}; + size_t written = snprintf(message, sizeof(message), "ETW Error %u (0x%x): ", result, result); + written += FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + result, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&message[written], + sizeof(message)-written, + NULL + ); + tracy::InitCallstackCritical(); + TracyMessageCS(message, written, tracy::Color::Red4, 60); +#ifdef __cpp_exceptions + // TODO: should we throw an exception? +#endif + return result; +} + +static DWORD ElevatePrivilege(LPCTSTR PrivilegeName) { + TOKEN_PRIVILEGES tp = {}; + tp.PrivilegeCount = 1; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if (LookupPrivilegeValue(nullptr, PrivilegeName, &tp.Privileges[0].Luid) == FALSE) { + return ETWError(GetLastError()); + } + HANDLE hToken = {}; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken) == FALSE) { + return ETWError(GetLastError()); + } + BOOL adjusted = AdjustTokenPrivileges(hToken, FALSE, &tp, 0, nullptr, nullptr); + DWORD status = GetLastError(); + CloseHandle(hToken); // NOTE: skipping error handling for CloseHandle() + return ETWError(status); +} + +static ULONG StartSession(Session& session) { + ULONG status = StartTrace(&session.handle, session.name, &session.properties); + if (status == ERROR_ALREADY_EXISTS) { + // Session is already running (likely from a previous run that did not terminate + // gracefully). There are two options: take control of the existing session with + // ControlSession(UPDATE), or stop the session and start fresh again. The latter + // is better because it also resets the event providers. + status = ControlTrace(session.handle, session.name, &session.properties, EVENT_TRACE_CONTROL_STOP); + if (status != ERROR_SUCCESS) + return ETWError(status); + status = StartTrace(&session.handle, session.name, &session.properties); + } + return ETWError(status); +} + +static ULONG StopSession(Session& session) { + ULONG status = ControlTrace(session.handle, session.name, &session.properties, EVENT_TRACE_CONTROL_STOP); + if (status != ERROR_SUCCESS) + return ETWError(status); + // once stopped, the session handle becomes invalid + session.handle = 0; + return ERROR_SUCCESS; +} + +static ULONG EnableProvider( + Session& session, + const GUID& ProviderId, + ULONG ControlCode = EVENT_CONTROL_CODE_ENABLE_PROVIDER, + UCHAR Level = TRACE_LEVEL_INFORMATION, + ULONGLONG MatchAnyKeyword = 0, + ULONGLONG MatchAllKeyword = 0, + ULONG Timeout = 0, + PENABLE_TRACE_PARAMETERS EnableParameters = NULL) { + ULONG status = EnableTraceEx2(session.handle, &ProviderId, ControlCode, Level, MatchAnyKeyword, MatchAllKeyword, Timeout, EnableParameters); + return ETWError(status); +} + +static ULONG EnableStackWalk(Session& session, GUID EventGuid, UCHAR Opcode) { +#if defined _WIN64 + constexpr bool isOs64Bit = true; +#else + BOOL _iswow64; + IsWow64Process( GetCurrentProcess(), &_iswow64 ); + const bool isOs64Bit = _iswow64; +#endif + if (!isOs64Bit) + return 0 /* ERROR_SUCCESS */; // TODO: return error instead? + CLASSIC_EVENT_ID stackId[1] = {}; + stackId[0].EventGuid = EventGuid; + stackId[0].Type = Opcode; + ULONG status = TraceSetInformation(session.handle, TraceStackTracingInfo, &stackId, sizeof(stackId)); + return ETWError(status); +} + +static Session StartPrivateKernelSession(const TCHAR* name) { + Session session = {}; + + size_t maxlen = std::size(session.name); + for (size_t i = 0; i < maxlen && name[i] != 0; ++i) { + session.name[i] = name[i]; + } + + auto& props = session.properties; + props.LoggerNameOffset = offsetof(Session, name); + props.Wnode.BufferSize = sizeof(Session); + props.Wnode.Guid = NullGuid; +#ifdef TRACY_TIMER_QPC + props.Wnode.ClientContext = 1; // 1: QueryPerformanceCounter +#else + props.Wnode.ClientContext = 3; // 3: CPU Ticks (e.g., rdtsc) +#endif + props.Wnode.Flags = WNODE_FLAG_TRACED_GUID; + props.LogFileMode |= EVENT_TRACE_SYSTEM_LOGGER_MODE; + props.LogFileMode |= EVENT_TRACE_REAL_TIME_MODE; + + // TODO: should we really be tweaking the buffering parameters? + props.BufferSize = 1024; + props.MinimumBuffers = std::thread::hardware_concurrency() * 4; + props.MaximumBuffers = std::thread::hardware_concurrency() * 6; + + ULONG status = StartSession(session); + if (status != ERROR_SUCCESS) + return {}; + + return session; +} + +static ULONG EnableCPUProfiling(Session& session, int microseconds = 125 /* 8KHz = 125us */) { + // CPU Profiling requires special privileges on top of admin privileges + DWORD access = ElevatePrivilege(SE_SYSTEM_PROFILE_NAME); + if (access != ERROR_SUCCESS) + return access; + + ULONG status = EnableProvider(session, SystemProfileProviderGuid); + if (status != ERROR_SUCCESS) + return status; + + TRACE_PROFILE_INTERVAL interval = {}; + interval.Source = 0; // 0: ProfileTime + interval.Interval = (microseconds * 1000) / 100; // in 100's of nanoseconds + CONTROLTRACE_ID TraceId = 0; // must be zero for TraceSampledProfileIntervalInfo + status = TraceSetInformation(TraceId, TraceSampledProfileIntervalInfo, &interval, sizeof(interval)); + if (status != ERROR_SUCCESS) + return status; + + status = EnableStackWalk(session, PerfInfoGuid, 46); // PerfInfoGuid Opcode 46: SampledProfile event + return status; +} + +static ULONG EnableContextSwitchMonitoring(Session& session) { + ULONGLONG MatchAnyKeyword = SYSTEM_SCHEDULER_KW_CONTEXT_SWITCH; + ULONG status = EnableProvider(session, SystemSchedulerProviderGuid, + EVENT_CONTROL_CODE_ENABLE_PROVIDER, TRACE_LEVEL_INFORMATION, MatchAnyKeyword); + if (status != ERROR_SUCCESS) + return status; + status = EnableStackWalk(session, ThreadGuid, 36); // ThreadGuid Opcode 36: CSwitch event + return status; +} + +static ULONG EnableVSyncMonitoring(Session& session) { +// TODO: is this correct? +#if (_WIN32_WINNT < _WIN32_WINNT_WINBLUE) || defined(__MINGW32__) + return ETWError(ERROR_NOT_SUPPORTED); +#endif + + enum Keyword : ULONGLONG { + DxgKrnlBase = 0x0000'0000'0000'0001, // Microsoft-Windows-DxgKrnl: Base + DxgKrnlPresent = 0x0000'0000'0800'0000, // Microsoft-Windows-DxgKrnl: Present + MSFTReserved62 = 0x4000'0000'0000'0000 // winmeta.h: WINEVENT_KEYWORD_RESERVED_62 + }; + ULONGLONG MatchAnyKeyword = Keyword::MSFTReserved62 | Keyword::DxgKrnlPresent | Keyword::DxgKrnlBase; + ULONGLONG MatchAllKeyword = MatchAnyKeyword; + + EVENT_FILTER_EVENT_ID fe = {}; + fe.FilterIn = TRUE; + fe.Count = 1; + fe.Events[0] = 0x0011; // 0x11 = 17 : VSyncDPC_Info + EVENT_FILTER_DESCRIPTOR desc = {}; + desc.Ptr = (ULONGLONG)&fe; + desc.Size = sizeof(fe); + desc.Type = EVENT_FILTER_TYPE_EVENT_ID; + ENABLE_TRACE_PARAMETERS EnableParamaters = {}; + EnableParamaters.Version = ENABLE_TRACE_PARAMETERS_VERSION_2; + EnableParamaters.EnableProperty = EVENT_ENABLE_PROPERTY_IGNORE_KEYWORD_0; + EnableParamaters.SourceId = DxgKrnlGuid; // or NullGuid? Does it even matter? + EnableParamaters.EnableFilterDesc = &desc; + EnableParamaters.FilterDescCount = 1; + + ULONG status = EnableProvider(session, DxgKrnlGuid, + EVENT_CONTROL_CODE_ENABLE_PROVIDER, TRACE_LEVEL_INFORMATION, + MatchAnyKeyword, MatchAllKeyword, 0, &EnableParamaters); + return status; +} + +static ULONG WINAPI OnBufferComplete(PEVENT_TRACE_LOGFILE Buffer) { + if (Buffer->EventsLost > 0) { + fprintf(stderr, "WARNING: %u events have been lost...\n", Buffer->EventsLost); + } + return TRUE; // or FALSE to break out of ProcessTrace() +} + +static ULONG EventConsumerLoop(const Session& session, PEVENT_RECORD_CALLBACK callback = NULL) +{ + EVENT_TRACE_LOGFILE trace = {}; + trace.LoggerName = (LPTSTR)session.name; + trace.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME; + trace.ProcessTraceMode |= PROCESS_TRACE_MODE_EVENT_RECORD; // request EVENT_RECORD, not EVENT_TRACE (legacy) + trace.ProcessTraceMode |= PROCESS_TRACE_MODE_RAW_TIMESTAMP; // no timestamp conversions (use whatever the session is using) + trace.EventRecordCallback = callback; + trace.BufferCallback = OnBufferComplete; + + PROCESSTRACE_HANDLE hConsumer = OpenTrace(&trace); + if (hConsumer == INVALID_PROCESSTRACE_HANDLE) + return ETWError(GetLastError()); + + ULONG status = ProcessTrace(&hConsumer, 1, NULL, NULL); + if (status != ERROR_SUCCESS && status != ERROR_CANCELLED) + return ETWError(status); + + status = CloseTrace(hConsumer); + ETWError(status); + + return status; +} + +} }