Richard Boegli's CnC_Generals_Zero_Hour Fork WIP
This is documentation of Richard Boegil's Zero Hour Fork
 
Loading...
Searching...
No Matches
profile_funclevel.cpp
Go to the documentation of this file.
1/*
2** Command & Conquer Generals Zero Hour(tm)
3** Copyright 2025 Electronic Arts Inc.
4**
5** This program is free software: you can redistribute it and/or modify
6** it under the terms of the GNU General Public License as published by
7** the Free Software Foundation, either version 3 of the License, or
8** (at your option) any later version.
9**
10** This program is distributed in the hope that it will be useful,
11** but WITHOUT ANY WARRANTY; without even the implied warranty of
12** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13** GNU General Public License for more details.
14**
15** You should have received a copy of the GNU General Public License
16** along with this program. If not, see <http://www.gnu.org/licenses/>.
17*/
18
20// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/profile_funclevel.cpp $
21// $Author: mhoffe $
22// $Revision: #4 $
23// $DateTime: 2003/08/14 13:43:29 $
24//
25// ©2003 Electronic Arts
26//
27// Function level profiling
29#include "_pch.h"
30#include "../debug/debug.h"
31#include <new>
32
33#ifdef HAS_PROFILE
34
35// TLS index (-1 if not yet initialized)
36static int TLSIndex=-1;
37
38// our own fast critical section
39static ProfileFastCS cs;
40
41// Unfortunately VC 6 doesn't support _pleave (or _pexit) so
42// we have to come up with our own type of implementation here...
43static void __declspec(naked) _pleave(void)
44{
46 unsigned curESP,leaveAddr;
47
48 _asm
49 {
50 push ebp // this will be overwritten down there...
51 push ebp // setup standard stack frame
52 push eax
53 mov ebp,esp
54 mov eax,esp
55 sub esp,3*4 // 3 local DWord vars
56 pushad
57 mov [curESP],eax
58 }
59
60 curESP+=8;
61 p=(ProfileFuncLevelTracer *)TlsGetValue(TLSIndex);
62 leaveAddr=p->Leave(curESP);
63 *(unsigned *)(curESP)=leaveAddr;
64
65 _asm
66 {
67 popad
68 add esp,3*4 // must match sub esp above
69 pop eax
70 pop ebp
71 ret
72 }
73}
74
75extern "C" void __declspec(naked) _cdecl _penter(void)
76{
77 unsigned callerFunc,ESPonReturn,callerRet;
79
80 _asm
81 {
82 push ebp
83 push eax
84 mov ebp,esp
85 mov eax,esp
86 sub esp,4*4 // 4 local DWord vars
87 pushad
88
89 // calc return address
90 add eax,4+4 // account for push ebp and push eax
91 mov ebx,[eax] // grab return address
92 mov [callerFunc],ebx
93
94 // get some more stuff
95 add eax,4
96 mov [ESPonReturn],eax
97 mov ebx,[eax]
98 mov [callerRet],ebx
99
100 // jam in our exit code
101 mov dword ptr [eax],offset _pleave
102 }
103
104 // do we need a new stack tracer?
105 if (TLSIndex==-1)
106 TLSIndex=TlsAlloc();
107 p=(ProfileFuncLevelTracer *)TlsGetValue(TLSIndex);
108 if (!p)
109 {
112 TlsSetValue(TLSIndex,p);
113 }
114
115 // enter function
116 p->Enter(callerFunc-5,ESPonReturn,callerRet);
117
118 // cleanup
119 _asm
120 {
121 popad
122 add esp,4*4 // must match sub esp above
123 pop eax
124 pop ebp
125 ret
126 }
127}
128
129ProfileFuncLevelTracer *ProfileFuncLevelTracer::head=NULL;
130bool ProfileFuncLevelTracer::shuttingDown=false;
131int ProfileFuncLevelTracer::curFrame=0;
132unsigned ProfileFuncLevelTracer::frameRecordMask;
133bool ProfileFuncLevelTracer::recordCaller=false;
134
136 stack(NULL), usedStack(0), totalStack(0), maxDepth(0)
137{
138 ProfileFastCS::Lock lock(cs);
139
140 next=head;
141 head=this;
142}
143
145{
146 // yes, I know we leak...
147}
148
149void ProfileFuncLevelTracer::Enter(unsigned addr, unsigned esp, unsigned ret)
150{
151 // must stack grow?
152 if (usedStack>=totalStack)
153 stack=(StackEntry *)ProfileReAllocMemory(stack,(totalStack+=100)*sizeof(StackEntry));
154
155 // save info
156 Function *f=func.Find(addr);
157 if (!f)
158 {
159 // new function
161 new (f) Function(this);
162 f->addr=addr;
163 f->glob.callCount=f->glob.tickPure=f->glob.tickTotal=0;
164 for (int i=0;i<MAX_FRAME_RECORDS;i++)
165 f->cur[i].callCount=f->cur[i].tickPure=f->cur[i].tickTotal=0;
166 f->depth=0;
167 func.Insert(f);
168 }
169
170 StackEntry &s=stack[usedStack++];
171 s.func=f;
172 s.esp=esp;
173 s.retVal=ret;
174 ProfileGetTime(s.tickEnter);
175 s.tickSubTime=0;
176 f->depth++;
177
178 // new max depth?
179 if (usedStack>=maxDepth)
180 maxDepth=usedStack;
181
182 DLOG_GROUP(profile_stack,Debug::RepeatChar(' ',usedStack-1)
183 << Debug::Hex() << this
184 << " Enter " << Debug::Width(8) << addr
185 << " ESP " << Debug::Width(8) << esp
186 << " return " << Debug::Width(8) << ret
187 << " level " << Debug::Dec() << usedStack
188 );
189}
190
191unsigned ProfileFuncLevelTracer::Leave(unsigned esp)
192{
193 // get current "time"
194 __int64 cur;
195 ProfileGetTime(cur);
196
197 while (usedStack>0)
198 {
199 // leave current function
200 usedStack--;
201 StackEntry &s=stack[usedStack],
202 &sPrev=stack[usedStack-1];
203
204 Function *f=s.func;
205
206 // decrease call depth
207 // note: add global time only if call depth is 0
208 f->depth--;
209
210 // insert caller
211 if (recordCaller&&usedStack)
212 f->glob.caller.Insert(sPrev.func->addr,1);
213
214 // inc call counter
215 f->glob.callCount++;
216
217 // add total time
218 __int64 delta=cur-s.tickEnter;
219 if (!f->depth)
220 f->glob.tickTotal+=delta;
221
222 // add pure time
223 f->glob.tickPure+=delta-s.tickSubTime;
224
225 // add sub time for higher function
226 if (usedStack)
227 sPrev.tickSubTime+=delta;
228
229 // frame based profiling?
230 if (frameRecordMask)
231 {
232 unsigned mask=frameRecordMask;
233 for (unsigned i=0;i<MAX_FRAME_RECORDS;i++)
234 {
235 if (mask&1)
236 {
237 if (recordCaller&&usedStack>0)
238 f->cur[i].caller.Insert(sPrev.func->addr,1);
239 f->cur[i].callCount++;
240 if (!f->depth)
241 f->cur[i].tickTotal+=delta;
242 f->cur[i].tickPure+=delta-s.tickSubTime;
243 }
244 if (!(mask>>=1))
245 break;
246 }
247 }
248
249 // exit if address match (somewhat...)
250 if (s.esp==esp)
251 break;
252
253 // catching those nasty ret<n>...
254 if (s.esp<esp&&
255 (esp-s.esp)%4==0&&
256 (esp-s.esp)<256)
257 break;
258
259 // emit warning
260 DCRASH("ESP " << Debug::Hex() << esp << " does not match " << stack[usedStack].esp << Debug>>Dec());
261 }
262
263 DLOG_GROUP(profile_stack,Debug::RepeatChar(' ',usedStack-1)
264 << Debug::Hex() << this
265 << " Leave " << Debug::Width(8) << ""
266 << " ESP " << Debug::Width(8) << stack[usedStack].esp
267 << " return " << Debug::Width(8) << stack[usedStack].retVal
268 << " level " << Debug::Dec() << usedStack
269 );
270
271 return stack[usedStack].retVal;
272}
273
275{
276 if (frameRecordMask)
277 {
278 for (unsigned i=0;i<MAX_FRAME_RECORDS;i++)
279 if (frameRecordMask&(1<<i))
280 for (ProfileFuncLevelTracer *p=head;p;p=p->next)
281 p->FrameEnd(i,-1);
282 }
283}
284
286{
287 ProfileFastCS::Lock lock(cs);
288
289 for (unsigned i=0;i<MAX_FRAME_RECORDS;i++)
290 if (!(frameRecordMask&(1<<i)))
291 break;
292 if (i==MAX_FRAME_RECORDS)
293 return -1;
294
295 for (ProfileFuncLevelTracer *p=head;p;p=p->next)
296 {
297 Function *f;
298 for (int k=0;(f=p->func.Enumerate(k))!=NULL;k++)
299 {
300 Profile &p=f->cur[i];
301 p.caller.Clear();
302 p.callCount=p.tickPure=p.tickTotal=0;
303 }
304 }
305
306 frameRecordMask|=1<<i;
307 return i;
308}
309
310void ProfileFuncLevelTracer::FrameEnd(int which, int mixIndex)
311{
312 DFAIL_IF(which<0||which>=MAX_FRAME_RECORDS)
313 return;
314 DFAIL_IF(!(frameRecordMask&(1<<which)))
315 return;
316 DFAIL_IF(mixIndex>=curFrame)
317 return;
318
319 ProfileFastCS::Lock lock(cs);
320
321 frameRecordMask^=1<<which;
322 if (mixIndex<0)
323 curFrame++;
324 for (ProfileFuncLevelTracer *p=head;p;p=p->next)
325 {
326 Function *f;
327 for (int k=0;(f=p->func.Enumerate(k))!=NULL;k++)
328 {
329 Profile &p=f->cur[which];
330 if (p.callCount)
331 {
332 if (mixIndex<0)
333 f->frame.Append(curFrame,p);
334 else
335 f->frame.MixIn(mixIndex,p);
336 }
337 p.caller.Clear();
338 p.callCount=p.tickPure=p.tickTotal=0;
339 }
340 }
341}
342
344{
345 ProfileFastCS::Lock lock(cs);
346
347 for (ProfileFuncLevelTracer *p=head;p;p=p->next)
348 {
349 Function *f;
350 for (int k=0;(f=p->func.Enumerate(k))!=NULL;k++)
351 {
352 f->glob.caller.Clear();
353 f->glob.callCount=0;
354 f->glob.tickPure=0;
355 f->glob.tickTotal=0;
356 }
357 }
358}
359
361 e(NULL), alloc(0), used(0), writeLock(false)
362{
363 memset(hash,0,sizeof(hash));
364}
365
367{
368 Clear();
369}
370
372{
374 e=NULL;
375 alloc=used=0;
376 memset(hash,0,sizeof(hash));
377}
378
379void ProfileFuncLevelTracer::UnsignedMap::_Insert(unsigned at, unsigned val, int countAdd)
380{
381 DFAIL_IF(writeLock) return;
382
383 // realloc list?
384 if (used==alloc)
385 {
386 // must fixup pointers...
387 unsigned delta=unsigned(e);
388 e=(Entry *)ProfileReAllocMemory(e,((alloc+=64)*sizeof(Entry)));
389 delta=unsigned(e)-delta;
390 if (used&&delta)
391 {
392 for (unsigned k=0;k<HASH_SIZE;k++)
393 if (hash[k])
394 ((unsigned &)hash[k])+=delta;
395 for (k=0;k<used;k++)
396 if (e[k].next)
397 ((unsigned &)e[k].next)+=delta;
398 }
399 }
400
401 // add new item
402 e[used].val=val;
403 e[used].count=countAdd;
404 e[used].next=hash[at];
405 hash[at]=e+used++;
406}
407
409{
410 if (index<0||index>=(int)used)
411 return 0;
412 return e[index].val;
413}
414
416{
417 if (index<0||index>=(int)used)
418 return 0;
419 return e[index].count;
420}
421
423{
424 Clear();
425 if (src.e)
426 {
427 alloc=used=src.used;
428 e=(Entry *)ProfileAllocMemory(alloc*sizeof(Entry));
429 memcpy(e,src.e,alloc*sizeof(Entry));
430 writeLock=true;
431 }
432}
433
435{
436 writeLock=false;
437 for (unsigned k=0;k<src.used;k++)
438 Insert(src.e[k].val,src.e[k].count);
439 writeLock=true;
440}
441
443 root(NULL), tail(&root)
444{
445}
446
448{
449 while (root)
450 {
451 List *next=root->next;
452 root->~List();
453 ProfileFreeMemory(root);
454 root=next;
455 }
456}
457
458ProfileFuncLevelTracer::Profile *ProfileFuncLevelTracer::ProfileMap::Find(int frame)
459{
460 for (List *p=root;p&&p->frame<frame;p=p->next);
461 return p&&p->frame==frame?&p->p:NULL;
462}
463
465{
466 List *newEntry=(List *)ProfileAllocMemory(sizeof(List));
467 new (newEntry) List;
468 newEntry->frame=frame;
469 newEntry->p.Copy(p);
470 newEntry->next=NULL;
471 *tail=newEntry;
472 tail=&newEntry->next;
473}
474
476{
477 // search correct list entry
478 for (List *oldEntry=root;oldEntry;oldEntry=oldEntry->next)
479 if (oldEntry->frame==frame)
480 break;
481 if (!oldEntry)
482 Append(frame,p);
483 else
484 oldEntry->p.MixIn(p);
485}
486
487ProfileFuncLevelTracer::FunctionMap::FunctionMap(void):
488 e(NULL), alloc(0), used(0)
489{
490 memset(hash,0,sizeof(hash));
491}
492
493ProfileFuncLevelTracer::FunctionMap::~FunctionMap()
494{
495 if (e)
496 {
497 for (unsigned k=0;k<used;k++)
498 {
499 e[k].funcPtr->~Function();
501 }
503 }
504}
505
506void ProfileFuncLevelTracer::FunctionMap::Insert(Function *funcPtr)
507{
508 // realloc list?
509 if (used==alloc)
510 {
511 // must fixup pointers...
512 unsigned delta=unsigned(e);
513 e=(Entry *)ProfileReAllocMemory(e,(alloc+=1024)*sizeof(Entry));
514 delta=unsigned(e)-delta;
515 if (used&&delta)
516 {
517 for (unsigned k=0;k<HASH_SIZE;k++)
518 if (hash[k])
519 ((unsigned &)hash[k])+=delta;
520 for (k=0;k<used;k++)
521 if (e[k].next)
522 ((unsigned &)e[k].next)+=delta;
523 }
524 }
525
526 // add to hash
527 unsigned at=(funcPtr->addr/16)%HASH_SIZE;
528 e[used].funcPtr=funcPtr;
529 e[used].next=hash[at];
530 hash[at]=e+used++;
531}
532
533ProfileFuncLevelTracer::Function *ProfileFuncLevelTracer::FunctionMap::Enumerate(int index)
534{
535 if (index<0||index>=(int)used)
536 return NULL;
537 return e[index].funcPtr;
538}
539
540bool ProfileFuncLevel::IdList::Enum(unsigned index, Id &id, unsigned *countPtr) const
541{
542 if (!m_ptr)
543 return false;
544
545 ProfileFuncLevelTracer::Profile &prof=*(ProfileFuncLevelTracer::Profile *)m_ptr;
546
547 unsigned addr;
548 if ((addr=prof.caller.Enumerate(index)))
549 {
550 id.m_funcPtr=prof.tracer->FindFunction(addr);
551 if (countPtr)
552 *countPtr=prof.caller.GetCount(index);
553 return true;
554 }
555 else
556 return false;
557}
558
559const char *ProfileFuncLevel::Id::GetSource(void) const
560{
561 if (!m_funcPtr)
562 return NULL;
563
564 ProfileFuncLevelTracer::Function *func=(ProfileFuncLevelTracer::Function *)m_funcPtr;
565 if (!func->funcSource)
566 {
567 char helpFunc[256],helpFile[256];
568 unsigned ofsFunc;
570 NULL,0,NULL,
571 helpFunc,sizeof(helpFunc),&ofsFunc,
572 helpFile,sizeof(helpFile),&func->funcLine,NULL);
573
574 char help[300];
575 wsprintf(help,ofsFunc?"%s+0x%x":"%s",helpFunc,ofsFunc);
576 func->funcSource=(char *)ProfileAllocMemory(strlen(helpFile)+1);
577 strcpy(func->funcSource,helpFile);
578 func->funcName=(char *)ProfileAllocMemory(strlen(help)+1);
579 strcpy(func->funcName,help);
580 }
581
582 return func->funcSource;
583}
584
585const char *ProfileFuncLevel::Id::GetFunction(void) const
586{
587 if (!m_funcPtr)
588 return NULL;
589 ProfileFuncLevelTracer::Function *func=(ProfileFuncLevelTracer::Function *)m_funcPtr;
590 if (!func->funcSource)
591 GetSource();
592 return func->funcName;
593}
594
595unsigned ProfileFuncLevel::Id::GetAddress(void) const
596{
597 if (!m_funcPtr)
598 return 0;
599 ProfileFuncLevelTracer::Function *func=(ProfileFuncLevelTracer::Function *)m_funcPtr;
600 return func->addr;
601}
602
603unsigned ProfileFuncLevel::Id::GetLine(void) const
604{
605 if (!m_funcPtr)
606 return NULL;
607 ProfileFuncLevelTracer::Function *func=(ProfileFuncLevelTracer::Function *)m_funcPtr;
608 if (!func->funcSource)
609 GetSource();
610 return func->funcLine;
611}
612
613unsigned _int64 ProfileFuncLevel::Id::GetCalls(unsigned frame) const
614{
615 if (!m_funcPtr)
616 return 0;
617
618 ProfileFuncLevelTracer::Function &func=*(ProfileFuncLevelTracer::Function *)m_funcPtr;
619
620 switch(frame)
621 {
622 case Total:
623 return func.glob.callCount;
624 default:
625 ProfileFuncLevelTracer::Profile *prof=func.frame.Find(frame);
626 return prof?prof->callCount:0;
627 }
628}
629
630unsigned _int64 ProfileFuncLevel::Id::GetTime(unsigned frame) const
631{
632 if (!m_funcPtr)
633 return 0;
634
635 ProfileFuncLevelTracer::Function &func=*(ProfileFuncLevelTracer::Function *)m_funcPtr;
636
637 switch(frame)
638 {
639 case Total:
640 return func.glob.tickTotal;
641 default:
642 ProfileFuncLevelTracer::Profile *prof=func.frame.Find(frame);
643 return prof?prof->tickTotal:0;
644 }
645}
646
647unsigned _int64 ProfileFuncLevel::Id::GetFunctionTime(unsigned frame) const
648{
649 if (!m_funcPtr)
650 return 0;
651
652 ProfileFuncLevelTracer::Function &func=*(ProfileFuncLevelTracer::Function *)m_funcPtr;
653
654 switch(frame)
655 {
656 case Total:
657 return func.glob.tickPure;
658 default:
659 ProfileFuncLevelTracer::Profile *prof=func.frame.Find(frame);
660 return prof?prof->tickPure:0;
661 }
662}
663
664ProfileFuncLevel::IdList ProfileFuncLevel::Id::GetCaller(unsigned frame) const
665{
666 if (!m_funcPtr)
667 return IdList();
668
669 ProfileFuncLevelTracer::Function &func=*(ProfileFuncLevelTracer::Function *)m_funcPtr;
670
671 IdList ret;
672 switch(frame)
673 {
674 case Total:
675 ret.m_ptr=&func.glob;
676 break;
677 default:
678 ProfileFuncLevelTracer::Profile *prof=func.frame.Find(frame);
679 if (prof)
680 ret.m_ptr=prof;
681 }
682
683 return ret;
684}
685
686bool ProfileFuncLevel::Thread::EnumProfile(unsigned index, Id &id) const
687{
688 if (!m_threadID)
689 return false;
690
691 ProfileFastCS::Lock lock(cs);
692
693 ProfileFuncLevelTracer::Function *f=m_threadID->EnumFunction(index);
694 if (f)
695 {
696 id.m_funcPtr=f;
697 return true;
698 }
699 else
700 return false;
701}
702
703bool ProfileFuncLevel::EnumThreads(unsigned index, Thread &thread)
704{
705 ProfileFastCS::Lock lock(cs);
706
707 for (ProfileFuncLevelTracer *p=ProfileFuncLevelTracer::GetFirst();p;p=p->GetNext())
708 if (!index--)
709 break;
710 if (p)
711 {
712 thread.m_threadID=p;
713 return true;
714 }
715 else
716 return false;
717}
718
719ProfileFuncLevel::ProfileFuncLevel(void)
720{
721}
722
723#else // !defined HAS_PROFILE
724
725bool ProfileFuncLevel::IdList::Enum(unsigned index, Id &id, unsigned *) const
726{
727 return false;
728}
729
730const char *ProfileFuncLevel::Id::GetSource(void) const
731{
732 return NULL;
733}
734
736{
737 return NULL;
738}
739
741{
742 return 0;
743}
744
746{
747 return 0;
748}
749
750unsigned _int64 ProfileFuncLevel::Id::GetCalls(unsigned frame) const
751{
752 return 0;
753}
754
755unsigned _int64 ProfileFuncLevel::Id::GetTime(unsigned frame) const
756{
757 return 0;
758}
759
760unsigned _int64 ProfileFuncLevel::Id::GetFunctionTime(unsigned frame) const
761{
762 return 0;
763}
764
769
770bool ProfileFuncLevel::Thread::EnumProfile(unsigned index, Id &id) const
771{
772 return false;
773}
774
775bool ProfileFuncLevel::EnumThreads(unsigned index, Thread &thread)
776{
777 return false;
778}
779
780ProfileFuncLevel::ProfileFuncLevel(void)
781{
782}
783
784#endif // !defined HAS_PROFILE
785
786ProfileFuncLevel ProfileFuncLevel::Instance;
787HANDLE ProfileFastCS::testEvent=::CreateEvent(NULL,FALSE,FALSE,"");
#define NULL
Definition BaseType.h:92
#define FALSE
Definition BaseType.h:113
void __declspec(dllexport) CreateDebugDialog(void)
@ false
Definition bool.h:59
__forceinline void ProfileGetTime(__int64 &t)
Definition PerfTimer.cpp:38
void add(float *sum, float *addend)
static void GetSymbol(unsigned addr, char *buf, unsigned bufSize)
Determines symbol for given address.
friend class Lock
Definition internal.h:103
A function level profile ID.
unsigned GetAddress(void) const
Returns function address.
unsigned _int64 GetFunctionTime(unsigned frame) const
Determine time spend in this function only (exclude any time spend in child functions).
unsigned _int64 GetCalls(unsigned frame) const
Determine call counts.
unsigned GetLine(void) const
Returns the line number for this Id.
unsigned _int64 GetTime(unsigned frame) const
Determine time spend in this function and its children.
const char * GetSource(void) const
Returns the source file this Id is in.
const char * GetFunction(void) const
Returns the function name for this Id.
IdList GetCaller(unsigned frame) const
Determine the list of caller Ids.
A list of function level profile IDs.
bool Enum(unsigned index, Id &id, unsigned *countPtr=0) const
Enumerates the list of IDs.
bool EnumProfile(unsigned index, Id &id) const
Enumerates the list of known function level profile values.
static bool EnumThreads(unsigned index, Thread &thread)
Enumerates the list of known and profiled threads.
void Append(int frame, const Profile &p)
void MixIn(int frame, const Profile &p)
simple unique unsigned/unsigned map
void Copy(const UnsignedMap &src)
void MixIn(const UnsignedMap &src)
static ProfileFuncLevelTracer * GetFirst(void)
static int FrameStart(void)
unsigned Leave(unsigned esp)
static void FrameEnd(int which, int mixIndex)
Function * FindFunction(unsigned addr)
void Enter(unsigned addr, unsigned esp, unsigned ret)
static void ClearTotals(void)
static void Shutdown(void)
#define DLOG_GROUP(group, what)
#define DFAIL_IF(cond)
#define DCRASH(msg)
unsigned funcPtr[1]
void ProfileFreeMemory(void *ptr)
Definition profile.cpp:82
void * ProfileReAllocMemory(void *oldPtr, unsigned newSize)
Definition profile.cpp:52
void * ProfileAllocMemory(unsigned numBytes)
Definition profile.cpp:44
function entry (map address -> Function)
int depth
current call depth (for recursion)
unsigned addr
address of this function
ProfileFuncLevelTracer * tracer
tracer for this profile