//========= 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
#ifdef ANDROID
#include <linux/stat.h>
#endif

#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 ) );

				for( uint i = 0; i < nCharRead; i++ )
				{
					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;
}

PLATFORM_INTERFACE uint64 Plat_Rdtsc()
{
#if (defined( __arm__ ) || defined( __arm64__ ) || defined( __aarch64__ ) || defined( _M_ARM ) || defined( _M_ARM64 )) && defined (POSIX)
	struct timespec t;
	clock_gettime(CLOCK_REALTIME, &t);
	return t.tv_sec * 1000000000ULL + t.tv_nsec;
#elif defined( _X360 )
	return (uint64)__mftb32();
#elif defined( _WIN32 )
	LARGE_INTEGER ret;
	QueryPerformanceCounter(&ret);
	return ret.QuadPart;
#elif defined( __i386__ )
	uint64 val;
	__asm__ __volatile__("rdtsc" : "=A" (val));
	return val;
#elif defined( __x86_64__ )
	uint32 lo, hi;
	__asm__ __volatile__("rdtsc" : "=a" (lo), "=d" (hi));
	return (((uint64)hi) << 32) | lo;
#else
#error
#endif
}

#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.

#ifdef OSX
#include <malloc/malloc.h>
#else
#include <malloc.h>
#endif
#include <tier1/utlintrusivelist.h>
#include <execinfo.h>
#include <tier1/utlvector.h>
     
#define MEMALLOC_HASHSIZE 8193
typedef uintp ptrint_t;



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 );
    Plat_EndWatchdogTimer();
	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 );
    Plat_EndWatchdogTimer();
	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"


