mirror of
https://github.com/wolfpld/tracy.git
synced 2026-08-27 13:38:24 +00:00
refactoring ETW kernel session
This commit is contained in:
@@ -52,8 +52,6 @@ static int GetSamplingPeriod()
|
||||
# include <string.h>
|
||||
# include <windows.h>
|
||||
# include <dbghelp.h>
|
||||
# include <evntrace.h>
|
||||
# include <evntcons.h>
|
||||
# include <psapi.h>
|
||||
# include <winternl.h>
|
||||
|
||||
@@ -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 )
|
||||
|
||||
248
public/client/windows/TracyETW.cpp
Normal file
248
public/client/windows/TracyETW.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
#include <windows.h>
|
||||
#include <evntrace.h>
|
||||
#include <evntcons.h>
|
||||
|
||||
#include <thread>
|
||||
#include <stdio.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
} }
|
||||
Reference in New Issue
Block a user