//===== Copyright 1996-2013, Valve Corporation, All rights reserved. ======// // // Purpose: // //===========================================================================// #ifdef _WIN32 #if !defined(_X360) #include "winlite.h" #endif #include "tier0/memdbgon.h" // needed because in release builds crtdbg.h is handled specially if USE_MEM_DEBUG is defined #include "tier0/memdbgoff.h" #include // For getting at current heap size #endif #include "tier0/vprof.h" #include "tier0/etwprof.h" #include "tier0/icommandline.h" #include "tier0/systeminformation.h" #include "tier1/utlbuffer.h" #include "tier1/keyvalues.h" #include "matchmaking/imatchframework.h" #include "edict.h" #include "cdll_engine_int.h" #include "igame.h" #include "host_cmd.h" #include "status.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //----------------------------------------------------------------------------- class CKeyValuesDumpForStatusReport : public IKeyValuesDumpContextAsText { public: CKeyValuesDumpForStatusReport( CUtlBuffer &buffer ) : m_buffer( buffer ) {} public: virtual bool KvWriteText( char const *szText ) { m_buffer.Printf( "%s", szText ); return true; } protected: CUtlBuffer &m_buffer; }; //----------------------------------------------------------------------------- static char g_szCombinedStatusBuffer[10240] = {0}; static char g_szMinidumpInfoBuffer[4094] = {0}; // 4094 because ETW marks are limited to this. static char g_szHostStatusBuffer[1024] = {0}; static char g_szMemoryStatusBuffer[1024] = {0}; static char g_szMatchmakingStatusBuffer[4094] = {0}; // 4094 because ETW marks are limited to this. static char* g_szAudioStatusBuffer; // Returned by audio code extern CGlobalVars g_ServerGlobalVariables; extern int gHostSpawnCount; extern int g_nMapLoadCount; #ifdef DEDICATED PAGED_POOL_INFO_t g_pagedpoolinfo; char g_minidumpinfo[ 4094 ] = {0}; #else extern PAGED_POOL_INFO_t g_pagedpoolinfo; extern char g_minidumpinfo[ 4094 ]; #endif extern bool g_bUpdateMinidumpComment; static PAGED_POOL_INFO_t final; //----------------------------------------------------------------------------- static void Status_UpdateMemoryStatus( bool bIncludeFullMemoryInfo ) { g_szMemoryStatusBuffer[0] = 0; CUtlBuffer buf( g_szMemoryStatusBuffer, sizeof(g_szMemoryStatusBuffer), CUtlBuffer::TEXT_BUFFER ); if ( bIncludeFullMemoryInfo ) { #ifdef _WIN32 MEMORYSTATUSEX memStat; ZeroMemory(&memStat, sizeof(MEMORYSTATUSEX)); memStat.dwLength = sizeof(MEMORYSTATUSEX); if ( GlobalMemoryStatusEx( &memStat ) ) { double MbDiv = 1024.0 * 1024.0; buf.Printf( "Windows OS memory status:\nmemusage( %d %% )\ntotalPhysical Mb(%.2f)\nfreePhysical Mb(%.2f)\ntotalPaging Mb(%.2f)\nfreePaging Mb(%.2f)\ntotalVirtualMem Mb(%.2f)\nfreeVirtualMem Mb(%.2f)\nextendedVirtualFree Mb(%.2f)\n\n", memStat.dwMemoryLoad, (double)memStat.ullTotalPhys/MbDiv, (double)memStat.ullAvailPhys/MbDiv, (double)memStat.ullTotalPageFile/MbDiv, (double)memStat.ullAvailPageFile/MbDiv, (double)memStat.ullTotalVirtual/MbDiv, (double)memStat.ullAvailVirtual/MbDiv, (double)memStat.ullAvailExtendedVirtual/MbDiv); } #endif } if ( g_pMemAlloc->MemoryAllocFailed() ) { buf.Printf( "*** Memory allocation failed for %d bytes! ***\n", g_pMemAlloc->MemoryAllocFailed() ); } GenericMemoryStat_t *pMemoryStats = NULL; int nMemoryStatCount = g_pMemAlloc->GetGenericMemoryStats( &pMemoryStats ); if ( nMemoryStatCount > 0 ) { buf.Printf( "g_pMemAlloc->GetGenericMemoryStats(): %d\n", nMemoryStatCount ); for ( int i = 0; i < nMemoryStatCount; i++ ) { buf.Printf( "%d. %s : %d \n", i + 1, pMemoryStats[i].name, pMemoryStats[i].value ); } } buf.Printf( "\n" ); } static void Status_UpdateMinidumpCommentBuffer() { g_szMinidumpInfoBuffer[0] = 0; CUtlBuffer minidumpInfoBuffer( g_szMinidumpInfoBuffer, sizeof(g_szMinidumpInfoBuffer), CUtlBuffer::TEXT_BUFFER ); minidumpInfoBuffer.Printf( "Uptime: %f\n", Plat_FloatTime() ); if ( g_minidumpinfo[0] != 0 ) { minidumpInfoBuffer.Printf( "%s", g_minidumpinfo ); } minidumpInfoBuffer.Printf( "Active: %s\nSpawnCount %d\nMapLoad Count %d\n", ( game && game->IsActiveApp() ) ? "active" : "inactive", gHostSpawnCount, g_nMapLoadCount ); } static CUtlBuffer *s_pStatusBuffer = NULL; static void Status_PrintCallback( const char *fmt, ... ) { if ( s_pStatusBuffer ) { va_list argptr; va_start (argptr,fmt); s_pStatusBuffer->VaPrintf( fmt, argptr ); va_end (argptr); } } static void Status_UpdateHostStatusBuffer() { g_szHostStatusBuffer[0] = 0; CUtlBuffer hostStatusBuffer( g_szHostStatusBuffer, sizeof(g_szHostStatusBuffer), CUtlBuffer::TEXT_BUFFER ); s_pStatusBuffer = &hostStatusBuffer; Host_PrintStatus( kCommandSrcCode, Status_PrintCallback, false ); s_pStatusBuffer = NULL; } static void Status_UpdateMatchmakingBuffer() { // matchmaking status text g_szMatchmakingStatusBuffer[0] = 0; if ( g_pMatchFramework ) { if ( IMatchSession *pMatchSession = g_pMatchFramework->GetMatchSession() ) { CUtlBuffer matchmakingStatusBuffer( g_szMatchmakingStatusBuffer, sizeof(g_szMatchmakingStatusBuffer), CUtlBuffer::TEXT_BUFFER ); matchmakingStatusBuffer.Printf( "match session %p\n", pMatchSession ); CKeyValuesDumpForStatusReport kvDumpContext( matchmakingStatusBuffer ); matchmakingStatusBuffer.Printf( "session system data:\n" ); pMatchSession->GetSessionSystemData()->Dump( &kvDumpContext ); matchmakingStatusBuffer.Printf( "session settings:\n" ); pMatchSession->GetSessionSettings()->Dump( &kvDumpContext ); } } if ( !g_szMatchmakingStatusBuffer[0] ) { V_sprintf_safe( g_szMatchmakingStatusBuffer, "No matchmaking.\n" ); } } static void ConvertNewlinesToTabsinPlace( char *pBuffer ) { while( pBuffer && *pBuffer != 0 ) { pBuffer = V_strstr(pBuffer, "\n"); if ( pBuffer ) { *pBuffer = '\t'; } } } static void Status_UpdateBuffers( bool bForETW ) { Status_UpdateMinidumpCommentBuffer(); Status_UpdateMemoryStatus( !bForETW ); Status_UpdateHostStatusBuffer(); Status_UpdateMatchmakingBuffer(); #ifndef DEDICATED extern char* Status_UpdateAudioBuffer(); g_szAudioStatusBuffer = Status_UpdateAudioBuffer(); #else static char chNoAudio[8] = { 'n', '/', 'a', 0 }; g_szAudioStatusBuffer = chNoAudio; #endif if ( bForETW ) { // walk through buffers converting \n to \t ConvertNewlinesToTabsinPlace( g_szMinidumpInfoBuffer ); ConvertNewlinesToTabsinPlace( g_szMemoryStatusBuffer ); ConvertNewlinesToTabsinPlace( g_szHostStatusBuffer ); ConvertNewlinesToTabsinPlace( g_szMatchmakingStatusBuffer ); ConvertNewlinesToTabsinPlace( g_szAudioStatusBuffer ); } } static void Status_UpdateCombinedBuffer() { Status_UpdateBuffers( false ); g_szCombinedStatusBuffer[0] = 0; CUtlBuffer combinedStatusBuffer( g_szCombinedStatusBuffer, sizeof(g_szCombinedStatusBuffer), CUtlBuffer::TEXT_BUFFER ); combinedStatusBuffer.Printf( "\nGame Info:\n%s", g_szMinidumpInfoBuffer ); combinedStatusBuffer.Printf( "\nMemory Info:\n%s", g_szMemoryStatusBuffer ); combinedStatusBuffer.Printf( "\nHost Info:\n%s", g_szHostStatusBuffer ); combinedStatusBuffer.Printf( "\nMatchmaking Info:\n%s", g_szMatchmakingStatusBuffer ); combinedStatusBuffer.Printf( "\nAudio Info:\n%s", g_szAudioStatusBuffer ); } // public functions const char *Status_GetBuffer() { return g_szCombinedStatusBuffer; } void Status_Update() { Status_UpdateCombinedBuffer(); } void Status_CheckSendETWMark() { // If ETW (Event Tracing for Windows) is available, // collect status text every 5 seconds #ifdef ETW_MARKS_ENABLED // Don't emit status events if nobody is listening. if ( ETWIsTracingEnabled() ) { static double fLastETWUpdateTime = -5.0; double fCurrentTime = Plat_FloatTime(); // update status buffer every 5 seconds and write to vtrace/xperf mark if ( fCurrentTime - fLastETWUpdateTime > 5.0 ) { fLastETWUpdateTime = fCurrentTime; Status_UpdateBuffers( true ); ETWMark( g_szMinidumpInfoBuffer ); ETWMark( g_szMemoryStatusBuffer ); ETWMark( g_szHostStatusBuffer ); ETWMark( g_szMatchmakingStatusBuffer ); ETWMark( g_szAudioStatusBuffer ); } } #endif }