383 lines
9.4 KiB
C++
383 lines
9.4 KiB
C++
/*
|
|
** 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 $
|
|
//
|
|
// ©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);
|