#include "stdafx.h" #include "System.h" #include //#include "ini_vars.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _XBOX #ifdef WIN32 #define WIN32_LEAN_AND_MEAN #include "windows.h" #endif #else #include #endif #include "SourceSafeHelper.h" // _GetSSFileInfo #ifdef WIN32 #include "DebugCallStack.h" #endif #ifdef WIN32 #include "luadebugger/luadbginterface.h" #include "luadebugger/LuaDbg.h" #endif #include "XConsole.h" #include "CrySizerStats.h" #include "CrySizerImpl.h" // this is the list of modules that can be loaded into the game process // Each array element contains 2 strings: the name of the module (case-insensitive) // and the name of the group the module belongs to ////////////////////////////////////////////////////////////////////////// const char g_szGroupCore[] = "CryENGINE"; const char g_szGroupGame[] = "CryGAME"; const char g_szGroupSupport[] = "System"; const char g_szGroupWindows[] = "System"; const char* g_szModuleGroups[][2] = { {"FarCry.exe", g_szGroupGame}, {"FarCry_WinSV.exe", g_szGroupGame}, {"Editor.exe", g_szGroupGame}, {"CrySystem.dll", g_szGroupCore}, {"CryScriptSystem.dll", g_szGroupGame}, {"CryNetwork.dll", g_szGroupGame}, {"CryPhysics.dll", g_szGroupCore}, {"CryMovie.dll", g_szGroupCore}, {"CryInput.dll", g_szGroupCore}, {"CrySoundSystem.dll", g_szGroupCore}, #ifdef WIN64 {"crysound64.dll", g_szGroupCore}, #else {"crysound.dll", g_szGroupCore}, #endif {"CryFont.dll", g_szGroupCore}, {"CryAISystem.dll", g_szGroupGame}, {"CryEntitySystem.dll", g_szGroupCore}, {"Cry3DEngine.dll", g_szGroupCore}, {"CryGame.dll", g_szGroupGame}, {"CryAnimation.dll", g_szGroupCore}, {"XRenderD3D9.dll", g_szGroupCore}, {"XRenderOGL.dll", g_szGroupCore}, {"XRenderNULL.dll", g_szGroupCore} }; ////////////////////////////////////////////////////////////////////////// void CSystem::SetAffinity() { // the following code is only for Windows #ifdef WIN32 // set the process affinity ICVar* pcvAffinityMask = GetIConsole()->GetCVar("sys_affinity"); if (!pcvAffinityMask) pcvAffinityMask = GetIConsole()->CreateVariable("sys_affinity","0", 0); if (pcvAffinityMask) { unsigned nAffinity = pcvAffinityMask->GetIVal(); if (nAffinity) { typedef BOOL (WINAPI *FnSetProcessAffinityMask)(IN HANDLE hProcess,IN DWORD_PTR dwProcessAffinityMask); HMODULE hKernel = CryLoadLibrary ("kernel32.dll"); if (hKernel) { FnSetProcessAffinityMask SetProcessAffinityMask = (FnSetProcessAffinityMask)GetProcAddress(hKernel, "SetProcessAffinityMask"); if (SetProcessAffinityMask && !SetProcessAffinityMask(GetCurrentProcess(), nAffinity)) GetILog()->LogToFile ("\003Error: Cannot set affinity mask %d, error code %d", nAffinity, GetLastError()); FreeLibrary (hKernel); } } } #endif } //! dumps the memory usage statistics to the log ////////////////////////////////////////////////////////////////////////// void CSystem::DumpMemoryUsageStatistics() { TickMemStats(nMSP_ForDump); /* CrySizerImpl Sizer; CrySizerStats MemStats; MemStats.startTimer(0,GetITimer()); CollectMemStats (&Sizer); MemStats.stopTimer(0,GetITimer()); MemStats.startTimer(1,GetITimer()); CrySizerStatsBuilder builder (&Sizer); builder.build (&MemStats); MemStats.stopTimer(1,GetITimer()); MemStats.startTimer(2,GetITimer()); Sizer.clear(); MemStats.stopTimer(2,GetITimer()); */ CrySizerStatsRenderer StatsRenderer (this, m_pMemStats, 10, 0); StatsRenderer.dump(); // since we've recalculated this mem stats for dumping, we'll want to calculate it anew the next time it's rendered SAFE_DELETE(m_pMemStats); } // collects the whole memory statistics into the given sizer object ////////////////////////////////////////////////////////////////////////// void CSystem::CollectMemStats (CrySizerImpl* pSizer, MemStatsPurposeEnum nPurpose) { #ifdef WIN32 { SIZER_COMPONENT_NAME(pSizer, "Code"); GetExeSizes (pSizer, nPurpose); } #endif { SIZER_COMPONENT_NAME(pSizer, "VFS"); if (m_pStreamEngine) { SIZER_COMPONENT_NAME(pSizer, "Stream Engine"); m_pStreamEngine->GetMemoryStatistics(pSizer); } if (m_pIPak) { SIZER_COMPONENT_NAME(pSizer, "CryPak"); m_pIPak->GetMemoryStatistics(pSizer); } } if (m_pI3DEngine) { SIZER_COMPONENT_NAME(pSizer, "3DEngine"); m_pI3DEngine->GetMemoryUsage (pSizer); } if (m_pICryCharManager) { SIZER_COMPONENT_NAME(pSizer, "Animation"); m_pICryCharManager->GetMemoryUsage(pSizer); } if (m_pIPhysicalWorld) { SIZER_COMPONENT_NAME(pSizer, "Physics"); m_pIPhysicalWorld->GetMemoryStatistics (pSizer); } assert (m_pRenderer); { SIZER_COMPONENT_NAME(pSizer, "Renderer"); m_pRenderer->GetMemoryUsage (pSizer); } if (m_pICryFont) { SIZER_COMPONENT_NAME(pSizer, "Fonts"); m_pICryFont->GetMemoryUsage(pSizer); } if (m_pConsole) { SIZER_COMPONENT_NAME (pSizer, "Console"); m_pConsole->GetMemoryUsage (pSizer); } if (m_pISound) { SIZER_COMPONENT_NAME(pSizer, "Sound"); m_pISound->GetMemoryUsage(pSizer); } if (m_pIMusic) { SIZER_COMPONENT_NAME(pSizer, "Music"); m_pIMusic->GetMemoryUsage(pSizer); } if (m_pScriptSystem) { SIZER_COMPONENT_NAME(pSizer, "Script"); m_pScriptSystem->GetMemoryStatistics(pSizer); } if (m_pAISystem) { SIZER_COMPONENT_NAME(pSizer, "AI"); m_pAISystem->GetMemoryStatistics (pSizer); } if (m_pGame) { SIZER_COMPONENT_NAME(pSizer, "Game"); m_pGame->GetMemoryStatistics (pSizer); } if (m_pNetwork) { SIZER_COMPONENT_NAME(pSizer, "Network"); m_pNetwork->GetMemoryStatistics(pSizer); } if (m_pEntitySystem) { SIZER_COMPONENT_NAME(pSizer, "Entities"); m_pEntitySystem->GetMemoryStatistics(pSizer); } pSizer->end(); } ////////////////////////////////////////////////////////////////////////// const char *CSystem::GetUserName() { static char szNameBuffer[1024]; memset(szNameBuffer, 0, 1024); DWORD dwSize = 1024; ::GetUserName(szNameBuffer, &dwSize); return szNameBuffer; } // refreshes the m_pMemStats if necessary; creates it if it's not created ////////////////////////////////////////////////////////////////////////// void CSystem::TickMemStats(MemStatsPurposeEnum nPurpose) { // gather the statistics, if required // if there's no object, or if it's time to recalculate, or if it's for dump, then recalculate it if (!m_pMemStats || (m_pRenderer->GetFrameID()%m_cvMemStats->GetIVal())==0 || nPurpose == nMSP_ForDump) { if (!m_pMemStats) { if (m_cvMemStats->GetIVal() < 4 && m_cvMemStats->GetIVal()) GetILog()->LogToConsole ("memstats is too small (%d). Performnce impact can be significant. Please set to a greater value.",m_cvMemStats->GetIVal()); m_pMemStats = new CrySizerStats(); } if (!m_pSizer) m_pSizer = new CrySizerImpl(); m_pMemStats->startTimer(0,GetITimer()); CollectMemStats (m_pSizer,nPurpose); m_pMemStats->stopTimer(0,GetITimer()); m_pMemStats->startTimer(1,GetITimer()); CrySizerStatsBuilder builder (m_pSizer); builder.build (m_pMemStats); m_pMemStats->stopTimer(1,GetITimer()); m_pMemStats->startTimer(2,GetITimer()); m_pSizer->clear(); m_pMemStats->stopTimer(2,GetITimer()); } else m_pMemStats->incAgeFrames(); } //#define __HASXP // these 2 functions are duplicated in System.cpp in editor ////////////////////////////////////////////////////////////////////////// #if !defined(LINUX) extern int CryStats(char *buf); #endif int CSystem::DumpMMStats(bool log) { #if defined(LINUX) return 0; #else if(log) { char buf[1024]; int n = CryStats(buf); GetILog()->Log(buf); return n; } else { return CryStats(NULL); }; #endif }; ////////////////////////////////////////////////////////////////////////// struct CryDbgModule { HANDLE heap; WIN_HMODULE handle; string name; DWORD dwSize; }; ////////////////////////////////////////////////////////////////////////// void CSystem::DebugStats(bool checkpoint, bool leaks) { #ifdef WIN32 std::vector dbgmodules; /* { { NULL, (WIN_HMODULE)GetModuleHandle("CrySystem.dll"), "SYSTEM"}, #ifdef _WIN32 { NULL, (WIN_HMODULE)GetModuleHandle("Editor.exe"), "EDITOR"}, { NULL, (WIN_HMODULE)GetModuleHandle("FarCry.exe"), "FARCRY"}, #endif { NULL, m_dll.hNetwork, "NETWORK" }, { NULL, m_dll.hGame, "GAME" }, { NULL, m_dll.hAI, "AI" }, { NULL, m_dll.hEntitySystem, "ENTITY" }, { NULL, m_dll.hRenderer, "RENDERER" }, { NULL, m_dll.hInput, "INPUT" }, { NULL, m_dll.hSound, "SOUND" }, { NULL, m_dll.hPhysics, "PHYSICS" }, { NULL, m_dll.hFont, "FONT" }, { NULL, m_dll.hScript, "SCRIPT" }, { NULL, m_dll.h3DEngine, "3DENGINE" }, //{ NULL, (WIN_HMODULE)GetModuleHandle("CrySystem.dll"), "SYSTEM"}, { NULL, m_dll.hAnimation, "ANIMATION" }, #ifdef WIN64 { NULL, LoadDLL("crysound64.dll"), "FMOD" } // temp! #else { NULL, LoadDLL("crysound.dll"), "FMOD" } // temp! #endif // missing: OPENGL }; */ ////////////////////////////////////////////////////////////////////////// // Use windows Performance Monitoring API to enumerate all modules of current process. ////////////////////////////////////////////////////////////////////////// HANDLE hSnapshot; hSnapshot = CreateToolhelp32Snapshot (TH32CS_SNAPMODULE, 0); if (hSnapshot != INVALID_HANDLE_VALUE) { MODULEENTRY32 me; memset (&me, 0, sizeof(me)); me.dwSize = sizeof(me); if (Module32First (hSnapshot, &me)) { // the sizes of each module group do { CryDbgModule module; module.handle = me.hModule; module.name = me.szModule; module.dwSize = me.modBaseSize; dbgmodules.push_back( module ); } while(Module32Next (hSnapshot, &me)); } CloseHandle (hSnapshot); } ////////////////////////////////////////////////////////////////////////// ILog *log = GetILog(); int totalal = 0, totalbl = 0, nolib = 0; #ifdef _DEBUG int extrastats[10]; #endif int totalUsedInModules = 0; int countedMemoryModules = 0; for(int i = 0; i < (int)(dbgmodules.size()); i++) { if(!dbgmodules[i].handle) { CryLogAlways( "WARNING: CSystem::DebugStats: NULL handle for %s", dbgmodules[i].name.c_str() ); nolib++; continue; }; typedef int (*PFN_MODULEMEMORY)(); PFN_MODULEMEMORY fpCryModuleGetAllocatedMemory = (PFN_MODULEMEMORY)::GetProcAddress(dbgmodules[i].handle, "CryModuleGetAllocatedMemory"); if (fpCryModuleGetAllocatedMemory) { int allocatedMemory = fpCryModuleGetAllocatedMemory(); totalUsedInModules += allocatedMemory; countedMemoryModules++; CryLogAlways("%8d K used in Module %s: ",allocatedMemory/1024,dbgmodules[i].name.c_str() ); } #ifdef _DEBUG typedef void (*PFNUSAGESUMMARY)(ILog *log, const char *, int *); typedef void (*PFNCHECKPOINT)(); PFNUSAGESUMMARY fpu = (PFNUSAGESUMMARY)::GetProcAddress(dbgmodules[i].handle, "UsageSummary"); PFNCHECKPOINT fpc = (PFNCHECKPOINT)::GetProcAddress(dbgmodules[i].handle, "CheckPoint"); if(fpu && fpc) { if(checkpoint) fpc(); else { extrastats[2] = (int)leaks; fpu(log, dbgmodules[i].name.c_str(), extrastats); totalal += extrastats[0]; totalbl += extrastats[1]; }; } else { CryLogAlways( "WARNING: CSystem::DebugStats: could not retrieve function from DLL %s", dbgmodules[i].name.c_str()); nolib++; }; #endif typedef HANDLE(*PFNGETDLLHEAP)(); PFNGETDLLHEAP fpg = (PFNGETDLLHEAP)::GetProcAddress(dbgmodules[i].handle, "GetDLLHeap"); if(fpg) { dbgmodules[i].heap = fpg(); }; }; CryLogAlways("-------------------------------------------------------" ); CryLogAlways("%8d K Total Memory Allocated in %d Modules",totalUsedInModules/1024,countedMemoryModules ); #ifdef _DEBUG CryLogAlways("$8GRAND TOTAL: %d k, %d blocks (%d dlls not included)", totalal/1024, totalbl, nolib); CryLogAlways("estimated debugalloc overhead: between %d k and %d k", totalbl*36/1024, totalbl*72/1024); #endif ////////////////////////////////////////////////////////////////////////// // Get HeapQueryInformation pointer if on windows XP. ////////////////////////////////////////////////////////////////////////// typedef BOOL (WINAPI *FUNC_HeapQueryInformation)( HANDLE,HEAP_INFORMATION_CLASS,PVOID,SIZE_T,PSIZE_T ); FUNC_HeapQueryInformation pFnHeapQueryInformation = NULL; HMODULE hKernelInstance = CryLoadLibrary(_T("Kernel32.dll")); if (hKernelInstance) { pFnHeapQueryInformation = (FUNC_HeapQueryInformation)(::GetProcAddress(hKernelInstance,"HeapQueryInformation" )); } ////////////////////////////////////////////////////////////////////////// const int MAXHANDLES = 100; HANDLE handles[MAXHANDLES]; int realnumh = GetProcessHeaps(MAXHANDLES, handles); char hinfo[1024]; PROCESS_HEAP_ENTRY phe; CryLogAlways("$6--------------------- dump of windows heaps ---------------------"); int nTotalC = 0, nTotalCP = 0, nTotalUC = 0, nTotalUCP = 0, totalo = 0; for(int i = 0; i StringToSizeMap; void AddSize (StringToSizeMap& mapSS, const char* szString, unsigned nSize) { StringToSizeMap::iterator it = mapSS.find (szString); if (it == mapSS.end()) mapSS.insert (StringToSizeMap::value_type(szString, nSize)); else it->second += nSize; } ////////////////////////////////////////////////////////////////////////// const char* GetModuleGroup (const char* szString) { for (unsigned i = 0; i < sizeof(g_szModuleGroups)/sizeof(g_szModuleGroups[0]); ++i) if (stricmp(szString, g_szModuleGroups[i][0]) == 0) return g_szModuleGroups[i][1]; return "Other"; } ////////////////////////////////////////////////////////////////////////// void CSystem::GetExeSizes (ICrySizer* pSizer, MemStatsPurposeEnum nPurpose) { HANDLE hSnapshot; hSnapshot = CreateToolhelp32Snapshot (TH32CS_SNAPMODULE, 0); if (hSnapshot == INVALID_HANDLE_VALUE) { CryLogAlways ("Cannot get the module snapshot, error code %d", GetLastError()); return; } DWORD dwProcessID = GetCurrentProcessId(); MODULEENTRY32 me; memset (&me, 0, sizeof(me)); me.dwSize = sizeof(me); if (Module32First (hSnapshot, &me)) { // the sizes of each module group StringToSizeMap mapGroupSize; DWORD dwTotalModuleSize = 0; do { dwProcessID = me.th32ProcessID; const char* szGroup = GetModuleGroup (me.szModule); SIZER_COMPONENT_NAME(pSizer, szGroup); if (nPurpose == nMSP_ForDump) { SIZER_COMPONENT_NAME(pSizer, me.szModule); pSizer->AddObject(me.modBaseAddr, me.modBaseSize); } else pSizer->AddObject(me.modBaseAddr, me.modBaseSize); } while(Module32Next (hSnapshot, &me)); } else CryLogAlways ("No modules to dump"); CloseHandle (hSnapshot); } #endif ////////////////////////////////////////////////////////////////////////// void CSystem::DumpWinHeaps() { #ifdef WIN32 // // Retrieve modules and log them; remember the process id HANDLE hSnapshot; hSnapshot = CreateToolhelp32Snapshot (TH32CS_SNAPMODULE, 0); if (hSnapshot == INVALID_HANDLE_VALUE) { CryLogAlways ("Cannot get the module snapshot, error code %d", GetLastError()); return; } DWORD dwProcessID = GetCurrentProcessId(); MODULEENTRY32 me; memset (&me, 0, sizeof(me)); me.dwSize = sizeof(me); if (Module32First (hSnapshot, &me)) { // the sizes of each module group StringToSizeMap mapGroupSize; DWORD dwTotalModuleSize = 0; CryLogAlways ("base size module"); do { dwProcessID = me.th32ProcessID; const char* szGroup = GetModuleGroup (me.szModule); CryLogAlways ("%08X %8X %25s - %s", me.modBaseAddr, me.modBaseSize, me.szModule, stricmp(szGroup,"Other")?szGroup:""); dwTotalModuleSize += me.modBaseSize; AddSize (mapGroupSize, szGroup, me.modBaseSize); } while(Module32Next (hSnapshot, &me)); CryLogAlways ("------------------------------------"); for (StringToSizeMap::iterator it = mapGroupSize.begin(); it != mapGroupSize.end(); ++it) CryLogAlways (" %6.3f Mbytes - %s", double(it->second)/0x100000, it->first); CryLogAlways ("------------------------------------"); CryLogAlways (" %6.3f Mbytes - TOTAL", double(dwTotalModuleSize)/0x100000); CryLogAlways ("------------------------------------"); } else CryLogAlways ("No modules to dump"); CloseHandle (hSnapshot); // // Retrieve the heaps and dump each of them with a special function hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST, 0); if (hSnapshot == INVALID_HANDLE_VALUE) { CryLogAlways ("Cannot get the heap LIST snapshot, error code %d", GetLastError()); return; } HEAPLIST32 hl; memset (&hl, 0, sizeof(hl)); hl.dwSize = sizeof(hl); CryLogAlways ("__Heap__ fixed free move (unknown)"); if (Heap32ListFirst (hSnapshot, &hl)) { DumpHeap32Stats stats; do { DumpHeap32 (hl, stats); } while(Heap32ListNext (hSnapshot,&hl)); CryLogAlways ("-------------------------------------------------"); CryLogAlways ("$6 %6.3f %6.3f %6.3f (%.3f) Mbytes", double(stats.dwFixed)/0x100000, double(stats.dwFree)/0x100000, double(stats.dwMoveable)/0x100000, double(stats.dwUnknown)/0x100000); CryLogAlways ("-------------------------------------------------"); } else CryLogAlways ("No heaps to dump"); CloseHandle(hSnapshot); #endif } // Make system error message string ////////////////////////////////////////////////////////////////////////// //! \return pointer to the null terminated error string or 0 static const char* GetLastSystemErrorMessage() { #ifdef WIN32 DWORD dwError = GetLastError(); static char szBuffer[512]; // function will return pointer to this buffer if(dwError) { //#ifdef _XBOX LPVOID lpMsgBuf=0; if(FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &lpMsgBuf, 0, NULL)) { strncpy(szBuffer, (char*)lpMsgBuf, sizeof(szBuffer)); LocalFree(lpMsgBuf); } else return 0; //#else //sprintf(szBuffer, "Win32 ERROR: %i", dwError); //OutputDebugString(szBuffer); //#endif return szBuffer; } #else return 0; #endif //WIN32 return 0; } ////////////////////////////////////////////////////////////////////////// bool CSystem::GetSSFileInfo( const char *inszFileName, char *outszInfo, const DWORD indwBufferSize ) { if(!(m_cvSSInfo && m_cvSSInfo->GetIVal()!=0)) { // SSInfo is deavtivated strcpy(outszInfo,"SourceSafe-Info is deactivated (sys_SSInfo=0)"); assert(indwBufferSize>strlen(outszInfo)+1); return(true); } for(int iDatabase=0;iDatabase<=1;iDatabase++) // search in all databases - if neccessry { const char *szDatabase=0; const char *szRoot=0; // (hard coded) switch(iDatabase) { case 0: // artist sourcesafe file (first try this) szDatabase="\\\\server2\\XIsle\\ArtworkVSS\\srcsafe.ini"; szRoot="$/MasterCD"; break; case 1: // programmer sourcesafe file szDatabase="\\\\server1\\vss\\srcsafe.ini"; szRoot="$/MasterCD_Programmers"; break; } assert(szDatabase); assert(szRoot); const int iSize=256; char name[iSize]; char comment[iSize]; char date[iSize]; // get mastercd directory (like in editor) - can be cleaned up char szMasterCD[_MAX_PATH]; GetCurrentDirectory( _MAX_PATH,szMasterCD ); if(::_GetSSFileInfo( szDatabase, // sourcesafe file szRoot, // project sourcesafe path szMasterCD, // project folder path inszFileName, name,comment,date,iSize)) { if(_snprintf(outszInfo,indwBufferSize,"SourceSafe-Info: name='%s' comment='%s' date='%s'",name,comment,date)<0) { *outszInfo=0; return false; // buffer size exceeded } return true; } } // search in all databases return false; // _GetSSFileInfo failed } ////////////////////////////////////////////////////////////////////////// void CSystem::Error( const char *format,... ) { // format message va_list ArgList; char szBuffer[MAX_WARNING_LENGTH]; const char *sPrefix = "\001CRITICAL ERROR: "; strcpy( szBuffer,sPrefix ); va_start(ArgList, format); _vsnprintf(szBuffer+strlen(sPrefix), MAX_WARNING_LENGTH-strlen(sPrefix), format, ArgList); va_end(ArgList); // get system error message before any attempt to write into log const char * szSysErrorMessage = GetLastSystemErrorMessage(); // write both messages into log if (m_pLog) m_pLog->Log( szBuffer ); if (szSysErrorMessage && m_pLog) m_pLog->Log( " Last System Error: %s",szSysErrorMessage ); bool bHandled = false; if (GetUserCallback()) bHandled = GetUserCallback()->OnError( szBuffer ); // remove verbosity tag since it is not supported by ::MessageBox strcpy(szBuffer,szBuffer+1); #ifdef WIN32 if (!bHandled) ::MessageBox( NULL,szBuffer,"CryEngine Error",MB_OK|MB_ICONERROR|MB_SYSTEMMODAL ); // Dump callstack. DebugCallStack::instance()->LogCallstack(); #endif #ifndef PS2 ::OutputDebugString(szBuffer); #endif //PS2 // try to shutdown renderer (if we crash here - error message will already stay in the log) if(m_pRenderer) m_pRenderer->ShutDown(); // app can not continue #ifdef _DEBUG #if defined(WIN32) && !defined(WIN64) DEBUG_BREAK; #endif #else exit(1); #endif } // tries to log the call stack . for DEBUG purposes only ////////////////////////////////////////////////////////////////////////// void CSystem::LogCallStack() { #if !defined(LINUX) DebugCallStack::instance()->LogCallstack(); #endif }