2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
# include "tier0/platform.h"
# include "tier0/vcrmode.h"
# include "tier0/memalloc.h"
# include "tier0/dbg.h"
# include <algorithm>
# include <vector>
# include <sys/time.h>
# include <sys/resource.h>
# include <unistd.h>
# ifdef OSX
# include <mach/mach.h>
# include <mach/mach_time.h>
# include <stdbool.h>
# include <sys/types.h>
# include <unistd.h>
# include <sys/sysctl.h>
# endif
# ifdef LINUX
# include <time.h>
# include <fcntl.h>
# endif
2021-09-02 17:33:03 +00:00
# ifdef ANDROID
# include <linux/stat.h>
# endif
2020-04-22 16:56:21 +00:00
# include "tier0/memdbgon.h"
// Benchmark mode uses this heavy-handed method
// *** WARNING ***. On Linux gettimeofday returns the system's best guess at
// actual wall clock time and this can go backwards. You need to use
// clock_gettime( CLOCK_MONOTONIC ... ) if this isn't what you want.
// If you want to try using rdtsc for Plat_FloatTime(), enable USE_RDTSC_FOR_FLOATTIME:
//
// Make sure you know what you're doing. This was disabled due to the long startup time, and
// in our testing, even though constant_tsc was set, we couldn't rely on the
// max frequency result returned from CalculateCPUFreq() (ie /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq).
//
// #define USE_RDTSC_FOR_FLOATTIME
extern VCRMode_t g_VCRMode ;
static bool g_bBenchmarkMode = false ;
static double g_FakeBenchmarkTime = 0 ;
static double g_FakeBenchmarkTimeInc = 1.0 / 66.0 ;
# ifdef USE_RDTSC_FOR_FLOATTIME
static bool s_bTimeInitted ;
static bool s_bUseRDTSC ;
static uint64 s_nRDTSCBase ;
static float s_flRDTSCToMicroSeconds ;
static double s_flRDTSCScale ;
# endif // USE_RDTSC_FOR_FLOATTIME
bool Plat_IsInBenchmarkMode ( )
{
return g_bBenchmarkMode ;
}
void Plat_SetBenchmarkMode ( bool bBenchmark )
{
g_bBenchmarkMode = bBenchmark ;
}
# define N_ITERATIONS_OF_RDTSC_TEST_TO_RUN 5 // should be odd
# define TEST_RDTSC_FLOATTIME 0
size_t ApproximateProcessMemoryUsage ( void )
{
/*
From http : //man7.org/linux/man-pages/man5/proc.5.html:
/ proc / [ pid ] / statm
Provides information about memory usage , measured in pages .
The columns are :
size ( 1 ) total program size
( same as VmSize in / proc / [ pid ] / status )
resident ( 2 ) resident set size
( same as VmRSS in / proc / [ pid ] / status )
share ( 3 ) shared pages ( i . e . , backed by a file )
text ( 4 ) text ( code )
lib ( 5 ) library ( unused in Linux 2.6 )
data ( 6 ) data + stack
dt ( 7 ) dirty pages ( unused in Linux 2.6 )
*/
// This returns the resident memory size (RES column in 'top') in bytes.
size_t nRet = 0 ;
FILE * pFile = fopen ( " /proc/self/statm " , " r " ) ;
if ( pFile )
{
size_t nSize , nResident , nShare , nText , nLib_Unused , nDataPlusStack , nDt_Unused ;
if ( fscanf ( pFile , " %zu %zu %zu %zu %zu %zu %zu " , & nSize , & nResident , & nShare , & nText , & nLib_Unused , & nDataPlusStack , & nDt_Unused ) > = 2 )
{
nRet = 4096 * nResident ;
}
fclose ( pFile ) ;
}
return nRet ;
}
# ifdef USE_RDTSC_FOR_FLOATTIME
static void InitTimeSystem ( void )
{
s_bTimeInitted = true ;
// now, see if we can use rdtsc instead. If this is one of the chips with a separate constant clock for rdtsc, we can
FILE * pCpuInfo = fopen ( " /proc/cpuinfo " , " r " ) ;
if ( pCpuInfo )
{
bool bAnyBadCores = false ;
char lbuf [ 2048 ] ;
while ( fgets ( lbuf , sizeof ( lbuf ) , pCpuInfo ) )
{
if ( memcmp ( lbuf , " flags " , 4 ) = = 0 )
{
if ( ! strstr ( lbuf , " constant_tsc " ) )
{
bAnyBadCores = true ;
break ;
}
}
}
fclose ( pCpuInfo ) ;
if ( ! bAnyBadCores )
{
// this system appears to have the proper cpu setup to use rdtsc from reliable timing. Let's either read the cpu frequency from an
// environment variable, or time it ourselves
char const * pEnv = getenv ( " RDTSC_FREQUENCY " ) ;
if ( pEnv )
{
// the environment variable is allowed to hold either a benchmark result, or a string such as "disable"
if ( pEnv & & ( ( pEnv [ 0 ] > ' 9 ' ) | | ( pEnv [ 0 ] < ' 0 ' ) ) )
return ; // leave rdtsc disabled
// the variable holds the number of ticks per microsecond
s_flRDTSCToMicroSeconds = atof ( pEnv ) ;
// sanity check
if ( s_flRDTSCToMicroSeconds > 1.0 )
{
s_bUseRDTSC = true ;
s_flRDTSCScale = 1.0 / ( 1000.0 * 1000.0 * s_flRDTSCToMicroSeconds ) ;
s_nRDTSCBase = Plat_Rdtsc ( ) ;
return ;
}
}
else
{
printf ( " Running a benchmark to measure system clock frequency... \n " ) ;
// run n iterations and use the median
double flRDTSCToMicroSeconds [ N_ITERATIONS_OF_RDTSC_TEST_TO_RUN ] ;
for ( int i = 0 ; i < ARRAYSIZE ( flRDTSCToMicroSeconds ) ; i + + )
{
uint64 stime = Plat_Rdtsc ( ) ;
struct timeval stimeval ;
gettimeofday ( & stimeval , NULL ) ;
sleep ( 1 ) ;
uint64 etime = Plat_Rdtsc ( ) - stime ;
struct timeval etimeval ;
gettimeofday ( & etimeval , NULL ) ;
// subtract timevals to get elapsed microseconds
struct timeval elapsedtimeval ;
timersub ( & etimeval , & stimeval , & elapsedtimeval ) ;
uint64 nUs = 1000000 * elapsedtimeval . tv_sec + elapsedtimeval . tv_usec ;
flRDTSCToMicroSeconds [ i ] = ( etime / nUs ) ;
}
std : : make_heap ( flRDTSCToMicroSeconds , flRDTSCToMicroSeconds + ARRAYSIZE ( flRDTSCToMicroSeconds ) - 1 ) ;
std : : sort_heap ( flRDTSCToMicroSeconds , flRDTSCToMicroSeconds + ARRAYSIZE ( flRDTSCToMicroSeconds ) - 1 ) ;
s_flRDTSCToMicroSeconds = flRDTSCToMicroSeconds [ ARRAYSIZE ( flRDTSCToMicroSeconds ) / 2 ] ;
s_flRDTSCScale = 1.0 / ( 1000.0 * 1000.0 * s_flRDTSCToMicroSeconds ) ;
printf ( " Finished RDTSC test. To prevent the startup delay from this benchmark, set the environment variable RDTSC_FREQUENCY to %f on this system. "
" This value is dependent upon the CPU clock speed and architecture and should be determined separately for each server. The use of this mechanism "
" for timing can be disabled by setting RDTSC_FREQUENCY to 'disabled'. \n " ,
s_flRDTSCToMicroSeconds ) ;
s_nRDTSCBase = Plat_Rdtsc ( ) ;
s_bUseRDTSC = true ;
# if TEST_RDTSC_FLOATTIME
printf ( " RDTSC test results: \n " ) ;
for ( int i = 0 ; i < ARRAYSIZE ( flRDTSCToMicroSeconds ) ; i + + )
printf ( " [%d] = %f \n " , i , flRDTSCToMicroSeconds [ i ] ) ;
printf ( " scale factor = %f \n " , s_flRDTSCScale ) ;
uint64 srdtsc_time = Plat_Rdtsc ( ) ;
for ( int i = 0 ; i < 1000 * 1000 * 10 ; i + + )
{
float p = Plat_FloatTime ( ) ;
}
printf ( " slow = %lld \n " , Plat_Rdtsc ( ) - srdtsc_time ) ;
// now, run a benchmark to see how much this optimization buys us
srdtsc_time = Plat_Rdtsc ( ) ;
for ( int i = 0 ; i < 1000 * 1000 * 10 ; i + + )
{
float p = Plat_FloatTime ( ) ;
}
printf ( " sfast = %lld \n " , Plat_Rdtsc ( ) - srdtsc_time ) ;
# endif
}
}
}
}
static FORCEINLINE void TestTimeSystem ( void )
{
# if TEST_RDTSC_FLOATTIME
// now, test that plat_float time actually works
for ( int t = 0 ; t < 5 ; t + + )
{
float flStartT = Plat_FloatTime ( ) ;
struct timeval stime ;
gettimeofday ( & stime , NULL ) ;
sleep ( 5 ) ;
float flElapsedT = Plat_FloatTime ( ) - flStartT ;
struct timeval etime ;
gettimeofday ( & etime , NULL ) ;
struct timeval dtime ;
timersub ( & etime , & stime , & dtime ) ;
printf ( " plat_float time says %f elapsed. gettimeofday says %f \n " ,
flElapsedT , dtime . tv_sec + dtime . tv_usec / 1000000.0 ) ;
}
# endif
}
# endif // USE_RDTSC_FOR_FLOATTIME
double Plat_FloatTime ( )
{
if ( g_bBenchmarkMode )
{
g_FakeBenchmarkTime + = g_FakeBenchmarkTimeInc ;
return g_FakeBenchmarkTime ;
}
# ifdef OSX
// OSX
static uint64 start_time = 0 ;
static mach_timebase_info_data_t sTimebaseInfo ;
static double conversion = 0.0 ;
if ( ! start_time )
{
start_time = mach_absolute_time ( ) ;
mach_timebase_info ( & sTimebaseInfo ) ;
conversion = 1e-9 * ( double ) sTimebaseInfo . numer / ( double ) sTimebaseInfo . denom ;
}
uint64 now = mach_absolute_time ( ) ;
return ( now - start_time ) * conversion ;
# else
// Linux
static struct timespec start_time = { 0 , 0 } ;
static bool bInitialized = false ;
if ( ! bInitialized )
{
bInitialized = true ;
clock_gettime ( CLOCK_MONOTONIC , & start_time ) ;
}
struct timespec now ;
clock_gettime ( CLOCK_MONOTONIC , & now ) ;
return ( now . tv_sec - start_time . tv_sec ) + ( now . tv_nsec * 1e-9 ) ;
# ifdef USE_RDTSC_FOR_FLOATTIME
if ( ! s_bTimeInitted )
{
InitTimeSystem ( ) ;
TestTimeSystem ( ) ;
}
if ( s_bUseRDTSC )
{
uint64 nTicks = Plat_Rdtsc ( ) - s_nRDTSCBase ;
return ( ( double ) nTicks ) * s_flRDTSCScale ;
}
else
{
struct timeval tp ;
gettimeofday ( & tp , NULL ) ;
if ( VCRGetMode ( ) = = VCR_Disabled )
return ( ( tp . tv_sec - s_nSecBase ) + tp . tv_usec / 1000000.0 ) ;
return VCRHook_Sys_FloatTime ( ( tp . tv_sec - s_nSecBase ) + tp . tv_usec / 1000000.0 ) ;
}
# endif // USE_RDTSC_FOR_FLOATTIME
# endif
}
unsigned int Plat_MSTime ( )
{
if ( g_bBenchmarkMode )
{
g_FakeBenchmarkTime + = g_FakeBenchmarkTimeInc ;
return ( unsigned int ) ( g_FakeBenchmarkTime * 1000.0 ) ;
}
# ifdef USE_RDTSC_FOR_FLOATTIME
if ( ! s_bTimeInitted )
{
InitTimeSystem ( ) ;
TestTimeSystem ( ) ;
}
if ( s_bUseRDTSC )
{
uint64 nTicks = Plat_Rdtsc ( ) - s_nRDTSCBase ;
return 1000.0 * nTicks * s_flRDTSCScale ;
}
else
# endif // USE_RDTSC_FOR_FLOATTIME
{
return ( uint ) ( Plat_FloatTime ( ) * 1000 ) ;
}
}
uint64 Plat_USTime ( )
{
if ( g_bBenchmarkMode )
{
g_FakeBenchmarkTime + = g_FakeBenchmarkTimeInc ;
return ( unsigned int ) ( g_FakeBenchmarkTime * 1000000.0 ) ;
}
# ifdef USE_RDTSC_FOR_FLOATTIME
if ( ! s_bTimeInitted )
{
InitTimeSystem ( ) ;
TestTimeSystem ( ) ;
}
if ( s_bUseRDTSC )
{
uint64 nTicks = Plat_Rdtsc ( ) - s_nRDTSCBase ;
return 1000000.0 * nTicks * s_flRDTSCScale ;
}
else
# endif // USE_RDTSC_FOR_FLOATTIME
{
return ( uint64 ) ( Plat_FloatTime ( ) * 1000000 ) ;
}
}
// Wraps the thread-safe versions of ctime. buf must be at least 26 bytes
char * Plat_ctime ( const time_t * timep , char * buf , size_t bufsize )
{
return ctime_r ( timep , buf ) ;
}
// Wraps the thread-safe versions of gmtime
struct tm * Plat_gmtime ( const time_t * timep , struct tm * result )
{
return gmtime_r ( timep , result ) ;
}
time_t Plat_timegm ( struct tm * timeptr )
{
return timegm ( timeptr ) ;
}
// Wraps the thread-safe versions of localtime
struct tm * Plat_localtime ( const time_t * timep , struct tm * result )
{
return localtime_r ( timep , result ) ;
}
bool vtune ( bool resume )
{
return 0 ;
}
// -------------------------------------------------------------------------------------------------- //
// Memory stuff.
// -------------------------------------------------------------------------------------------------- //
# ifndef NO_HOOK_MALLOC
PLATFORM_INTERFACE void Plat_DefaultAllocErrorFn ( unsigned long size )
{
}
typedef void ( * Plat_AllocErrorFn ) ( unsigned long size ) ;
Plat_AllocErrorFn g_AllocError = Plat_DefaultAllocErrorFn ;
PLATFORM_INTERFACE void * Plat_Alloc ( unsigned long size )
{
void * pRet = MemAlloc_Alloc ( size ) ;
if ( pRet )
{
return pRet ;
}
else
{
g_AllocError ( size ) ;
return 0 ;
}
}
PLATFORM_INTERFACE void * Plat_Realloc ( void * ptr , unsigned long size )
{
void * pRet = g_pMemAlloc - > Realloc ( ptr , size ) ;
if ( pRet )
{
return pRet ;
}
else
{
g_AllocError ( size ) ;
return 0 ;
}
}
PLATFORM_INTERFACE void Plat_Free ( void * ptr )
{
g_pMemAlloc - > Free ( ptr ) ;
}
PLATFORM_INTERFACE void Plat_SetAllocErrorFn ( Plat_AllocErrorFn fn )
{
g_AllocError = fn ;
}
# endif // !NO_HOOK_MALLOC
# if defined( OSX )
// From the Apple tech note: http://developer.apple.com/library/mac/#qa/qa1361/_index.html
bool Plat_IsInDebugSession ( )
{
int junk ;
int mib [ 4 ] ;
struct kinfo_proc info ;
size_t size ;
static int s_IsInDebugSession = - 1 ;
if ( s_IsInDebugSession = = - 1 )
{
// Initialize the flags so that, if sysctl fails for some bizarre
// reason, we get a predictable result.
info . kp_proc . p_flag = 0 ;
// Initialize mib, which tells sysctl the info we want, in this case
// we're looking for information about a specific process ID.
mib [ 0 ] = CTL_KERN ;
mib [ 1 ] = KERN_PROC ;
mib [ 2 ] = KERN_PROC_PID ;
mib [ 3 ] = getpid ( ) ;
// Call sysctl.
size = sizeof ( info ) ;
junk = sysctl ( mib , sizeof ( mib ) / sizeof ( * mib ) , & info , & size , NULL , 0 ) ;
// We're being debugged if the P_TRACED flag is set.
s_IsInDebugSession = ( ( info . kp_proc . p_flag & P_TRACED ) ! = 0 ) ;
}
return ! ! s_IsInDebugSession ;
}
# elif defined( LINUX )
bool Plat_IsInDebugSession ( )
{
// For linux: http://stackoverflow.com/questions/3596781/detect-if-gdb-is-running
// Don't use "if (ptrace(PTRACE_TRACEME, 0, NULL, 0) == -1)" as it means debuggers can't attach.
// Other solutions they mention involve forking. Ugh.
//
// Good solution from Pierre-Loup: Check TracerPid in /proc/self/status.
// from "man proc"
// TracerPid: PID of process tracing this process (0 if not being traced).
int tracerpid = - 1 ;
int fd = open ( " /proc/self/status " , O_RDONLY , S_IRUSR ) ;
if ( fd > = 0 )
{
char buf [ 1024 ] ;
static const char s_TracerPid [ ] = " TracerPid: " ;
int len = read ( fd , buf , sizeof ( buf ) - 1 ) ;
if ( len > 0 )
{
buf [ len ] = 0 ;
const char * str = strstr ( buf , s_TracerPid ) ;
tracerpid = str ? atoi ( str + sizeof ( s_TracerPid ) ) : - 1 ;
}
close ( fd ) ;
}
return ( tracerpid > 0 ) ;
}
# endif // defined( LINUX )
void Plat_DebugString ( const char * psz )
{
printf ( " %s " , psz ) ;
}
static char g_CmdLine [ 2048 ] ;
PLATFORM_INTERFACE void Plat_SetCommandLine ( const char * cmdLine )
{
strncpy ( g_CmdLine , cmdLine , sizeof ( g_CmdLine ) ) ;
g_CmdLine [ sizeof ( g_CmdLine ) - 1 ] = 0 ;
}
PLATFORM_INTERFACE const tchar * Plat_GetCommandLine ( )
{
# ifdef LINUX
if ( ! g_CmdLine [ 0 ] )
{
FILE * fp = fopen ( " /proc/self/cmdline " , " rb " ) ;
if ( fp )
{
size_t nCharRead = 0 ;
// -1 to leave room for the '\0'
nCharRead = fread ( g_CmdLine , sizeof ( g_CmdLine [ 0 ] ) , ARRAYSIZE ( g_CmdLine ) - 1 , fp ) ;
if ( feof ( fp ) & & ! ferror ( fp ) ) // Should have read the whole command line without error
{
Assert ( nCharRead < ARRAYSIZE ( g_CmdLine ) ) ;
2021-11-16 20:46:29 +00:00
for ( uint i = 0 ; i < nCharRead ; i + + )
2020-04-22 16:56:21 +00:00
{
if ( g_CmdLine [ i ] = = ' \0 ' )
g_CmdLine [ i ] = ' ' ;
}
g_CmdLine [ nCharRead ] = ' \0 ' ;
}
fclose ( fp ) ;
}
Assert ( g_CmdLine [ 0 ] ) ;
}
# endif // LINUX
return g_CmdLine ;
}
PLATFORM_INTERFACE const char * Plat_GetCommandLineA ( )
{
return Plat_GetCommandLine ( ) ;
}
PLATFORM_INTERFACE bool GetMemoryInformation ( MemoryInformation * pOutMemoryInfo )
{
# if defined( LINUX ) || defined( OSX )
return false ;
# else
# error "Need to fill out GetMemoryInformation or at least return false for this platform"
# endif
}
PLATFORM_INTERFACE bool Is64BitOS ( )
{
# if defined OSX
return true ;
# elif defined LINUX
FILE * pp = popen ( " uname -m " , " r " ) ;
if ( pp ! = NULL )
{
char rgchArchString [ 256 ] ;
fgets ( rgchArchString , sizeof ( rgchArchString ) , pp ) ;
pclose ( pp ) ;
if ( ! strncasecmp ( rgchArchString , " x86_64 " , strlen ( " x86_64 " ) ) )
return true ;
}
# else
Assert ( ! " implement Is64BitOS " ) ;
# endif
return false ;
}
PLATFORM_INTERFACE void Plat_ExitProcess ( int nCode )
{
_exit ( nCode ) ;
}
static int s_nWatchDogTimerTimeScale = 0 ;
static bool s_bInittedWD = false ;
static int s_WatchdogTime = 0 ;
static Plat_WatchDogHandlerFunction_t s_pWatchDogHandlerFunction ;
static void InitWatchDogTimer ( void )
{
if ( ! strstr ( g_CmdLine , " -nowatchdog " ) )
{
# ifdef _DEBUG
s_nWatchDogTimerTimeScale = 10 ; // debug is slow
# else
s_nWatchDogTimerTimeScale = 1 ;
# endif
}
}
// SIGALRM handler. Used by Watchdog timer code.
static void WatchDogHandler ( int s )
{
Plat_DebugString ( " WatchDog! Server took too long to process (probably infinite loop). \n " ) ;
DebuggerBreakIfDebugging ( ) ;
if ( s_pWatchDogHandlerFunction )
{
s_pWatchDogHandlerFunction ( ) ;
}
else
{
// force a crash
abort ( ) ;
}
}
// watchdog timer support
PLATFORM_INTERFACE void Plat_BeginWatchdogTimer ( int nSecs )
{
if ( ! s_bInittedWD )
{
s_bInittedWD = true ;
InitWatchDogTimer ( ) ;
}
nSecs * = s_nWatchDogTimerTimeScale ;
nSecs = MIN ( nSecs , 5 * 60 ) ; // no more than 5 minutes no matter what
if ( nSecs )
{
s_WatchdogTime = nSecs ;
signal ( SIGALRM , WatchDogHandler ) ;
alarm ( nSecs ) ;
}
}
PLATFORM_INTERFACE void Plat_EndWatchdogTimer ( void )
{
alarm ( 0 ) ;
signal ( SIGALRM , SIG_DFL ) ;
s_WatchdogTime = 0 ;
}
PLATFORM_INTERFACE int Plat_GetWatchdogTime ( void )
{
return s_WatchdogTime ;
}
PLATFORM_INTERFACE void Plat_SetWatchdogHandlerFunction ( Plat_WatchDogHandlerFunction_t function )
{
s_pWatchDogHandlerFunction = function ;
}
# ifndef NO_HOOK_MALLOC
// memory logging this functionality is portable code, except for the way in which it hooks
// malloc/free. glibc contains the ability for the app to install hooks into malloc/free.
2022-02-23 11:56:29 +00:00
# ifdef OSX
# include <malloc/malloc.h>
# else
2020-04-22 16:56:21 +00:00
# include <malloc.h>
2022-02-23 11:56:29 +00:00
# endif
2020-04-22 16:56:21 +00:00
# include <tier1/utlintrusivelist.h>
# include <execinfo.h>
# include <tier1/utlvector.h>
# define MEMALLOC_HASHSIZE 8193
2022-02-23 11:56:29 +00:00
typedef uintp ptrint_t ;
2020-04-22 16:56:21 +00:00
struct CLinuxMemStats
{
int nNumMallocs ; // total every
int nNumFrees ; // total
int nNumMallocsInUse ;
int nTotalMallocInUse ;
} ;
# define MAX_STACK_TRACEBACK 20
struct CLinuxMallocContext
{
CLinuxMallocContext * m_pNext ;
void * pStackTraceBack [ MAX_STACK_TRACEBACK ] ;
int m_nCurrentAllocSize ;
int m_nNumAllocsInUse ;
int m_nMaximumSize ;
int m_TotalNumAllocs ;
int m_nLastAllocSize ;
CLinuxMallocContext ( void )
{
memset ( this , 0 , sizeof ( * this ) ) ;
}
} ;
static CUtlIntrusiveList < CLinuxMallocContext > s_ContextHash [ MEMALLOC_HASHSIZE ] ;
CLinuxMemStats g_LinuxMemStats ;
struct RememberedAlloc_t
{
RememberedAlloc_t * m_pNext , * m_pPrev ; // all addresses that hash to the same value are linked
CLinuxMallocContext * m_pAllocContext ;
ptrint_t m_nAddress ; // the address of the memory that came from malloc/realloc
size_t m_nSize ;
void AdjustSize ( size_t nsize )
{
int nDelta = nsize - m_nSize ;
m_nSize = nsize ;
m_pAllocContext - > m_nCurrentAllocSize + = nDelta ;
m_pAllocContext - > m_nMaximumSize = MAX ( m_pAllocContext - > m_nMaximumSize , m_pAllocContext - > m_nCurrentAllocSize ) ;
}
} ;
static inline int AddressHash ( ptrint_t nAdr )
{
return ( nAdr % MEMALLOC_HASHSIZE ) ;
}
static CUtlIntrusiveDList < RememberedAlloc_t > s_AddressData [ MEMALLOC_HASHSIZE ] ;
static struct RememberedAlloc_t * FindAddress ( void * pAdr , int * pHash = NULL )
{
ptrint_t nAdr = ( ptrint_t ) pAdr ;
int nHash = AddressHash ( nAdr ) ;
if ( pHash )
* pHash = nHash ;
for ( RememberedAlloc_t * i = s_AddressData [ nHash ] . m_pHead ; i ; i = i - > m_pNext )
{
if ( i - > m_nAddress = = nAdr )
return i ;
}
return NULL ;
}
# ifdef LINUX
static void * MallocHook ( size_t , const void * ) ;
static void FreeHook ( void * , const void * ) ;
static void * ReallocHook ( void * ptr , size_t size , const void * caller ) ;
static void RemoveHooks ( void )
{
__malloc_hook = NULL ;
__free_hook = NULL ;
__realloc_hook = NULL ;
}
static void InstallHooks ( void )
{
__malloc_hook = MallocHook ;
__free_hook = FreeHook ;
__realloc_hook = ReallocHook ;
}
# elif OSX
static void RemoveHooks ( void )
{
}
static void InstallHooks ( void )
{
}
# else
# error
# endif
static void AddMemoryAllocation ( void * pResult , size_t size )
{
if ( pResult )
{
g_LinuxMemStats . nNumMallocs + + ;
g_LinuxMemStats . nNumMallocsInUse + + ;
g_LinuxMemStats . nTotalMallocInUse + = size ;
RememberedAlloc_t * pNew = new RememberedAlloc_t ;
pNew - > m_nAddress = ( ptrint_t ) pResult ;
pNew - > m_nSize = size ;
s_AddressData [ AddressHash ( pNew - > m_nAddress ) ] . AddToHead ( pNew ) ;
// now, find the stack traceback context for this call
void * pTraceBack [ MAX_STACK_TRACEBACK ] ;
int nNumGot = backtrace ( pTraceBack , ARRAYSIZE ( pTraceBack ) ) ;
for ( int n = MAX ( 0 , nNumGot - 1 ) ; n < MAX_STACK_TRACEBACK ; n + + )
pTraceBack [ n ] = NULL ;
uint32 nHash = 0 ;
for ( int i = 0 ; i < MAX_STACK_TRACEBACK ; i + + )
{
nHash = ( nHash * 3 ) + ( ( ptrint_t ) pTraceBack [ i ] ) ;
}
nHash % = MEMALLOC_HASHSIZE ;
CLinuxMallocContext * pFoundCtx = NULL ;
// see if we have this context
for ( CLinuxMallocContext * i = s_ContextHash [ nHash ] . m_pHead ; i ; i = i - > m_pNext )
{
if ( memcmp ( pTraceBack , i - > pStackTraceBack , sizeof ( pTraceBack ) ) = = 0 )
{
pFoundCtx = i ;
break ;
}
}
if ( ! pFoundCtx )
{
pFoundCtx = new CLinuxMallocContext ;
memcpy ( pFoundCtx - > pStackTraceBack , pTraceBack , sizeof ( pTraceBack ) ) ;
s_ContextHash [ nHash ] . AddToHead ( pFoundCtx ) ;
}
pNew - > m_pAllocContext = pFoundCtx ;
pFoundCtx - > m_nCurrentAllocSize + = size ;
pFoundCtx - > m_nNumAllocsInUse + + ;
pFoundCtx - > m_nMaximumSize = MAX ( pFoundCtx - > m_nCurrentAllocSize , pFoundCtx - > m_nMaximumSize ) ;
pFoundCtx - > m_TotalNumAllocs + + ;
}
}
static CThreadFastMutex s_MemoryMutex ;
static void * ReallocHook ( void * ptr , size_t size , const void * caller )
{
AUTO_LOCK ( s_MemoryMutex ) ;
RemoveHooks ( ) ;
void * nResult = realloc ( ptr , size ) ;
if ( ptr ) // did we have this memory before
{
int nHash ;
RememberedAlloc_t * pBlock = FindAddress ( ptr , & nHash ) ;
if ( pBlock )
{
if ( ptr = = nResult )
{
// it successfully alloced, just need to update size info, etc
pBlock - > AdjustSize ( size ) ;
g_LinuxMemStats . nTotalMallocInUse + = ( size - pBlock - > m_nSize ) ;
}
else
{
pBlock - > m_pAllocContext - > m_nCurrentAllocSize - = pBlock - > m_nSize ;
pBlock - > m_pAllocContext - > m_nNumAllocsInUse - - ;
s_AddressData [ nHash ] . RemoveNode ( pBlock ) ; // throw away this node
AddMemoryAllocation ( nResult , size ) ;
}
}
else
AddMemoryAllocation ( nResult , size ) ;
}
else
AddMemoryAllocation ( nResult , size ) ;
InstallHooks ( ) ;
return nResult ;
}
static void * MallocHook ( size_t size , const void * caller )
{
// turn off hooking so we won't recurse
AUTO_LOCK ( s_MemoryMutex ) ;
RemoveHooks ( ) ;
void * pResult = malloc ( size ) ;
// now, add this memory chunk to our list
AddMemoryAllocation ( pResult , size ) ;
InstallHooks ( ) ;
return pResult ;
}
static void FreeHook ( void * ptr , const void * caller )
{
AUTO_LOCK ( s_MemoryMutex ) ;
RemoveHooks ( ) ;
// call real free
free ( ptr ) ;
// look in our list
if ( ptr )
{
int nHash ;
RememberedAlloc_t * pFound = FindAddress ( ptr , & nHash ) ;
if ( ! pFound )
{
//printf(" free of unallocated adr %p (maybe)\n", ptr );
}
else
{
pFound - > m_pAllocContext - > m_nCurrentAllocSize - = pFound - > m_nSize ;
pFound - > m_pAllocContext - > m_nNumAllocsInUse - - ;
g_LinuxMemStats . nTotalMallocInUse - = pFound - > m_nSize ;
g_LinuxMemStats . nNumFrees + + ;
g_LinuxMemStats . nNumMallocsInUse - - ;
s_AddressData [ nHash ] . RemoveNode ( pFound ) ;
delete pFound ;
}
}
InstallHooks ( ) ;
}
void EnableMemoryLogging ( bool bOnOff )
{
if ( bOnOff )
{
InstallHooks ( ) ;
#if 0
// simple test
char * p [ 10 ] ;
for ( int i = 0 ; i < 10 ; i + + )
p [ i ] = new char [ 10 ] ;
printf ( " log with memory \n " ) ;
DumpMemoryLog ( ) ;
for ( int i = 0 ; i < 10 ; i + + )
delete [ ] p [ i ] ;
printf ( " after free, \n " ) ;
DumpMemoryLog ( ) ;
// now, try som realloc action
int * p1 = NULL ;
int * p2 ;
for ( int i = 1 ; i < 10 ; i + + )
{
p1 = ( int * ) realloc ( p1 , i * 100 ) ;
if ( i = = 3 )
p2 = new int [ 300 ] ;
}
printf ( " after realloc loop \n " ) ;
DumpMemoryLog ( ) ;
delete [ ] p2 ;
free ( p1 ) ;
printf ( " after realloc frees \n " ) ;
DumpMemoryLog ( ) ;
# endif
}
else
RemoveHooks ( ) ;
}
static inline bool SortLessFunc ( CLinuxMallocContext * const & left , CLinuxMallocContext * const & right )
{
return left - > m_nCurrentAllocSize > right - > m_nCurrentAllocSize ;
}
void DumpMemoryLog ( int nThresh )
{
AUTO_LOCK ( s_MemoryMutex ) ;
2022-02-23 11:56:29 +00:00
Plat_EndWatchdogTimer ( ) ;
2020-04-22 16:56:21 +00:00
RemoveHooks ( ) ;
std : : vector < CLinuxMallocContext * > memList ;
for ( int i = 0 ; i < MEMALLOC_HASHSIZE ; i + + )
{
for ( CLinuxMallocContext * p = s_ContextHash [ i ] . m_pHead ; p ; p = p - > m_pNext )
{
if ( p - > m_nCurrentAllocSize > = nThresh )
{
memList . push_back ( p ) ;
}
}
}
std : : sort ( memList . begin ( ) , memList . end ( ) , SortLessFunc ) ;
for ( int i = 0 ; i < memList . size ( ) ; i + + )
{
CLinuxMallocContext * p = memList [ i ] ;
char * * strings = backtrace_symbols ( p - > pStackTraceBack , MAX_STACK_TRACEBACK ) ;
Msg ( " Context cursize=%d nallocs=%d maxsize=%d total_allocs=%d \n " , p - > m_nCurrentAllocSize , p - > m_nNumAllocsInUse , p - > m_nMaximumSize , p - > m_TotalNumAllocs ) ;
Msg ( " stack \n " ) ;
for ( int n = 0 ; n < MAX_STACK_TRACEBACK ; n + + )
if ( p - > pStackTraceBack [ n ] )
Msg ( " %p %s \n " , p - > pStackTraceBack [ n ] , strings [ n ] ) ;
free ( strings ) ;
}
Msg ( " End of memory list \n " ) ;
InstallHooks ( ) ;
}
void DumpChangedMemory ( int nThresh )
{
AUTO_LOCK ( s_MemoryMutex ) ;
2022-02-23 11:56:29 +00:00
Plat_EndWatchdogTimer ( ) ;
2020-04-22 16:56:21 +00:00
RemoveHooks ( ) ;
std : : vector < CLinuxMallocContext * > memList ;
for ( int i = 0 ; i < MEMALLOC_HASHSIZE ; i + + )
{
for ( CLinuxMallocContext * p = s_ContextHash [ i ] . m_pHead ; p ; p = p - > m_pNext )
{
if ( p - > m_nCurrentAllocSize - p - > m_nLastAllocSize > nThresh )
{
memList . push_back ( p ) ;
}
}
}
std : : sort ( memList . begin ( ) , memList . end ( ) , SortLessFunc ) ;
for ( int i = 0 ; i < memList . size ( ) ; i + + )
{
CLinuxMallocContext * p = memList [ i ] ;
char * * strings = backtrace_symbols ( p - > pStackTraceBack , MAX_STACK_TRACEBACK ) ;
Msg ( " Context cursize=%d lastsize=%d nallocs=%d maxsize=%d total_allocs=%d \n " , p - > m_nCurrentAllocSize , p - > m_nLastAllocSize , p - > m_nNumAllocsInUse , p - > m_nMaximumSize , p - > m_TotalNumAllocs ) ;
Msg ( " stack \n " ) ;
for ( int n = 0 ; n < MAX_STACK_TRACEBACK ; n + + )
if ( p - > pStackTraceBack [ n ] )
Msg ( " %p %s \n " , p - > pStackTraceBack [ n ] , strings [ n ] ) ;
free ( strings ) ;
}
Msg ( " End of memory list \n " ) ;
InstallHooks ( ) ;
}
void SetMemoryMark ( void )
{
AUTO_LOCK ( s_MemoryMutex ) ;
for ( int i = 0 ; i < MEMALLOC_HASHSIZE ; i + + )
{
for ( CLinuxMallocContext * p = s_ContextHash [ i ] . m_pHead ; p ; p = p - > m_pNext )
{
p - > m_nLastAllocSize = p - > m_nCurrentAllocSize ;
}
}
}
void DumpMemorySummary ( void )
{
Msg ( " Total memory in use = %d, NumMallocs=%d, Num Frees=%d approx process usage=%ul \n " , g_LinuxMemStats . nTotalMallocInUse , g_LinuxMemStats . nNumMallocs , g_LinuxMemStats . nNumFrees ,
( unsigned int ) ApproximateProcessMemoryUsage ( ) ) ;
}
# endif // !NO_HOOK_MALLOC
// Turn off memdbg macros (turned on up top) since this is included like a header
# include "tier0/memdbgoff.h"