/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/profile_funclevel.cpp $
// $Author: mhoffe $
// $Revision: #4 $
// $DateTime: 2003/08/14 13:43:29 $
//
// ©2003 Electronic Arts
//
// Function level profiling
//////////////////////////////////////////////////////////////////////////////
#include "_pch.h"
#include "../debug/debug.h"
#include
#ifdef HAS_PROFILE
// TLS index (-1 if not yet initialized)
static int TLSIndex=-1;
// our own fast critical section
static ProfileFastCS cs;
// Unfortunately VC 6 doesn't support _pleave (or _pexit) so
// we have to come up with our own type of implementation here...
static void __declspec(naked) _pleave(void)
{
ProfileFuncLevelTracer *p;
unsigned curESP,leaveAddr;
_asm
{
push ebp // this will be overwritten down there...
push ebp // setup standard stack frame
push eax
mov ebp,esp
mov eax,esp
sub esp,3*4 // 3 local DWord vars
pushad
mov [curESP],eax
}
curESP+=8;
p=(ProfileFuncLevelTracer *)TlsGetValue(TLSIndex);
leaveAddr=p->Leave(curESP);
*(unsigned *)(curESP)=leaveAddr;
_asm
{
popad
add esp,3*4 // must match sub esp above
pop eax
pop ebp
ret
}
}
extern "C" void __declspec(naked) _cdecl _penter(void)
{
unsigned callerFunc,ESPonReturn,callerRet;
ProfileFuncLevelTracer *p;
_asm
{
push ebp
push eax
mov ebp,esp
mov eax,esp
sub esp,4*4 // 4 local DWord vars
pushad
// calc return address
add eax,4+4 // account for push ebp and push eax
mov ebx,[eax] // grab return address
mov [callerFunc],ebx
// get some more stuff
add eax,4
mov [ESPonReturn],eax
mov ebx,[eax]
mov [callerRet],ebx
// jam in our exit code
mov dword ptr [eax],offset _pleave
}
// do we need a new stack tracer?
if (TLSIndex==-1)
TLSIndex=TlsAlloc();
p=(ProfileFuncLevelTracer *)TlsGetValue(TLSIndex);
if (!p)
{
p=(ProfileFuncLevelTracer *)ProfileAllocMemory(sizeof(ProfileFuncLevelTracer));
new (p) ProfileFuncLevelTracer;
TlsSetValue(TLSIndex,p);
}
// enter function
p->Enter(callerFunc-5,ESPonReturn,callerRet);
// cleanup
_asm
{
popad
add esp,4*4 // must match sub esp above
pop eax
pop ebp
ret
}
}
ProfileFuncLevelTracer *ProfileFuncLevelTracer::head=NULL;
bool ProfileFuncLevelTracer::shuttingDown=false;
int ProfileFuncLevelTracer::curFrame=0;
unsigned ProfileFuncLevelTracer::frameRecordMask;
bool ProfileFuncLevelTracer::recordCaller=false;
ProfileFuncLevelTracer::ProfileFuncLevelTracer(void):
stack(NULL), usedStack(0), totalStack(0), maxDepth(0)
{
ProfileFastCS::Lock lock(cs);
next=head;
head=this;
}
ProfileFuncLevelTracer::~ProfileFuncLevelTracer()
{
// yes, I know we leak...
}
void ProfileFuncLevelTracer::Enter(unsigned addr, unsigned esp, unsigned ret)
{
// must stack grow?
if (usedStack>=totalStack)
stack=(StackEntry *)ProfileReAllocMemory(stack,(totalStack+=100)*sizeof(StackEntry));
// save info
Function *f=func.Find(addr);
if (!f)
{
// new function
f=(Function *)ProfileAllocMemory(sizeof(Function));
new (f) Function(this);
f->addr=addr;
f->glob.callCount=f->glob.tickPure=f->glob.tickTotal=0;
for (int i=0;icur[i].callCount=f->cur[i].tickPure=f->cur[i].tickTotal=0;
f->depth=0;
func.Insert(f);
}
StackEntry &s=stack[usedStack++];
s.func=f;
s.esp=esp;
s.retVal=ret;
ProfileGetTime(s.tickEnter);
s.tickSubTime=0;
f->depth++;
// new max depth?
if (usedStack>=maxDepth)
maxDepth=usedStack;
DLOG_GROUP(profile_stack,Debug::RepeatChar(' ',usedStack-1)
<< Debug::Hex() << this
<< " Enter " << Debug::Width(8) << addr
<< " ESP " << Debug::Width(8) << esp
<< " return " << Debug::Width(8) << ret
<< " level " << Debug::Dec() << usedStack
);
}
unsigned ProfileFuncLevelTracer::Leave(unsigned esp)
{
// get current "time"
__int64 cur;
ProfileGetTime(cur);
while (usedStack>0)
{
// leave current function
usedStack--;
StackEntry &s=stack[usedStack],
&sPrev=stack[usedStack-1];
Function *f=s.func;
// decrease call depth
// note: add global time only if call depth is 0
f->depth--;
// insert caller
if (recordCaller&&usedStack)
f->glob.caller.Insert(sPrev.func->addr,1);
// inc call counter
f->glob.callCount++;
// add total time
__int64 delta=cur-s.tickEnter;
if (!f->depth)
f->glob.tickTotal+=delta;
// add pure time
f->glob.tickPure+=delta-s.tickSubTime;
// add sub time for higher function
if (usedStack)
sPrev.tickSubTime+=delta;
// frame based profiling?
if (frameRecordMask)
{
unsigned mask=frameRecordMask;
for (unsigned i=0;i0)
f->cur[i].caller.Insert(sPrev.func->addr,1);
f->cur[i].callCount++;
if (!f->depth)
f->cur[i].tickTotal+=delta;
f->cur[i].tickPure+=delta-s.tickSubTime;
}
if (!(mask>>=1))
break;
}
}
// exit if address match (somewhat...)
if (s.esp==esp)
break;
// catching those nasty ret...
if (s.esp>Dec());
}
DLOG_GROUP(profile_stack,Debug::RepeatChar(' ',usedStack-1)
<< Debug::Hex() << this
<< " Leave " << Debug::Width(8) << ""
<< " ESP " << Debug::Width(8) << stack[usedStack].esp
<< " return " << Debug::Width(8) << stack[usedStack].retVal
<< " level " << Debug::Dec() << usedStack
);
return stack[usedStack].retVal;
}
void ProfileFuncLevelTracer::Shutdown(void)
{
if (frameRecordMask)
{
for (unsigned i=0;inext)
p->FrameEnd(i,-1);
}
}
int ProfileFuncLevelTracer::FrameStart(void)
{
ProfileFastCS::Lock lock(cs);
for (unsigned i=0;inext)
{
Function *f;
for (int k=0;(f=p->func.Enumerate(k))!=NULL;k++)
{
Profile &p=f->cur[i];
p.caller.Clear();
p.callCount=p.tickPure=p.tickTotal=0;
}
}
frameRecordMask|=1<=MAX_FRAME_RECORDS)
return;
DFAIL_IF(!(frameRecordMask&(1<=curFrame)
return;
ProfileFastCS::Lock lock(cs);
frameRecordMask^=1<next)
{
Function *f;
for (int k=0;(f=p->func.Enumerate(k))!=NULL;k++)
{
Profile &p=f->cur[which];
if (p.callCount)
{
if (mixIndex<0)
f->frame.Append(curFrame,p);
else
f->frame.MixIn(mixIndex,p);
}
p.caller.Clear();
p.callCount=p.tickPure=p.tickTotal=0;
}
}
}
void ProfileFuncLevelTracer::ClearTotals(void)
{
ProfileFastCS::Lock lock(cs);
for (ProfileFuncLevelTracer *p=head;p;p=p->next)
{
Function *f;
for (int k=0;(f=p->func.Enumerate(k))!=NULL;k++)
{
f->glob.caller.Clear();
f->glob.callCount=0;
f->glob.tickPure=0;
f->glob.tickTotal=0;
}
}
}
ProfileFuncLevelTracer::UnsignedMap::UnsignedMap(void):
e(NULL), alloc(0), used(0), writeLock(false)
{
memset(hash,0,sizeof(hash));
}
ProfileFuncLevelTracer::UnsignedMap::~UnsignedMap()
{
Clear();
}
void ProfileFuncLevelTracer::UnsignedMap::Clear(void)
{
ProfileFreeMemory(e);
e=NULL;
alloc=used=0;
memset(hash,0,sizeof(hash));
}
void ProfileFuncLevelTracer::UnsignedMap::_Insert(unsigned at, unsigned val, int countAdd)
{
DFAIL_IF(writeLock) return;
// realloc list?
if (used==alloc)
{
// must fixup pointers...
unsigned delta=unsigned(e);
e=(Entry *)ProfileReAllocMemory(e,((alloc+=64)*sizeof(Entry)));
delta=unsigned(e)-delta;
if (used&&delta)
{
for (unsigned k=0;k=(int)used)
return 0;
return e[index].val;
}
unsigned ProfileFuncLevelTracer::UnsignedMap::GetCount(int index)
{
if (index<0||index>=(int)used)
return 0;
return e[index].count;
}
void ProfileFuncLevelTracer::UnsignedMap::Copy(const UnsignedMap &src)
{
Clear();
if (src.e)
{
alloc=used=src.used;
e=(Entry *)ProfileAllocMemory(alloc*sizeof(Entry));
memcpy(e,src.e,alloc*sizeof(Entry));
writeLock=true;
}
}
void ProfileFuncLevelTracer::UnsignedMap::MixIn(const UnsignedMap &src)
{
writeLock=false;
for (unsigned k=0;knext;
root->~List();
ProfileFreeMemory(root);
root=next;
}
}
ProfileFuncLevelTracer::Profile *ProfileFuncLevelTracer::ProfileMap::Find(int frame)
{
for (List *p=root;p&&p->framenext);
return p&&p->frame==frame?&p->p:NULL;
}
void ProfileFuncLevelTracer::ProfileMap::Append(int frame, const Profile &p)
{
List *newEntry=(List *)ProfileAllocMemory(sizeof(List));
new (newEntry) List;
newEntry->frame=frame;
newEntry->p.Copy(p);
newEntry->next=NULL;
*tail=newEntry;
tail=&newEntry->next;
}
void ProfileFuncLevelTracer::ProfileMap::MixIn(int frame, const Profile &p)
{
// search correct list entry
for (List *oldEntry=root;oldEntry;oldEntry=oldEntry->next)
if (oldEntry->frame==frame)
break;
if (!oldEntry)
Append(frame,p);
else
oldEntry->p.MixIn(p);
}
ProfileFuncLevelTracer::FunctionMap::FunctionMap(void):
e(NULL), alloc(0), used(0)
{
memset(hash,0,sizeof(hash));
}
ProfileFuncLevelTracer::FunctionMap::~FunctionMap()
{
if (e)
{
for (unsigned k=0;k~Function();
ProfileFreeMemory(e[k].funcPtr);
}
ProfileFreeMemory(e);
}
}
void ProfileFuncLevelTracer::FunctionMap::Insert(Function *funcPtr)
{
// realloc list?
if (used==alloc)
{
// must fixup pointers...
unsigned delta=unsigned(e);
e=(Entry *)ProfileReAllocMemory(e,(alloc+=1024)*sizeof(Entry));
delta=unsigned(e)-delta;
if (used&&delta)
{
for (unsigned k=0;kaddr/16)%HASH_SIZE;
e[used].funcPtr=funcPtr;
e[used].next=hash[at];
hash[at]=e+used++;
}
ProfileFuncLevelTracer::Function *ProfileFuncLevelTracer::FunctionMap::Enumerate(int index)
{
if (index<0||index>=(int)used)
return NULL;
return e[index].funcPtr;
}
bool ProfileFuncLevel::IdList::Enum(unsigned index, Id &id, unsigned *countPtr) const
{
if (!m_ptr)
return false;
ProfileFuncLevelTracer::Profile &prof=*(ProfileFuncLevelTracer::Profile *)m_ptr;
unsigned addr;
if ((addr=prof.caller.Enumerate(index)))
{
id.m_funcPtr=prof.tracer->FindFunction(addr);
if (countPtr)
*countPtr=prof.caller.GetCount(index);
return true;
}
else
return false;
}
const char *ProfileFuncLevel::Id::GetSource(void) const
{
if (!m_funcPtr)
return NULL;
ProfileFuncLevelTracer::Function *func=(ProfileFuncLevelTracer::Function *)m_funcPtr;
if (!func->funcSource)
{
char helpFunc[256],helpFile[256];
unsigned ofsFunc;
DebugStackwalk::Signature::GetSymbol(func->addr,
NULL,0,NULL,
helpFunc,sizeof(helpFunc),&ofsFunc,
helpFile,sizeof(helpFile),&func->funcLine,NULL);
char help[300];
wsprintf(help,ofsFunc?"%s+0x%x":"%s",helpFunc,ofsFunc);
func->funcSource=(char *)ProfileAllocMemory(strlen(helpFile)+1);
strcpy(func->funcSource,helpFile);
func->funcName=(char *)ProfileAllocMemory(strlen(help)+1);
strcpy(func->funcName,help);
}
return func->funcSource;
}
const char *ProfileFuncLevel::Id::GetFunction(void) const
{
if (!m_funcPtr)
return NULL;
ProfileFuncLevelTracer::Function *func=(ProfileFuncLevelTracer::Function *)m_funcPtr;
if (!func->funcSource)
GetSource();
return func->funcName;
}
unsigned ProfileFuncLevel::Id::GetAddress(void) const
{
if (!m_funcPtr)
return 0;
ProfileFuncLevelTracer::Function *func=(ProfileFuncLevelTracer::Function *)m_funcPtr;
return func->addr;
}
unsigned ProfileFuncLevel::Id::GetLine(void) const
{
if (!m_funcPtr)
return NULL;
ProfileFuncLevelTracer::Function *func=(ProfileFuncLevelTracer::Function *)m_funcPtr;
if (!func->funcSource)
GetSource();
return func->funcLine;
}
unsigned _int64 ProfileFuncLevel::Id::GetCalls(unsigned frame) const
{
if (!m_funcPtr)
return 0;
ProfileFuncLevelTracer::Function &func=*(ProfileFuncLevelTracer::Function *)m_funcPtr;
switch(frame)
{
case Total:
return func.glob.callCount;
default:
ProfileFuncLevelTracer::Profile *prof=func.frame.Find(frame);
return prof?prof->callCount:0;
}
}
unsigned _int64 ProfileFuncLevel::Id::GetTime(unsigned frame) const
{
if (!m_funcPtr)
return 0;
ProfileFuncLevelTracer::Function &func=*(ProfileFuncLevelTracer::Function *)m_funcPtr;
switch(frame)
{
case Total:
return func.glob.tickTotal;
default:
ProfileFuncLevelTracer::Profile *prof=func.frame.Find(frame);
return prof?prof->tickTotal:0;
}
}
unsigned _int64 ProfileFuncLevel::Id::GetFunctionTime(unsigned frame) const
{
if (!m_funcPtr)
return 0;
ProfileFuncLevelTracer::Function &func=*(ProfileFuncLevelTracer::Function *)m_funcPtr;
switch(frame)
{
case Total:
return func.glob.tickPure;
default:
ProfileFuncLevelTracer::Profile *prof=func.frame.Find(frame);
return prof?prof->tickPure:0;
}
}
ProfileFuncLevel::IdList ProfileFuncLevel::Id::GetCaller(unsigned frame) const
{
if (!m_funcPtr)
return IdList();
ProfileFuncLevelTracer::Function &func=*(ProfileFuncLevelTracer::Function *)m_funcPtr;
IdList ret;
switch(frame)
{
case Total:
ret.m_ptr=&func.glob;
break;
default:
ProfileFuncLevelTracer::Profile *prof=func.frame.Find(frame);
if (prof)
ret.m_ptr=prof;
}
return ret;
}
bool ProfileFuncLevel::Thread::EnumProfile(unsigned index, Id &id) const
{
if (!m_threadID)
return false;
ProfileFastCS::Lock lock(cs);
ProfileFuncLevelTracer::Function *f=m_threadID->EnumFunction(index);
if (f)
{
id.m_funcPtr=f;
return true;
}
else
return false;
}
bool ProfileFuncLevel::EnumThreads(unsigned index, Thread &thread)
{
ProfileFastCS::Lock lock(cs);
for (ProfileFuncLevelTracer *p=ProfileFuncLevelTracer::GetFirst();p;p=p->GetNext())
if (!index--)
break;
if (p)
{
thread.m_threadID=p;
return true;
}
else
return false;
}
ProfileFuncLevel::ProfileFuncLevel(void)
{
}
#else // !defined HAS_PROFILE
bool ProfileFuncLevel::IdList::Enum(unsigned index, Id &id, unsigned *) const
{
return false;
}
const char *ProfileFuncLevel::Id::GetSource(void) const
{
return NULL;
}
const char *ProfileFuncLevel::Id::GetFunction(void) const
{
return NULL;
}
unsigned ProfileFuncLevel::Id::GetAddress(void) const
{
return 0;
}
unsigned ProfileFuncLevel::Id::GetLine(void) const
{
return 0;
}
unsigned _int64 ProfileFuncLevel::Id::GetCalls(unsigned frame) const
{
return 0;
}
unsigned _int64 ProfileFuncLevel::Id::GetTime(unsigned frame) const
{
return 0;
}
unsigned _int64 ProfileFuncLevel::Id::GetFunctionTime(unsigned frame) const
{
return 0;
}
ProfileFuncLevel::IdList ProfileFuncLevel::Id::GetCaller(unsigned frame) const
{
return ProfileFuncLevel::IdList();
}
bool ProfileFuncLevel::Thread::EnumProfile(unsigned index, Id &id) const
{
return false;
}
bool ProfileFuncLevel::EnumThreads(unsigned index, Thread &thread)
{
return false;
}
ProfileFuncLevel::ProfileFuncLevel(void)
{
}
#endif // !defined HAS_PROFILE
ProfileFuncLevel ProfileFuncLevel::Instance;
HANDLE ProfileFastCS::testEvent=::CreateEvent(NULL,FALSE,FALSE,"");