/* ** Command & Conquer Red Alert(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 . */ /*********************************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** *********************************************************************************************** * * * Project Name : Library profiler * * * * File Name : WPROFILE.CPP * * * * Programmer : Steve Tall * * * * Start Date : 11/17/95 * * * * Last Update : November 20th 1995 [ST] * * * *---------------------------------------------------------------------------------------------* * Overview: * * * * New System * * ~~~~~~~~~~~ * * * * The new profiler system creates a seperate thread and then starts a timer off there. The * * timer in the second thread uses GetThreadContext to sample the IP address of each user * * thread. This system has the advantage of being able to sample what is happening in all the * * threads we own not just the main thread. Another advantage is that it doesnt require a * * major recompilation. * * The disadvantage is that we dont really know what is going on when the IP is outside the * * scope of our threads - We could be in direct draw, direct sound or even something like the * * VMM and there is no way to tell. * * * * * * * * Old System * * ~~~~~~~~~~~ * * * * The profiler works by using the function prologue and epilogue hooks available in Watcom * * to register the current functions address in a global variable and then sampling the * * contents of the variable using a windows timer which runs at up to 1000 samples per second.* * * * Compile the code to be sampled with the -ep and -ee flags to enable the prologue (__PRO) * * and epilogue (__EPI) calls to be generated. * * At the beginning of the section to be profiled (just before main loop normally) call the * * Start_Profiler function to start sampling. At the end of the section, call Stop_Profiler * * which will stop the timer and write the profile data to disk in the PROFILE.BIN file. * * * * Use PROFILE.EXE to view the results of the session. * * * * The addition of prologue and epilogue code will slow down the product and the profiler * * allocates a huge buffer for data so it should not be linked in unless it is going to be * * used. * * * * The advantage of the prologue/epilogue approach is that all samples represent valid * * addresses within our code so we get valid results we can use even when the IP is in system * * code. * * * *---------------------------------------------------------------------------------------------* * * * Functions: * * Start_Profiler -- initialises the profiler data and starts gathering data * * Stop_Profiler -- stops the timer and writes the profile data to disk * * * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #define WIN32 #ifndef _WIN32 // Denzil 6/2/98 Watcom 11.0 complains without this check #define _WIN32 #endif // _WIN32 #include #include #include #include #include #include "profile.h" #include #include #define PROFILE extern "C"{ #ifdef PROFILE unsigned ProfileList [PROFILE_RATE*MAX_PROFILE_TIME]; #else unsigned ProfileList [2]; #endif unsigned ProfilePtr; } extern "C" void Old_Profiler_Callback ( UINT, UINT , DWORD, DWORD, DWORD ); extern "C" void New_Profiler_Callback (void); extern "C" { extern unsigned ProfileFunctionAddress; } unsigned long ProfilerEvent; //Handle for profiler callback unsigned long ProfilerThread; //Handle for profiler thread HANDLE CCThreadHandle; CONTEXT ThreadContext; #if (PROFILE_SYSTEM == NEW_PROFILE_SYSTEM) /*********************************************************************************************** * Thread_Callback -- gets the IP address of our thread and registers it * * * * * * * * INPUT: Windows timer callback parms - not used * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 1/2/96 6:37AM ST : Created * *=============================================================================================*/ void CALLBACK Thread_Callback (UINT,UINT,DWORD,DWORD,DWORD) { ThreadContext.ContextFlags = VDMCONTEXT_CONTROL; if (!InTimerCallback){ GetThreadContext ( CCThreadHandle , &ThreadContext ); }else{ GetThreadContext (TimerThreadHandle , &ThreadContext); } ProfileFunctionAddress = ThreadContext.Eip; New_Profiler_Callback(); } /*********************************************************************************************** * Profile_Thread -- this is the thread our profiler runs in. It just starts off a timer and * * then buggers off into an infinite message loop. We shouldnt get messages * * here as this isnt our primary thread * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: None * * * * HISTORY: * * 1/2/96 6:39AM ST : Created * *=============================================================================================*/ void Profile_Thread (void) { MSG msg; ProfilerEvent = timeSetEvent (1000/PROFILE_RATE , 1 , Thread_Callback , 0 , TIME_PERIODIC); //ProfilerEvent = timeSetEvent (100 , 1 , Thread_Callback , 0 , TIME_ONESHOT); do { GetMessage(&msg,NULL,0,0); } while(1); } #endif //(PROFILE_SYSTEM == OLD_PROFILE_SYSTEM) /*********************************************************************************************** * Start_Profiler -- initialises the profiler system and starts sampling * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: There may be a pause when sampling starts as Win95 does some VMM stuff * * * * HISTORY: * * 11/20/95 5:12PM ST : Created * *=============================================================================================*/ void __cdecl Start_Profiler (void) { #ifdef PROFILE if (!ProfilerEvent){ memset (&ProfileList[0],-1,PROFILE_RATE*MAX_PROFILE_TIME*4); } Profile_Init(); if (!ProfilerEvent){ #if (PROFILE_SYSTEM == OLD_PROFILE_SYSTEM) /* ** Old profile system - just set up a timer to monitor the global variable based on ** the last place __PRO was called from */ ProfilerEvent = timeSetEvent (1000/PROFILE_RATE , 1 , (void CALLBACK (UINT,UINT,DWORD,DWORD,DWORD))Old_Profiler_Callback , 0 , TIME_PERIODIC); #else /* ** New profile system - create a second thread that will do all the profiling ** using GetThreadContext */ if ( DuplicateHandle( GetCurrentProcess(), GetCurrentThread() , GetCurrentProcess() ,&CCThreadHandle , 0 , TRUE , DUPLICATE_SAME_ACCESS) ){ ProfilerEvent= (unsigned)CreateThread(NULL,2048,(LPTHREAD_START_ROUTINE)&Profile_Thread,NULL,0,&ProfilerThread); } #endif } #else ProfilerEvent = 0; #endif } /*********************************************************************************************** * Stop_Profiler -- stops the sampling timer and writes the colledted data to disk * * * * * * * * INPUT: Nothing * * * * OUTPUT: Nothing * * * * WARNINGS: Writes to file PROFILE.BIN * * * * HISTORY: * * 11/20/95 5:13PM ST : Created * *=============================================================================================*/ void __cdecl Stop_Profiler (void) { if (ProfilerEvent){ #if (PROFILE_SYSTEM == OLD_PROFILE_SYSTEM) // // Old system - just remove the timer event // timeKillEvent(ProfilerEvent); #else // // New system - kill the profiling thread // TerminateThread((HANDLE)ProfilerThread,0); #endif ProfilerEvent=NULL; Profile_End(); int handle = Open_File ( "profile.bin" , WRITE ); if (handle != WW_ERROR){ Write_File (handle , &ProfileList[0] , ProfilePtr*4); Close_File (handle); } } }