Team Fortress 2 Source Code as on 22/4/2020
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.

1083 lines
26 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "tier0/platform.h"
  8. #include "tier0/vcrmode.h"
  9. #include "tier0/memalloc.h"
  10. #include "tier0/dbg.h"
  11. #include <algorithm>
  12. #include <vector>
  13. #include <sys/time.h>
  14. #include <sys/resource.h>
  15. #include <unistd.h>
  16. #ifdef OSX
  17. #include <mach/mach.h>
  18. #include <mach/mach_time.h>
  19. #include <stdbool.h>
  20. #include <sys/types.h>
  21. #include <unistd.h>
  22. #include <sys/sysctl.h>
  23. #endif
  24. #ifdef LINUX
  25. #include <time.h>
  26. #include <fcntl.h>
  27. #endif
  28. #include "tier0/memdbgon.h"
  29. // Benchmark mode uses this heavy-handed method
  30. // *** WARNING ***. On Linux gettimeofday returns the system's best guess at
  31. // actual wall clock time and this can go backwards. You need to use
  32. // clock_gettime( CLOCK_MONOTONIC ... ) if this isn't what you want.
  33. // If you want to try using rdtsc for Plat_FloatTime(), enable USE_RDTSC_FOR_FLOATTIME:
  34. //
  35. // Make sure you know what you're doing. This was disabled due to the long startup time, and
  36. // in our testing, even though constant_tsc was set, we couldn't rely on the
  37. // max frequency result returned from CalculateCPUFreq() (ie /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq).
  38. //
  39. // #define USE_RDTSC_FOR_FLOATTIME
  40. extern VCRMode_t g_VCRMode;
  41. static bool g_bBenchmarkMode = false;
  42. static double g_FakeBenchmarkTime = 0;
  43. static double g_FakeBenchmarkTimeInc = 1.0 / 66.0;
  44. #ifdef USE_RDTSC_FOR_FLOATTIME
  45. static bool s_bTimeInitted;
  46. static bool s_bUseRDTSC;
  47. static uint64 s_nRDTSCBase;
  48. static float s_flRDTSCToMicroSeconds;
  49. static double s_flRDTSCScale;
  50. #endif // USE_RDTSC_FOR_FLOATTIME
  51. bool Plat_IsInBenchmarkMode()
  52. {
  53. return g_bBenchmarkMode;
  54. }
  55. void Plat_SetBenchmarkMode( bool bBenchmark )
  56. {
  57. g_bBenchmarkMode = bBenchmark;
  58. }
  59. #define N_ITERATIONS_OF_RDTSC_TEST_TO_RUN 5 // should be odd
  60. #define TEST_RDTSC_FLOATTIME 0
  61. size_t ApproximateProcessMemoryUsage( void )
  62. {
  63. /*
  64. From http://man7.org/linux/man-pages/man5/proc.5.html:
  65. /proc/[pid]/statm
  66. Provides information about memory usage, measured in pages.
  67. The columns are:
  68. size (1) total program size
  69. (same as VmSize in /proc/[pid]/status)
  70. resident (2) resident set size
  71. (same as VmRSS in /proc/[pid]/status)
  72. share (3) shared pages (i.e., backed by a file)
  73. text (4) text (code)
  74. lib (5) library (unused in Linux 2.6)
  75. data (6) data + stack
  76. dt (7) dirty pages (unused in Linux 2.6)
  77. */
  78. // This returns the resident memory size (RES column in 'top') in bytes.
  79. size_t nRet = 0;
  80. FILE *pFile = fopen( "/proc/self/statm", "r" );
  81. if ( pFile )
  82. {
  83. size_t nSize, nResident, nShare, nText, nLib_Unused, nDataPlusStack, nDt_Unused;
  84. if ( fscanf( pFile, "%zu %zu %zu %zu %zu %zu %zu", &nSize, &nResident, &nShare, &nText, &nLib_Unused, &nDataPlusStack, &nDt_Unused ) >= 2 )
  85. {
  86. nRet = 4096 * nResident;
  87. }
  88. fclose( pFile );
  89. }
  90. return nRet;
  91. }
  92. #ifdef USE_RDTSC_FOR_FLOATTIME
  93. static void InitTimeSystem( void )
  94. {
  95. s_bTimeInitted = true;
  96. // now, see if we can use rdtsc instead. If this is one of the chips with a separate constant clock for rdtsc, we can
  97. FILE *pCpuInfo = fopen( "/proc/cpuinfo", "r" );
  98. if ( pCpuInfo )
  99. {
  100. bool bAnyBadCores = false;
  101. char lbuf[2048];
  102. while( fgets( lbuf, sizeof( lbuf ), pCpuInfo ) )
  103. {
  104. if ( memcmp( lbuf, "flags", 4 ) == 0 )
  105. {
  106. if ( ! strstr( lbuf, "constant_tsc" ) )
  107. {
  108. bAnyBadCores = true;
  109. break;
  110. }
  111. }
  112. }
  113. fclose( pCpuInfo );
  114. if ( ! bAnyBadCores )
  115. {
  116. // this system appears to have the proper cpu setup to use rdtsc from reliable timing. Let's either read the cpu frequency from an
  117. // environment variable, or time it ourselves
  118. char const *pEnv = getenv( "RDTSC_FREQUENCY" );
  119. if ( pEnv )
  120. {
  121. // the environment variable is allowed to hold either a benchmark result, or a string such as "disable"
  122. if ( pEnv && ( ( pEnv[0] > '9' ) || ( pEnv[0] < '0' ) ) )
  123. return; // leave rdtsc disabled
  124. // the variable holds the number of ticks per microsecond
  125. s_flRDTSCToMicroSeconds = atof( pEnv );
  126. // sanity check
  127. if ( s_flRDTSCToMicroSeconds > 1.0 )
  128. {
  129. s_bUseRDTSC = true;
  130. s_flRDTSCScale = 1.0 / ( 1000.0 * 1000.0 * s_flRDTSCToMicroSeconds );
  131. s_nRDTSCBase = Plat_Rdtsc();
  132. return;
  133. }
  134. }
  135. else
  136. {
  137. printf( "Running a benchmark to measure system clock frequency...\n" );
  138. // run n iterations and use the median
  139. double flRDTSCToMicroSeconds[N_ITERATIONS_OF_RDTSC_TEST_TO_RUN];
  140. for( int i = 0; i < ARRAYSIZE( flRDTSCToMicroSeconds ) ; i++ )
  141. {
  142. uint64 stime = Plat_Rdtsc();
  143. struct timeval stimeval;
  144. gettimeofday( &stimeval, NULL );
  145. sleep( 1 );
  146. uint64 etime = Plat_Rdtsc() - stime;
  147. struct timeval etimeval;
  148. gettimeofday( &etimeval, NULL );
  149. // subtract timevals to get elapsed microseconds
  150. struct timeval elapsedtimeval;
  151. timersub( &etimeval, &stimeval, &elapsedtimeval );
  152. uint64 nUs = 1000000 * elapsedtimeval.tv_sec + elapsedtimeval.tv_usec;
  153. flRDTSCToMicroSeconds[ i ] = ( etime / nUs );
  154. }
  155. std::make_heap( flRDTSCToMicroSeconds, flRDTSCToMicroSeconds + ARRAYSIZE( flRDTSCToMicroSeconds ) - 1 );
  156. std::sort_heap( flRDTSCToMicroSeconds, flRDTSCToMicroSeconds + ARRAYSIZE( flRDTSCToMicroSeconds ) - 1 );
  157. s_flRDTSCToMicroSeconds = flRDTSCToMicroSeconds[ARRAYSIZE( flRDTSCToMicroSeconds ) / 2 ];
  158. s_flRDTSCScale = 1.0 / ( 1000.0 * 1000.0 * s_flRDTSCToMicroSeconds );
  159. printf( "Finished RDTSC test. To prevent the startup delay from this benchmark, set the environment variable RDTSC_FREQUENCY to %f on this system."
  160. " This value is dependent upon the CPU clock speed and architecture and should be determined separately for each server. The use of this mechanism"
  161. " for timing can be disabled by setting RDTSC_FREQUENCY to 'disabled'.\n",
  162. s_flRDTSCToMicroSeconds );
  163. s_nRDTSCBase = Plat_Rdtsc();
  164. s_bUseRDTSC = true;
  165. #if TEST_RDTSC_FLOATTIME
  166. printf( "RDTSC test results:\n" );
  167. for( int i = 0; i < ARRAYSIZE( flRDTSCToMicroSeconds ); i++ )
  168. printf(" [%d] = %f\n", i, flRDTSCToMicroSeconds[i] );
  169. printf( "scale factor = %f\n", s_flRDTSCScale );
  170. uint64 srdtsc_time = Plat_Rdtsc();
  171. for( int i = 0; i < 1000 * 1000 * 10; i++ )
  172. {
  173. float p = Plat_FloatTime();
  174. }
  175. printf( "slow = %lld\n", Plat_Rdtsc() - srdtsc_time );
  176. // now, run a benchmark to see how much this optimization buys us
  177. srdtsc_time = Plat_Rdtsc();
  178. for( int i = 0; i < 1000 * 1000 * 10; i++ )
  179. {
  180. float p = Plat_FloatTime();
  181. }
  182. printf( "sfast = %lld\n", Plat_Rdtsc() - srdtsc_time );
  183. #endif
  184. }
  185. }
  186. }
  187. }
  188. static FORCEINLINE void TestTimeSystem( void )
  189. {
  190. #if TEST_RDTSC_FLOATTIME
  191. // now, test that plat_float time actually works
  192. for( int t = 0 ; t < 5; t++ )
  193. {
  194. float flStartT = Plat_FloatTime();
  195. struct timeval stime;
  196. gettimeofday( &stime, NULL );
  197. sleep( 5 );
  198. float flElapsedT = Plat_FloatTime() - flStartT;
  199. struct timeval etime;
  200. gettimeofday( &etime, NULL );
  201. struct timeval dtime;
  202. timersub( &etime, &stime, &dtime );
  203. printf( " plat_float time says %f elapsed. gettimeofday says %f\n",
  204. flElapsedT, dtime.tv_sec + dtime.tv_usec / 1000000.0 );
  205. }
  206. #endif
  207. }
  208. #endif // USE_RDTSC_FOR_FLOATTIME
  209. double Plat_FloatTime()
  210. {
  211. if ( g_bBenchmarkMode )
  212. {
  213. g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc;
  214. return g_FakeBenchmarkTime;
  215. }
  216. #ifdef OSX
  217. // OSX
  218. static uint64 start_time = 0;
  219. static mach_timebase_info_data_t sTimebaseInfo;
  220. static double conversion = 0.0;
  221. if ( !start_time )
  222. {
  223. start_time = mach_absolute_time();
  224. mach_timebase_info(&sTimebaseInfo);
  225. conversion = 1e-9 * (double) sTimebaseInfo.numer / (double) sTimebaseInfo.denom;
  226. }
  227. uint64 now = mach_absolute_time();
  228. return ( now - start_time ) * conversion;
  229. #else
  230. // Linux
  231. static struct timespec start_time = { 0, 0 };
  232. static bool bInitialized = false;
  233. if ( !bInitialized )
  234. {
  235. bInitialized = true;
  236. clock_gettime( CLOCK_MONOTONIC, &start_time );
  237. }
  238. struct timespec now;
  239. clock_gettime( CLOCK_MONOTONIC, &now );
  240. return ( now.tv_sec - start_time.tv_sec ) + ( now.tv_nsec * 1e-9 );
  241. #ifdef USE_RDTSC_FOR_FLOATTIME
  242. if ( ! s_bTimeInitted )
  243. {
  244. InitTimeSystem();
  245. TestTimeSystem();
  246. }
  247. if ( s_bUseRDTSC )
  248. {
  249. uint64 nTicks = Plat_Rdtsc() - s_nRDTSCBase;
  250. return ( (double) nTicks) * s_flRDTSCScale;
  251. }
  252. else
  253. {
  254. struct timeval tp;
  255. gettimeofday( &tp, NULL );
  256. if (VCRGetMode() == VCR_Disabled)
  257. return (( tp.tv_sec - s_nSecBase ) + tp.tv_usec / 1000000.0 );
  258. return VCRHook_Sys_FloatTime( ( tp.tv_sec - s_nSecBase ) + tp.tv_usec / 1000000.0 );
  259. }
  260. #endif // USE_RDTSC_FOR_FLOATTIME
  261. #endif
  262. }
  263. unsigned int Plat_MSTime()
  264. {
  265. if ( g_bBenchmarkMode )
  266. {
  267. g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc;
  268. return (unsigned int)(g_FakeBenchmarkTime * 1000.0);
  269. }
  270. #ifdef USE_RDTSC_FOR_FLOATTIME
  271. if ( ! s_bTimeInitted )
  272. {
  273. InitTimeSystem();
  274. TestTimeSystem();
  275. }
  276. if ( s_bUseRDTSC )
  277. {
  278. uint64 nTicks = Plat_Rdtsc() - s_nRDTSCBase;
  279. return 1000.0 * nTicks * s_flRDTSCScale;
  280. }
  281. else
  282. #endif // USE_RDTSC_FOR_FLOATTIME
  283. {
  284. return ( uint )( Plat_FloatTime() * 1000 );
  285. }
  286. }
  287. uint64 Plat_USTime()
  288. {
  289. if ( g_bBenchmarkMode )
  290. {
  291. g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc;
  292. return (unsigned int)(g_FakeBenchmarkTime * 1000000.0);
  293. }
  294. #ifdef USE_RDTSC_FOR_FLOATTIME
  295. if ( ! s_bTimeInitted )
  296. {
  297. InitTimeSystem();
  298. TestTimeSystem();
  299. }
  300. if ( s_bUseRDTSC )
  301. {
  302. uint64 nTicks = Plat_Rdtsc() - s_nRDTSCBase;
  303. return 1000000.0 * nTicks * s_flRDTSCScale;
  304. }
  305. else
  306. #endif // USE_RDTSC_FOR_FLOATTIME
  307. {
  308. return ( uint64 )( Plat_FloatTime() * 1000000 );
  309. }
  310. }
  311. // Wraps the thread-safe versions of ctime. buf must be at least 26 bytes
  312. char *Plat_ctime( const time_t *timep, char *buf, size_t bufsize )
  313. {
  314. return ctime_r( timep, buf );
  315. }
  316. // Wraps the thread-safe versions of gmtime
  317. struct tm *Plat_gmtime( const time_t *timep, struct tm *result )
  318. {
  319. return gmtime_r( timep, result );
  320. }
  321. time_t Plat_timegm( struct tm *timeptr )
  322. {
  323. return timegm( timeptr );
  324. }
  325. // Wraps the thread-safe versions of localtime
  326. struct tm *Plat_localtime( const time_t *timep, struct tm *result )
  327. {
  328. return localtime_r( timep, result );
  329. }
  330. bool vtune( bool resume )
  331. {
  332. return 0;
  333. }
  334. // -------------------------------------------------------------------------------------------------- //
  335. // Memory stuff.
  336. // -------------------------------------------------------------------------------------------------- //
  337. #ifndef NO_HOOK_MALLOC
  338. PLATFORM_INTERFACE void Plat_DefaultAllocErrorFn( unsigned long size )
  339. {
  340. }
  341. typedef void (*Plat_AllocErrorFn)( unsigned long size );
  342. Plat_AllocErrorFn g_AllocError = Plat_DefaultAllocErrorFn;
  343. PLATFORM_INTERFACE void* Plat_Alloc( unsigned long size )
  344. {
  345. void *pRet = MemAlloc_Alloc( size );
  346. if ( pRet )
  347. {
  348. return pRet;
  349. }
  350. else
  351. {
  352. g_AllocError( size );
  353. return 0;
  354. }
  355. }
  356. PLATFORM_INTERFACE void* Plat_Realloc( void *ptr, unsigned long size )
  357. {
  358. void *pRet = g_pMemAlloc->Realloc( ptr, size );
  359. if ( pRet )
  360. {
  361. return pRet;
  362. }
  363. else
  364. {
  365. g_AllocError( size );
  366. return 0;
  367. }
  368. }
  369. PLATFORM_INTERFACE void Plat_Free( void *ptr )
  370. {
  371. g_pMemAlloc->Free( ptr );
  372. }
  373. PLATFORM_INTERFACE void Plat_SetAllocErrorFn( Plat_AllocErrorFn fn )
  374. {
  375. g_AllocError = fn;
  376. }
  377. #endif // !NO_HOOK_MALLOC
  378. #if defined( OSX )
  379. // From the Apple tech note: http://developer.apple.com/library/mac/#qa/qa1361/_index.html
  380. bool Plat_IsInDebugSession()
  381. {
  382. int junk;
  383. int mib[4];
  384. struct kinfo_proc info;
  385. size_t size;
  386. static int s_IsInDebugSession = -1;
  387. if ( s_IsInDebugSession == -1 )
  388. {
  389. // Initialize the flags so that, if sysctl fails for some bizarre
  390. // reason, we get a predictable result.
  391. info.kp_proc.p_flag = 0;
  392. // Initialize mib, which tells sysctl the info we want, in this case
  393. // we're looking for information about a specific process ID.
  394. mib[0] = CTL_KERN;
  395. mib[1] = KERN_PROC;
  396. mib[2] = KERN_PROC_PID;
  397. mib[3] = getpid();
  398. // Call sysctl.
  399. size = sizeof(info);
  400. junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
  401. // We're being debugged if the P_TRACED flag is set.
  402. s_IsInDebugSession = ( (info.kp_proc.p_flag & P_TRACED) != 0 );
  403. }
  404. return !!s_IsInDebugSession;
  405. }
  406. #elif defined( LINUX )
  407. bool Plat_IsInDebugSession()
  408. {
  409. // For linux: http://stackoverflow.com/questions/3596781/detect-if-gdb-is-running
  410. // Don't use "if (ptrace(PTRACE_TRACEME, 0, NULL, 0) == -1)" as it means debuggers can't attach.
  411. // Other solutions they mention involve forking. Ugh.
  412. //
  413. // Good solution from Pierre-Loup: Check TracerPid in /proc/self/status.
  414. // from "man proc"
  415. // TracerPid: PID of process tracing this process (0 if not being traced).
  416. int tracerpid = -1;
  417. int fd = open( "/proc/self/status", O_RDONLY, S_IRUSR );
  418. if( fd >= 0 )
  419. {
  420. char buf[ 1024 ];
  421. static const char s_TracerPid[] = "TracerPid:";
  422. int len = read( fd, buf, sizeof( buf ) - 1 );
  423. if ( len > 0 )
  424. {
  425. buf[ len ] = 0;
  426. const char *str = strstr( buf, s_TracerPid );
  427. tracerpid = str ? atoi( str + sizeof( s_TracerPid ) ) : -1;
  428. }
  429. close( fd );
  430. }
  431. return ( tracerpid > 0 );
  432. }
  433. #endif // defined( LINUX )
  434. void Plat_DebugString( const char * psz )
  435. {
  436. printf( "%s", psz );
  437. }
  438. static char g_CmdLine[ 2048 ];
  439. PLATFORM_INTERFACE void Plat_SetCommandLine( const char *cmdLine )
  440. {
  441. strncpy( g_CmdLine, cmdLine, sizeof(g_CmdLine) );
  442. g_CmdLine[ sizeof(g_CmdLine) -1 ] = 0;
  443. }
  444. PLATFORM_INTERFACE const tchar *Plat_GetCommandLine()
  445. {
  446. #ifdef LINUX
  447. if( !g_CmdLine[ 0 ] )
  448. {
  449. FILE *fp = fopen( "/proc/self/cmdline", "rb" );
  450. if( fp )
  451. {
  452. size_t nCharRead = 0;
  453. // -1 to leave room for the '\0'
  454. nCharRead = fread( g_CmdLine, sizeof( g_CmdLine[0] ), ARRAYSIZE( g_CmdLine ) - 1, fp );
  455. if ( feof( fp ) && !ferror( fp ) ) // Should have read the whole command line without error
  456. {
  457. Assert ( nCharRead < ARRAYSIZE( g_CmdLine ) );
  458. for( int i = 0; i < nCharRead; i++ )
  459. {
  460. if( g_CmdLine[ i ] == '\0' )
  461. g_CmdLine[ i ] = ' ';
  462. }
  463. g_CmdLine[ nCharRead ] = '\0';
  464. }
  465. fclose( fp );
  466. }
  467. Assert( g_CmdLine[ 0 ] );
  468. }
  469. #endif // LINUX
  470. return g_CmdLine;
  471. }
  472. PLATFORM_INTERFACE const char *Plat_GetCommandLineA()
  473. {
  474. return Plat_GetCommandLine();
  475. }
  476. PLATFORM_INTERFACE bool GetMemoryInformation( MemoryInformation *pOutMemoryInfo )
  477. {
  478. #if defined( LINUX ) || defined( OSX )
  479. return false;
  480. #else
  481. #error "Need to fill out GetMemoryInformation or at least return false for this platform"
  482. #endif
  483. }
  484. PLATFORM_INTERFACE bool Is64BitOS()
  485. {
  486. #if defined OSX
  487. return true;
  488. #elif defined LINUX
  489. FILE *pp = popen( "uname -m", "r" );
  490. if ( pp != NULL )
  491. {
  492. char rgchArchString[256];
  493. fgets( rgchArchString, sizeof( rgchArchString ), pp );
  494. pclose( pp );
  495. if ( !strncasecmp( rgchArchString, "x86_64", strlen( "x86_64" ) ) )
  496. return true;
  497. }
  498. #else
  499. Assert( !"implement Is64BitOS" );
  500. #endif
  501. return false;
  502. }
  503. PLATFORM_INTERFACE void Plat_ExitProcess( int nCode )
  504. {
  505. _exit( nCode );
  506. }
  507. static int s_nWatchDogTimerTimeScale = 0;
  508. static bool s_bInittedWD = false;
  509. static int s_WatchdogTime = 0;
  510. static Plat_WatchDogHandlerFunction_t s_pWatchDogHandlerFunction;
  511. static void InitWatchDogTimer( void )
  512. {
  513. if( !strstr( g_CmdLine, "-nowatchdog" ) )
  514. {
  515. #ifdef _DEBUG
  516. s_nWatchDogTimerTimeScale = 10; // debug is slow
  517. #else
  518. s_nWatchDogTimerTimeScale = 1;
  519. #endif
  520. }
  521. }
  522. // SIGALRM handler. Used by Watchdog timer code.
  523. static void WatchDogHandler( int s )
  524. {
  525. Plat_DebugString( "WatchDog! Server took too long to process (probably infinite loop).\n" );
  526. DebuggerBreakIfDebugging();
  527. if ( s_pWatchDogHandlerFunction )
  528. {
  529. s_pWatchDogHandlerFunction();
  530. }
  531. else
  532. {
  533. // force a crash
  534. abort();
  535. }
  536. }
  537. // watchdog timer support
  538. PLATFORM_INTERFACE void Plat_BeginWatchdogTimer( int nSecs )
  539. {
  540. if ( !s_bInittedWD )
  541. {
  542. s_bInittedWD = true;
  543. InitWatchDogTimer();
  544. }
  545. nSecs *= s_nWatchDogTimerTimeScale;
  546. nSecs = MIN( nSecs, 5 * 60 ); // no more than 5 minutes no matter what
  547. if ( nSecs )
  548. {
  549. s_WatchdogTime = nSecs;
  550. signal( SIGALRM, WatchDogHandler );
  551. alarm( nSecs );
  552. }
  553. }
  554. PLATFORM_INTERFACE void Plat_EndWatchdogTimer( void )
  555. {
  556. alarm( 0 );
  557. signal( SIGALRM, SIG_DFL );
  558. s_WatchdogTime = 0;
  559. }
  560. PLATFORM_INTERFACE int Plat_GetWatchdogTime( void )
  561. {
  562. return s_WatchdogTime;
  563. }
  564. PLATFORM_INTERFACE void Plat_SetWatchdogHandlerFunction( Plat_WatchDogHandlerFunction_t function )
  565. {
  566. s_pWatchDogHandlerFunction = function;
  567. }
  568. #ifndef NO_HOOK_MALLOC
  569. // memory logging this functionality is portable code, except for the way in which it hooks
  570. // malloc/free. glibc contains the ability for the app to install hooks into malloc/free.
  571. #include <malloc.h>
  572. #include <tier1/utlintrusivelist.h>
  573. #include <execinfo.h>
  574. #include <tier1/utlvector.h>
  575. #define MEMALLOC_HASHSIZE 8193
  576. typedef uint32 ptrint_t;
  577. struct CLinuxMemStats
  578. {
  579. int nNumMallocs; // total every
  580. int nNumFrees; // total
  581. int nNumMallocsInUse;
  582. int nTotalMallocInUse;
  583. };
  584. #define MAX_STACK_TRACEBACK 20
  585. struct CLinuxMallocContext
  586. {
  587. CLinuxMallocContext *m_pNext;
  588. void *pStackTraceBack[MAX_STACK_TRACEBACK];
  589. int m_nCurrentAllocSize;
  590. int m_nNumAllocsInUse;
  591. int m_nMaximumSize;
  592. int m_TotalNumAllocs;
  593. int m_nLastAllocSize;
  594. CLinuxMallocContext( void )
  595. {
  596. memset( this, 0, sizeof( *this ) );
  597. }
  598. };
  599. static CUtlIntrusiveList<CLinuxMallocContext> s_ContextHash[MEMALLOC_HASHSIZE];
  600. CLinuxMemStats g_LinuxMemStats;
  601. struct RememberedAlloc_t
  602. {
  603. RememberedAlloc_t *m_pNext, *m_pPrev; // all addresses that hash to the same value are linked
  604. CLinuxMallocContext *m_pAllocContext;
  605. ptrint_t m_nAddress; // the address of the memory that came from malloc/realloc
  606. size_t m_nSize;
  607. void AdjustSize( size_t nsize )
  608. {
  609. int nDelta = nsize - m_nSize;
  610. m_nSize = nsize;
  611. m_pAllocContext->m_nCurrentAllocSize += nDelta;
  612. m_pAllocContext->m_nMaximumSize = MAX( m_pAllocContext->m_nMaximumSize, m_pAllocContext->m_nCurrentAllocSize );
  613. }
  614. };
  615. static inline int AddressHash( ptrint_t nAdr )
  616. {
  617. return ( nAdr % MEMALLOC_HASHSIZE );
  618. }
  619. static CUtlIntrusiveDList<RememberedAlloc_t> s_AddressData[MEMALLOC_HASHSIZE];
  620. static struct RememberedAlloc_t *FindAddress( void *pAdr, int *pHash = NULL )
  621. {
  622. ptrint_t nAdr = ( ptrint_t ) pAdr;
  623. int nHash = AddressHash( nAdr );
  624. if ( pHash )
  625. *pHash = nHash;
  626. for( RememberedAlloc_t *i = s_AddressData[nHash].m_pHead; i; i = i->m_pNext )
  627. {
  628. if ( i->m_nAddress == nAdr )
  629. return i;
  630. }
  631. return NULL;
  632. }
  633. #ifdef LINUX
  634. static void *MallocHook( size_t, const void * );
  635. static void FreeHook( void*, const void * );
  636. static void *ReallocHook( void *ptr, size_t size, const void *caller );
  637. static void RemoveHooks( void )
  638. {
  639. __malloc_hook = NULL;
  640. __free_hook = NULL;
  641. __realloc_hook = NULL;
  642. }
  643. static void InstallHooks( void )
  644. {
  645. __malloc_hook = MallocHook;
  646. __free_hook = FreeHook;
  647. __realloc_hook = ReallocHook;
  648. }
  649. #elif OSX
  650. static void RemoveHooks( void )
  651. {
  652. }
  653. static void InstallHooks( void )
  654. {
  655. }
  656. #else
  657. #error
  658. #endif
  659. static void AddMemoryAllocation( void *pResult, size_t size )
  660. {
  661. if ( pResult )
  662. {
  663. g_LinuxMemStats.nNumMallocs++;
  664. g_LinuxMemStats.nNumMallocsInUse++;
  665. g_LinuxMemStats.nTotalMallocInUse += size;
  666. RememberedAlloc_t *pNew = new RememberedAlloc_t;
  667. pNew->m_nAddress = ( ptrint_t ) pResult;
  668. pNew->m_nSize = size;
  669. s_AddressData[AddressHash( pNew->m_nAddress )].AddToHead( pNew );
  670. // now, find the stack traceback context for this call
  671. void *pTraceBack[MAX_STACK_TRACEBACK];
  672. int nNumGot = backtrace( pTraceBack, ARRAYSIZE( pTraceBack ) );
  673. for( int n = MAX( 0, nNumGot - 1 ); n < MAX_STACK_TRACEBACK; n++ )
  674. pTraceBack[n] = NULL;
  675. uint32 nHash = 0;
  676. for( int i = 0; i < MAX_STACK_TRACEBACK; i++ )
  677. {
  678. nHash = ( nHash * 3 ) + ( ( ptrint_t ) pTraceBack[i] );
  679. }
  680. nHash %= MEMALLOC_HASHSIZE;
  681. CLinuxMallocContext *pFoundCtx = NULL;
  682. // see if we have this context
  683. for( CLinuxMallocContext *i = s_ContextHash[nHash].m_pHead; i ; i = i->m_pNext )
  684. {
  685. if ( memcmp( pTraceBack, i->pStackTraceBack, sizeof( pTraceBack ) ) == 0 )
  686. {
  687. pFoundCtx = i;
  688. break;
  689. }
  690. }
  691. if ( ! pFoundCtx )
  692. {
  693. pFoundCtx = new CLinuxMallocContext;
  694. memcpy( pFoundCtx->pStackTraceBack, pTraceBack, sizeof( pTraceBack ) );
  695. s_ContextHash[nHash].AddToHead( pFoundCtx );
  696. }
  697. pNew->m_pAllocContext = pFoundCtx;
  698. pFoundCtx->m_nCurrentAllocSize += size;
  699. pFoundCtx->m_nNumAllocsInUse++;
  700. pFoundCtx->m_nMaximumSize = MAX( pFoundCtx->m_nCurrentAllocSize, pFoundCtx->m_nMaximumSize );
  701. pFoundCtx->m_TotalNumAllocs++;
  702. }
  703. }
  704. static CThreadFastMutex s_MemoryMutex;
  705. static void *ReallocHook( void *ptr, size_t size, const void *caller )
  706. {
  707. AUTO_LOCK( s_MemoryMutex );
  708. RemoveHooks();
  709. void *nResult = realloc( ptr, size );
  710. if ( ptr ) // did we have this memory before
  711. {
  712. int nHash;
  713. RememberedAlloc_t *pBlock = FindAddress( ptr, &nHash );
  714. if ( pBlock )
  715. {
  716. if ( ptr == nResult )
  717. {
  718. // it successfully alloced, just need to update size info, etc
  719. pBlock->AdjustSize( size );
  720. g_LinuxMemStats.nTotalMallocInUse += ( size - pBlock->m_nSize );
  721. }
  722. else
  723. {
  724. pBlock->m_pAllocContext->m_nCurrentAllocSize -= pBlock->m_nSize;
  725. pBlock->m_pAllocContext->m_nNumAllocsInUse--;
  726. s_AddressData[nHash].RemoveNode( pBlock ); // throw away this node
  727. AddMemoryAllocation( nResult, size );
  728. }
  729. }
  730. else
  731. AddMemoryAllocation( nResult, size );
  732. }
  733. else
  734. AddMemoryAllocation( nResult, size );
  735. InstallHooks();
  736. return nResult;
  737. }
  738. static void *MallocHook(size_t size, const void *caller)
  739. {
  740. // turn off hooking so we won't recurse
  741. AUTO_LOCK( s_MemoryMutex );
  742. RemoveHooks();
  743. void *pResult = malloc (size);
  744. // now, add this memory chunk to our list
  745. AddMemoryAllocation( pResult, size );
  746. InstallHooks();
  747. return pResult;
  748. }
  749. static void FreeHook(void *ptr, const void *caller )
  750. {
  751. AUTO_LOCK( s_MemoryMutex );
  752. RemoveHooks();
  753. // call real free
  754. free (ptr);
  755. // look in our list
  756. if ( ptr )
  757. {
  758. int nHash;
  759. RememberedAlloc_t *pFound = FindAddress( ptr, &nHash );
  760. if ( !pFound )
  761. {
  762. //printf(" free of unallocated adr %p (maybe)\n", ptr );
  763. }
  764. else
  765. {
  766. pFound->m_pAllocContext->m_nCurrentAllocSize -= pFound->m_nSize;
  767. pFound->m_pAllocContext->m_nNumAllocsInUse--;
  768. g_LinuxMemStats.nTotalMallocInUse -= pFound->m_nSize;
  769. g_LinuxMemStats.nNumFrees++;
  770. g_LinuxMemStats.nNumMallocsInUse--;
  771. s_AddressData[nHash].RemoveNode( pFound );
  772. delete pFound;
  773. }
  774. }
  775. InstallHooks();
  776. }
  777. void EnableMemoryLogging( bool bOnOff )
  778. {
  779. if ( bOnOff )
  780. {
  781. InstallHooks();
  782. #if 0
  783. // simple test
  784. char *p[10];
  785. for( int i =0; i < 10; i++ )
  786. p[i] = new char[10];
  787. printf( "log with memory\n" );
  788. DumpMemoryLog();
  789. for( int i = 0; i < 10; i++ )
  790. delete[] p[i];
  791. printf( "after free,\n" );
  792. DumpMemoryLog();
  793. // now, try som realloc action
  794. int *p1 = NULL;
  795. int *p2;
  796. for( int i =1 ; i < 10; i++ )
  797. {
  798. p1 = (int * ) realloc( p1, i * 100 );
  799. if ( i == 3 )
  800. p2 = new int[300];
  801. }
  802. printf(" after realloc loop\n" );
  803. DumpMemoryLog();
  804. delete[] p2;
  805. free( p1 );
  806. printf(" after realloc frees\n" );
  807. DumpMemoryLog();
  808. #endif
  809. }
  810. else
  811. RemoveHooks();
  812. }
  813. static inline bool SortLessFunc( CLinuxMallocContext * const &left, CLinuxMallocContext * const &right )
  814. {
  815. return left->m_nCurrentAllocSize > right->m_nCurrentAllocSize;
  816. }
  817. void DumpMemoryLog( int nThresh )
  818. {
  819. AUTO_LOCK( s_MemoryMutex );
  820. EndWatchdogTimer();
  821. RemoveHooks();
  822. std::vector<CLinuxMallocContext *> memList;
  823. for( int i =0 ; i < MEMALLOC_HASHSIZE; i++ )
  824. {
  825. for( CLinuxMallocContext *p = s_ContextHash[i].m_pHead; p; p=p->m_pNext )
  826. {
  827. if ( p->m_nCurrentAllocSize >= nThresh )
  828. {
  829. memList.push_back( p );
  830. }
  831. }
  832. }
  833. std::sort( memList.begin(), memList.end(), SortLessFunc );
  834. for( int i = 0; i < memList.size(); i++ )
  835. {
  836. CLinuxMallocContext *p = memList[i];
  837. char **strings = backtrace_symbols( p->pStackTraceBack, MAX_STACK_TRACEBACK );
  838. Msg( "Context cursize=%d nallocs=%d maxsize=%d total_allocs=%d\n", p->m_nCurrentAllocSize, p->m_nNumAllocsInUse, p->m_nMaximumSize, p->m_TotalNumAllocs );
  839. Msg(" stack\n" );
  840. for( int n = 0 ; n < MAX_STACK_TRACEBACK; n++ )
  841. if ( p->pStackTraceBack[n] )
  842. Msg(" %p %s\n", p->pStackTraceBack[n], strings[n] );
  843. free( strings );
  844. }
  845. Msg("End of memory list\n" );
  846. InstallHooks();
  847. }
  848. void DumpChangedMemory( int nThresh )
  849. {
  850. AUTO_LOCK( s_MemoryMutex );
  851. EndWatchdogTimer();
  852. RemoveHooks();
  853. std::vector<CLinuxMallocContext *> memList;
  854. for( int i =0 ; i < MEMALLOC_HASHSIZE; i++ )
  855. {
  856. for( CLinuxMallocContext *p = s_ContextHash[i].m_pHead; p; p=p->m_pNext )
  857. {
  858. if ( p->m_nCurrentAllocSize - p->m_nLastAllocSize > nThresh )
  859. {
  860. memList.push_back( p );
  861. }
  862. }
  863. }
  864. std::sort( memList.begin(), memList.end(), SortLessFunc );
  865. for( int i = 0; i < memList.size(); i++ )
  866. {
  867. CLinuxMallocContext *p = memList[i];
  868. char **strings = backtrace_symbols( p->pStackTraceBack, MAX_STACK_TRACEBACK );
  869. 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 );
  870. Msg(" stack\n" );
  871. for( int n = 0 ; n < MAX_STACK_TRACEBACK; n++ )
  872. if ( p->pStackTraceBack[n] )
  873. Msg(" %p %s\n", p->pStackTraceBack[n], strings[n] );
  874. free( strings );
  875. }
  876. Msg("End of memory list\n" );
  877. InstallHooks();
  878. }
  879. void SetMemoryMark( void )
  880. {
  881. AUTO_LOCK( s_MemoryMutex );
  882. for( int i =0 ; i < MEMALLOC_HASHSIZE; i++ )
  883. {
  884. for( CLinuxMallocContext *p = s_ContextHash[i].m_pHead; p; p=p->m_pNext )
  885. {
  886. p->m_nLastAllocSize = p->m_nCurrentAllocSize;
  887. }
  888. }
  889. }
  890. void DumpMemorySummary( void )
  891. {
  892. 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,
  893. (unsigned int)ApproximateProcessMemoryUsage() );
  894. }
  895. #endif // !NO_HOOK_MALLOC
  896. // Turn off memdbg macros (turned on up top) since this is included like a header
  897. #include "tier0/memdbgoff.h"