You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
406 lines
13 KiB
406 lines
13 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// ETW (Event Tracing for Windows) profiling helpers. |
|
// This allows easy insertion of Generic Event markers into ETW/xperf tracing |
|
// which then aids in analyzing the traces and finding performance problems. |
|
// |
|
//=============================================================================== |
|
|
|
#include "pch_tier0.h" |
|
#include "tier0/etwprof.h" |
|
|
|
#ifdef ETW_MARKS_ENABLED |
|
|
|
#include <memory> |
|
|
|
// After building the DLL if it has never been registered on this machine or |
|
// if the providers have changed you need to go: |
|
// xcopy /y %vgame%\bin\tier0.dll %temp% |
|
// wevtutil um %vgame%\..\src\tier0\ValveETWProvider.man |
|
// wevtutil im %vgame%\..\src\tier0\ValveETWProvider.man |
|
|
|
#define WIN32_LEAN_AND_MEAN |
|
#include <windows.h> |
|
// These are defined in evntrace.h but you need a Vista+ Windows |
|
// SDK to have them available, so I define them here. |
|
#define EVENT_CONTROL_CODE_DISABLE_PROVIDER 0 |
|
#define EVENT_CONTROL_CODE_ENABLE_PROVIDER 1 |
|
#define EVENT_CONTROL_CODE_CAPTURE_STATE 2 |
|
|
|
// EVNTAPI is used in evntprov.h which is included by ValveETWProviderEvents.h |
|
// We define EVNTAPI without the DECLSPEC_IMPORT specifier so that |
|
// we can implement these functions locally instead of using the import library, |
|
// and can therefore still run on Windows XP. |
|
#define EVNTAPI __stdcall |
|
// Include the event register/write/unregister macros compiled from the manifest file. |
|
// Note that this includes evntprov.h which requires a Vista+ Windows SDK |
|
// which we don't currently have, so evntprov.h is checked in. |
|
#include "ValveETWProviderEvents.h" |
|
|
|
// Typedefs for use with GetProcAddress |
|
typedef ULONG (__stdcall *tEventRegister)( LPCGUID ProviderId, PENABLECALLBACK EnableCallback, PVOID CallbackContext, PREGHANDLE RegHandle); |
|
typedef ULONG (__stdcall *tEventWrite)( REGHANDLE RegHandle, PCEVENT_DESCRIPTOR EventDescriptor, ULONG UserDataCount, PEVENT_DATA_DESCRIPTOR UserData); |
|
typedef ULONG (__stdcall *tEventUnregister)( REGHANDLE RegHandle ); |
|
|
|
// Helper class to dynamically load Advapi32.dll, find the ETW functions, |
|
// register the providers if possible, and get the performance counter frequency. |
|
class CETWRegister |
|
{ |
|
public: |
|
CETWRegister() |
|
{ |
|
QueryPerformanceFrequency( &m_frequency ); |
|
|
|
// Find Advapi32.dll. This should always succeed. |
|
HMODULE pAdvapiDLL = LoadLibraryW( L"Advapi32.dll" ); |
|
if ( pAdvapiDLL ) |
|
{ |
|
// Try to find the ETW functions. This will fail on XP. |
|
m_pEventRegister = ( tEventRegister )GetProcAddress( pAdvapiDLL, "EventRegister" ); |
|
m_pEventWrite = ( tEventWrite )GetProcAddress( pAdvapiDLL, "EventWrite" ); |
|
m_pEventUnregister = ( tEventUnregister )GetProcAddress( pAdvapiDLL, "EventUnregister" ); |
|
|
|
// Register two ETW providers. If registration fails then the event logging calls will fail. |
|
// On XP these calls will do nothing. |
|
// On Vista and above, if these providers have been enabled by xperf or logman then |
|
// the VALVE_FRAMERATE_Context and VALVE_MAIN_Context globals will be modified |
|
// like this: |
|
// MatchAnyKeyword: 0xffffffffffffffff |
|
// IsEnabled: 1 |
|
// Level: 255 |
|
// In other words, fully enabled. |
|
|
|
EventRegisterValve_FrameRate(); |
|
EventRegisterValve_ServerFrameRate(); |
|
EventRegisterValve_Main(); |
|
EventRegisterValve_Input(); |
|
EventRegisterValve_Network(); |
|
|
|
// Emit the thread ID for the main thread. This also indicates that |
|
// the main provider is initialized. |
|
EventWriteThread_ID( GetCurrentThreadId(), "Main thread" ); |
|
// Emit an input system event so we know that it is active. |
|
EventWriteKey_down( "Valve input provider initialized.", 0, 0 ); |
|
} |
|
} |
|
~CETWRegister() |
|
{ |
|
// Unregister our providers. |
|
EventUnregisterValve_Network(); |
|
EventUnregisterValve_Input(); |
|
EventUnregisterValve_Main(); |
|
EventUnregisterValve_ServerFrameRate(); |
|
EventUnregisterValve_FrameRate(); |
|
} |
|
|
|
tEventRegister m_pEventRegister; |
|
tEventWrite m_pEventWrite; |
|
tEventUnregister m_pEventUnregister; |
|
|
|
// QPC frequency |
|
LARGE_INTEGER m_frequency; |
|
|
|
} g_ETWRegister; |
|
|
|
// Redirector function for EventRegister. Called by macros in ValveETWProviderEvents.h |
|
ULONG EVNTAPI EventRegister( LPCGUID ProviderId, PENABLECALLBACK EnableCallback, PVOID CallbackContext, PREGHANDLE RegHandle ) |
|
{ |
|
if ( g_ETWRegister.m_pEventRegister ) |
|
return g_ETWRegister.m_pEventRegister( ProviderId, EnableCallback, CallbackContext, RegHandle ); |
|
|
|
// RegHandle is an _Out_ parameter and must always be initialized. |
|
*RegHandle = 0; |
|
return 0; |
|
} |
|
|
|
// Redirector function for EventWrite. Called by macros in ValveETWProviderEvents.h |
|
ULONG EVNTAPI EventWrite( REGHANDLE RegHandle, PCEVENT_DESCRIPTOR EventDescriptor, ULONG UserDataCount, PEVENT_DATA_DESCRIPTOR UserData ) |
|
{ |
|
if ( g_ETWRegister.m_pEventWrite ) |
|
return g_ETWRegister.m_pEventWrite( RegHandle, EventDescriptor, UserDataCount, UserData ); |
|
return 0; |
|
} |
|
|
|
// Redirector function for EventUnregister. Called by macros in ValveETWProviderEvents.h |
|
ULONG EVNTAPI EventUnregister( REGHANDLE RegHandle ) |
|
{ |
|
if ( g_ETWRegister.m_pEventUnregister ) |
|
return g_ETWRegister.m_pEventUnregister( RegHandle ); |
|
return 0; |
|
} |
|
|
|
// Call QueryPerformanceCounter |
|
static int64 GetQPCTime() |
|
{ |
|
LARGE_INTEGER time; |
|
|
|
QueryPerformanceCounter( &time ); |
|
return time.QuadPart; |
|
} |
|
|
|
// Convert a QueryPerformanceCounter delta into milliseconds |
|
static float QPCToMS( int64 nDelta ) |
|
{ |
|
// Convert from a QPC delta to seconds. |
|
float flSeconds = ( float )( nDelta / double( g_ETWRegister.m_frequency.QuadPart ) ); |
|
|
|
// Convert from seconds to milliseconds |
|
return flSeconds * 1000; |
|
} |
|
|
|
// Public functions for emitting ETW events. |
|
|
|
int64 ETWMark( const char *pMessage ) |
|
{ |
|
int64 nTime = GetQPCTime(); |
|
EventWriteMark( pMessage ); |
|
return nTime; |
|
} |
|
|
|
void ETWMarkPrintf( const char *pMessage, ... ) |
|
{ |
|
// If we are running on Windows XP or if our providers have not been enabled |
|
// (by xperf or other) then this will be false and we can early out. |
|
// Be sure to check the appropriate context for the event. This is only |
|
// worth checking if there is some cost beyond the EventWrite that we can |
|
// avoid -- the redirectors in this file guarantee that EventWrite is always |
|
// safe to call. |
|
if ( !VALVE_MAIN_Context.IsEnabled ) |
|
{ |
|
return; |
|
} |
|
|
|
char buffer[1000]; |
|
va_list args; |
|
va_start( args, pMessage ); |
|
vsprintf_s( buffer, pMessage, args ); |
|
va_end( args ); |
|
|
|
EventWriteMark( buffer ); |
|
} |
|
|
|
void ETWMark1F( const char *pMessage, float data1 ) |
|
{ |
|
EventWriteMark1F( pMessage, data1 ); |
|
} |
|
|
|
void ETWMark2F( const char *pMessage, float data1, float data2 ) |
|
{ |
|
EventWriteMark2F( pMessage, data1, data2 ); |
|
} |
|
|
|
void ETWMark3F( const char *pMessage, float data1, float data2, float data3 ) |
|
{ |
|
EventWriteMark3F( pMessage, data1, data2, data3 ); |
|
} |
|
|
|
void ETWMark4F( const char *pMessage, float data1, float data2, float data3, float data4 ) |
|
{ |
|
EventWriteMark4F( pMessage, data1, data2, data3, data4 ); |
|
} |
|
|
|
void ETWMark1I( const char *pMessage, int data1 ) |
|
{ |
|
EventWriteMark1I( pMessage, data1 ); |
|
} |
|
|
|
void ETWMark2I( const char *pMessage, int data1, int data2 ) |
|
{ |
|
EventWriteMark2I( pMessage, data1, data2 ); |
|
} |
|
|
|
void ETWMark3I( const char *pMessage, int data1, int data2, int data3 ) |
|
{ |
|
EventWriteMark3I( pMessage, data1, data2, data3 ); |
|
} |
|
|
|
void ETWMark4I( const char *pMessage, int data1, int data2, int data3, int data4 ) |
|
{ |
|
EventWriteMark4I( pMessage, data1, data2, data3, data4 ); |
|
} |
|
|
|
void ETWMark1S( const char *pMessage, const char* data1 ) |
|
{ |
|
EventWriteMark1S( pMessage, data1 ); |
|
} |
|
|
|
void ETWMark2S( const char *pMessage, const char* data1, const char* data2 ) |
|
{ |
|
EventWriteMark2S( pMessage, data1, data2 ); |
|
} |
|
|
|
// Track the depth of ETW Begin/End pairs. This needs to be per-thread |
|
// if we start emitting marks on multiple threads. Using __declspec(thread) |
|
// has some problems on Windows XP, but since these ETW functions only work |
|
// on Vista+ that doesn't matter. |
|
static __declspec( thread ) int s_nDepth; |
|
|
|
int64 ETWBegin( const char *pMessage ) |
|
{ |
|
// If we are running on Windows XP or if our providers have not been enabled |
|
// (by xperf or other) then this will be false and we can early out. |
|
// Be sure to check the appropriate context for the event. This is only |
|
// worth checking if there is some cost beyond the EventWrite that we can |
|
// avoid -- the redirectors in this file guarantee that EventWrite is always |
|
// safe to call. |
|
// In this case we also avoid the potentially unreliable TLS implementation |
|
// (for dynamically loaded DLLs) on Windows XP. |
|
if ( !VALVE_MAIN_Context.IsEnabled ) |
|
{ |
|
return 0; |
|
} |
|
|
|
int64 nTime = GetQPCTime(); |
|
EventWriteStart( pMessage, s_nDepth++ ); |
|
return nTime; |
|
} |
|
|
|
int64 ETWEnd( const char *pMessage, int64 nStartTime ) |
|
{ |
|
// If we are running on Windows XP or if our providers have not been enabled |
|
// (by xperf or other) then this will be false and we can early out. |
|
// Be sure to check the appropriate context for the event. This is only |
|
// worth checking if there is some cost beyond the EventWrite that we can |
|
// avoid -- the redirectors in this file guarantee that EventWrite is always |
|
// safe to call. |
|
// In this case we also avoid the potentially unreliable TLS implementation |
|
// (for dynamically loaded DLLs) on Windows XP. |
|
if ( !VALVE_MAIN_Context.IsEnabled ) |
|
{ |
|
return 0; |
|
} |
|
|
|
int64 nTime = GetQPCTime(); |
|
EventWriteStop( pMessage, --s_nDepth, QPCToMS( nTime - nStartTime ) ); |
|
return nTime; |
|
} |
|
|
|
// Record server and client frame counts separately, in case they are |
|
// in the same process. |
|
static int s_nRenderFrameCount[2]; |
|
|
|
int ETWGetRenderFrameNumber() |
|
{ |
|
return s_nRenderFrameCount[0]; |
|
} |
|
|
|
// Insert a render frame marker using the Valve-FrameRate provider. Automatically |
|
// count the frame number and frame time. |
|
void ETWRenderFrameMark( bool bIsServerProcess ) |
|
{ |
|
Assert( bIsServerProcess == false || bIsServerProcess == true ); |
|
// Record server and client frame counts separately, in case they are |
|
// in the same process. |
|
static int64 s_lastFrameTime[2]; |
|
|
|
int64 nCurrentFrameTime = GetQPCTime(); |
|
float flElapsedFrameTime = 0.0f; |
|
if ( s_nRenderFrameCount[bIsServerProcess] ) |
|
{ |
|
flElapsedFrameTime = QPCToMS( nCurrentFrameTime - s_lastFrameTime[bIsServerProcess] ); |
|
} |
|
|
|
if ( bIsServerProcess ) |
|
{ |
|
EventWriteServerRenderFrameMark( s_nRenderFrameCount[bIsServerProcess], flElapsedFrameTime ); |
|
} |
|
else |
|
{ |
|
EventWriteRenderFrameMark( s_nRenderFrameCount[bIsServerProcess], flElapsedFrameTime ); |
|
} |
|
|
|
++s_nRenderFrameCount[bIsServerProcess]; |
|
s_lastFrameTime[bIsServerProcess] = nCurrentFrameTime; |
|
} |
|
|
|
// Insert a simulation frame marker using the Valve-FrameRate provider. Automatically |
|
// count the frame number and frame time. |
|
void ETWSimFrameMark( bool bIsServerProcess ) |
|
{ |
|
Assert( bIsServerProcess == false || bIsServerProcess == true ); |
|
// Record server and client frame counts separately, in case they are |
|
// in the same process. |
|
static int s_nFrameCount[2]; |
|
static int64 s_lastFrameTime[2]; |
|
|
|
int64 nCurrentFrameTime = GetQPCTime(); |
|
float flElapsedFrameTime = 0.0f; |
|
if ( s_nFrameCount[bIsServerProcess] ) |
|
{ |
|
flElapsedFrameTime = QPCToMS( nCurrentFrameTime - s_lastFrameTime[bIsServerProcess] ); |
|
} |
|
|
|
if ( bIsServerProcess ) |
|
{ |
|
EventWriteServerSimFrameMark( s_nFrameCount[bIsServerProcess], flElapsedFrameTime ); |
|
} |
|
else |
|
{ |
|
EventWriteSimFrameMark( s_nFrameCount[bIsServerProcess], flElapsedFrameTime ); |
|
} |
|
|
|
++s_nFrameCount[bIsServerProcess]; |
|
s_lastFrameTime[bIsServerProcess] = nCurrentFrameTime; |
|
} |
|
|
|
void ETWMouseDown( int whichButton, int x, int y ) |
|
{ |
|
// Always have x/y first to make the summary tables easier to read. |
|
EventWriteMouse_down( x, y, whichButton ); |
|
} |
|
|
|
void ETWMouseUp( int whichButton, int x, int y ) |
|
{ |
|
// Always have x/y first to make the summary tables easier to read. |
|
EventWriteMouse_up( x, y, whichButton ); |
|
} |
|
|
|
void ETWMouseMove( int nX, int nY ) |
|
{ |
|
static int lastX, lastY; |
|
|
|
// Only emit mouse-move events if the mouse position has changed, since |
|
// otherwise source2 emits a continous stream of events which makes it |
|
// harder to find 'real' mouse-move events. |
|
if ( lastX != nX || lastY != nY ) |
|
{ |
|
lastX = nX; |
|
lastY = nY; |
|
// Always have x/y first to make the summary tables easier to read. |
|
EventWriteMouse_Move( nX, nY ); |
|
} |
|
} |
|
|
|
void ETWMouseWheel( int nWheelDelta, int nX, int nY ) |
|
{ |
|
// Always have x/y first to make the summary tables easier to read. |
|
EventWriteMouse_Wheel( nX, nY, nWheelDelta ); |
|
} |
|
|
|
void ETWKeyDown( int nScanCode, int nVirtualCode, const char *pChar ) |
|
{ |
|
EventWriteKey_down( pChar, nScanCode, nVirtualCode ); |
|
} |
|
|
|
void ETWSendPacket( const char *pTo, int nWireSize, int nOutSequenceNR, int nOutSequenceNrAck ) |
|
{ |
|
static int s_nCumulativeWireSize; |
|
s_nCumulativeWireSize += nWireSize; |
|
|
|
EventWriteSendPacket( pTo, nWireSize, nOutSequenceNR, nOutSequenceNrAck, s_nCumulativeWireSize ); |
|
} |
|
|
|
void ETWThrottled() |
|
{ |
|
EventWriteThrottled(); |
|
} |
|
|
|
void ETWReadPacket( const char *pFrom, int nWireSize, int nInSequenceNR, int nOutSequenceNRAck ) |
|
{ |
|
static int s_nCumulativeWireSize; |
|
s_nCumulativeWireSize += nWireSize; |
|
|
|
EventWriteReadPacket( pFrom, nWireSize, nInSequenceNR, nOutSequenceNRAck, s_nCumulativeWireSize ); |
|
} |
|
|
|
#endif // ETW_MARKS_ENABLED
|
|
|