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.
1201 lines
33 KiB
1201 lines
33 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: VProf engine integration |
|
// |
|
//===========================================================================// |
|
|
|
#include "tier0/platform.h" |
|
#include "sys.h" |
|
#include "vprof_engine.h" |
|
#include "sv_main.h" |
|
#include "iengine.h" |
|
#include "basetypes.h" |
|
#include "convar.h" |
|
#include "cmd.h" |
|
#include "tier1/strtools.h" |
|
#include "con_nprint.h" |
|
#include "tier0/vprof.h" |
|
#include "materialsystem/imaterialsystem.h" |
|
#ifndef SWDS |
|
#include "vgui_baseui_interface.h" |
|
#include "vgui_vprofpanel.h" |
|
#endif |
|
#include "utlvector.h" |
|
#include "sv_remoteaccess.h" |
|
#include "ivprofexport.h" |
|
#include "vprof_record.h" |
|
#include "filesystem_engine.h" |
|
#include "tier1/utlstring.h" |
|
#include "tier1/utlvector.h" |
|
#include "tier0/etwprof.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
#ifdef _XBOX |
|
#ifdef VPROF_ENABLED |
|
CVProfile *g_pVProfileForDisplay = &g_VProfCurrentProfile; |
|
#endif |
|
#endif |
|
|
|
#ifdef VPROF_ENABLED |
|
void VProfExport_StartOrStop(); |
|
|
|
static ConVar vprof_dump_spikes( "vprof_dump_spikes","0", 0, "Framerate at which vprof will begin to dump spikes to the console. 0 = disabled, negative to reset after dump" ); |
|
static ConVar vprof_dump_spikes_node( "vprof_dump_spikes_node","", 0, "Node to start report from when doing a dump spikes" ); |
|
static ConVar vprof_dump_spikes_budget_group( "vprof_dump_spikes_budget_group","", 0, "Budget gtNode to start report from when doing a dump spikes" ); |
|
static ConVar vprof_dump_oninterval( "vprof_dump_oninterval", "0", 0, "Interval (in seconds) at which vprof will batch up data and dump it to the console." ); |
|
// vprof_report_oninterval gives more detail. If both vprof_report_oninterval and vprof_dump_oninterval |
|
// are set then vprof_report_oninterval wins. |
|
static ConVar vprof_report_oninterval( "vprof_report_oninterval", "0", 0, "Interval (in seconds) at which vprof will batch up a full report to the console -- more detailed than vprof_dump_oninterval." ); |
|
|
|
static void (*g_pfnDeferredOp)(); |
|
|
|
static void ExecuteDeferredOp() |
|
{ |
|
if ( g_pfnDeferredOp ) |
|
{ |
|
(*g_pfnDeferredOp)(); |
|
g_pfnDeferredOp = NULL; |
|
} |
|
} |
|
|
|
const double MAX_SPIKE_REPORT = 1.0; |
|
const int MAX_SPIKE_REPORT_FRAMES = 10; |
|
static double LastSpikeTime = 0; |
|
static int LastSpikeFrame = 0; |
|
//bool g_VProfSignalSpike; // used by xbox |
|
static ConVar vprof_counters( "vprof_counters", "0" ); |
|
|
|
extern bool con_debuglog; |
|
extern ConVar con_logfile; |
|
static bool g_fVprofOnByUI; |
|
static bool g_bVProfNoVSyncOff = false; |
|
static bool g_fVprofToVTrace = false; |
|
|
|
class ConsoleLogger |
|
{ |
|
public: |
|
ConsoleLogger( void ) |
|
{ |
|
#if !defined( SWDS ) |
|
m_condebugEnabled = con_debuglog; |
|
#else |
|
m_condebugEnabled = false; |
|
#endif |
|
if ( !m_condebugEnabled ) |
|
{ |
|
g_pFileSystem->CreateDirHierarchy( "vprof" ); |
|
while ( 1 ) |
|
{ |
|
++m_index; |
|
const char *fname = va( "vprof/vprof%d.txt", m_index ); |
|
if ( g_pFileSystem->FileExists( fname ) ) |
|
{ |
|
continue; |
|
} |
|
|
|
#if !defined( SWDS ) |
|
con_logfile.SetValue( fname ); |
|
#endif |
|
break; |
|
} |
|
} |
|
} |
|
|
|
~ConsoleLogger() |
|
{ |
|
if ( !m_condebugEnabled ) |
|
{ |
|
#if !defined( SWDS ) |
|
con_logfile.SetValue( "" ); |
|
#endif |
|
} |
|
} |
|
|
|
private: |
|
static int m_index; |
|
bool m_condebugEnabled; |
|
}; |
|
|
|
int ConsoleLogger::m_index = 0; |
|
|
|
static float s_flIntervalStartTime = 0.0f; |
|
|
|
void PreUpdateProfile( float filteredtime ) |
|
{ |
|
Assert( g_VProfCurrentProfile.AtRoot() ); |
|
|
|
ExecuteDeferredOp(); |
|
VProfExport_StartOrStop(); |
|
VProfRecord_StartOrStop(); |
|
|
|
// Check to see if it is time to dump the data and restart collection. |
|
if ( g_VProfCurrentProfile.IsEnabled() ) |
|
{ |
|
float flCurrentTime = eng->GetCurTime(); |
|
float flIntervalTime = vprof_dump_oninterval.GetFloat(); |
|
// vprof_report_oninterval trumps vprof_dump_oninterval |
|
if ( vprof_report_oninterval.GetFloat() != 0.0f ) |
|
flIntervalTime = vprof_report_oninterval.GetFloat(); |
|
|
|
g_VProfCurrentProfile.MarkFrame(); |
|
|
|
if ( ( s_flIntervalStartTime + flIntervalTime ) < flCurrentTime ) |
|
{ |
|
if ( vprof_report_oninterval.GetFloat() != 0.0f ) |
|
{ |
|
// Detailed report. |
|
g_VProfCurrentProfile.Pause(); |
|
ConsoleLogger consoleLog; |
|
// Just do one report in order to avoid excessive overhead when this is |
|
// called on a timer. Each report can take about 1.5 ms on a fast machine. |
|
g_VProfCurrentProfile.OutputReport( VPRT_LIST_BY_TIME, NULL ); |
|
g_VProfCurrentProfile.Resume(); |
|
} |
|
else if ( vprof_dump_oninterval.GetFloat() != 0.0f ) |
|
{ |
|
// Dump the current profile. |
|
g_VProfCurrentProfile.OutputReport( VPRT_SUMMARY | VPRT_LIST_BY_TIME | VPRT_LIST_BY_AVG_TIME | VPRT_LIST_BY_TIME_LESS_CHILDREN | VPRT_LIST_TOP_ITEMS_ONLY ); |
|
|
|
// Stop the current profile. |
|
g_VProfCurrentProfile.Stop(); |
|
|
|
// Reset and restart the current profile. |
|
g_VProfCurrentProfile.Reset(); |
|
g_VProfCurrentProfile.Start(); |
|
} |
|
|
|
s_flIntervalStartTime = flCurrentTime; |
|
} |
|
} |
|
|
|
if( g_VProfCurrentProfile.IsEnabled() && vprof_dump_spikes.GetFloat() ) |
|
{ |
|
float spikeThreash = fabsf( vprof_dump_spikes.GetFloat() ); |
|
g_VProfCurrentProfile.MarkFrame(); |
|
bool bSuppressRestart = false; |
|
if ( g_VProfSignalSpike || eng->GetFrameTime() > ( 1.f / spikeThreash ) ) |
|
{ |
|
if( g_VProfSignalSpike || ( Sys_FloatTime() - LastSpikeTime > MAX_SPIKE_REPORT && g_ServerGlobalVariables.framecount > LastSpikeFrame + MAX_SPIKE_REPORT_FRAMES ) ) |
|
{ |
|
ConsoleLogger consoleLog; |
|
// Print a message so that spikes can be seen even when going to VTrace. |
|
if ( g_fVprofToVTrace ) |
|
Msg( "%1.3f ms spike detected.\n", eng->GetFrameTime() * 1000.0f ); |
|
g_VProfCurrentProfile.OutputReport( VPRT_SUMMARY | VPRT_LIST_BY_TIME | VPRT_LIST_BY_TIME_LESS_CHILDREN | VPRT_LIST_TOP_ITEMS_ONLY, |
|
( vprof_dump_spikes_node.GetString()[0] ) ? vprof_dump_spikes_node.GetString() : NULL, |
|
( vprof_dump_spikes_budget_group.GetString()[0] ) ? g_VProfCurrentProfile.BudgetGroupNameToBudgetGroupID( vprof_dump_spikes_budget_group.GetString() ) : -1 ); |
|
#ifdef _XBOX // X360TBD |
|
if ( GetLastProfileFileRead() ) |
|
Msg( "******* %s\n", GetLastProfileFileRead() ); |
|
#endif |
|
LastSpikeTime = Sys_FloatTime(); |
|
LastSpikeFrame = g_ServerGlobalVariables.framecount; |
|
|
|
if ( vprof_dump_spikes.GetFloat() < 0.0 ) |
|
{ |
|
vprof_dump_spikes.SetValue( 0.0f ); |
|
// g_VProfCurrentProfile.Stop(); |
|
g_fVprofOnByUI = false; |
|
bSuppressRestart = true; |
|
} |
|
} |
|
g_VProfSignalSpike = false; |
|
} |
|
|
|
int iStartDepth = 0; |
|
do |
|
{ |
|
g_VProfCurrentProfile.Stop(); |
|
iStartDepth++; |
|
} while( g_VProfCurrentProfile.IsEnabled() ); |
|
|
|
if (!bSuppressRestart) |
|
{ |
|
|
|
g_VProfCurrentProfile.Reset(); |
|
|
|
while ( iStartDepth-- ) |
|
{ |
|
g_VProfCurrentProfile.Start(); |
|
} |
|
} |
|
|
|
Assert( g_VProfCurrentProfile.AtRoot() ); |
|
Assert( g_VProfCurrentProfile.IsEnabled() ); |
|
} |
|
|
|
int nCounterType = vprof_counters.GetInt(); |
|
if( nCounterType ) |
|
{ |
|
int i; |
|
int n = g_VProfCurrentProfile.GetNumCounters(); |
|
int nprintIndex = 0; |
|
for( i = 0; i < n; i++ ) |
|
{ |
|
if( g_VProfCurrentProfile.GetCounterGroup( i ) != ( nCounterType - 1 ) ) |
|
continue; |
|
const char *pName; |
|
int val; |
|
pName = g_VProfCurrentProfile.GetCounterNameAndValue( i, val ); |
|
Con_NPrintf( nprintIndex, "%s = %d\n", pName, val ); |
|
nprintIndex++; |
|
} |
|
} |
|
g_VProfCurrentProfile.ResetCounters( COUNTER_GROUP_DEFAULT ); |
|
g_VProfCurrentProfile.ResetCounters( COUNTER_GROUP_TEXTURE_PER_FRAME ); |
|
|
|
// This MUST come before GetVProfPanel()->UpdateProfile(), because UpdateProfile uses the data we snapshot here. |
|
VProfExport_SnapshotVProfHistory(); |
|
#ifdef VPROF_ENABLED |
|
VProfRecord_Snapshot(); |
|
#endif |
|
|
|
#ifndef SWDS |
|
// Update the vgui panel |
|
if ( GetVProfPanel() ) |
|
GetVProfPanel()->UpdateProfile( filteredtime ); |
|
#endif |
|
} |
|
|
|
void PostUpdateProfile() |
|
{ |
|
if ( g_VProfCurrentProfile.IsEnabled() && !vprof_dump_spikes.GetFloat() && !vprof_dump_oninterval.GetFloat() ) |
|
{ |
|
g_VProfCurrentProfile.MarkFrame(); |
|
} |
|
} |
|
|
|
#if defined( _X360 ) |
|
void UpdateVXConsoleProfile() |
|
{ |
|
g_VProfCurrentProfile.VXProfileUpdate(); |
|
} |
|
#endif |
|
|
|
static bool g_fVprofCacheMissOnByUI = false; |
|
|
|
// When a DEFERRED_CON_COMMAND is called these will contain the first and |
|
// second arguments, or zero-length strings if these arguments don't exist. |
|
static char g_szDefferedArg1[128]; |
|
static char g_szDefferedArg2[128]; |
|
|
|
// Con commands that are defined with DEFERRED_CON_COMMAND are called by PreUpdateProfile() |
|
// which calls ExecuteDeferredOp(). This ensures that vprof operations are done at the appropriate |
|
// time in the frame loop. Note that only one deferred command can be set at a time, so only one |
|
// deferred command can be on the command line. |
|
#define DEFERRED_CON_COMMAND( cmd, help ) \ |
|
static void cmd##_Impl(); \ |
|
CON_COMMAND(cmd, help) \ |
|
{ \ |
|
g_pfnDeferredOp = cmd##_Impl; \ |
|
V_strcpy_safe( g_szDefferedArg1, args[1] ); \ |
|
V_strcpy_safe( g_szDefferedArg2, args[2] ); \ |
|
} \ |
|
static void cmd##_Impl() |
|
|
|
CON_COMMAND_F( spike,"generates a fake spike", FCVAR_CHEAT ) |
|
{ |
|
Sys_Sleep(1000); |
|
} |
|
|
|
CON_COMMAND( vprof_vtune_group, "enable vtune for a particular vprof group (\"disable\" to disable)" ) |
|
{ |
|
if( args.ArgC() != 2 ) |
|
{ |
|
Warning( "vprof_vtune_group groupName (disable to turn off)\n" ); |
|
return; |
|
} |
|
const char *pArg = args[ 1 ]; |
|
if( Q_stricmp( pArg, "disable" ) == 0 ) |
|
{ |
|
g_VProfCurrentProfile.DisableVTuneGroup(); |
|
} |
|
else |
|
{ |
|
g_VProfCurrentProfile.EnableVTuneGroup( args[ 1 ] ); |
|
} |
|
} |
|
|
|
CON_COMMAND( vprof_dump_groupnames, "Write the names of all of the vprof groups to the console." ) |
|
{ |
|
int n = g_VProfCurrentProfile.GetNumBudgetGroups(); |
|
int i; |
|
for( i = 0; i < n; i++ ) |
|
{ |
|
Msg( "group %d: \"%s\"\n", i, g_VProfCurrentProfile.GetBudgetGroupName( i ) ); |
|
} |
|
} |
|
|
|
DEFERRED_CON_COMMAND( vprof_cachemiss, "Toggle VProf cache miss checking" ) |
|
{ |
|
if ( !g_fVprofCacheMissOnByUI ) |
|
{ |
|
Msg("VProf cache miss enabled.\n"); |
|
g_VProfCurrentProfile.PMEEnable( true ); |
|
g_fVprofCacheMissOnByUI = true; |
|
} |
|
else |
|
{ |
|
Msg("VProf cache miss disabled.\n"); |
|
g_VProfCurrentProfile.PMEEnable( false ); |
|
g_fVprofCacheMissOnByUI = false; |
|
} |
|
} |
|
|
|
DEFERRED_CON_COMMAND( vprof_cachemiss_on, "Turn on VProf cache miss checking" ) |
|
{ |
|
if ( !g_fVprofCacheMissOnByUI ) |
|
{ |
|
Msg("VProf cache miss enabled.\n"); |
|
g_VProfCurrentProfile.PMEEnable( true ); |
|
g_fVprofCacheMissOnByUI = true; |
|
} |
|
} |
|
|
|
DEFERRED_CON_COMMAND( vprof_cachemiss_off, "Turn off VProf cache miss checking" ) |
|
{ |
|
if ( g_fVprofCacheMissOnByUI ) |
|
{ |
|
Msg("VProf cache miss disabled.\n"); |
|
g_VProfCurrentProfile.PMEEnable( false ); |
|
g_fVprofCacheMissOnByUI = false; |
|
} |
|
} |
|
|
|
DEFERRED_CON_COMMAND( vprof, "Toggle VProf profiler" ) |
|
{ |
|
if ( !g_fVprofOnByUI ) |
|
{ |
|
Msg("VProf enabled.\n"); |
|
g_VProfCurrentProfile.Start(); |
|
g_fVprofOnByUI = true; |
|
} |
|
else |
|
{ |
|
Msg("VProf disabled.\n"); |
|
g_VProfCurrentProfile.Stop(); |
|
g_fVprofOnByUI = false; |
|
} |
|
} |
|
|
|
#ifdef ETW_MARKS_ENABLED |
|
CON_COMMAND( vprof_vtrace, "Toggle whether vprof data is sent to VTrace" ) |
|
{ |
|
if ( g_fVprofToVTrace ) |
|
{ |
|
Msg("Vprof data now returns to the console.\n"); |
|
g_VProfCurrentProfile.SetOutputStream( NULL ); |
|
} |
|
else |
|
{ |
|
Msg("VProf data is now being sent to vtrace.\n"); |
|
g_VProfCurrentProfile.SetOutputStream( ETWMarkPrintf ); |
|
} |
|
g_fVprofToVTrace = !g_fVprofToVTrace; |
|
} |
|
#endif |
|
|
|
#ifdef _X360 |
|
|
|
DEFERRED_CON_COMMAND( vprof_novsync_off, "Leaves vsync on when vxconsole brings up showbudget." ) |
|
{ |
|
g_bVProfNoVSyncOff = !g_bVProfNoVSyncOff; |
|
Msg("VProf novsync auto setting %s.\n", g_bVProfNoVSyncOff ? "disabled" : "enabled" ); |
|
} |
|
|
|
DEFERRED_CON_COMMAND( vprof_show_time, "Shows time in vprof" ) |
|
{ |
|
g_VProfCurrentProfile.VXConsoleReportMode( CVProfile::VXCONSOLE_REPORT_TIME ); |
|
} |
|
|
|
DEFERRED_CON_COMMAND( vprof_show_cachemiss, "Shows cachemisses in vprof" ) |
|
{ |
|
if ( !g_fVprofCacheMissOnByUI ) |
|
{ |
|
Msg("VProf cache miss enabled.\n"); |
|
g_VProfCurrentProfile.PMEEnable( true ); |
|
g_fVprofCacheMissOnByUI = true; |
|
} |
|
|
|
g_VProfCurrentProfile.VXConsoleReportMode( CVProfile::VXCONSOLE_REPORT_L2CACHE_MISSES ); |
|
} |
|
|
|
DEFERRED_CON_COMMAND( vprof_show_loadhitstore, "Shows load-hit-stores in vprof" ) |
|
{ |
|
if ( !g_fVprofCacheMissOnByUI ) |
|
{ |
|
Msg("VProf cache miss enabled.\n"); |
|
g_VProfCurrentProfile.PMEEnable( true ); |
|
g_fVprofCacheMissOnByUI = true; |
|
} |
|
|
|
g_VProfCurrentProfile.VXConsoleReportMode( CVProfile::VXCONSOLE_REPORT_LOAD_HIT_STORE ); |
|
} |
|
|
|
DEFERRED_CON_COMMAND( vprof_time_scale, "Scale used when displaying time (0 = use default)" ) |
|
{ |
|
float flScale = atof(g_szDefferedArg1); |
|
if ( flScale <= 0.0f ) |
|
{ |
|
flScale = 1000.0f; |
|
} |
|
g_VProfCurrentProfile.VXConsoleReportScale( CVProfile::VXCONSOLE_REPORT_TIME, flScale ); |
|
} |
|
|
|
DEFERRED_CON_COMMAND( vprof_cachemiss_scale, "Scale used when displaying cachemisses (0 = use default)" ) |
|
{ |
|
float flScale = atof(g_szDefferedArg1); |
|
if ( flScale <= 0.0f ) |
|
{ |
|
flScale = 1.0f; |
|
} |
|
g_VProfCurrentProfile.VXConsoleReportScale( CVProfile::VXCONSOLE_REPORT_L2CACHE_MISSES, flScale ); |
|
} |
|
|
|
DEFERRED_CON_COMMAND( vprof_loadhitstore_scale, "Scale used when displaying load-hit-stores (0 = use default)" ) |
|
{ |
|
float flScale = atof(g_szDefferedArg1); |
|
if ( flScale <= 0.0f ) |
|
{ |
|
flScale = 0.1f; |
|
} |
|
g_VProfCurrentProfile.VXConsoleReportScale( CVProfile::VXCONSOLE_REPORT_LOAD_HIT_STORE, flScale ); |
|
} |
|
|
|
#endif // 360 |
|
|
|
DEFERRED_CON_COMMAND( vprof_on, "Turn on VProf profiler" ) |
|
{ |
|
if ( !g_fVprofOnByUI ) |
|
{ |
|
Msg("VProf enabled.\n"); |
|
g_VProfCurrentProfile.Start(); |
|
g_fVprofOnByUI = true; |
|
if ( IsX360() && !g_bVProfNoVSyncOff ) |
|
{ |
|
ConVarRef mat_vsyncref( "mat_vsync" ); |
|
if ( mat_vsyncref.GetBool() ) |
|
{ |
|
Warning( "Disabling vsync (via mat_vsync) to increase profiling accuracy.\n" ); |
|
mat_vsyncref.SetValue( false ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
CON_COMMAND( budget_toggle_group, "Turn a budget group on/off" ) |
|
{ |
|
if( args.ArgC() != 2 ) |
|
{ |
|
return; |
|
} |
|
|
|
int budgetGroup = g_VProfCurrentProfile.BudgetGroupNameToBudgetGroupIDNoCreate( args[1] ); |
|
|
|
if ( budgetGroup == -1 ) |
|
{ |
|
return; |
|
} |
|
|
|
g_VProfCurrentProfile.HideBudgetGroup( budgetGroup, !(g_VProfCurrentProfile.GetBudgetGroupFlags( budgetGroup ) & BUDGETFLAG_HIDDEN) ); |
|
} |
|
|
|
|
|
#if defined( _X360 ) |
|
CON_COMMAND( vprof_update, "" ) |
|
{ |
|
if ( args.ArgC() < 2 ) |
|
return; |
|
|
|
const char *pArg = args[1]; |
|
if ( !Q_stricmp( pArg, "cpu" ) ) |
|
{ |
|
g_VProfCurrentProfile.VXEnableUpdateMode(VPROF_UPDATE_BUDGET, true); |
|
} |
|
else if ( !Q_stricmp( pArg, "texture" ) ) |
|
{ |
|
g_VProfCurrentProfile.VXEnableUpdateMode(VPROF_UPDATE_TEXTURE_GLOBAL, true); |
|
g_VProfCurrentProfile.VXEnableUpdateMode(VPROF_UPDATE_TEXTURE_PERFRAME, false); |
|
} |
|
else if ( !Q_stricmp( pArg, "texture_frame" ) ) |
|
{ |
|
g_VProfCurrentProfile.VXEnableUpdateMode(VPROF_UPDATE_TEXTURE_PERFRAME, true); |
|
g_VProfCurrentProfile.VXEnableUpdateMode(VPROF_UPDATE_TEXTURE_GLOBAL, false); |
|
} |
|
} |
|
#endif |
|
|
|
void VProfOn( void ) |
|
{ |
|
CCommand args; |
|
vprof_on( args ); |
|
} |
|
|
|
DEFERRED_CON_COMMAND( vprof_off, "Turn off VProf profiler" ) |
|
{ |
|
if ( g_fVprofOnByUI ) |
|
{ |
|
Msg("VProf disabled.\n"); |
|
g_VProfCurrentProfile.Stop(); |
|
g_fVprofOnByUI = false; |
|
|
|
#if defined( _X360 ) |
|
// disable all updating |
|
g_VProfCurrentProfile.VXEnableUpdateMode( 0xFFFFFFFF, false ); |
|
#endif |
|
} |
|
} |
|
|
|
DEFERRED_CON_COMMAND( vprof_reset, "Reset the stats in VProf profiler" ) |
|
{ |
|
Msg("VProf reset.\n"); |
|
g_VProfCurrentProfile.Reset(); |
|
|
|
#ifndef SWDS |
|
if ( GetVProfPanel() ) |
|
{ |
|
GetVProfPanel()->Reset(); |
|
} |
|
#endif |
|
} |
|
|
|
DEFERRED_CON_COMMAND(vprof_reset_peaks, "Reset just the peak time in VProf profiler") |
|
{ |
|
Msg("VProf peaks reset.\n"); |
|
g_VProfCurrentProfile.ResetPeaks(); |
|
} |
|
|
|
DEFERRED_CON_COMMAND(vprof_generate_report, "Generate a report to the console.") |
|
{ |
|
g_VProfCurrentProfile.Pause(); |
|
ConsoleLogger consoleLog; |
|
// This used to generate six different reports, which is expensive and hard to read. Default to |
|
// two to save time and space. |
|
g_VProfCurrentProfile.OutputReport( VPRT_LIST_BY_TIME | VPRT_LIST_BY_TIME_LESS_CHILDREN, (g_szDefferedArg1[0]) ? g_szDefferedArg1 : NULL ); |
|
g_VProfCurrentProfile.Resume(); |
|
} |
|
|
|
DEFERRED_CON_COMMAND(vprof_generate_report_budget, "Generate a report to the console based on budget group.") |
|
{ |
|
if ( !g_szDefferedArg1[0] ) |
|
{ |
|
return; |
|
} |
|
g_VProfCurrentProfile.Pause(); |
|
ConsoleLogger consoleLog; |
|
g_VProfCurrentProfile.OutputReport( VPRT_FULL & ~VPRT_HIERARCHY, NULL, g_VProfCurrentProfile.BudgetGroupNameToBudgetGroupID( g_szDefferedArg1 ) ); |
|
g_VProfCurrentProfile.Resume(); |
|
} |
|
|
|
DEFERRED_CON_COMMAND(vprof_generate_report_hierarchy, "Generate a report to the console.") |
|
{ |
|
g_VProfCurrentProfile.Pause(); |
|
ConsoleLogger consoleLog; |
|
g_VProfCurrentProfile.OutputReport( VPRT_HIERARCHY ); |
|
g_VProfCurrentProfile.Resume(); |
|
} |
|
|
|
DEFERRED_CON_COMMAND(vprof_generate_report_AI, "Generate a report to the console.") |
|
{ |
|
// This is an unfortunate artifact of deferred commands not supporting arguments |
|
g_VProfCurrentProfile.Pause(); |
|
ConsoleLogger consoleLog; |
|
g_VProfCurrentProfile.OutputReport( (VPRT_FULL & ~VPRT_HIERARCHY), "NPCs" ); |
|
g_VProfCurrentProfile.Resume(); |
|
} |
|
|
|
DEFERRED_CON_COMMAND(vprof_generate_report_AI_only, "Generate a report to the console.") |
|
{ |
|
// This is an unfortunate artifact of deferred commands not supporting arguments |
|
g_VProfCurrentProfile.Pause(); |
|
ConsoleLogger consoleLog; |
|
g_VProfCurrentProfile.OutputReport( (VPRT_FULL & ~VPRT_HIERARCHY), "NPCs", g_VProfCurrentProfile.BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_NPCS ) ); |
|
g_VProfCurrentProfile.Resume(); |
|
} |
|
|
|
DEFERRED_CON_COMMAND(vprof_generate_report_map_load, "Generate a report to the console.") |
|
{ |
|
// This is an unfortunate artifact of deferred commands not supporting arguments |
|
g_VProfCurrentProfile.Pause(); |
|
ConsoleLogger consoleLog; |
|
g_VProfCurrentProfile.OutputReport( VPRT_FULL, "Host_NewGame" ); |
|
g_VProfCurrentProfile.Resume(); |
|
} |
|
|
|
#ifdef _X360 |
|
DEFERRED_CON_COMMAND(vprof_360_enable_counters, "Enable 360 L2 and LHS counters for a node") |
|
{ |
|
g_VProfCurrentProfile.Pause(); |
|
if (g_VProfCurrentProfile.PMCEnableL2Upon(g_szDefferedArg1)) |
|
{ |
|
g_VProfCurrentProfile.DumpEnabledPMCNodes(); |
|
// Msg("PMC enabled for only node %s\n", g_szDefferedArg1); |
|
} |
|
else |
|
{ |
|
Msg("Node not found.\n"); |
|
} |
|
g_VProfCurrentProfile.Resume(); |
|
} |
|
|
|
DEFERRED_CON_COMMAND(vprof_360_enable_counters_recursive, "Enable 360 L2 and LHS counters for a node and all subnodes") |
|
{ |
|
g_VProfCurrentProfile.Pause(); |
|
if (g_VProfCurrentProfile.PMCEnableL2Upon(g_szDefferedArg1,true)) |
|
{ |
|
g_VProfCurrentProfile.DumpEnabledPMCNodes(); |
|
// Msg("PMC enabled for only node %s\n", g_szDefferedArg1); |
|
} |
|
else |
|
{ |
|
Msg("Node not found.\n"); |
|
} |
|
g_VProfCurrentProfile.Resume(); |
|
} |
|
|
|
DEFERRED_CON_COMMAND(vprof_360_disable_counters, "Disable 360 L2 and LHS counters for a node. Specify 'all' to mean all nodes.") |
|
{ |
|
g_VProfCurrentProfile.Pause(); |
|
if (stricmp(g_szDefferedArg1,"all") == 0) |
|
{ |
|
g_VProfCurrentProfile.PMCDisableAllNodes(); |
|
} |
|
else |
|
{ |
|
if (g_VProfCurrentProfile.PMCDisableL2Upon(g_szDefferedArg1,false)) |
|
{ |
|
g_VProfCurrentProfile.DumpEnabledPMCNodes(); |
|
// Msg("PMC enabled for only node %s\n", g_szDefferedArg1); |
|
} |
|
else |
|
{ |
|
Msg("Node not found.\n"); |
|
} |
|
} |
|
g_VProfCurrentProfile.Resume(); |
|
} |
|
|
|
DEFERRED_CON_COMMAND(vprof_360_disable_counters_recursive, "Disable 360 L2 and LHS counters for a node and all children.") |
|
{ |
|
g_VProfCurrentProfile.Pause(); |
|
|
|
if ( g_VProfCurrentProfile.PMCDisableL2Upon(g_szDefferedArg1,true) ) |
|
{ |
|
g_VProfCurrentProfile.DumpEnabledPMCNodes(); |
|
// Msg("PMC enabled for only node %s\n", g_szDefferedArg1); |
|
} |
|
else |
|
{ |
|
Msg("Node not found.\n"); |
|
} |
|
|
|
g_VProfCurrentProfile.Resume(); |
|
} |
|
|
|
DEFERRED_CON_COMMAND(vprof_360_report_counters, "Report L2/LHS info for specified node") |
|
{ |
|
g_VProfCurrentProfile.Pause(); |
|
ConsoleLogger consoleLog; |
|
|
|
CVProfNode *pNode = g_VProfCurrentProfile.FindNode( g_VProfCurrentProfile.GetRoot(), g_szDefferedArg1 ); |
|
if (pNode) |
|
{ |
|
Msg("NODE %s\n\tL2 misses: %d\n\tLHS misses: %d\n", g_szDefferedArg1, pNode->GetL2CacheMisses(), pNode->GetLoadHitStores() ); |
|
} |
|
else |
|
{ |
|
Msg("Node %s not found.", g_szDefferedArg1); |
|
} |
|
|
|
g_VProfCurrentProfile.Resume(); |
|
} |
|
|
|
|
|
DEFERRED_CON_COMMAND(vprof_360_cpu_trace_enable, "Enable CPU tracing: it will begin when specified node starts, and end when it stops. Do this before calling vprof_360_cpu_trace_go.") |
|
{ |
|
CVProfNode * RESTRICT upon = g_VProfCurrentProfile.CPUTraceEnableForNode(g_szDefferedArg1); |
|
if (upon != NULL) |
|
{ |
|
Msg( "%s will be traced from start to end. Make sure vprof is enabled, and enter \nvprof_360_cpu_trace_go <filename> to engage!\n", upon->GetName() ); |
|
} |
|
else |
|
{ |
|
Msg( "Could not find node %s. Maybe you need to run vprof for a bit so I can know about that node? Or, you might need to wrap it in \"double-quotes\". \n", g_szDefferedArg1 ); |
|
} |
|
} |
|
|
|
|
|
DEFERRED_CON_COMMAND(vprof_360_cpu_trace_disable, "Disable CPU tracing on all nodes.") |
|
{ |
|
g_VProfCurrentProfile.CPUTraceDisableAllNodes(); |
|
g_VProfCurrentProfile.SetCPUTraceEnabled(CVProfile::kDisabled); |
|
} |
|
|
|
DEFERRED_CON_COMMAND(vprof_360_cpu_trace_go, "syntax: vprof_360_cpu_trace_go <filename>. Will record one CPU trace of the node specified in vprof_360_cpu_trace_enable, dumping it to e:/filename.pix2.") |
|
{ |
|
if ( !g_fVprofOnByUI ) |
|
{ |
|
Msg("VProf enabled.\n"); |
|
g_VProfCurrentProfile.Start(); |
|
g_fVprofOnByUI = true; |
|
if ( IsX360() ) |
|
{ |
|
ConVarRef mat_vsyncref( "mat_vsync" ); |
|
if ( mat_vsyncref.GetBool() ) |
|
{ |
|
Warning( "Disabling vsync (via mat_vsync) to increase profiling accuracy.\n" ); |
|
mat_vsyncref.SetValue( false ); |
|
} |
|
} |
|
} |
|
|
|
if (g_VProfCurrentProfile.CPUTraceGetEnabledNode() == NULL || g_VProfCurrentProfile.CPUTraceGetEnabledNode() == g_VProfCurrentProfile.GetRoot() ) |
|
{ |
|
Msg( "Defaulting PIX trace node to CEngine::Frame\n" ); |
|
g_VProfCurrentProfile.CPUTraceEnableForNode( "CEngine::Frame" ); |
|
} |
|
|
|
if (g_VProfCurrentProfile.CPUTraceGetEnabledNode() != NULL) |
|
{ |
|
if ( !g_szDefferedArg1[0] ) |
|
{ |
|
SYSTEMTIME systemTime; |
|
GetLocalTime( &systemTime ); |
|
V_snprintf( g_szDefferedArg1, ARRAYSIZE(g_szDefferedArg1), "vprof_%d_%d_%d_%d_%d_%d", systemTime.wMonth, systemTime.wDay, systemTime.wHour, systemTime.wMinute, systemTime.wSecond, systemTime.wMilliseconds ); |
|
} |
|
const char *filename = g_VProfCurrentProfile.SetCPUTraceFilename(g_szDefferedArg1); |
|
g_VProfCurrentProfile.SetCPUTraceEnabled(CVProfile::kFirstHitNode); |
|
Msg( "Trace will be written to %s\n", filename ); |
|
} |
|
else |
|
{ |
|
Msg( "Please specify a node to profile with vprof_360_cpu_trace_enable <node>.\n" ); |
|
} |
|
} |
|
|
|
DEFERRED_CON_COMMAND(vprof_360_cpu_trace_go_repeat, "syntax: vprof_360_cpu_trace_go_repeat <filename>. For each time the node specified in vprof_360_cpu_trace_enable is hit during the next frame, dump a CPU trace to e:/filenameXXXX.pix2.") |
|
{ |
|
if (g_VProfCurrentProfile.CPUTraceGetEnabledNode() != NULL) |
|
{ |
|
const char *filename = g_VProfCurrentProfile.SetCPUTraceFilename(g_szDefferedArg1); |
|
g_VProfCurrentProfile.SetCPUTraceEnabled(CVProfile::kAllNodesInFrame_WaitingForMark); |
|
Msg( "Trace will be written to %s%.4d ... \n", filename, g_VProfCurrentProfile.GetMultiTraceIndex() ); |
|
} |
|
else |
|
{ |
|
Msg( "Please specify a node to profile with vprof_360_cpu_trace_enable <node>.\n" ); |
|
} |
|
} |
|
#endif |
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------------------------------------------ // |
|
// Exports for the dedicated server UI. |
|
// ------------------------------------------------------------------------------------------------------------------------------------ // |
|
class CVProfExport : public IVProfExport |
|
{ |
|
public: |
|
|
|
CVProfExport() |
|
{ |
|
m_nListeners = 0; |
|
m_bStart = m_bStop = false; |
|
m_BudgetFlagsFilter = 0; |
|
} |
|
|
|
inline CVProfile* GetActiveVProfile() |
|
{ |
|
return g_pVProfileForDisplay; |
|
} |
|
|
|
inline bool CanShowBudgetGroup( int iGroup ) |
|
{ |
|
return ( GetActiveVProfile()->GetBudgetGroupFlags( iGroup ) & m_BudgetFlagsFilter ) != 0; |
|
} |
|
|
|
virtual void AddListener() |
|
{ |
|
++m_nListeners; |
|
if ( m_nListeners == 1 ) |
|
m_bStart = true; // Defer the command till vprof is ready. |
|
} |
|
|
|
virtual void RemoveListener() |
|
{ |
|
--m_nListeners; |
|
if ( m_nListeners == 0 ) |
|
m_bStop = true; // Defer the command till vprof is ready. |
|
} |
|
|
|
virtual void SetBudgetFlagsFilter( int filter ) |
|
{ |
|
m_BudgetFlagsFilter = filter; |
|
} |
|
|
|
virtual int GetNumBudgetGroups() |
|
{ |
|
int nTotalGroups = min( m_Times.Count(), GetActiveVProfile()->GetNumBudgetGroups() ); |
|
int nRet = 0; |
|
for ( int i=0; i < nTotalGroups; i++ ) |
|
{ |
|
if ( CanShowBudgetGroup( i ) ) |
|
++nRet; |
|
} |
|
return nRet; |
|
} |
|
|
|
virtual void GetBudgetGroupInfos( CExportedBudgetGroupInfo *pInfos ) |
|
{ |
|
int iOut = 0; |
|
int nTotalGroups = min( m_Times.Count(), GetActiveVProfile()->GetNumBudgetGroups() ); |
|
for ( int i=0; i < nTotalGroups; i++ ) |
|
{ |
|
if ( CanShowBudgetGroup( i ) ) |
|
{ |
|
pInfos[iOut].m_pName = GetActiveVProfile()->GetBudgetGroupName( i ); |
|
|
|
int red, green, blue, alpha; |
|
GetActiveVProfile()->GetBudgetGroupColor( i, red, green, blue, alpha ); |
|
pInfos[iOut].m_Color = Color( red, green, blue, alpha ); |
|
|
|
pInfos[iOut].m_BudgetFlags = GetActiveVProfile()->GetBudgetGroupFlags( i ); |
|
++iOut; |
|
} |
|
} |
|
} |
|
|
|
virtual void GetBudgetGroupTimes( float times[IVProfExport::MAX_BUDGETGROUP_TIMES] ) |
|
{ |
|
int nTotalGroups = min( m_Times.Count(), GetActiveVProfile()->GetNumBudgetGroups() ); |
|
int nGroups = min( nTotalGroups, (int)IVProfExport::MAX_BUDGETGROUP_TIMES ); |
|
memset( times, 0, sizeof( times[0] ) * nGroups ); |
|
|
|
int iOut = 0; |
|
for ( int i=0; i < nTotalGroups; i++ ) |
|
{ |
|
if ( CanShowBudgetGroup( i ) ) |
|
{ |
|
times[iOut] = m_Times[i]; |
|
++iOut; |
|
} |
|
} |
|
} |
|
|
|
void GetAllBudgetGroupTimes( float *pTimes ) |
|
{ |
|
int nTotalGroups = GetActiveVProfile()->GetNumBudgetGroups(); |
|
for ( int i=0; i < nTotalGroups; i++ ) |
|
{ |
|
pTimes[i] = CanShowBudgetGroup( i ) ? m_Times[i] : 0.0f; |
|
} |
|
} |
|
|
|
virtual void PauseProfile() |
|
{ |
|
if ( materials ) |
|
materials->Flush(); |
|
|
|
g_VProfCurrentProfile.Pause(); |
|
} |
|
|
|
virtual void ResumeProfile() |
|
{ |
|
if ( materials ) |
|
materials->Flush(); |
|
|
|
g_VProfCurrentProfile.Resume(); |
|
} |
|
|
|
|
|
public: |
|
|
|
void StartOrStop() |
|
{ |
|
if ( m_bStart ) |
|
{ |
|
g_VProfCurrentProfile.Start(); |
|
m_bStart = false; |
|
} |
|
|
|
if ( m_bStop ) |
|
{ |
|
g_VProfCurrentProfile.Stop(); |
|
m_bStop = false; |
|
} |
|
} |
|
|
|
void CalculateBudgetGroupTimes_Recursive( CVProfNode *pNode ) |
|
{ |
|
// If this node's info is filtered out, then put it in its parent's budget group. |
|
CVProfNode *pTestNode = pNode; |
|
while ( pTestNode != GetActiveVProfile()->GetRoot() && |
|
( !CanShowBudgetGroup( pTestNode->GetBudgetGroupID() ) || |
|
( GetActiveVProfile()->GetBudgetGroupFlags( pTestNode->GetBudgetGroupID() ) & BUDGETFLAG_HIDDEN ) != 0 ) ) |
|
{ |
|
pTestNode = pTestNode->GetParent(); |
|
} |
|
|
|
int groupID = pTestNode->GetBudgetGroupID(); |
|
double nodeTime = pNode->GetPrevTimeLessChildren(); |
|
if ( groupID >= 0 && groupID < min( m_Times.Count(), (int)IVProfExport::MAX_BUDGETGROUP_TIMES ) ) |
|
{ |
|
m_Times[groupID] += nodeTime; |
|
} |
|
else |
|
{ |
|
Assert( false ); |
|
} |
|
|
|
if( pNode->GetSibling() ) |
|
{ |
|
CalculateBudgetGroupTimes_Recursive( pNode->GetSibling() ); |
|
} |
|
if( pNode->GetChild() ) |
|
{ |
|
CalculateBudgetGroupTimes_Recursive( pNode->GetChild() ); |
|
} |
|
|
|
if ( !VProfRecord_IsPlayingBack() ) |
|
{ |
|
pNode->ClearPrevTime(); |
|
} |
|
} |
|
|
|
void SnapshotVProfHistory() |
|
{ |
|
// Don't do the work if there are no listeners. |
|
if ( !GetActiveVProfile()->IsEnabled() ) |
|
return; |
|
|
|
if ( m_Times.Count() < GetActiveVProfile()->GetNumBudgetGroups() ) |
|
{ |
|
m_Times.SetSize( GetActiveVProfile()->GetNumBudgetGroups() ); |
|
} |
|
|
|
memset( m_Times.Base(), 0, sizeof( m_Times[0] ) * GetActiveVProfile()->GetNumBudgetGroups() ); |
|
CVProfNode *pNode = GetActiveVProfile()->GetRoot(); |
|
if( pNode && pNode->GetChild() ) |
|
{ |
|
CalculateBudgetGroupTimes_Recursive( pNode->GetChild() ); |
|
} |
|
} |
|
|
|
private: |
|
CUtlVector<float> m_Times; // Times from the most recent snapshot. |
|
int m_nListeners; |
|
int m_BudgetFlagsFilter; // We can only capture one type of filtered data at a time. |
|
bool m_bStart; |
|
bool m_bStop; |
|
}; |
|
|
|
CVProfExport g_VProfExport; |
|
IVProfExport *g_pVProfExport = &g_VProfExport; |
|
|
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVProfExport, IVProfExport, VPROF_EXPORT_INTERFACE_VERSION, g_VProfExport ); |
|
|
|
void VProfExport_SnapshotVProfHistory() |
|
{ |
|
g_VProfExport.SnapshotVProfHistory(); |
|
} |
|
|
|
void VProfExport_StartOrStop() |
|
{ |
|
g_VProfExport.StartOrStop(); |
|
} |
|
|
|
// Used by rpt |
|
void VProfExport_Pause() |
|
{ |
|
g_VProfExport.PauseProfile(); |
|
} |
|
|
|
void VProfExport_Resume() |
|
{ |
|
g_VProfExport.ResumeProfile(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used to point the budget panel at remote data |
|
//----------------------------------------------------------------------------- |
|
void OverrideVProfExport( IVProfExport *pExport ) |
|
{ |
|
if ( g_pVProfExport == &g_VProfExport ) |
|
{ |
|
g_pVProfExport = pExport; |
|
} |
|
} |
|
|
|
void ResetVProfExport( IVProfExport *pExport ) |
|
{ |
|
if ( g_pVProfExport == pExport ) |
|
{ |
|
g_pVProfExport = &g_VProfExport; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Listener to vprof data |
|
//----------------------------------------------------------------------------- |
|
struct VProfListenInfo_t |
|
{ |
|
ra_listener_id m_nListenerId; |
|
float m_flLastSentVProfDataTime; |
|
CUtlVector< CUtlString > m_SentGroups; |
|
|
|
VProfListenInfo_t() : m_flLastSentVProfDataTime( 0.0f ) {} |
|
VProfListenInfo_t( ra_listener_id nListenerId ) : m_nListenerId( nListenerId ), m_flLastSentVProfDataTime( 0.0f ) {} |
|
bool operator==( const VProfListenInfo_t& src ) const { return src.m_nListenerId == m_nListenerId; } |
|
|
|
private: |
|
VProfListenInfo_t( const VProfListenInfo_t& src ); |
|
}; |
|
|
|
static CUtlVector<VProfListenInfo_t> s_VProfListeners; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: serialize and send data to remote listeners |
|
//----------------------------------------------------------------------------- |
|
static int FindSentGroupIndex( VProfListenInfo_t &info, const char *pGroupName ) |
|
{ |
|
int nCount = info.m_SentGroups.Count(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
if ( !Q_strcmp( pGroupName, info.m_SentGroups[i].Get() ) ) |
|
return i; |
|
} |
|
return -1; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: serialize and send data to remote listeners |
|
//----------------------------------------------------------------------------- |
|
void WriteRemoteVProfGroupData( VProfListenInfo_t &info ) |
|
{ |
|
if ( IsX360() ) |
|
return; |
|
|
|
int nGroupCount = g_pVProfileForDisplay->GetNumBudgetGroups(); |
|
int nInitialCount = info.m_SentGroups.Count(); |
|
|
|
// Build list of unsent groups to send |
|
int nSendCount = 0; |
|
int *pIndex = (int*)stackalloc( nGroupCount * sizeof(int) ); |
|
for ( int i = 0; i < nGroupCount; ++i ) |
|
{ |
|
const char *pName = g_pVProfileForDisplay->GetBudgetGroupName( i ); |
|
if ( FindSentGroupIndex( info, pName ) >= 0 ) |
|
continue; |
|
int j = info.m_SentGroups.AddToTail(); |
|
info.m_SentGroups[j] = pName; |
|
pIndex[nSendCount++] = i; |
|
} |
|
|
|
if ( nSendCount == 0 ) |
|
return; |
|
|
|
CUtlBuffer buf( 1024, 1024 ); |
|
buf.PutInt( nInitialCount ); |
|
buf.PutInt( nSendCount ); |
|
|
|
for ( int i=0; i < nSendCount; i++ ) |
|
{ |
|
int nIndex = pIndex[i]; |
|
int red, green, blue, alpha; |
|
g_pVProfileForDisplay->GetBudgetGroupColor( nIndex, red, green, blue, alpha ); |
|
buf.PutUnsignedChar( (unsigned char)red ); |
|
buf.PutUnsignedChar( (unsigned char)green ); |
|
buf.PutUnsignedChar( (unsigned char)blue ); |
|
buf.PutUnsignedChar( (unsigned char)alpha ); |
|
|
|
const char *pName = g_pVProfileForDisplay->GetBudgetGroupName( nIndex ); |
|
buf.PutString( pName ); |
|
} |
|
|
|
g_ServerRemoteAccess.SendVProfData( info.m_nListenerId, true, buf.Base(), buf.TellMaxPut() ); |
|
} |
|
|
|
static ConVar rpt_vprof_time( "rpt_vprof_time","0.25", FCVAR_HIDDEN | FCVAR_DONTRECORD, "" ); |
|
void WriteRemoteVProfData() |
|
{ |
|
if ( IsX360() ) |
|
return; |
|
|
|
// Throttle sending too much data |
|
float flMaxDelta = rpt_vprof_time.GetFloat(); |
|
float flTime = Plat_FloatTime(); |
|
bool bShouldSend = false; |
|
int nListenerCount = s_VProfListeners.Count(); |
|
for( int i = 0; i < nListenerCount; i++ ) |
|
{ |
|
if ( flTime - s_VProfListeners[i].m_flLastSentVProfDataTime >= flMaxDelta ) |
|
{ |
|
bShouldSend = true; |
|
break; |
|
} |
|
} |
|
|
|
if ( !bShouldSend ) |
|
return; |
|
|
|
int nGroupCount = g_pVProfileForDisplay->GetNumBudgetGroups(); |
|
int nBufSize = nGroupCount * sizeof(float); |
|
float *pTimes = (float*)stackalloc( nBufSize ); |
|
g_VProfExport.GetAllBudgetGroupTimes( pTimes ); |
|
|
|
for( int i = 0; i < nListenerCount; i++ ) |
|
{ |
|
if ( flTime - s_VProfListeners[i].m_flLastSentVProfDataTime < flMaxDelta ) |
|
continue; |
|
|
|
WriteRemoteVProfGroupData( s_VProfListeners[i] ); |
|
s_VProfListeners[i].m_flLastSentVProfDataTime = flTime; |
|
|
|
// Re-order send times to match send group order |
|
int nSentSize = s_VProfListeners[i].m_SentGroups.Count() * sizeof(float); |
|
float *pSentTimes = (float*)stackalloc( nSentSize ); |
|
memset( pSentTimes, 0, nSentSize ); |
|
for ( int j = 0; j < nGroupCount; ++j ) |
|
{ |
|
int nIndex = FindSentGroupIndex( s_VProfListeners[i], g_pVProfileForDisplay->GetBudgetGroupName( j ) ); |
|
Assert( nIndex >= 0 ); |
|
pSentTimes[ nIndex ] = pTimes[j]; |
|
} |
|
g_ServerRemoteAccess.SendVProfData( s_VProfListeners[i].m_nListenerId, false, pSentTimes, nSentSize ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: add a new endpoint to send data to |
|
//----------------------------------------------------------------------------- |
|
void RegisterVProfDataListener( ra_listener_id listenerID ) |
|
{ |
|
RemoveVProfDataListener( listenerID ); |
|
int nIndex = s_VProfListeners.AddToTail( ); |
|
s_VProfListeners[nIndex].m_nListenerId = listenerID; |
|
g_VProfExport.AddListener(); |
|
WriteRemoteVProfGroupData( s_VProfListeners[nIndex] ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: remove an endpoint we are sending data to |
|
//----------------------------------------------------------------------------- |
|
void RemoveVProfDataListener( ra_listener_id listenerID ) |
|
{ |
|
VProfListenInfo_t findInfo( listenerID ); |
|
if ( s_VProfListeners.FindAndRemove( findInfo ) ) |
|
{ |
|
g_VProfExport.RemoveListener(); |
|
} |
|
} |
|
|
|
#endif |
|
|
|
|