Initial commit of Command & Conquer Generals and Command & Conquer Generals Zero Hour source code.

This commit is contained in:
LFeenanEA
2025-02-27 17:34:39 +00:00
parent 2e338c00cb
commit 3d0ee53a05
6072 changed files with 2283311 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/_pch.cpp $
// $Author: mhoffe $
// $Revision: #1 $
// $DateTime: 2003/07/03 11:55:26 $
//
// <20>2003 Electronic Arts
//
// Precompiled header (module internal)
//////////////////////////////////////////////////////////////////////////////
#include "_pch.h"

View File

@@ -0,0 +1,42 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/_pch.h $
// $Author: mhoffe $
// $Revision: #1 $
// $DateTime: 2003/07/03 11:55:26 $
//
// <20>2003 Electronic Arts
//
// Precompiled header (module internal)
//////////////////////////////////////////////////////////////////////////////
#ifdef _MSC_VER
# pragma once
#endif
#ifndef _PCH_H // Include guard
#define _PCH_H
#define WIN32_LEAN_AND_MEAN
#define STRICT
#include <windows.h>
#include "profile.h"
#include "internal.h"
#endif // _PCH_H

View File

@@ -0,0 +1,26 @@
@echo off
rem
rem This batch file generates DoxyGen info and compiles the separate
rem HTML files into CHMs.
rem
rem Assumptions:
rem ------------
rem - doxygen installed and in path
rem - hhc (HTML Help Compiler) installed and in path
rem - dot (Graphviz package) installed and in path
rem
rem Both generated CHM files are then copied over to the
rem common DOC area.
doxygen profile.dox
doxygen profile_priv.dox
cd doc\html
hhc index.hhp
copy index.chm ..\..\profile.chm
cd ..\html_priv
hhc index.hhp
copy index.chm ..\..\profile_priv.chm
cd ..\..
copy profile.chm ..\..\docs

View File

@@ -0,0 +1,125 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/internal.h $
// $Author: mhoffe $
// $Revision: #3 $
// $DateTime: 2003/07/09 10:57:23 $
//
// <20>2003 Electronic Arts
//
// Internal header
//////////////////////////////////////////////////////////////////////////////
#ifdef _MSC_VER
# pragma once
#endif
#ifndef INTERNAL_H // Include guard
#define INTERNAL_H
#include "../debug/debug.h"
#include "internal_funclevel.h"
#include "internal_highlevel.h"
#include "internal_cmd.h"
#include "internal_result.h"
class ProfileFastCS
{
ProfileFastCS(const ProfileFastCS&);
ProfileFastCS& operator=(const ProfileFastCS&);
volatile unsigned m_Flag;
static HANDLE testEvent;
void ThreadSafeSetFlag()
{
volatile unsigned& nFlag=m_Flag;
#define ts_lock _emit 0xF0
DASSERT(((unsigned)&nFlag % 4) == 0);
__asm mov ebx, [nFlag]
__asm ts_lock
__asm bts dword ptr [ebx], 0
__asm jc The_Bit_Was_Previously_Set_So_Try_Again
return;
The_Bit_Was_Previously_Set_So_Try_Again:
// can't use SwitchToThread() here because Win9X doesn't have it!
if (testEvent)
::WaitForSingleObject(testEvent,1);
__asm mov ebx, [nFlag]
__asm ts_lock
__asm bts dword ptr [ebx], 0
__asm jc The_Bit_Was_Previously_Set_So_Try_Again
}
void ThreadSafeClearFlag()
{
m_Flag=0;
}
public:
ProfileFastCS(void):
m_Flag(0)
{
}
class Lock
{
Lock(const Lock&);
Lock& operator=(const Lock&);
ProfileFastCS& CriticalSection;
public:
Lock(ProfileFastCS& cs):
CriticalSection(cs)
{
CriticalSection.ThreadSafeSetFlag();
}
~Lock()
{
CriticalSection.ThreadSafeClearFlag();
}
};
friend class Lock;
};
void *ProfileAllocMemory(unsigned numBytes);
void *ProfileReAllocMemory(void *oldPtr, unsigned newSize);
void ProfileFreeMemory(void *ptr);
__forceinline void ProfileGetTime(__int64 &t)
{
_asm
{
mov ecx,[t]
push eax
push edx
rdtsc
mov [ecx],eax
mov [ecx+4],edx
pop edx
pop eax
};
}
#endif // INTERNAL_H

View File

@@ -0,0 +1,61 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/internal_cmd.h $
// $Author: mhoffe $
// $Revision: #1 $
// $DateTime: 2003/07/09 10:57:23 $
//
// <20>2003 Electronic Arts
//
// Profile module command interface
//////////////////////////////////////////////////////////////////////////////
#ifdef _MSC_VER
# pragma once
#endif
#ifndef INTERNAL_CMD_H // Include guard
#define INTERNAL_CMD_H
class ProfileCmdInterface: public DebugCmdInterface
{
struct Factory
{
ProfileResultInterface* (*func)(int, const char * const *);
const char *name,*arg;
};
static unsigned numResIf;
static Factory *resIf;
unsigned numResFunc; // optimizer bug: must be declared volatile!
ProfileResultInterface **resFunc;
public:
ProfileCmdInterface(void): numResFunc(0), resFunc(0) {}
static void AddResultFunction(ProfileResultInterface* (*func)(int, const char * const *),
const char *name, const char *arg);
void RunResultFunctions(void);
virtual bool Execute(class Debug& dbg, const char *cmd, CommandMode cmdmode,
unsigned argn, const char * const * argv);
virtual void Delete(void) {}
};
#endif // INTERNAL_CMD_H

View File

@@ -0,0 +1,362 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/internal_funclevel.h $
// $Author: mhoffe $
// $Revision: #4 $
// $DateTime: 2003/08/14 13:43:29 $
//
// <20>2003 Electronic Arts
//
// Function level profiling (internal header)
//////////////////////////////////////////////////////////////////////////////
#ifdef _MSC_VER
# pragma once
#endif
#ifndef INTERNAL_FUNCLEVEL_H // Include guard
#define INTERNAL_FUNCLEVEL_H
class ProfileFuncLevelTracer
{
friend class ProfileCmdInterface;
// can't copy this
ProfileFuncLevelTracer(const ProfileFuncLevelTracer&);
ProfileFuncLevelTracer& operator=(const ProfileFuncLevelTracer&);
public:
enum
{
// # of simultaneous frame recordings
MAX_FRAME_RECORDS = 4
};
/// simple unique unsigned/unsigned map
class UnsignedMap
{
UnsignedMap(const UnsignedMap&);
UnsignedMap& operator=(const UnsignedMap&);
struct Entry
{
Entry *next;
unsigned val;
unsigned count;
};
enum
{
// do not make this number too large
// a single function uses approx HASH_SIZE*20 bytes!
HASH_SIZE = 131
};
Entry *e;
unsigned alloc,used;
Entry *hash[HASH_SIZE];
bool writeLock;
void _Insert(unsigned at, unsigned val, int countAdd);
public:
UnsignedMap(void);
~UnsignedMap();
void Clear(void);
void Insert(unsigned val, int countAdd);
unsigned Enumerate(int index);
unsigned GetCount(int index);
void Copy(const UnsignedMap &src);
void MixIn(const UnsignedMap &src);
};
/// profile entry
struct Profile
{
/// call count
__int64 callCount;
/// pure time
__int64 tickPure;
/// total time
__int64 tickTotal;
/// caller list
UnsignedMap caller;
/// tracer for this profile
ProfileFuncLevelTracer *tracer;
void Copy(const Profile &src)
{
callCount=src.callCount;
tickPure=src.tickPure;
tickTotal=src.tickTotal;
caller.Copy(src.caller);
}
void MixIn(const Profile &src)
{
callCount+=src.callCount;
tickPure+=src.tickPure;
tickTotal+=src.tickTotal;
caller.MixIn(src.caller);
}
};
/// map of profiles
class ProfileMap
{
ProfileMap(const ProfileMap&);
ProfileMap& operator=(const ProfileMap&);
struct List
{
List *next;
int frame;
Profile p;
};
List *root,**tail;
public:
ProfileMap(void);
~ProfileMap();
Profile *Find(int frame);
void Append(int frame, const Profile &p);
void MixIn(int frame, const Profile &p);
};
/// function entry (map address -> Function)
struct Function
{
/// address of this function
unsigned addr;
/// global profile
Profile glob;
/// profile for current frame(s)
Profile cur[MAX_FRAME_RECORDS];
/// frame based profiles
ProfileMap frame;
/// current call depth (for recursion)
int depth;
/// function source
char *funcSource;
/// function source line
unsigned funcLine;
/// function name
char *funcName;
Function(ProfileFuncLevelTracer *tr)
{
glob.tracer=tr;
for (int k=0;k<MAX_FRAME_RECORDS;k++)
cur[k].tracer=tr;
funcSource=funcName=NULL;
funcLine=0;
}
};
ProfileFuncLevelTracer(void);
~ProfileFuncLevelTracer();
/**
Enters the function at the given address.
@param addr function address
@param esp current ESP value
@param ret return address for given function
*/
void Enter(unsigned addr, unsigned esp, unsigned ret);
/**
Leaves the function at the ESP value.
If this does not match with the last Enter call
the internal stack is unwound until a match is found.
@param esp current ESP value
@return return address
*/
unsigned Leave(unsigned esp);
/**
Shutdown function.
*/
static void Shutdown(void);
/**
Starts frame based profiling, starts a new frame.
*/
static int FrameStart(void);
/**
Ends frame based profiling.
*/
static void FrameEnd(int which, int mixIndex);
/**
Clears all total values.
*/
static void ClearTotals(void);
/**
Retrieves the first function level tracer.
\return first function level tracer
*/
static ProfileFuncLevelTracer *GetFirst(void)
{
return head;
}
/**
Retrieves next function level tracer.
\return next function level tracer, NULL if none
*/
ProfileFuncLevelTracer *GetNext(void)
{
return next;
}
Function *FindFunction(unsigned addr)
{
return func.Find(addr);
}
Function *EnumFunction(unsigned index)
{
return func.Enumerate(index);
}
private:
/// single stack entry
struct StackEntry
{
/// function
Function *func;
/// ESP value
unsigned esp;
/// return value
unsigned retVal;
/// enter tick count
__int64 tickEnter;
/// time spend in called functions
__int64 tickSubTime;
};
/// map of functions
class FunctionMap
{
FunctionMap(const FunctionMap&);
FunctionMap& operator=(const FunctionMap&);
struct Entry
{
Entry *next;
Function *funcPtr;
};
enum
{
HASH_SIZE = 15551
};
Entry *e;
unsigned alloc,used;
Entry *hash[HASH_SIZE];
public:
FunctionMap(void);
~FunctionMap();
Function *Find(unsigned addr);
void Insert(Function *funcPtr);
Function *Enumerate(int index);
};
/// head of list
static ProfileFuncLevelTracer *head;
/// next tracer object
ProfileFuncLevelTracer *next;
/// are we in shutdown mode?
static bool shuttingDown;
/// stack
StackEntry *stack;
/// number of used entries
int usedStack;
/// total number of entries
int totalStack;
/// max call depth
int maxDepth;
/// current frame
static int curFrame;
/// function map
FunctionMap func;
/// bit mask, currently recording which cur[] entries?
static unsigned frameRecordMask;
/// record caller information?
static bool recordCaller;
};
inline void ProfileFuncLevelTracer::UnsignedMap::Insert(unsigned val, int countAdd)
{
// in hash?
unsigned at=(val/16)%HASH_SIZE;
for (Entry *e=hash[at];e;e=e->next)
if (e->val==val)
{
e->count+=countAdd;
return;
}
_Insert(at,val,countAdd);
}
inline ProfileFuncLevelTracer::Function *ProfileFuncLevelTracer::FunctionMap::Find(unsigned addr)
{
for (Entry *e=hash[(addr/16)%HASH_SIZE];e;e=e->next)
if (e->funcPtr->addr==addr)
return e->funcPtr;
return NULL;
}
#endif // INTERNAL_FUNCLEVEL_H

View File

@@ -0,0 +1,247 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/internal_highlevel.h $
// $Author: mhoffe $
// $Revision: #2 $
// $DateTime: 2003/08/14 13:43:29 $
//
// <20>2003 Electronic Arts
//
// High level profiling (internal header)
//////////////////////////////////////////////////////////////////////////////
#ifdef _MSC_VER
# pragma once
#endif
#ifndef INTERNAL_HIGHLEVEL_H // Include guard
#define INTERNAL_HIGHLEVEL_H
/// an internal high level profile ID
class ProfileId
{
ProfileId(const ProfileId&);
ProfileId& operator=(const ProfileId&);
public:
/**
Creates a new high level profile ID.
\param name profile name
\param descr descriptive name
\param unit unit name
\param precision number of decimal places to show
\param exp10 10 base exponent (used for scaleing)
*/
ProfileId(const char *name, const char *descr, const char *unit, int precision, int exp10);
/**
Retrieves the first profile ID.
\return first profile ID
*/
static ProfileId *GetFirst(void) { return first; }
/**
Retrieves next profile ID.
\return next profile ID, NULL if none
*/
ProfileId *GetNext(void) const { return m_next; }
/**
Retrieves name of the profile ID.
\return profile ID name
*/
const char *GetName(void) const { return m_name; }
/**
Retrieves unit name of the profile ID.
\return profile ID unit name
*/
const char *GetUnit(void) const { return m_unit?m_unit:""; }
/**
Retrieves description of the profile ID.
\return profile description
*/
const char *GetDescr(void) const { return m_descr?m_descr:""; }
/**
Increments the profile value.
\param add add value
*/
void Increment(double add);
/**
Sets a new maximum value.
\param max new maximum value
*/
void Maximum(double max);
/**
Returns current value, internally resetting it.
\return current value
*/
double GetCurrentValue(void)
{
double help=m_curVal;
m_curVal=0.;
return help;
}
/**
Returns total value
\return total value
*/
double GetTotalValue(void) const
{
return m_totalVal;
}
/**
Returns value at the given frame.
\param frame frame number
\param value value at frame
\return true if frame found, false if not
*/
bool GetFrameValue(unsigned frame, double &value) const
{
if (frame<(unsigned)m_firstFrame||
frame>=(unsigned)curFrame)
return false;
value=m_recFrameVal[frame-m_firstFrame];
return true;
}
/**
Translate given numeric value into a string, using an internal temp buffer.
\param v value
\return given numeric value as string
*/
const char *AsString(double v) const;
/**
Shutdown function.
*/
static void Shutdown(void);
/**
Starts frame based profiling, starts a new frame.
*/
static int FrameStart(void);
/**
Ends frame based profiling.
*/
static void FrameEnd(int which, int mixIndex);
/**
Clears all total values.
*/
static void ClearTotals(void)
{
for (ProfileId *cur=first;cur;cur=cur->m_next)
cur->m_totalVal=0.;
}
private:
/**
ProfileId's can't be destructed.
*/
~ProfileId() {}
enum
{
/// # of simultaneous frame recordings
MAX_FRAME_RECORDS = 4,
/// size of internal string buffer
STRING_BUFFER_SIZE = 1024
};
/// possible value modes
enum ValueMode
{
Unknown,
ModeIncrement,
ModeMaximum
};
/// first profile ID
static ProfileId *first;
/// next profile ID
ProfileId *m_next;
/// profile name
char *m_name;
/// profile description
char *m_descr;
/// profile unit
char *m_unit;
/// number of decimal places to show
int m_precision;
/// 10 base exponent (used for scaleing)
int m_exp10;
/// current value
double m_curVal;
/// total value
double m_totalVal;
/// frame values
double m_frameVal[MAX_FRAME_RECORDS];
/// list of recorded frame values
double *m_recFrameVal;
/// index of first recorded frame
int m_firstFrame;
/// value mode
ValueMode m_valueMode;
/// current frame
static int curFrame;
/// bit mask, currently recording which cur[] entries?
static unsigned frameRecordMask;
/// internal string buffer
static char stringBuf[STRING_BUFFER_SIZE];
/// next unused char in string buffer
static unsigned stringBufUnused;
};
#endif // INTERNAL_HIGHLEVEL_H

View File

@@ -0,0 +1,102 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/internal_result.h $
// $Author: mhoffe $
// $Revision: #1 $
// $DateTime: 2003/07/09 10:57:23 $
//
// <20>2003 Electronic Arts
//
// Internal result functions
//////////////////////////////////////////////////////////////////////////////
#ifdef _MSC_VER
# pragma once
#endif
#ifndef INTERNAL_RESULT_H // Include guard
#define INTERNAL_RESULT_H
/// \brief Simple CSV format flat file result function, for all threads.
class ProfileResultFileCSV: public ProfileResultInterface
{
ProfileResultFileCSV(void) {}
void WriteThread(ProfileFuncLevel::Thread &thread);
public:
static ProfileResultInterface *Create(int argn, const char * const *);
virtual const char *GetName(void) const { return "file_csv"; }
virtual void WriteResults(void);
virtual void Delete(void);
};
/**
\brief Write out DOT file for calling hierarchy.
The frame name and the file name must be specified when creating an
instance of this result function. The result function will always pick
the thread with the highest function count (which is usually the
main thread).
\note A DOT file is used with the DOT tool from the GraphViz package
for generating directed graphs, e.g. by issuing dot -Tgif -ograph.gif profile.dot
*/
class ProfileResultFileDOT: public ProfileResultInterface
{
public:
enum
{
MAX_FUNCTIONS_PER_FILE = 200
};
/**
\brief Creates a class instance.
\param fileName name of DOT file to generate (defaults to profile.dot)
\param frameName name of frame to use (NULL for global)
\param foldThreshold if the number of functions exceeds the given threshold
then all functions belonging to the same source file
will be folded into a single entry
\return new instance
*/
static ProfileResultInterface *Create(int argn, const char * const *);
virtual const char *GetName(void) const { return "file_dot"; }
virtual void WriteResults(void);
virtual void Delete(void);
private:
ProfileResultFileDOT(const char *fileName, const char *frameName, int foldThreshold);
struct FoldHelper
{
FoldHelper *next;
const char *source;
ProfileFuncLevel::Id id[MAX_FUNCTIONS_PER_FILE];
unsigned numId;
bool mark;
};
char *m_fileName;
char *m_frameName;
int m_foldThreshold;
};
#endif // INTERNAL_RESULT_H

View File

@@ -0,0 +1,382 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/profile.cpp $
// $Author: mhoffe $
// $Revision: #6 $
// $DateTime: 2003/08/14 13:43:29 $
//
// <20>2003 Electronic Arts
//
// Profile module main code
//////////////////////////////////////////////////////////////////////////////
#include "_pch.h"
#include <new>
#include "mmsystem.h"
#pragma comment (lib,"winmm")
// yuk, I'm doing this so weird because the destructor
// of cmd must never be called...
static ProfileCmdInterface &cmd=*(ProfileCmdInterface *)new
(ProfileAllocMemory(sizeof(ProfileCmdInterface))) ProfileCmdInterface();
// we have this here so that our command interface will always
// be linked in as well...
static bool __RegisterDebugCmdGroup_Profile=Debug::AddCommands("profile",&cmd);
void *ProfileAllocMemory(unsigned numBytes)
{
HGLOBAL h=GlobalAlloc(GMEM_FIXED,numBytes);
if (!h)
DCRASH_RELEASE("Debug mem alloc failed");
return (void *)h;
}
void *ProfileReAllocMemory(void *oldPtr, unsigned newSize)
{
// Windows doesn't like ReAlloc with NULL handle/ptr...
if (!oldPtr)
return newSize?ProfileAllocMemory(newSize):0;
// Shrinking to 0 size is basically freeing memory
if (!newSize)
{
GlobalFree((HGLOBAL)oldPtr);
return 0;
}
// now try GlobalReAlloc first
HGLOBAL h=GlobalReAlloc((HGLOBAL)oldPtr,newSize,0);
if (!h)
{
// this failed (Windows doesn't like ReAlloc'ing larger
// fixed memory blocks) - go with Alloc/Free instead
h=GlobalAlloc(GMEM_FIXED,newSize);
if (!h)
DCRASH_RELEASE("Debug mem realloc failed");
unsigned oldSize=GlobalSize((HGLOBAL)oldPtr);
memcpy((void *)h,oldPtr,oldSize<newSize?oldSize:newSize);
GlobalFree((HGLOBAL)oldPtr);
}
return (void *)h;
}
void ProfileFreeMemory(void *ptr)
{
if (ptr)
GlobalFree((HGLOBAL)ptr);
}
//////////////////////////////////////////////////////////////////////////////
static _int64 GetClockCyclesFast(void)
{
// this is where we're adding our internal result functions
Profile::AddResultFunction(ProfileResultFileCSV::Create,
"file_csv",
"");
Profile::AddResultFunction(ProfileResultFileCSV::Create,
"file_dot",
"[ file [ frame_name [ fold_threshold ] ] ]");
// this must not take a very huge CPU hit...
// measure clock cycles 3 times for 20 msec each
// then take the 2 counts that are closest, average
_int64 n[3];
for (int k=0;k<3;k++)
{
// wait for end of current tick
unsigned timeEnd=timeGetTime()+2;
while (timeGetTime()<timeEnd);
// get cycles
_int64 start,startQPC,endQPC;
QueryPerformanceCounter((LARGE_INTEGER *)&startQPC);
ProfileGetTime(start);
timeEnd+=20;
while (timeGetTime()<timeEnd);
ProfileGetTime(n[k]);
n[k]-=start;
// convert to 1 second
if (QueryPerformanceCounter((LARGE_INTEGER *)&endQPC))
{
_int64 freq;
QueryPerformanceFrequency((LARGE_INTEGER *)&freq);
n[k]=(n[k]*freq)/(endQPC-startQPC);
}
else
{
n[k]=(n[k]*1000)/20;
}
}
// find two closest values
_int64 d01=n[1]-n[0],d02=n[2]-n[0],d12=n[2]-n[1];
if (d01<0) d01=-d01;
if (d02<0) d02=-d02;
if (d12<0) d12=-d12;
_int64 avg;
if (d01<d02)
{
avg=d01<d12?n[0]+n[1]:n[1]+n[2];
}
else
{
avg=d02<d12?n[0]+n[2]:n[1]+n[2];
}
// return result
// (rounded to the next MHz)
return ((avg/2+500000)/1000000)*1000000;
}
unsigned Profile::m_rec;
char **Profile::m_recNames;
unsigned Profile::m_names;
Profile::FrameName *Profile::m_frameNames;
_int64 Profile::m_clockCycles=GetClockCyclesFast();
Profile::PatternListEntry *Profile::firstPatternEntry;
Profile::PatternListEntry *Profile::lastPatternEntry;
void Profile::StartRange(const char *range)
{
// set default
if (!range)
range="frame";
// known name?
for (unsigned k=0;k<m_names;++k)
if (!strcmp(range,m_frameNames[k].name))
break;
if (k==m_names)
{
// no, must add to list
m_frameNames=(FrameName *)ProfileReAllocMemory(m_frameNames,(++m_names)*sizeof(FrameName));
m_frameNames[k].name=(char *)ProfileAllocMemory(strlen(range)+1);
strcpy(m_frameNames[k].name,range);
m_frameNames[k].frames=0;
m_frameNames[k].isRecording=false;
m_frameNames[k].doAppend=false;
m_frameNames[k].lastGlobalIndex=-1;
}
// stop old recording?
if (m_frameNames[k].isRecording)
StopRange(range);
// start new recording
m_frameNames[k].isRecording=true;
m_frameNames[k].doAppend=false;
// but check first: is recording enabled?
bool active=false;
for (PatternListEntry *cur=firstPatternEntry;cur;cur=cur->next)
{
if (SimpleMatch(range,cur->pattern))
active=cur->isActive;
}
if (active)
{
#ifdef _PROFILE
m_frameNames[k].funcIndex=ProfileFuncLevelTracer::FrameStart();
DASSERT(m_frameNames[k].funcIndex>=0);
#endif
m_frameNames[k].highIndex=ProfileId::FrameStart();
DASSERT(m_frameNames[k].highIndex>=0);
}
else
{
m_frameNames[k].funcIndex=-1;
m_frameNames[k].highIndex=-1;
}
}
void Profile::AppendRange(const char *range)
{
// set default
if (!range)
range="frame";
// known name?
for (unsigned k=0;k<m_names;++k)
if (!strcmp(range,m_frameNames[k].name))
break;
if (k==m_names)
{
// no, so StartRange will do the job for us
StartRange(range);
return;
}
// still recording?
if (m_frameNames[k].isRecording)
// don't do anything
return;
// start new recording
m_frameNames[k].isRecording=true;
m_frameNames[k].doAppend=true;
// but check first: is recording enabled?
bool active=false;
for (PatternListEntry *cur=firstPatternEntry;cur;cur=cur->next)
{
if (SimpleMatch(range,cur->pattern))
active=cur->isActive;
}
if (active)
{
#ifdef _PROFILE
m_frameNames[k].funcIndex=ProfileFuncLevelTracer::FrameStart();
DASSERT(m_frameNames[k].funcIndex>=0);
#endif
m_frameNames[k].highIndex=ProfileId::FrameStart();
DASSERT(m_frameNames[k].highIndex>=0);
}
else
{
m_frameNames[k].funcIndex=-1;
m_frameNames[k].highIndex=-1;
}
}
void Profile::StopRange(const char *range)
{
// set default
if (!range)
range="frame";
// known name?
for (unsigned k=0;k<m_names;++k)
if (!strcmp(range,m_frameNames[k].name))
break;
DFAIL_IF(k==m_names) return;
DFAIL_IF(!m_frameNames[k].isRecording) return;
// stop recording
m_frameNames[k].isRecording=false;
if (
#ifdef _PROFILE
m_frameNames[k].funcIndex>=0 ||
#endif
m_frameNames[k].highIndex>=0
)
{
// add to list of known frames?
int atIndex;
if (!m_frameNames[k].doAppend||
m_frameNames[k].lastGlobalIndex<0)
{
atIndex=-1;
m_frameNames[k].lastGlobalIndex=m_rec;
m_recNames=(char **)ProfileReAllocMemory(m_recNames,(m_rec+1)*sizeof(char *));
m_recNames[m_rec]=(char *)ProfileAllocMemory(strlen(range)+1+6);
wsprintf(m_recNames[m_rec++],"%s:%i",range,++m_frameNames[k].frames);
}
else
atIndex=m_frameNames[k].lastGlobalIndex;
#ifdef _PROFILE
if (m_frameNames[k].funcIndex>=0)
ProfileFuncLevelTracer::FrameEnd(m_frameNames[k].funcIndex,atIndex);
if (m_frameNames[k].highIndex>=0)
#endif
ProfileId::FrameEnd(m_frameNames[k].highIndex,atIndex);
}
}
bool Profile::IsEnabled(void)
{
for (unsigned k=0;k<m_names;++k)
if (m_frameNames[k].isRecording)
return true;
return false;
}
unsigned Profile::GetFrameCount(void)
{
return m_rec;
}
const char *Profile::GetFrameName(unsigned frame)
{
return frame>=m_rec?NULL:m_recNames[frame];
}
void Profile::ClearTotals(void)
{
#ifdef _PROFILE
ProfileFuncLevelTracer::ClearTotals();
#endif
ProfileId::ClearTotals();
}
_int64 Profile::GetClockCyclesPerSecond(void)
{
return m_clockCycles;
}
void Profile::AddResultFunction(ProfileResultInterface* (*func)(int, const char * const *),
const char *name, const char *arg)
{
ProfileCmdInterface::AddResultFunction(func,name,arg);
}
bool Profile::SimpleMatch(const char *str, const char *pattern)
{
DASSERT(str);
DASSERT(pattern);
while (*str&&*pattern)
{
if (*pattern=='*')
{
pattern++;
while (*str)
if (SimpleMatch(str++,pattern))
return true;
return *str==*pattern;
}
else
{
if (*str++!=*pattern++)
return false;
}
}
return *str==*pattern;
}
static void ProfileShutdown(void)
{
#ifdef _PROFILE
ProfileFuncLevelTracer::Shutdown();
#endif
ProfileId::Shutdown();
DLOG("CPU speed is " << unsigned(Profile::GetClockCyclesPerSecond()) << " Hz.\n");
cmd.RunResultFunctions();
}
int profileTracerInit=atexit(ProfileShutdown);

View File

@@ -0,0 +1,208 @@
# Doxyfile 1.3.1
#---------------------------------------------------------------------------
# General configuration options
#---------------------------------------------------------------------------
PROJECT_NAME = "EA/Profile module"
PROJECT_NUMBER =
OUTPUT_DIRECTORY = doc/
OUTPUT_LANGUAGE = English
USE_WINDOWS_ENCODING = YES
EXTRACT_ALL = NO
EXTRACT_PRIVATE = NO
EXTRACT_STATIC = YES
EXTRACT_LOCAL_CLASSES = YES
HIDE_UNDOC_MEMBERS = YES
HIDE_UNDOC_CLASSES = YES
HIDE_FRIEND_COMPOUNDS = YES
HIDE_IN_BODY_DOCS = YES
BRIEF_MEMBER_DESC = YES
REPEAT_BRIEF = YES
ALWAYS_DETAILED_SEC = NO
INLINE_INHERITED_MEMB = YES
FULL_PATH_NAMES = NO
STRIP_FROM_PATH =
INTERNAL_DOCS = NO
CASE_SENSE_NAMES = YES
SHORT_NAMES = NO
HIDE_SCOPE_NAMES = NO
SHOW_INCLUDE_FILES = YES
JAVADOC_AUTOBRIEF = NO
MULTILINE_CPP_IS_BRIEF = NO
DETAILS_AT_TOP = NO
INHERIT_DOCS = YES
INLINE_INFO = YES
SORT_MEMBER_DOCS = YES
DISTRIBUTE_GROUP_DOC = NO
TAB_SIZE = 8
GENERATE_TODOLIST = NO
GENERATE_TESTLIST = YES
GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
ALIASES = "todo_opt=\todo"
ALIASES += "todo_urgent=\todo"
ALIASES += "todo_feature=\todo"
ENABLED_SECTIONS =
MAX_INITIALIZER_LINES = 30
OPTIMIZE_OUTPUT_FOR_C = NO
OPTIMIZE_OUTPUT_JAVA = NO
SHOW_USED_FILES = YES
#---------------------------------------------------------------------------
# configuration options related to warning and progress messages
#---------------------------------------------------------------------------
QUIET = NO
WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES
WARN_FORMAT = "$file:$line: $text"
WARN_LOGFILE =
#---------------------------------------------------------------------------
# configuration options related to the input files
#---------------------------------------------------------------------------
INPUT = ./
FILE_PATTERNS = profile*.h
RECURSIVE = NO
EXCLUDE =
EXCLUDE_SYMLINKS = NO
EXCLUDE_PATTERNS =
EXAMPLE_PATH =
EXAMPLE_PATTERNS =
EXAMPLE_RECURSIVE = NO
IMAGE_PATH =
INPUT_FILTER =
FILTER_SOURCE_FILES = NO
#---------------------------------------------------------------------------
# configuration options related to source browsing
#---------------------------------------------------------------------------
SOURCE_BROWSER = NO
INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = YES
REFERENCED_BY_RELATION = YES
REFERENCES_RELATION = YES
VERBATIM_HEADERS = YES
#---------------------------------------------------------------------------
# configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
ALPHABETICAL_INDEX = YES
COLS_IN_ALPHA_INDEX = 5
IGNORE_PREFIX =
#---------------------------------------------------------------------------
# configuration options related to the HTML output
#---------------------------------------------------------------------------
GENERATE_HTML = YES
HTML_OUTPUT = html
HTML_FILE_EXTENSION = .html
HTML_HEADER =
HTML_FOOTER =
HTML_STYLESHEET =
HTML_ALIGN_MEMBERS = YES
GENERATE_HTMLHELP = YES
CHM_FILE =
HHC_LOCATION =
GENERATE_CHI = NO
BINARY_TOC = NO
TOC_EXPAND = NO
DISABLE_INDEX = NO
ENUM_VALUES_PER_LINE = 4
GENERATE_TREEVIEW = YES
TREEVIEW_WIDTH = 250
#---------------------------------------------------------------------------
# configuration options related to the LaTeX output
#---------------------------------------------------------------------------
GENERATE_LATEX = NO
LATEX_OUTPUT = latex
LATEX_CMD_NAME = latex
MAKEINDEX_CMD_NAME = makeindex
COMPACT_LATEX = NO
PAPER_TYPE = a4wide
EXTRA_PACKAGES =
LATEX_HEADER =
PDF_HYPERLINKS = NO
USE_PDFLATEX = NO
LATEX_BATCHMODE = NO
LATEX_HIDE_INDICES = NO
#---------------------------------------------------------------------------
# configuration options related to the RTF output
#---------------------------------------------------------------------------
GENERATE_RTF = YES
RTF_OUTPUT = rtf
COMPACT_RTF = NO
RTF_HYPERLINKS = NO
RTF_STYLESHEET_FILE =
RTF_EXTENSIONS_FILE =
#---------------------------------------------------------------------------
# configuration options related to the man page output
#---------------------------------------------------------------------------
GENERATE_MAN = NO
MAN_OUTPUT = man
MAN_EXTENSION = .3
MAN_LINKS = NO
#---------------------------------------------------------------------------
# configuration options related to the XML output
#---------------------------------------------------------------------------
GENERATE_XML = NO
XML_OUTPUT = xml
XML_SCHEMA =
XML_DTD =
#---------------------------------------------------------------------------
# configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
# configuration options related to the Perl module output
#---------------------------------------------------------------------------
GENERATE_PERLMOD = NO
PERLMOD_LATEX = NO
PERLMOD_PRETTY = YES
PERLMOD_MAKEVAR_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = NO
EXPAND_ONLY_PREDEF = NO
SEARCH_INCLUDES = YES
INCLUDE_PATH =
INCLUDE_FILE_PATTERNS =
PREDEFINED = _DEBUG \
DOXYGEN
EXPAND_AS_DEFINED =
SKIP_FUNCTION_MACROS = YES
#---------------------------------------------------------------------------
# Configuration::addtions related to external references
#---------------------------------------------------------------------------
TAGFILES =
GENERATE_TAGFILE =
ALLEXTERNALS = NO
EXTERNAL_GROUPS = YES
PERL_PATH = /usr/bin/perl
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
CLASS_DIAGRAMS = YES
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = YES
CLASS_GRAPH = YES
COLLABORATION_GRAPH = NO
TEMPLATE_RELATIONS = NO
INCLUDE_GRAPH = YES
INCLUDED_BY_GRAPH = YES
GRAPHICAL_HIERARCHY = YES
DOT_IMAGE_FORMAT = png
DOT_PATH =
DOTFILE_DIRS =
MAX_DOT_GRAPH_WIDTH = 1024
MAX_DOT_GRAPH_HEIGHT = 1024
MAX_DOT_GRAPH_DEPTH = 0
GENERATE_LEGEND = YES
DOT_CLEANUP = YES
#---------------------------------------------------------------------------
# Configuration::addtions related to the search engine
#---------------------------------------------------------------------------
SEARCHENGINE = NO
CGI_NAME = search.cgi
CGI_URL =
DOC_URL =
DOC_ABSPATH =
BIN_ABSPATH = /usr/local/bin/
EXT_DOC_PATHS =

View File

@@ -0,0 +1,289 @@
# Microsoft Developer Studio Project File - Name="profile" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **
# TARGTYPE "Win32 (x86) Static Library" 0x0104
CFG=profile - Win32 Debug
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
!MESSAGE use the Export Makefile command and run
!MESSAGE
!MESSAGE NMAKE /f "profile.mak".
!MESSAGE
!MESSAGE You can specify a configuration when running NMAKE
!MESSAGE by defining the macro CFG on the command line. For example:
!MESSAGE
!MESSAGE NMAKE /f "profile.mak" CFG="profile - Win32 Debug"
!MESSAGE
!MESSAGE Possible choices for configuration are:
!MESSAGE
!MESSAGE "profile - Win32 Release" (based on "Win32 (x86) Static Library")
!MESSAGE "profile - Win32 Debug" (based on "Win32 (x86) Static Library")
!MESSAGE "profile - Win32 Internal" (based on "Win32 (x86) Static Library")
!MESSAGE "profile - Win32 Profile" (based on "Win32 (x86) Static Library")
!MESSAGE
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName "profile"
# PROP Scc_LocalPath "."
CPP=cl.exe
RSC=rc.exe
!IF "$(CFG)" == "profile - Win32 Release"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "Release"
# PROP BASE Intermediate_Dir "Release"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "Release"
# PROP Intermediate_Dir "Release"
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
# ADD CPP /nologo /G6 /MD /W3 /WX /Zi /Ot /Og /Oi /Oy /Ob2 /Gy /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /Yu"_pch.h" /FD /GF /Gs /c
# SUBTRACT CPP /Oa
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LIB32=link.exe -lib
# ADD BASE LIB32 /nologo
# ADD LIB32 /nologo
# Begin Special Build Tool
SOURCE="$(InputPath)"
PostBuild_Desc=copying
PostBuild_Cmds=copy release\profile.lib ..\..\lib
# End Special Build Tool
!ELSEIF "$(CFG)" == "profile - Win32 Debug"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 1
# PROP BASE Output_Dir "Debug"
# PROP BASE Intermediate_Dir "Debug"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 1
# PROP Output_Dir "Debug"
# PROP Intermediate_Dir "Debug"
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c
# ADD CPP /nologo /G6 /MDd /W3 /WX /Gm /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /Yu"_pch.h" /FD /GZ /c
# ADD BASE RSC /l 0x409 /d "_DEBUG"
# ADD RSC /l 0x409 /d "_DEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LIB32=link.exe -lib
# ADD BASE LIB32 /nologo
# ADD LIB32 /nologo /out:"Debug\profiledebug.lib"
# Begin Special Build Tool
SOURCE="$(InputPath)"
PostBuild_Desc=copying
PostBuild_Cmds=copy debug\profiledebug.lib ..\..\lib
# End Special Build Tool
!ELSEIF "$(CFG)" == "profile - Win32 Internal"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "Internal"
# PROP BASE Intermediate_Dir "Internal"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "Internal"
# PROP Intermediate_Dir "Internal"
# PROP Target_Dir ""
# ADD BASE CPP /nologo /G6 /MD /W3 /WX /Zi /Ot /Oa /Og /Oi /Oy /Ob2 /Gy /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GF /Gs /c
# ADD CPP /nologo /G6 /MD /W3 /WX /Zi /Ot /Og /Oi /Oy /Ob2 /Gy /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /D "_INTERNAL" /Yu"_pch.h" /FD /GF /Gs /c
# SUBTRACT CPP /Oa
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LIB32=link.exe -lib
# ADD BASE LIB32 /nologo
# ADD LIB32 /nologo /out:"Internal\profileinternal.lib"
# Begin Special Build Tool
SOURCE="$(InputPath)"
PostBuild_Desc=copying
PostBuild_Cmds=copy internal\profileinternal.lib ..\..\lib
# End Special Build Tool
!ELSEIF "$(CFG)" == "profile - Win32 Profile"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "Profile"
# PROP BASE Intermediate_Dir "Profile"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "Profile"
# PROP Intermediate_Dir "Profile"
# PROP Target_Dir ""
# ADD BASE CPP /nologo /G6 /MD /W3 /WX /Zi /Ot /Oa /Og /Oi /Oy /Ob2 /Gy /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GF /Gs /c
# ADD CPP /nologo /G6 /MD /W3 /WX /Zi /Ot /Og /Oi /Oy /Ob2 /Gy /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /D "_PROFILE" /Yu"_pch.h" /FD /GF /Gs /c
# SUBTRACT CPP /Oa /Ow
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LIB32=link.exe -lib
# ADD BASE LIB32 /nologo
# ADD LIB32 /nologo /out:"Profile\profileprofile.lib"
# Begin Special Build Tool
SOURCE="$(InputPath)"
PostBuild_Desc=copying
PostBuild_Cmds=copy profile\profileprofile.lib ..\..\lib
# End Special Build Tool
!ENDIF
# Begin Target
# Name "profile - Win32 Release"
# Name "profile - Win32 Debug"
# Name "profile - Win32 Internal"
# Name "profile - Win32 Profile"
# Begin Group "Precompiled header"
# PROP Default_Filter ""
# Begin Source File
SOURCE=.\_pch.cpp
# ADD CPP /Yc"_pch.h"
# End Source File
# Begin Source File
SOURCE=.\_pch.h
# End Source File
# End Group
# Begin Group "Doxygen"
# PROP Default_Filter ""
# Begin Source File
SOURCE=.\compile_doxygen.bat
# End Source File
# Begin Source File
SOURCE=.\profile.dox
# End Source File
# Begin Source File
SOURCE=.\profile_priv.dox
# End Source File
# End Group
# Begin Group "Function level"
# PROP Default_Filter ""
# Begin Source File
SOURCE=.\internal_funclevel.h
# End Source File
# Begin Source File
SOURCE=.\profile_funclevel.cpp
!IF "$(CFG)" == "profile - Win32 Release"
!ELSEIF "$(CFG)" == "profile - Win32 Debug"
!ELSEIF "$(CFG)" == "profile - Win32 Internal"
!ELSEIF "$(CFG)" == "profile - Win32 Profile"
# ADD CPP /FAcs
!ENDIF
# End Source File
# Begin Source File
SOURCE=.\profile_funclevel.h
# End Source File
# End Group
# Begin Group "High level"
# PROP Default_Filter ""
# Begin Source File
SOURCE=.\internal_highlevel.h
# End Source File
# Begin Source File
SOURCE=.\profile_highlevel.cpp
# End Source File
# Begin Source File
SOURCE=.\profile_highlevel.h
# End Source File
# End Group
# Begin Group "Command interface"
# PROP Default_Filter ""
# Begin Source File
SOURCE=.\internal_cmd.h
# End Source File
# Begin Source File
SOURCE=.\profile_cmd.cpp
!IF "$(CFG)" == "profile - Win32 Release"
!ELSEIF "$(CFG)" == "profile - Win32 Debug"
!ELSEIF "$(CFG)" == "profile - Win32 Internal"
!ELSEIF "$(CFG)" == "profile - Win32 Profile"
# ADD CPP /FAcs
# SUBTRACT CPP /Oa /Ow
!ENDIF
# End Source File
# End Group
# Begin Group "Result functions"
# PROP Default_Filter ""
# Begin Source File
SOURCE=.\internal_result.h
# End Source File
# Begin Source File
SOURCE=.\profile_result.cpp
# End Source File
# Begin Source File
SOURCE=.\profile_result.h
# End Source File
# End Group
# Begin Source File
SOURCE=.\internal.h
# End Source File
# Begin Source File
SOURCE=.\profile.cpp
# End Source File
# Begin Source File
SOURCE=.\profile.h
# End Source File
# Begin Source File
SOURCE=.\profile_doc.h
# End Source File
# End Target
# End Project

View File

@@ -0,0 +1,75 @@
Microsoft Developer Studio Workspace File, Format Version 6.00
# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
###############################################################################
Project: "debug"=..\debug\debug.dsp - Package Owner=<4>
Package=<5>
{{{
begin source code control
Perforce Project
..\debug
end source code control
}}}
Package=<4>
{{{
}}}
###############################################################################
Project: "profile"=.\profile.dsp - Package Owner=<4>
Package=<5>
{{{
begin source code control
Perforce Project
.
end source code control
}}}
Package=<4>
{{{
}}}
###############################################################################
Project: "test1"=.\test1\test1.dsp - Package Owner=<4>
Package=<5>
{{{
begin source code control
Perforce Project
.\test1
end source code control
}}}
Package=<4>
{{{
Begin Project Dependency
Project_Dep_Name profile
End Project Dependency
Begin Project Dependency
Project_Dep_Name debug
End Project Dependency
}}}
###############################################################################
Global:
Package=<5>
{{{
begin source code control
Perforce Project
.
end source code control
}}}
Package=<3>
{{{
}}}
###############################################################################

View File

@@ -0,0 +1,225 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/profile.h $
// $Author: mhoffe $
// $Revision: #4 $
// $DateTime: 2003/08/14 13:43:29 $
//
// <20>2003 Electronic Arts
//
// Profiling module
//////////////////////////////////////////////////////////////////////////////
#ifdef _MSC_VER
# pragma once
#endif
#ifndef PROFILE_H // Include guard
#define PROFILE_H
#if defined(_DEBUG) && defined(_INTERNAL)
#error "Only either _DEBUG or _INTERNAL should ever be defined"
#endif
// Define which libraries to use.
#if defined(_INTERNAL)
# pragma comment (lib,"profileinternal.lib")
#elif defined(_DEBUG)
# pragma comment (lib,"profiledebug.lib")
#elif defined(_PROFILE)
# pragma comment (lib,"profileprofile.lib")
#else
# pragma comment (lib,"profile.lib")
#endif
// include all our public header files (use double quotes here)
#include "profile_doc.h"
#include "profile_highlevel.h"
#include "profile_funclevel.h"
#include "profile_result.h"
/**
\brief Functions common to both profilers.
*/
class Profile
{
friend class ProfileCmdInterface;
// nobody can construct this class
Profile();
public:
/**
\brief Starts range recording.
\param range name of range to record, ==NULL for "frame"
*/
static void StartRange(const char *range=0);
/**
\brief Appends profile data to the last recorded frame
of the given range.
\param range name of range to record, ==NULL for "frame"
*/
static void AppendRange(const char *range=0);
/**
\brief Stops range recording.
\note After this call the recorded range data will be available
as a new range frame.
\param range name of range to record, ==NULL for "frame"
*/
static void StopRange(const char *range=0);
/**
\brief Determines if any range recording is enabled or not.
\return true if range profiling is enabled, false if not
*/
static bool IsEnabled(void);
/**
\brief Determines the number of known (recorded) range frames.
Note that if function level profiling is enabled then the number
of recorded high level frames is the same as the number of recorded
function level frames.
\return number of recorded range frames
*/
static unsigned GetFrameCount(void);
/**
\brief Determines the range name of a recorded range frame.
\note A unique number will be added to the frame name, separated by
a ':', e.g. 'frame:3'
\param frame number of recorded frame
\return range name
*/
static const char *GetFrameName(unsigned frame);
/**
\brief Resets all 'total' counter values to 0.
This function does not change any recorded frames.
*/
static void ClearTotals(void);
/**
\brief Determines number of CPU clock cycles per second.
\note This value is cached internally so this function is
quite fast.
\return number of CPU clock cycles per second
*/
static _int64 GetClockCyclesPerSecond(void);
/**
\brief Add the given result function interface.
\param func factory function
\param name factory name
\param arg description of optional parameters the factory function recognizes
*/
static void AddResultFunction(ProfileResultInterface* (*func)(int, const char * const *),
const char *name, const char *arg);
private:
/** \internal
\brief Simple recursive pattern matcher.
\param str string to match
\param pattern pattern, only wildcard valid is '*'
\return true if string matches pattern, false if not
*/
static bool SimpleMatch(const char *str, const char *pattern);
/// known frame names
struct FrameName
{
/// frame name
char *name;
/// number of recorded frames for this name
unsigned frames;
/// are we currently recording?
bool isRecording;
/// should current frame be appended to last frame with same name
bool doAppend;
/// internal index for function level profiler
int funcIndex;
/// internal index for high level profiler
int highIndex;
/// global frame number of last recorded frame for this range, -1 if none
int lastGlobalIndex;
};
/// \internal pattern list entry
struct PatternListEntry
{
/// next entry
PatternListEntry *next;
/// active (true) or inactive (false)?
bool isActive;
/// pattern itself (dynamic allocated memory)
char *pattern;
};
/** \internal
First pattern list list entry. A singly linked list is
okay for this because checking patterns is a costly
operation anyway and is therefore cached.
*/
static PatternListEntry *firstPatternEntry;
/// \internal last pattern list entry for fast additions to list at end
static PatternListEntry *lastPatternEntry;
/// number of recorded frames
static unsigned m_rec;
/// names of recorded frames
static char **m_recNames;
/// number of known frame names
static unsigned m_names;
/// list of known frame names
static FrameName *m_frameNames;
/// CPU clock cycles/second
static _int64 m_clockCycles;
};
#endif // PROFILE_H

View File

@@ -0,0 +1,253 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/profile_cmd.cpp $
// $Author: mhoffe $
// $Revision: #2 $
// $DateTime: 2003/08/12 15:05:00 $
//
// <20>2003 Electronic Arts
//
// Profile module command interface
//////////////////////////////////////////////////////////////////////////////
#include "_pch.h"
unsigned ProfileCmdInterface::numResIf;
ProfileCmdInterface::Factory *ProfileCmdInterface::resIf;
void ProfileCmdInterface::AddResultFunction(ProfileResultInterface* (*func)(int, const char * const *),
const char *name, const char *arg)
{
DFAIL_IF(!func) return;
DFAIL_IF(!name) return;
for (unsigned k=0;k<numResIf;k++)
if (!strcmp(resIf[k].name,name))
return;
++numResIf;
resIf=(Factory *)ProfileReAllocMemory(resIf,numResIf*sizeof(Factory));
resIf[numResIf-1].func=func;
resIf[numResIf-1].name=name;
resIf[numResIf-1].arg=arg;
}
void ProfileCmdInterface::RunResultFunctions(void)
{
// no result functions registered?
if (!numResFunc)
Debug::Command("profile.result file_csv");
// process result interfaces
for (unsigned k=0;k<numResFunc;k++)
{
resFunc[k]->WriteResults();
resFunc[k]->Delete();
}
}
bool ProfileCmdInterface::Execute(class Debug& dbg, const char *cmd, CommandMode cmdmode,
unsigned argn, const char * const * argv)
{
// just for convenience...
bool normalMode=cmdmode==CommandMode::Normal;
if (!strcmp(cmd,"help"))
{
if (!normalMode)
return true;
if (!argn)
{
dbg << "profile group help:\n"
" result, caller, clear, add, view\n";
return true;
}
else if (!strcmp(argv[0],"result"))
{
dbg << "result\n\n"
"Shows the list of available result functions and their\n"
"optional parameters.\n"
"\n"
"result <res_func_name> [ <arg1> .. <argN> ]\n\n"
"Adds the given result function to be executed on program\n"
"exit.\n";
}
else if (!strcmp(argv[0],"caller"))
{
dbg << "caller [ (+|-) ]\n\n"
"Enables/disables recording of caller information while\n"
"performing function level profiling. Turned off by default\n"
"since CPU hit is non-zero.\n";
}
else if (!strcmp(argv[0],"clear"))
{
dbg << "clear\n\n"
"Clears the profile inclusion/exclusion list.\n";
return true;
}
else if (!strcmp(argv[0],"add"))
{
dbg << "add (+|-) <pattern>\n"
"\n"
"Adds a pattern to the profile list. By default all\n"
"profile ranges are disabled. Each new range is then checked\n"
"against all pattern in this list. If a match is found the\n"
"active/inactive state is modified accordingly (+ for active,\n"
"- for inactive). The final state is always the last match.";
return true;
}
else if (!strcmp(argv[0],"view"))
{
dbg << "view\n\n"
"Shows the active pattern list.\n";
return true;
}
return false;
}
// command: result
if (!strcmp(cmd,"result"))
{
if (!argn)
{
for (unsigned k=0;k<numResIf;k++)
{
dbg << resIf[k].name;
if ((resIf[k].arg&&*resIf[k].arg)||!normalMode)
dbg << "\n " << resIf[k].arg;
dbg << "\n";
}
}
else
{
for (unsigned k=0;k<numResIf;k++)
if (!strcmp(argv[0],resIf[k].name))
break;
if (k==numResIf)
{
dbg << "Unknown result function\n";
return true;
}
ProfileResultInterface *newIf=resIf[k].func(argn-1,argv+1);
if (!newIf)
{
dbg << "Could not add result function\n";
return true;
}
++numResFunc;
resFunc=(ProfileResultInterface **)ProfileReAllocMemory(resFunc,numResFunc*sizeof(ProfileResultInterface *));
resFunc[numResFunc-1]=newIf;
if (normalMode)
dbg << "Result function " << argv[0] << " added\n";
}
return true;
}
// command: caller
if (!strcmp(cmd,"caller"))
{
#ifdef HAS_PROFILE
if (argn)
{
if (*argv[0]=='+')
ProfileFuncLevelTracer::recordCaller=true;
if (*argv[0]=='-')
ProfileFuncLevelTracer::recordCaller=false;
}
if (normalMode)
dbg << "Record caller: " << (ProfileFuncLevelTracer::recordCaller?"on":"off");
else
dbg << (ProfileFuncLevelTracer::recordCaller?"1":"0");
#endif
return true;
}
// command: clear
if (!strcmp(cmd,"clear"))
{
// remove some (or all) pattern
const char *pattern=argn<1?"*":argv[0];
for (Profile::PatternListEntry **entryPtr=&Profile::firstPatternEntry;*entryPtr;)
{
if (Profile::SimpleMatch((*entryPtr)->pattern,pattern))
{
// remove this entry
Profile::PatternListEntry *cur=*entryPtr;
*entryPtr=cur->next;
ProfileFreeMemory(cur->pattern);
ProfileFreeMemory(cur);
}
else
entryPtr=&((*entryPtr)->next);
}
// must fixup lastPatternEntry now
if (Profile::firstPatternEntry)
{
for (Profile::PatternListEntry *cur=Profile::firstPatternEntry;cur->next;cur=cur->next);
Profile::lastPatternEntry=cur;
}
else
Profile::lastPatternEntry=NULL;
return true;
}
// command: add
if (!strcmp(cmd,"add"))
{
// add a pattern
if (argn<2)
dbg << "Please specify mode and pattern";
else
{
// alloc new pattern entry
Profile::PatternListEntry *cur=(Profile::PatternListEntry *)
ProfileAllocMemory(sizeof(Profile::PatternListEntry));
// init
cur->next=NULL;
cur->isActive=*argv[0]=='+';
cur->pattern=(char *)ProfileAllocMemory(strlen(argv[1])+1);
strcpy(cur->pattern,argv[1]);
// add to list
if (Profile::lastPatternEntry)
Profile::lastPatternEntry->next=cur;
else
Profile::firstPatternEntry=cur;
Profile::lastPatternEntry=cur;
}
return true;
}
// command: view
if (!strcmp(cmd,"view"))
{
// show list of defined patterns
for (Profile::PatternListEntry *cur=Profile::firstPatternEntry;cur;cur=cur->next)
dbg << (cur->isActive?"+ ":"- ") << cur->pattern << "\n";
return true;
}
// unknown command
return false;
}

View File

@@ -0,0 +1,196 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/profile_doc.h $
// $Author: mhoffe $
// $Revision: #3 $
// $DateTime: 2003/07/09 10:57:23 $
//
// <20>2003 Electronic Arts
//
// additional Doxygen module documentation
//////////////////////////////////////////////////////////////////////////////
#ifdef _MSC_VER
# pragma once
#endif
#ifndef PROFILE_DOC_H // Include guard
#define PROFILE_DOC_H
// This generates a small main page for Doxygen if a module only
// documentation is built.
#ifndef DOXYGEN_GLOBAL_DOC
/**
\mainpage %Profile module
The following pages contain the most useful information:
- \ref module_profile
- \ref profile_cmd
\internal This is the internal module documentation meant only for
programmers who are working on the module internals.
*/
#endif
/**
\page module_profile Profile module overview
\section overview Overview
The profile module contains the following logical groups:
- high level hierarchical timer and logical profiling (frame based and global)
- function level hierarchical timer based profiling (frame based and global)
Profiling data can be accessed in different ways:
- using debug commands to query for profiling data
- using debug commands to set up a write-to-file mode for profile data
- using the C++ interface herein
\section highlevel High Level Profiling
High level profiling can be done both timer based and logical. An example
for a logical profile would be the number of texture changes per frame
or the number of triangles rendered.
The hierarchy is enforced by using a hierarchical naming scheme. For
timer based profiles this hierachy is build automatically, for
logical profiles each profile must be named accordingly when being created.
High level profiling is available in all build configurations. All
high level profile functions are optimized for speed (at least while
profiling is disabled) so that there is an almost zero cost for
having profiling in all configurations.
\section funclevel Function Level Profiling
Function level profiling determines general call statistics, e.g. call
count per frame/total, time spent in function, time per call, time
spend in function and children, etc.
Function level profiling is available in the 'profile' build configuration
only since it relies on the fact that the compiler generates '_penter'
function calls at the beginning of each function.
\section frames Profile frames
Instead of just providing frame based profiles this module has the concept
of profile ranges. A range is a period of time specified by a Begin and
and End function call. All data within this range is recorded as a range.
It is possible to capture to more than one active range at the same time.
Ordinary frame capturing can be achieved by recording a range back-to-back.
Using ranges makes it very easy to profile logically connected events, e.g.
profiling a level load time.
\section cmdif Command interface
This module provides a Debug Module command interface called 'profile'.
The commands in this command interface are basically:
- enabling/disabling profiles, either for frame counts or ranges
- querying of current profile data
- enabling write-to-file mode for profile data
*/
/**
\page profile_cmd Profile commands
Notation used:
- [ abc ] means that 'abc' may or may not be specified
- { abc } means that 'abc' may be specified any number of times (or not at all)
- (a|b|c) means that either 'a', 'b' or 'c' must be specified once
The following commands exist (group: profile):
<table><tr>
<td><b>Command</b></td>
<td><b>Parameters</b></td>
<td><b>Description</b></td>
</tr><tr>
<!---------------------------------->
<td valign=top>result</td>
<td valign=top>[ \<res_func_name\> [ \<arg1\> .. \<argN\> ] ]</td>
<td>
Without parameters this command shows the list of available
result functions and their optional parameters.
If however a result function name is given then this result
function (and the given parameters) are added to the list of
active result functions. They are executed on program exit and
used to generate e.g. a CSV file containing the profile results.
</td>
</tr><tr>
<!---------------------------------->
<td valign=top>caller</td>
<td valign=top>[ (+|-) ]</td>
<td>
Enables/disables recording of caller information while
performing function level profiling. Turned off by default
since CPU hit is non-zero.
</td>
</tr><tr>
<!---------------------------------->
<td valign=top>clear</td>
<td valign=top></td>
<td>
Clears the profile inclusion/exclusion list.
</td>
</tr><tr>
<!---------------------------------->
<td valign=top>add</td>
<td valign=top>(+|-) \<pattern\></td>
<td>
Adds a pattern to the profile list. By default all
profile ranges are disabled. Each new range is then checked
against all pattern in this list. If a match is found the
active/inactive state is modified accordingly (+ for active,
- for inactive). The final state is always the last match.
</td>
</tr><tr>
<!---------------------------------->
<td valign=top>view</td>
<td valign=top></td>
<td>
Shows the active pattern list.
</td>
<!-- keep this as template for new commands -->
</tr><tr>
<!---------------------------------->
<td valign=top></td>
<td valign=top></td>
<td>
</td>
</tr></table>
*/
#endif // PROFILE_DOC_H

View File

@@ -0,0 +1,787 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/profile_funclevel.cpp $
// $Author: mhoffe $
// $Revision: #4 $
// $DateTime: 2003/08/14 13:43:29 $
//
// <20>2003 Electronic Arts
//
// Function level profiling
//////////////////////////////////////////////////////////////////////////////
#include "_pch.h"
#include "../debug/debug.h"
#include <new>
#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;i<MAX_FRAME_RECORDS;i++)
f->cur[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;i<MAX_FRAME_RECORDS;i++)
{
if (mask&1)
{
if (recordCaller&&usedStack>0)
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<n>...
if (s.esp<esp&&
(esp-s.esp)%4==0&&
(esp-s.esp)<256)
break;
// emit warning
DCRASH("ESP " << Debug::Hex() << esp << " does not match " << stack[usedStack].esp << Debug>>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;i<MAX_FRAME_RECORDS;i++)
if (frameRecordMask&(1<<i))
for (ProfileFuncLevelTracer *p=head;p;p=p->next)
p->FrameEnd(i,-1);
}
}
int ProfileFuncLevelTracer::FrameStart(void)
{
ProfileFastCS::Lock lock(cs);
for (unsigned i=0;i<MAX_FRAME_RECORDS;i++)
if (!(frameRecordMask&(1<<i)))
break;
if (i==MAX_FRAME_RECORDS)
return -1;
for (ProfileFuncLevelTracer *p=head;p;p=p->next)
{
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<<i;
return i;
}
void ProfileFuncLevelTracer::FrameEnd(int which, int mixIndex)
{
DFAIL_IF(which<0||which>=MAX_FRAME_RECORDS)
return;
DFAIL_IF(!(frameRecordMask&(1<<which)))
return;
DFAIL_IF(mixIndex>=curFrame)
return;
ProfileFastCS::Lock lock(cs);
frameRecordMask^=1<<which;
if (mixIndex<0)
curFrame++;
for (ProfileFuncLevelTracer *p=head;p;p=p->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<HASH_SIZE;k++)
if (hash[k])
((unsigned &)hash[k])+=delta;
for (k=0;k<used;k++)
if (e[k].next)
((unsigned &)e[k].next)+=delta;
}
}
// add new item
e[used].val=val;
e[used].count=countAdd;
e[used].next=hash[at];
hash[at]=e+used++;
}
unsigned ProfileFuncLevelTracer::UnsignedMap::Enumerate(int index)
{
if (index<0||index>=(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;k<src.used;k++)
Insert(src.e[k].val,src.e[k].count);
writeLock=true;
}
ProfileFuncLevelTracer::ProfileMap::ProfileMap(void):
root(NULL), tail(&root)
{
}
ProfileFuncLevelTracer::ProfileMap::~ProfileMap()
{
while (root)
{
List *next=root->next;
root->~List();
ProfileFreeMemory(root);
root=next;
}
}
ProfileFuncLevelTracer::Profile *ProfileFuncLevelTracer::ProfileMap::Find(int frame)
{
for (List *p=root;p&&p->frame<frame;p=p->next);
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<used;k++)
{
e[k].funcPtr->~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;k<HASH_SIZE;k++)
if (hash[k])
((unsigned &)hash[k])+=delta;
for (k=0;k<used;k++)
if (e[k].next)
((unsigned &)e[k].next)+=delta;
}
}
// add to hash
unsigned at=(funcPtr->addr/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,"");

View File

@@ -0,0 +1,223 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/profile_funclevel.h $
// $Author: mhoffe $
// $Revision: #3 $
// $DateTime: 2003/07/09 10:57:23 $
//
// <20>2003 Electronic Arts
//
// Function level profiling
//////////////////////////////////////////////////////////////////////////////
#ifdef _MSC_VER
# pragma once
#endif
#ifndef PROFILE_FUNCLEVEL_H // Include guard
#define PROFILE_FUNCLEVEL_H
/**
\brief The function level profiler.
Note that this class exists even if the current build configuration
is not _PROFILE. In these cases all calls will simply return
empty data.
*/
class ProfileFuncLevel
{
friend class Profile;
// no, no copying allowed!
ProfileFuncLevel(const ProfileFuncLevel&);
ProfileFuncLevel& operator=(const ProfileFuncLevel&);
public:
class Id;
class Thread;
/// \brief A list of function level profile IDs
class IdList
{
friend Id;
public:
IdList(void): m_ptr(0) {}
/**
\brief Enumerates the list of IDs.
\note These values are not sorted in any way.
\param index index value, >=0
\param id return buffer for ID value
\param countPtr return buffer for count, if given
\return true if ID found at given index, false if not
*/
bool Enum(unsigned index, Id &id, unsigned *countPtr=0) const;
private:
/// internal value
void *m_ptr;
};
/// \brief A function level profile ID.
class Id
{
friend IdList;
friend Thread;
public:
Id(void): m_funcPtr(0) {}
/// special 'frame' numbers
enum
{
/// return the total value/count
Total = 0xffffffff
};
/**
\brief Returns the source file this Id is in.
\return source file name, may be NULL
*/
const char *GetSource(void) const;
/**
\brief Returns the function name for this Id.
\return function name, may be NULL
*/
const char *GetFunction(void) const;
/**
\brief Returns function address.
\return function address
*/
unsigned GetAddress(void) const;
/**
\brief Returns the line number for this Id.
\return line number, 0 if unknown
*/
unsigned GetLine(void) const;
/**
\brief Determine call counts.
\param frame number of recorded frame, or Total
\return number of calls
*/
unsigned _int64 GetCalls(unsigned frame) const;
/**
\brief Determine time spend in this function and its children.
\param frame number of recorded frame, or Total
\return time spend (in CPU ticks)
*/
unsigned _int64 GetTime(unsigned frame) const;
/**
\brief Determine time spend in this function only (exclude
any time spend in child functions).
\param frame number of recorded frame, or Total
\return time spend in this function alone (in CPU ticks)
*/
unsigned _int64 GetFunctionTime(unsigned frame) const;
/**
\brief Determine the list of caller Ids.
\param frame number of recorded frame, or Total
\return Caller Id list (actually just a handle value)
*/
IdList GetCaller(unsigned frame) const;
private:
/// internal function pointer
void *m_funcPtr;
};
/// \brief a profiled thread
class Thread
{
friend ProfileFuncLevel;
public:
Thread(void): m_threadID(0) {}
/**
\brief Enumerates the list of known function level profile values.
\note These values are not sorted in any way.
\param index index value, >=0
\param id return buffer for ID value
\return true if ID found at given index, false if not
*/
bool EnumProfile(unsigned index, Id &id) const;
/**
\brief Returns a unique thread ID (not related to Windows thread ID)
\return profile thread ID
*/
unsigned GetId(void) const
{
return unsigned(m_threadID);
}
private:
/// internal thread ID
class ProfileFuncLevelTracer *m_threadID;
};
/**
\brief Enumerates the list of known and profiled threads.
\note These values are not sorted in any way.
\param index index value, >=0
\param thread return buffer for thread handle
\return true if Thread found, false if not
*/
static bool EnumThreads(unsigned index, Thread &thread);
private:
/** \internal
Undocumented default constructor. Initializes function level profiler.
We can make this private as well so nobody accidently tries to create
another instance.
*/
ProfileFuncLevel(void);
/**
\brief The only function level profiler instance.
*/
static ProfileFuncLevel Instance;
};
#endif // PROFILE_FUNCLEVEL_H

View File

@@ -0,0 +1,339 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/profile_highlevel.cpp $
// $Author: mhoffe $
// $Revision: #2 $
// $DateTime: 2003/08/14 13:43:29 $
//
// <20>2003 Electronic Arts
//
// High level profiling
//////////////////////////////////////////////////////////////////////////////
#include "_pch.h"
#include <new>
#include <stdio.h>
// our own fast critical section
static ProfileFastCS cs;
//////////////////////////////////////////////////////////////////////////////
// ProfileHighLevel::Id
void ProfileHighLevel::Id::Increment(double add)
{
if (m_idPtr)
m_idPtr->Increment(add);
}
void ProfileHighLevel::Id::SetMax(double max)
{
if (m_idPtr)
m_idPtr->Maximum(max);
}
const char *ProfileHighLevel::Id::GetName(void) const
{
return m_idPtr?m_idPtr->GetName():NULL;
}
const char *ProfileHighLevel::Id::GetDescr(void) const
{
return m_idPtr?m_idPtr->GetDescr():NULL;
}
const char *ProfileHighLevel::Id::GetUnit(void) const
{
return m_idPtr?m_idPtr->GetUnit():NULL;
}
const char *ProfileHighLevel::Id::GetCurrentValue(void) const
{
return m_idPtr?m_idPtr->AsString(m_idPtr->GetCurrentValue()):NULL;
}
const char *ProfileHighLevel::Id::GetValue(unsigned frame) const
{
double v;
if (!m_idPtr||!m_idPtr->GetFrameValue(frame,v))
return NULL;
return m_idPtr->AsString(v);
}
const char *ProfileHighLevel::Id::GetTotalValue(void) const
{
return m_idPtr?m_idPtr->AsString(m_idPtr->GetTotalValue()):NULL;
}
//////////////////////////////////////////////////////////////////////////////
// ProfileHighLevel::Block
ProfileHighLevel::Block::Block(const char *name)
{
DFAIL_IF(!name) return;
m_idTime=AddProfile(name,NULL,"msec",6,-4);
char help[256];
strncpy(help,name,sizeof(help));
help[sizeof(help)-1-2]=0;
strcat(help,".c");
AddProfile(help,NULL,"calls",6,0).Increment();
ProfileGetTime(m_start);
}
ProfileHighLevel::Block::~Block()
{
_int64 end;
ProfileGetTime(end);
end-=m_start;
m_idTime.Increment(double(end)/(double)Profile::GetClockCyclesPerSecond());
}
//////////////////////////////////////////////////////////////////////////////
// ProfileId
// profile ID stuff
ProfileId *ProfileId::first;
int ProfileId::curFrame;
unsigned ProfileId::frameRecordMask;
char ProfileId::stringBuf[ProfileId::STRING_BUFFER_SIZE];
unsigned ProfileId::stringBufUnused;
ProfileId::ProfileId(const char *name, const char *descr, const char *unit, int precision, int exp10)
{
m_next=first; first=this;
m_name=(char *)ProfileAllocMemory(strlen(name)+1);
strcpy(m_name,name);
if (descr)
{
m_descr=(char *)ProfileAllocMemory(strlen(descr)+1);
strcpy(m_descr,descr);
}
else
m_descr=NULL;
if (unit)
{
m_unit=(char *)ProfileAllocMemory(strlen(unit)+1);
strcpy(m_unit,unit);
}
else
m_unit=NULL;
m_precision=precision;
m_exp10=exp10;
m_curVal=m_totalVal=0.;
m_recFrameVal=NULL;
m_firstFrame=curFrame;
m_valueMode=Unknown;
}
void ProfileId::Increment(double add)
{
DFAIL_IF(m_valueMode!=Unknown&&m_valueMode!=ModeIncrement)
return;
m_valueMode=ModeIncrement;
m_curVal+=add;
m_totalVal+=add;
if (frameRecordMask)
{
unsigned mask=frameRecordMask;
for (unsigned i=0;i<MAX_FRAME_RECORDS;i++)
{
if (mask&1)
m_frameVal[i]+=add;
if (!(mask>>=1))
break;
}
}
}
void ProfileId::Maximum(double max)
{
DFAIL_IF(m_valueMode!=Unknown&&m_valueMode!=ModeMaximum)
return;
m_valueMode=ModeMaximum;
if (max>m_curVal)
m_curVal=max;
if (max>m_totalVal)
m_totalVal=max;
if (frameRecordMask)
{
unsigned mask=frameRecordMask;
for (unsigned i=0;i<MAX_FRAME_RECORDS;i++)
{
if (mask&1)
{
if (max>m_frameVal[i])
m_frameVal[i]=max;
}
if (!(mask>>=1))
break;
}
}
}
const char *ProfileId::AsString(double v) const
{
char help1[10],help[40];
wsprintf(help1,"%%%i.lf",m_precision);
double mul=1.0;
int k;
for (k=m_exp10;k<0;k++) mul*=10.0;
for (;k>0;k--) mul/=10.0;
unsigned len=_snprintf(help,sizeof(help),help1,v*mul)+1;
ProfileFastCS::Lock lock(cs);
if (stringBufUnused+len>STRING_BUFFER_SIZE)
stringBufUnused=0;
char *ret=stringBuf+stringBufUnused;
memcpy(ret,help,len);
stringBufUnused+=len;
return ret;
}
int ProfileId::FrameStart(void)
{
ProfileFastCS::Lock lock(cs);
for (unsigned i=0;i<MAX_FRAME_RECORDS;i++)
if (!(frameRecordMask&(1<<i)))
break;
if (i==MAX_FRAME_RECORDS)
return -1;
for (ProfileId *p=first;p;p=p->m_next)
p->m_frameVal[i]=0.;
frameRecordMask|=1<<i;
return i;
}
void ProfileId::FrameEnd(int which, int mixIndex)
{
DFAIL_IF(which<0||which>=MAX_FRAME_RECORDS)
return;
DFAIL_IF(!(frameRecordMask&(1<<which)))
return;
DFAIL_IF(mixIndex>=curFrame)
return;
ProfileFastCS::Lock lock(cs);
frameRecordMask^=1<<which;
if (mixIndex<0)
{
// new frame
curFrame++;
for (ProfileId *p=first;p;p=p->m_next)
{
p->m_recFrameVal=(double *)ProfileReAllocMemory(p->m_recFrameVal,sizeof(double)*(curFrame-p->m_firstFrame));
p->m_recFrameVal[curFrame-p->m_firstFrame-1]=p->m_frameVal[which];
}
}
else
{
// append data
for (ProfileId *p=first;p;p=p->m_next)
{
if (p->m_firstFrame>mixIndex)
continue;
double &val=p->m_recFrameVal[mixIndex-p->m_firstFrame];
switch(p->m_valueMode)
{
case ProfileId::Unknown:
break;
case ProfileId::ModeIncrement:
val+=p->m_frameVal[which];
break;
case ProfileId::ModeMaximum:
if (p->m_frameVal[which]>val)
val=p->m_frameVal[which];
break;
default:
DFAIL();
}
}
}
}
void ProfileId::Shutdown(void)
{
if (frameRecordMask)
{
for (unsigned i=0;i<MAX_FRAME_RECORDS;i++)
if (frameRecordMask&(1<<i))
FrameEnd(i,-1);
}
}
//////////////////////////////////////////////////////////////////////////////
// ProfileHighLevel
ProfileHighLevel::Id ProfileHighLevel::AddProfile(const char *name, const char *descr, const char *unit, int precision, int exp10)
{
// check if there is already an ID with the given name...
Id id;
if (FindProfile(name,id))
return id;
// checks...
DFAIL_IF(!name) return id;
// no, allocate one
ProfileFastCS::Lock lock(cs);
id.m_idPtr=new (ProfileAllocMemory(sizeof(ProfileId))) ProfileId(name,descr,unit,precision,exp10);
return id;
}
bool ProfileHighLevel::EnumProfile(unsigned index, Id &id)
{
ProfileFastCS::Lock lock(cs);
for (ProfileId *cur=ProfileId::GetFirst();cur&&index--;cur=cur->GetNext());
id.m_idPtr=cur;
return cur!=NULL;
}
bool ProfileHighLevel::FindProfile(const char *name, Id &id)
{
DFAIL_IF(!name) return false;
ProfileFastCS::Lock lock(cs);
for (ProfileId *cur=ProfileId::GetFirst();cur;cur=cur->GetNext())
if (!strcmp(name,cur->GetName()))
{
id.m_idPtr=cur;
return true;
}
id.m_idPtr=NULL;
return false;
}
ProfileHighLevel::ProfileHighLevel(void)
{
}
ProfileHighLevel ProfileHighLevel::Instance;

View File

@@ -0,0 +1,241 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/profile_highlevel.h $
// $Author: mhoffe $
// $Revision: #2 $
// $DateTime: 2003/07/09 10:57:23 $
//
// <20>2003 Electronic Arts
//
// High level profiling
//////////////////////////////////////////////////////////////////////////////
#ifdef _MSC_VER
# pragma once
#endif
#ifndef PROFILE_HIGHLEVEL_H // Include guard
#define PROFILE_HIGHLEVEL_H
/// \internal internal Id representation
class ProfileId;
/**
\brief The high level profiler.
*/
class ProfileHighLevel
{
friend class Profile;
// no, no copying allowed!
ProfileHighLevel(const ProfileHighLevel&);
ProfileHighLevel& operator=(const ProfileHighLevel&);
public:
/// \brief A high level profile ID.
class Id
{
friend ProfileHighLevel;
public:
Id(void): m_idPtr(0) {}
/**
\brief Increment the internal profile value.
\note Do not mix with SetMax.
\param add amount to add to internal profile value
*/
void Increment(double add=1.0);
/**
\brief Set a new maximum value.
\note Do not mix with Increment.
This function sets a new maximum value (if the value
passed in is actually larger than the current max value).
\param max new maximum value (if larger than current max value,
otherwise current max value is left unchanged)
*/
void SetMax(double max);
/**
\brief Returns the internal Id name.
\return internal Id name, e.g. 'render.texture.count.512x512'
*/
const char *GetName(void) const;
/**
\brief Returns the descriptive name.
\return descriptive name, e.g. '# of 512x512 textures'
*/
const char *GetDescr(void) const;
/**
\brief Returns the value's unit text.
\return unit text, e.g. 'bytes'
*/
const char *GetUnit(void) const;
/**
\brief Returns the current value.
'Current' means the value since the last call to this function for
the same Id.
This function is intended for displaying profile data while the
application is running.
\note The contents of the buffer returned may be overwritten by
any consecutive call to any profile module function.
\return current value
*/
const char *GetCurrentValue(void) const;
/**
\brief Returns the value for the given recorded frame/range.
\note The contents of the buffer returned may be overwritten by
any consecutive call to any profile module function.
\param frame number of recorded frame/range
\return value at given frame, NULL if frame not found
*/
const char *GetValue(unsigned frame) const;
/**
\brief Returns the total value for all frames.
This even includes data collected while no frames have been
recorded.
\note A call to ProfileHighLevel::ClearTotals() resets this value.
\return total value
*/
const char *GetTotalValue(void) const;
private:
/// internal pointer
ProfileId *m_idPtr;
};
/// \brief Timer based function block profile
class Block
{
friend ProfileHighLevel;
// no copying
Block(const Block&);
Block& operator=(const Block&);
public:
/**
\brief Instructs high level profiler to start a new timer
based function block (or update if it already exists)
\note These function blocks are in the same name space as
high level profile values registered with ProfileHighLevel::AddProfile()
\param name name of function block
*/
explicit Block(const char *name);
/// \brief Updates timer based function block
~Block();
private:
/// internal id (time)
Id m_idTime;
/// start time
_int64 m_start;
};
/**
\brief Registers a new high level profile value.
If there is already a high level profile with the given name the
Id of that profile is returned instead.
High level profiles can only be added, never removed.
\note Important: This function can (and should) be used in static
initializers!
\note This function may be slow so don't use it too often.
\param name value name, e.g. "render.texture.count.512x512"
\param descr descriptive name, e.g. "# of 512x512 textures"
\param unit unit name, e.g. "byte" or "sec"
\param precision number of decimal places to show
\param exp10 10 base exponent (used for scaleing)
\return internal profile ID value
*/
static Id AddProfile(const char *name, const char *descr, const char *unit, int precision, int exp10=0);
/**
\brief Enumerates the list of known high level profile values.
\note Profiles are always sorted ascending by profile name.
\param index index value, >=0
\param id return buffer for ID value
\return true if ID found at given index, false if not
*/
static bool EnumProfile(unsigned index, Id &id);
/**
\brief Searches for the given high level profile.
\note Actually the ID returned belongs to the first profile
which has a name that is equal to or larger than the name
searched for.
\param name profile name to search for
\param id return buffer for ID value
\return true if ID found, false if not
*/
static bool FindProfile(const char *name, Id &id);
private:
/** \internal
Undocumented default constructor. Initializes high level profiler.
We can make this private as well so nobody accidently tries to create
another instance.
*/
ProfileHighLevel(void);
/**
\brief The only high level profiler instance.
*/
static ProfileHighLevel Instance;
};
#endif // PROFILE_HIGHLEVEL_H

View File

@@ -0,0 +1,208 @@
# Doxyfile 1.3.1
#---------------------------------------------------------------------------
# General configuration options
#---------------------------------------------------------------------------
PROJECT_NAME = "EA/Profile module"
PROJECT_NUMBER =
OUTPUT_DIRECTORY = doc/
OUTPUT_LANGUAGE = English
USE_WINDOWS_ENCODING = YES
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
EXTRACT_STATIC = YES
EXTRACT_LOCAL_CLASSES = YES
HIDE_UNDOC_MEMBERS = YES
HIDE_UNDOC_CLASSES = YES
HIDE_FRIEND_COMPOUNDS = YES
HIDE_IN_BODY_DOCS = YES
BRIEF_MEMBER_DESC = YES
REPEAT_BRIEF = YES
ALWAYS_DETAILED_SEC = NO
INLINE_INHERITED_MEMB = YES
FULL_PATH_NAMES = NO
STRIP_FROM_PATH =
INTERNAL_DOCS = YES
CASE_SENSE_NAMES = YES
SHORT_NAMES = NO
HIDE_SCOPE_NAMES = NO
SHOW_INCLUDE_FILES = YES
JAVADOC_AUTOBRIEF = NO
MULTILINE_CPP_IS_BRIEF = NO
DETAILS_AT_TOP = NO
INHERIT_DOCS = YES
INLINE_INFO = YES
SORT_MEMBER_DOCS = YES
DISTRIBUTE_GROUP_DOC = NO
TAB_SIZE = 8
GENERATE_TODOLIST = YES
GENERATE_TESTLIST = YES
GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
ALIASES = "todo_opt=\todo (Opt) " \
"todo_urgent=\todo <b>(URGENT)</b> " \
"todo_feature=\todo (Feature) "
ENABLED_SECTIONS =
MAX_INITIALIZER_LINES = 30
OPTIMIZE_OUTPUT_FOR_C = NO
OPTIMIZE_OUTPUT_JAVA = NO
SHOW_USED_FILES = YES
#---------------------------------------------------------------------------
# configuration options related to warning and progress messages
#---------------------------------------------------------------------------
QUIET = NO
WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES
WARN_FORMAT = "$file:$line: $text"
WARN_LOGFILE =
#---------------------------------------------------------------------------
# configuration options related to the input files
#---------------------------------------------------------------------------
INPUT = ./
FILE_PATTERNS = profile*.h internal*.h *.cpp
RECURSIVE = NO
EXCLUDE =
EXCLUDE_SYMLINKS = NO
EXCLUDE_PATTERNS =
EXAMPLE_PATH =
EXAMPLE_PATTERNS =
EXAMPLE_RECURSIVE = NO
IMAGE_PATH =
INPUT_FILTER =
FILTER_SOURCE_FILES = NO
#---------------------------------------------------------------------------
# configuration options related to source browsing
#---------------------------------------------------------------------------
SOURCE_BROWSER = NO
INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = YES
REFERENCED_BY_RELATION = YES
REFERENCES_RELATION = YES
VERBATIM_HEADERS = YES
#---------------------------------------------------------------------------
# configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
ALPHABETICAL_INDEX = YES
COLS_IN_ALPHA_INDEX = 5
IGNORE_PREFIX =
#---------------------------------------------------------------------------
# configuration options related to the HTML output
#---------------------------------------------------------------------------
GENERATE_HTML = YES
HTML_OUTPUT = html_priv
HTML_FILE_EXTENSION = .html
HTML_HEADER =
HTML_FOOTER =
HTML_STYLESHEET =
HTML_ALIGN_MEMBERS = YES
GENERATE_HTMLHELP = YES
CHM_FILE =
HHC_LOCATION =
GENERATE_CHI = NO
BINARY_TOC = NO
TOC_EXPAND = NO
DISABLE_INDEX = NO
ENUM_VALUES_PER_LINE = 4
GENERATE_TREEVIEW = YES
TREEVIEW_WIDTH = 250
#---------------------------------------------------------------------------
# configuration options related to the LaTeX output
#---------------------------------------------------------------------------
GENERATE_LATEX = NO
LATEX_OUTPUT = latex
LATEX_CMD_NAME = latex
MAKEINDEX_CMD_NAME = makeindex
COMPACT_LATEX = NO
PAPER_TYPE = a4wide
EXTRA_PACKAGES =
LATEX_HEADER =
PDF_HYPERLINKS = NO
USE_PDFLATEX = NO
LATEX_BATCHMODE = NO
LATEX_HIDE_INDICES = NO
#---------------------------------------------------------------------------
# configuration options related to the RTF output
#---------------------------------------------------------------------------
GENERATE_RTF = YES
RTF_OUTPUT = rtf_priv
COMPACT_RTF = NO
RTF_HYPERLINKS = NO
RTF_STYLESHEET_FILE =
RTF_EXTENSIONS_FILE =
#---------------------------------------------------------------------------
# configuration options related to the man page output
#---------------------------------------------------------------------------
GENERATE_MAN = NO
MAN_OUTPUT = man
MAN_EXTENSION = .3
MAN_LINKS = NO
#---------------------------------------------------------------------------
# configuration options related to the XML output
#---------------------------------------------------------------------------
GENERATE_XML = NO
XML_OUTPUT = xml
XML_SCHEMA =
XML_DTD =
#---------------------------------------------------------------------------
# configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
# configuration options related to the Perl module output
#---------------------------------------------------------------------------
GENERATE_PERLMOD = NO
PERLMOD_LATEX = NO
PERLMOD_PRETTY = YES
PERLMOD_MAKEVAR_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = NO
EXPAND_ONLY_PREDEF = NO
SEARCH_INCLUDES = YES
INCLUDE_PATH =
INCLUDE_FILE_PATTERNS =
PREDEFINED = _DEBUG \
DOXYGEN
EXPAND_AS_DEFINED =
SKIP_FUNCTION_MACROS = YES
#---------------------------------------------------------------------------
# Configuration::addtions related to external references
#---------------------------------------------------------------------------
TAGFILES =
GENERATE_TAGFILE =
ALLEXTERNALS = NO
EXTERNAL_GROUPS = YES
PERL_PATH = /usr/bin/perl
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
CLASS_DIAGRAMS = YES
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = YES
CLASS_GRAPH = YES
COLLABORATION_GRAPH = YES
TEMPLATE_RELATIONS = NO
INCLUDE_GRAPH = YES
INCLUDED_BY_GRAPH = YES
GRAPHICAL_HIERARCHY = YES
DOT_IMAGE_FORMAT = png
DOT_PATH =
DOTFILE_DIRS =
MAX_DOT_GRAPH_WIDTH = 1024
MAX_DOT_GRAPH_HEIGHT = 1024
MAX_DOT_GRAPH_DEPTH = 0
GENERATE_LEGEND = YES
DOT_CLEANUP = YES
#---------------------------------------------------------------------------
# Configuration::addtions related to the search engine
#---------------------------------------------------------------------------
SEARCHENGINE = NO
CGI_NAME = search.cgi
CGI_URL =
DOC_URL =
DOC_ABSPATH =
BIN_ABSPATH = /usr/local/bin/
EXT_DOC_PATHS =

View File

@@ -0,0 +1,304 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/profile_result.cpp $
// $Author: mhoffe $
// $Revision: #2 $
// $DateTime: 2003/08/12 15:05:00 $
//
// <20>2003 Electronic Arts
//
// Result function interface and result functions
//////////////////////////////////////////////////////////////////////////////
#include "_pch.h"
#include <new>
#include <stdio.h>
#include <stdlib.h>
//////////////////////////////////////////////////////////////////////////////
// ProfileResultFileCSV
ProfileResultInterface *ProfileResultFileCSV::Create(int, const char * const *)
{
return new (ProfileAllocMemory(sizeof(ProfileResultFileCSV))) ProfileResultFileCSV();
}
void ProfileResultFileCSV::WriteThread(ProfileFuncLevel::Thread &thread)
{
char help[40];
sprintf(help,"prof%08x-all.csv",thread.GetId());
FILE *f=fopen(help,"wt");
// CSV file header
fprintf(f,"Function\tFile\tCall count\tPTT (all)\tGTT (all)\tPT/C (all)\tGT/C (all)\tCaller (all)");
for (unsigned k=0;k<Profile::GetFrameCount();k++)
{
const char *s=Profile::GetFrameName(k);
fprintf(f,"\tCall (%s)\tPTT (%s)\tGTT (%s)\tPT/C (%s)\tGT/C (%s)\tCaller (%s)",s,s,s,s,s,s);
}
fprintf(f,"\n");
// now show all profile IDs (functions)
ProfileFuncLevel::Id id;
for (k=0;thread.EnumProfile(k,id);k++)
{
fprintf(f,"%s[%08x]\t%s, %i",id.GetFunction(),id.GetAddress(),
id.GetSource(),id.GetLine());
for (unsigned i=ProfileFuncLevel::Id::Total;i!=Profile::GetFrameCount();i++)
{
if (!id.GetCalls(i))
{
// early skip...
fprintf(f,"\t\t\t\t\t\t");
continue;
}
// call count
fprintf(f,"\t%I64i",id.GetCalls(i));
// pure total time
fprintf(f,"\t%I64i",id.GetFunctionTime(i));
// global total time
fprintf(f,"\t%I64i",id.GetTime(i));
// pure time per call
fprintf(f,"\t%I64i",id.GetFunctionTime(i)/id.GetCalls(i));
// global time per call
fprintf(f,"\t%I64i",id.GetTime(i)/id.GetCalls(i));
// list of callers
ProfileFuncLevel::IdList idlist=id.GetCaller(i);
fprintf(f,"\t");
ProfileFuncLevel::Id callid;
unsigned count;
for (unsigned j=0;idlist.Enum(j,callid,&count);j++)
fprintf(f," %s[%08x](%i)",callid.GetFunction(),callid.GetAddress(),count);
}
fprintf(f,"\n");
}
fclose(f);
}
void ProfileResultFileCSV::WriteResults(void)
{
ProfileFuncLevel::Thread t;
for (unsigned k=0;ProfileFuncLevel::EnumThreads(k,t);k++)
WriteThread(t);
FILE *f=fopen("profile-high.csv","wt");
// CSV file header
fprintf(f,"Profile\tUnit\ttotal");
for (k=0;k<Profile::GetFrameCount();k++)
fprintf(f,"\t%s",Profile::GetFrameName(k));
fprintf(f,"\n");
// now show all high level profile IDs
ProfileHighLevel::Id id;
for (k=0;ProfileHighLevel::EnumProfile(k,id);k++)
{
fprintf(f,"%s\t%s\t%s",id.GetName(),id.GetUnit(),id.GetTotalValue());
for (unsigned i=0;i<Profile::GetFrameCount();i++)
{
const char *p=id.GetValue(i);
fprintf(f,"\t%s",p?p:"");
}
fprintf(f,"\n");
}
fclose(f);
}
void ProfileResultFileCSV::Delete(void)
{
this->~ProfileResultFileCSV();
ProfileFreeMemory(this);
}
//////////////////////////////////////////////////////////////////////////////
// ProfileResultFileDOT
ProfileResultInterface *ProfileResultFileDOT::Create(int argn, const char * const *argv)
{
return new (ProfileAllocMemory(sizeof(ProfileResultFileDOT)))
ProfileResultFileDOT(argn>0?argv[0]:NULL,
argn>1?argv[1]:NULL,
argn>2?atoi(argv[2]):NULL);
}
ProfileResultFileDOT::ProfileResultFileDOT(const char *fileName, const char *frameName, int foldThreshold)
{
if (!fileName)
fileName="profile.dot";
m_fileName=(char *)ProfileAllocMemory(strlen(fileName)+1);
strcpy(m_fileName,fileName);
if (frameName)
{
m_frameName=(char *)ProfileAllocMemory(strlen(frameName)+1);
strcpy(m_frameName,frameName);
}
else
m_frameName=NULL;
m_foldThreshold=foldThreshold;
}
void ProfileResultFileDOT::WriteResults(void)
{
// search "main" thread
ProfileFuncLevel::Thread t,tMax;
if (!ProfileFuncLevel::EnumThreads(0,tMax))
return;
unsigned curMax=0;
for (unsigned k=1;ProfileFuncLevel::EnumThreads(k,t);k++)
{
for (;curMax++;)
{
ProfileFuncLevel::Id help;
if (!tMax.EnumProfile(curMax,help))
{
tMax=t;
break;
}
if (!t.EnumProfile(curMax,help))
break;
curMax++;
}
}
// search frame
unsigned frame=ProfileFuncLevel::Id::Total;
if (m_frameName)
{
for (unsigned k=0;k<Profile::GetFrameCount();k++)
if (!strcmp(Profile::GetFrameName(k),m_frameName))
{
frame=k;
break;
}
}
// determine number of active functions
unsigned active=0;
ProfileFuncLevel::Id id;
for (k=0;tMax.EnumProfile(k,id);k++)
if (id.GetCalls(frame))
active++;
FILE *f=fopen(m_fileName,"wt");
if (!f)
return;
// DOT header
fprintf(f,"digraph G { rankdir=\"LR\";\n");
fprintf(f,"node [shape=box, fontname=Arial]\n");
fprintf(f,"edge [arrowhead=%s, labelfontname=Arial, labelfontsize=10, labelangle=0, labelfontcolor=blue]\n",
active>m_foldThreshold?"closed":"none");
// fold or not?
if (active>m_foldThreshold)
{
// folding version
// build source code clusters first
FoldHelper *fold=NULL;
for (k=0;tMax.EnumProfile(k,id);k++)
{
const char *source=id.GetSource();
for (FoldHelper *cur=fold;cur;cur=cur->next)
if (!strcmp(source,cur->source))
{
if (cur->numId<MAX_FUNCTIONS_PER_FILE)
cur->id[cur->numId++]=id;
break;
}
if (!cur)
{
cur=(FoldHelper *)ProfileAllocMemory(sizeof(FoldHelper));
cur->next=fold;
fold=cur;
cur->source=source;
cur->numId=1;
cur->id[0]=id;
}
}
// now write data
for (FoldHelper *cur=fold;cur;cur=cur->next)
{
for (FoldHelper *cur2=fold;cur2;cur2=cur2->next)
cur2->mark=false;
for (k=0;k<cur->numId;k++)
{
ProfileFuncLevel::IdList idlist=id.GetCaller(frame);
ProfileFuncLevel::Id caller;
for (unsigned i=0;idlist.Enum(i,caller);i++)
{
const char *s=caller.GetSource();
for (FoldHelper *cur2=fold;cur2;cur2=cur2->next)
if (!strcmp(cur2->source,s))
break;
if (!cur2||cur2->mark)
continue;
cur2->mark=true;
fprintf(f,"\"%s\" -> \"%s\"\n",s,cur->source);
}
}
}
// cleanup
while (fold)
{
FoldHelper *next=fold->next;
ProfileFreeMemory(fold);
fold=next;
}
}
else
{
// non-folding version
for (k=0;tMax.EnumProfile(k,id);k++)
if (id.GetCalls(frame))
fprintf(f,"f%08x [label=\"%s\"]\n",id.GetAddress(),id.GetFunction());
for (k=0;tMax.EnumProfile(k,id);k++)
{
ProfileFuncLevel::IdList idlist=id.GetCaller(frame);
ProfileFuncLevel::Id caller;
unsigned count;
for (unsigned i=0;idlist.Enum(i,caller,&count);i++)
fprintf(f,"f%08x -> f%08x [headlabel=\"%i\"];\n",caller.GetAddress(),id.GetAddress(),count);
}
}
fprintf(f,"}\n");
fclose(f);
}
void ProfileResultFileDOT::Delete(void)
{
this->~ProfileResultFileDOT();
ProfileFreeMemory(this);
}

View File

@@ -0,0 +1,66 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/profile_result.h $
// $Author: mhoffe $
// $Revision: #1 $
// $DateTime: 2003/07/09 10:57:23 $
//
// <20>2003 Electronic Arts
//
// Result function interface and result functions
//////////////////////////////////////////////////////////////////////////////
#ifdef _MSC_VER
# pragma once
#endif
#ifndef PROFILE_RESULT_H // Include guard
#define PROFILE_RESULT_H
/**
\brief Result function class.
Factories for instances of this class are registered using
\ref Profile::AddResultFunction.
*/
class ProfileResultInterface
{
// no copying
ProfileResultInterface(const ProfileResultInterface&);
ProfileResultInterface& operator=(const ProfileResultInterface&);
public:
/**
\brief Write out results.
This function is called on program exit.
*/
virtual void WriteResults(void)=0;
/**
\brief Destroys the current result function.
Use this function instead of just delete'ing the instance.
*/
virtual void Delete(void)=0;
protected:
ProfileResultInterface(void) {}
};
#endif // PROFILE_RESULT_H

View File

@@ -0,0 +1,94 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
/////////////////////////////////////////////////////////////////////////EA-V1
// $File: //depot/GeneralsMD/Staging/code/Libraries/Source/profile/test1/test1.cpp $
// $Author: mhoffe $
// $Revision: #3 $
// $DateTime: 2003/07/09 10:57:23 $
//
// <20>2003 Electronic Arts
//
// Profile module - Test 1 (basic testing)
//////////////////////////////////////////////////////////////////////////////
#include "../profile.h"
#include "../../debug/debug.h"
#include <stdio.h>
const char *DebugGetDefaultCommands(void)
{
return "!debug.io con add\ndebug.add l + *\nprofile.result";
}
extern int q;
void calcThis(void)
{
q++;
}
void calcThat(void)
{
calcThis();
q--;
}
// it must be done this "complicated" because
// otherwise VC does not generate a real recursive
// function call for this simple function...
void recursion2(int level);
void recursion(int level)
{
q+=level;
if (level<5000)
recursion2(level+1);
}
void recursion2(int level)
{
recursion(level);
}
void recursionShell(void)
{
ProfileHighLevel::Block b("Test block");
recursion(0);
}
void showResults(void)
{
ProfileHighLevel::Id id;
for (unsigned index=0;ProfileHighLevel::EnumProfile(index,id);index++)
printf("%-16s%-6s %s\n",id.GetName(),id.GetTotalValue(),id.GetUnit());
}
void main(void)
{
for (int k=0;k<100;k++)
if (k%2&&k>80)
calcThat();
else
calcThis();
recursionShell();
showResults();
}
int q;

View File

@@ -0,0 +1,116 @@
# Microsoft Developer Studio Project File - Name="test1" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **
# TARGTYPE "Win32 (x86) Console Application" 0x0103
CFG=test1 - Win32 Debug
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
!MESSAGE use the Export Makefile command and run
!MESSAGE
!MESSAGE NMAKE /f "test1.mak".
!MESSAGE
!MESSAGE You can specify a configuration when running NMAKE
!MESSAGE by defining the macro CFG on the command line. For example:
!MESSAGE
!MESSAGE NMAKE /f "test1.mak" CFG="test1 - Win32 Debug"
!MESSAGE
!MESSAGE Possible choices for configuration are:
!MESSAGE
!MESSAGE "test1 - Win32 Release" (based on "Win32 (x86) Console Application")
!MESSAGE "test1 - Win32 Debug" (based on "Win32 (x86) Console Application")
!MESSAGE "test1 - Win32 Profile" (based on "Win32 (x86) Console Application")
!MESSAGE
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName "test1"
# PROP Scc_LocalPath "."
CPP=cl.exe
RSC=rc.exe
!IF "$(CFG)" == "test1 - Win32 Release"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "Release"
# PROP BASE Intermediate_Dir "Release"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "Release"
# PROP Intermediate_Dir "Release"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
# ADD CPP /nologo /MD /W3 /WX /GX /Zi /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /libpath:"..\..\..\lib"
!ELSEIF "$(CFG)" == "test1 - Win32 Debug"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 1
# PROP BASE Output_Dir "Debug"
# PROP BASE Intermediate_Dir "Debug"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 1
# PROP Output_Dir "Debug"
# PROP Intermediate_Dir "Debug"
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
# ADD CPP /nologo /MDd /W3 /WX /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
# ADD BASE RSC /l 0x409 /d "_DEBUG"
# ADD RSC /l 0x409 /d "_DEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /libpath:"..\..\..\lib"
!ELSEIF "$(CFG)" == "test1 - Win32 Profile"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "Profile"
# PROP BASE Intermediate_Dir "Profile"
# PROP BASE Ignore_Export_Lib 0
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "Profile"
# PROP Intermediate_Dir "Profile"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /MD /W3 /WX /GX /Zi /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
# ADD CPP /nologo /MD /W3 /WX /GX /Zi /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "_PROFILE" /YX /FD /Gh /GH /c
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /libpath:"..\..\..\lib"
!ENDIF
# Begin Target
# Name "test1 - Win32 Release"
# Name "test1 - Win32 Debug"
# Name "test1 - Win32 Profile"
# Begin Source File
SOURCE=.\test1.cpp
# End Source File
# End Target
# End Project