1054 lines
31 KiB
C++
1054 lines
31 KiB
C++
|
// Copyright (c) 1994 - 1996 Microsoft Corporation. All Rights Reserved.
|
||
|
|
||
|
#pragma warning(disable: 4201 4514)
|
||
|
|
||
|
/*
|
||
|
It is very easy to fill large amounts of storage with data
|
||
|
(e.g. 5 filters in a graph,
|
||
|
5 interesting start/stop times per sample each = 10 events
|
||
|
30 samples per second
|
||
|
16 bytes per sample (8 bytes time, 8 bytes to identify incident)
|
||
|
= 24KB/sec)
|
||
|
|
||
|
this means that even a quite large buffer (I have in mind 64KB) will
|
||
|
overflow after a few seconds. Writing it out to a file is completely out
|
||
|
(don't want to take page faults in the middle of things - even writing to
|
||
|
memory carries some risk).
|
||
|
|
||
|
I want to have two kinds of data available at the end of the run:
|
||
|
1. A record of what actually happened (i.e. what the sequence of events
|
||
|
actually was for at least a few frames)
|
||
|
2. Statistical information (e.g. for inter-frame video times I would like
|
||
|
to see number, average, standard deviation, greatest, smallest.
|
||
|
|
||
|
The volume of information that the actual sequence of events generates means
|
||
|
that it may only hold a second or two of information. The statistical
|
||
|
information should take in the whole run. This means that information will
|
||
|
be logged two ways.
|
||
|
|
||
|
For the detailed record I log
|
||
|
<incident, type, time>
|
||
|
in a circular buffer and regularly over-write the oldest information.
|
||
|
For the statistical information I record the informatin in an array, where
|
||
|
the incident identifier is the index. the array elements hold
|
||
|
<Number of readings, sum, sum of squares, largest, smallest, latest>
|
||
|
The readings are differences (start..next or start..stop). This means that
|
||
|
the actual number of Noted events will be one more than the number of
|
||
|
"readings", whereas for Start-Stop events they will be equal. To fix this,
|
||
|
the number of readings is articifially initialised to -1. If Start sees
|
||
|
this number it resets it to 0.
|
||
|
|
||
|
Times will be in tens of microseconds (this allows up to about 1 3/4 hrs)
|
||
|
|
||
|
The statistics array will have room for up to 128 types of incident (this is
|
||
|
4K - i.e. one page. I hope this will ensure that it never gets
|
||
|
paged out and so causes negligible overhead.
|
||
|
*/
|
||
|
#include <Windows.h> // BOOL etc
|
||
|
#include <limits.h> // for INTT_MAX
|
||
|
#include <math.h> // for sqrt
|
||
|
#include <stdio.h> // for sprintf
|
||
|
|
||
|
#include "Measure.h"
|
||
|
#include "Perf.h" // ultra fast QueryPerformanceCounter for pentium
|
||
|
|
||
|
// forwards
|
||
|
|
||
|
|
||
|
enum {START, STOP, NOTE, RESET, INTGR, PAUSE, RUN}; // values for Type field
|
||
|
|
||
|
typedef struct {
|
||
|
LONGLONG Time; // microsec since class construction
|
||
|
int Id;
|
||
|
int Type;
|
||
|
int n; // the integer for Msr_Integer
|
||
|
} LogDatum;
|
||
|
|
||
|
|
||
|
typedef struct {
|
||
|
LONGLONG Latest; // microsec since class construction
|
||
|
LONGLONG SumSq; // sum of squares of entries for this incident
|
||
|
int Largest; // tenmicrosec
|
||
|
int Smallest; // tenmicrosec
|
||
|
int Sum; // sum of entries for this incident
|
||
|
int Number; // number of entries for this incident
|
||
|
// for Start/Stop it counts the stops
|
||
|
// for Note it counts the intervals (Number of Notes-1)
|
||
|
int iType; // STOP, NOTE, INTGR
|
||
|
} Stat;
|
||
|
|
||
|
|
||
|
#define MAXLOG 4096
|
||
|
static BOOL bInitOk; // Set to true once initialised
|
||
|
static LogDatum Log[MAXLOG]; // 64K circular buffer
|
||
|
static int NextLog; // Next slot to overwrite in the log buffer.
|
||
|
static BOOL bFull; // TRUE => buffer has wrapped at least once.
|
||
|
static BOOL bPaused; // TRUE => do not record. No log, no stats.
|
||
|
|
||
|
#define MAXSTAT 128
|
||
|
static Stat StatBuffer[MAXSTAT];
|
||
|
static int NextStat; // next free slot in StatBuffer.
|
||
|
|
||
|
static LPTSTR Incidents[MAXSTAT];// Names of incidents
|
||
|
static LONGLONG QPFreq;
|
||
|
static LONGLONG QPStart; // base time in perf counts
|
||
|
#ifdef DEBUG
|
||
|
static LONGLONG tLast; // last time - looks for going backwards
|
||
|
#endif
|
||
|
|
||
|
static CRITICAL_SECTION CSMeasure; // Controls access to list
|
||
|
|
||
|
// set it to 100000 for 10 microsecs
|
||
|
// if you fiddle with it then you have to rewrite Format.
|
||
|
#define UNIT 100000
|
||
|
|
||
|
// Times are printed as 9 digits - this means that we can go
|
||
|
// up to 9,999.999,99 secs or about 2 and 3/4 hours.
|
||
|
|
||
|
|
||
|
// ASSERT(condition, msg) e.g. ASSERT(x>1, "Too many xs");
|
||
|
#define ASSERT(_cond_, _msg_) \
|
||
|
if (!(_cond_)) Assert(_msg_, __FILE__, __LINE__)
|
||
|
|
||
|
// print out debug message box
|
||
|
void Assert( const CHAR *pText
|
||
|
, const CHAR *pFile
|
||
|
, INT iLine
|
||
|
)
|
||
|
{
|
||
|
CHAR Buffer[200];
|
||
|
|
||
|
sprintf(Buffer, "%s\nAt line %d file %s"
|
||
|
, pText, iLine, pFile);
|
||
|
|
||
|
INT MsgId = MessageBox( NULL, Buffer, TEXT("ASSERT Failed")
|
||
|
, MB_SYSTEMMODAL |MB_ICONHAND |MB_ABORTRETRYIGNORE);
|
||
|
switch (MsgId)
|
||
|
{
|
||
|
case IDABORT: /* Kill the application */
|
||
|
|
||
|
FatalAppExit(FALSE, TEXT("Application terminated"));
|
||
|
break;
|
||
|
|
||
|
case IDRETRY: /* Break into the debugger */
|
||
|
DebugBreak();
|
||
|
break;
|
||
|
|
||
|
case IDIGNORE: /* Ignore assertion continue executing */
|
||
|
break;
|
||
|
}
|
||
|
} // Assert
|
||
|
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// Init
|
||
|
//
|
||
|
// Call this first.
|
||
|
//=============================================================================
|
||
|
void WINAPI Msr_Init()
|
||
|
{
|
||
|
// I would like this to be idempotent - that is, harmless if it
|
||
|
// gets called more than once. However that's not 100% possible
|
||
|
// At least we should be OK so long as it's not re-entered.
|
||
|
|
||
|
if (!bInitOk) {
|
||
|
bInitOk = TRUE;
|
||
|
InitializeCriticalSection(&CSMeasure);
|
||
|
NextLog = 0;
|
||
|
bFull = FALSE;
|
||
|
NextStat = 0;
|
||
|
LARGE_INTEGER li;
|
||
|
QUERY_PERFORMANCE_FREQUENCY(&li);
|
||
|
QPFreq = li.QuadPart;
|
||
|
QUERY_PERFORMANCE_COUNTER(&li);
|
||
|
QPStart = li.QuadPart;
|
||
|
#ifdef DEBUG
|
||
|
tLast = 0L;
|
||
|
#endif
|
||
|
|
||
|
Msr_Register("Scratch pad");
|
||
|
}
|
||
|
} // Msr_Init "constructor"
|
||
|
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// ResetAll
|
||
|
//
|
||
|
// Do a Reset on every Incident that has been registered.
|
||
|
//=============================================================================
|
||
|
void WINAPI ResetAll()
|
||
|
{
|
||
|
EnterCriticalSection(&CSMeasure);
|
||
|
int i;
|
||
|
for (i = 0; i<NextStat; ++i) {
|
||
|
Msr_Reset(i);
|
||
|
}
|
||
|
LeaveCriticalSection(&CSMeasure);
|
||
|
} // ResetAll
|
||
|
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// Pause
|
||
|
//
|
||
|
// Pause it all
|
||
|
//=============================================================================
|
||
|
void Pause()
|
||
|
{
|
||
|
if (!bInitOk) Msr_Init();
|
||
|
EnterCriticalSection(&CSMeasure);
|
||
|
|
||
|
bPaused = TRUE;
|
||
|
|
||
|
// log a PAUSE event for this id.
|
||
|
LARGE_INTEGER Time;
|
||
|
QUERY_PERFORMANCE_COUNTER(&Time);
|
||
|
|
||
|
// Get time in 10muSec from start - this gets the number small
|
||
|
// it's OK for nearly 6 hrs then an int overflows
|
||
|
LONGLONG Tim = (Time.QuadPart-QPStart) * UNIT / QPFreq;
|
||
|
|
||
|
Log[NextLog].Time = Tim;
|
||
|
Log[NextLog].Type = PAUSE;
|
||
|
Log[NextLog].Id = -1;
|
||
|
++NextLog;
|
||
|
if (NextLog==MAXLOG) {
|
||
|
NextLog = 0;
|
||
|
bFull = TRUE;
|
||
|
}
|
||
|
|
||
|
LeaveCriticalSection(&CSMeasure);
|
||
|
|
||
|
} // Pause
|
||
|
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// Run
|
||
|
//
|
||
|
// Set it all running again
|
||
|
//=============================================================================
|
||
|
void Run()
|
||
|
{
|
||
|
|
||
|
if (!bInitOk) Msr_Init();
|
||
|
EnterCriticalSection(&CSMeasure);
|
||
|
|
||
|
bPaused = FALSE;
|
||
|
|
||
|
// log a RUN event for this id.
|
||
|
LARGE_INTEGER Time;
|
||
|
QUERY_PERFORMANCE_COUNTER(&Time);
|
||
|
|
||
|
// Get time in 10muSec from start - this gets the number small
|
||
|
// it's OK for nearly 6 hrs then an int overflows
|
||
|
LONGLONG Tim = (Time.QuadPart-QPStart) * UNIT / QPFreq;
|
||
|
|
||
|
Log[NextLog].Time = Tim;
|
||
|
Log[NextLog].Type = RUN;
|
||
|
Log[NextLog].Id = -1;
|
||
|
++NextLog;
|
||
|
if (NextLog==MAXLOG) {
|
||
|
NextLog = 0;
|
||
|
bFull = TRUE;
|
||
|
}
|
||
|
|
||
|
LeaveCriticalSection(&CSMeasure);
|
||
|
|
||
|
} // Run
|
||
|
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// Msr_Control
|
||
|
//
|
||
|
// Do ResetAll, set bPaused to FALSE or TRUE according to iAction
|
||
|
//=============================================================================
|
||
|
void WINAPI Msr_Control(int iAction)
|
||
|
{
|
||
|
switch (iAction) {
|
||
|
case MSR_RESET_ALL:
|
||
|
ResetAll();
|
||
|
break;
|
||
|
case MSR_RUN:
|
||
|
Run();
|
||
|
break;
|
||
|
case MSR_PAUSE:
|
||
|
Pause();
|
||
|
break;
|
||
|
}
|
||
|
} // Msr_Control
|
||
|
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// Terminate
|
||
|
//
|
||
|
// Call this last. It frees storage for names of incidents.
|
||
|
//=============================================================================
|
||
|
void WINAPI Msr_Terminate()
|
||
|
{
|
||
|
int i;
|
||
|
if (bInitOk) {
|
||
|
EnterCriticalSection(&CSMeasure);
|
||
|
for (i = 0; i<NextStat; ++i) {
|
||
|
free(Incidents[i]);
|
||
|
}
|
||
|
bInitOk = FALSE;
|
||
|
LeaveCriticalSection(&CSMeasure);
|
||
|
DeleteCriticalSection(&CSMeasure);
|
||
|
}
|
||
|
} // Msr_Terminate "~Measure"
|
||
|
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// InitIncident
|
||
|
//
|
||
|
// Reset the statistical counters for this incident.
|
||
|
//=============================================================================
|
||
|
void InitIncident(int Id)
|
||
|
{
|
||
|
StatBuffer[Id].Latest = -1; // recogniseably odd (see STOP)
|
||
|
StatBuffer[Id].Largest = 0;
|
||
|
StatBuffer[Id].Smallest = INT_MAX;
|
||
|
StatBuffer[Id].Sum = 0;
|
||
|
StatBuffer[Id].SumSq = 0;
|
||
|
StatBuffer[Id].Number = -1;
|
||
|
StatBuffer[Id].iType = NOTE; // reset on first Start for Start/Stop
|
||
|
// reset on first Integer for INTGR
|
||
|
|
||
|
} // InitIncident
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// Register
|
||
|
//
|
||
|
// Register a new kind of incident. The id that is returned can then be used
|
||
|
// on calls to Start, Stop and Note to record the occurrences of these incidents
|
||
|
// so that statistical performance information can be dumped later.
|
||
|
//=============================================================================
|
||
|
int Msr_Register(LPTSTR Incident)
|
||
|
{
|
||
|
|
||
|
if (!bInitOk) {
|
||
|
Msr_Init();
|
||
|
}
|
||
|
// it's now safe to enter the critical section as it will be there!
|
||
|
EnterCriticalSection(&CSMeasure);
|
||
|
|
||
|
int i;
|
||
|
for (i = 0; i<NextStat; ++i) {
|
||
|
if (0==strcmp(Incidents[i],Incident) ) {
|
||
|
// Attempting to re-register the same name.
|
||
|
// Possible actions
|
||
|
// 1. ASSERT - that just causes trouble.
|
||
|
// 2. Register it as a new incident. That produced quartz bug 1
|
||
|
// 3. Hand the old number back and reset it.
|
||
|
// Msr_Reset(i); - possible, but not today.
|
||
|
// 4. Hand the old number back and just keep going.
|
||
|
|
||
|
LeaveCriticalSection(&CSMeasure);
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
if (NextStat==MAXSTAT-1) {
|
||
|
Assert("Too many types of incident\n(ignore is safe)", __FILE__, __LINE__);
|
||
|
LeaveCriticalSection(&CSMeasure);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
Incidents[NextStat] = (LPTSTR)malloc(strlen(Incident)+1);
|
||
|
strcpy(Incidents[NextStat], Incident);
|
||
|
|
||
|
InitIncident(NextStat);
|
||
|
|
||
|
LeaveCriticalSection(&CSMeasure);
|
||
|
return NextStat++;
|
||
|
|
||
|
} // Msr_Register
|
||
|
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// Reset
|
||
|
//
|
||
|
// Reset the statistical counters for this incident.
|
||
|
// Log that we did it.
|
||
|
//=============================================================================
|
||
|
void WINAPI Msr_Reset(int Id)
|
||
|
{
|
||
|
if (!bInitOk) {
|
||
|
Msr_Init();
|
||
|
}
|
||
|
// it's now safe to enter the critical section as it will be there!
|
||
|
|
||
|
EnterCriticalSection(&CSMeasure);
|
||
|
|
||
|
// log a RESET event for this id.
|
||
|
LARGE_INTEGER Time;
|
||
|
QUERY_PERFORMANCE_COUNTER(&Time);
|
||
|
|
||
|
// Get time in 10muSec from start - this gets the number small
|
||
|
// it's OK for nearly 6 hrs then an int overflows
|
||
|
LONGLONG Tim = (Time.QuadPart-QPStart) * UNIT / QPFreq;
|
||
|
|
||
|
Log[NextLog].Time = Tim;
|
||
|
Log[NextLog].Type = RESET;
|
||
|
Log[NextLog].Id = Id;
|
||
|
++NextLog;
|
||
|
if (NextLog==MAXLOG) {
|
||
|
NextLog = 0;
|
||
|
bFull = TRUE;
|
||
|
}
|
||
|
|
||
|
InitIncident(Id);
|
||
|
|
||
|
LeaveCriticalSection(&CSMeasure);
|
||
|
|
||
|
} // Msr_Reset
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// Msr_Start
|
||
|
//
|
||
|
// Record the start time of the event with registered id Id.
|
||
|
// Add it to the circular Log and record the time in StatBuffer.
|
||
|
// Do not update the statistical information, that happens when Stop is called.
|
||
|
//=============================================================================
|
||
|
void WINAPI Msr_Start(int Id)
|
||
|
{
|
||
|
if (bPaused) return;
|
||
|
|
||
|
// This is performance critical. Keep all array subscripting
|
||
|
// together and with any luck the compiler will only calculate the
|
||
|
// offset once. Avoid subroutine calls unless they are definitely inline.
|
||
|
|
||
|
// An id of -1 is the standard rotten registration one.
|
||
|
// We already did an assert for that - so let it go.
|
||
|
if (Id<-1 || Id>=NextStat) {
|
||
|
// ASSERT(!"Performance logging with bad Id");
|
||
|
return;
|
||
|
}
|
||
|
EnterCriticalSection(&CSMeasure);
|
||
|
LARGE_INTEGER Time;
|
||
|
QUERY_PERFORMANCE_COUNTER(&Time);
|
||
|
|
||
|
LONGLONG Tim = (Time.QuadPart-QPStart) * UNIT / QPFreq;
|
||
|
#ifdef DEBUG
|
||
|
ASSERT(Tim>=tLast, "Time is going backwards!!"); tLast = Tim;
|
||
|
#endif
|
||
|
Log[NextLog].Time = Tim;
|
||
|
Log[NextLog].Type = START;
|
||
|
Log[NextLog].Id = Id;
|
||
|
++NextLog;
|
||
|
if (NextLog==MAXLOG) {
|
||
|
NextLog = 0;
|
||
|
bFull = TRUE;
|
||
|
}
|
||
|
|
||
|
StatBuffer[Id].Latest = Tim;
|
||
|
|
||
|
if (StatBuffer[Id].Number == -1) {
|
||
|
StatBuffer[Id].Number = 0;
|
||
|
StatBuffer[Id].iType = STOP;
|
||
|
}
|
||
|
LeaveCriticalSection(&CSMeasure);
|
||
|
|
||
|
} // Msr_Start
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// Msr_Stop
|
||
|
//
|
||
|
// Record the stop time of the event with registered id Id.
|
||
|
// Add it to the circular Log and
|
||
|
// add (StopTime-StartTime) to the statistical record StatBuffer.
|
||
|
//=============================================================================
|
||
|
void WINAPI Msr_Stop(int Id)
|
||
|
{
|
||
|
if (bPaused) return;
|
||
|
|
||
|
// This is performance critical. Keep all array subscripting
|
||
|
// together and with any luck the compiler will only calculate the
|
||
|
// offset once. Avoid subroutine calls unless they are definitely inline.
|
||
|
|
||
|
EnterCriticalSection(&CSMeasure);
|
||
|
LARGE_INTEGER Time;
|
||
|
QUERY_PERFORMANCE_COUNTER(&Time);
|
||
|
|
||
|
// An id of -1 is the standard rotten registration one.
|
||
|
// We already did an assert for that - so let it go.
|
||
|
if (Id<-1 || Id>=NextStat) {
|
||
|
// ASSERT(!"Performance logging with bad Id");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Get time in 10muSec from start - this gets the number small
|
||
|
// it's OK for nearly 6 hrs, then an int overflows
|
||
|
LONGLONG Tim = (Time.QuadPart-QPStart) * UNIT / QPFreq;
|
||
|
#ifdef DEBUG
|
||
|
ASSERT(Tim>=tLast, "Time is going backwards!!"); tLast = Tim;
|
||
|
#endif
|
||
|
Log[NextLog].Time = Tim;
|
||
|
Log[NextLog].Type = STOP;
|
||
|
Log[NextLog].Id = Id;
|
||
|
++NextLog;
|
||
|
if (NextLog==MAXLOG) {
|
||
|
NextLog = 0;
|
||
|
bFull = TRUE;
|
||
|
}
|
||
|
|
||
|
if (StatBuffer[Id].Latest!=-1) {
|
||
|
int t = (int)(Tim - StatBuffer[Id].Latest); // convert to delta
|
||
|
// this is now OK for almost 6hrs since the last Start of this quantity.
|
||
|
|
||
|
if (t > StatBuffer[Id].Largest) StatBuffer[Id].Largest = t;
|
||
|
if (t < StatBuffer[Id].Smallest) StatBuffer[Id].Smallest = t;
|
||
|
StatBuffer[Id].Sum += t;
|
||
|
LONGLONG lt = t;
|
||
|
StatBuffer[Id].SumSq += lt*lt;
|
||
|
++StatBuffer[Id].Number;
|
||
|
}
|
||
|
LeaveCriticalSection(&CSMeasure);
|
||
|
|
||
|
} // Msr_Stop
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// Msr_Note
|
||
|
//
|
||
|
// Record the event with registered id Id. Add it to the circular Log and
|
||
|
// add (ThisTime-PreviousTime) to the statistical record StatBuffer
|
||
|
//=============================================================================
|
||
|
void WINAPI Msr_Note(int Id)
|
||
|
{
|
||
|
if (bPaused) return;
|
||
|
|
||
|
// This is performance critical. Keep all array subscripting
|
||
|
// together and with any luck the compiler will only calculate the
|
||
|
// offset once. Avoid subroutine calls unless they are definitely inline.
|
||
|
|
||
|
// An id of -1 is the standard rotten registration one.
|
||
|
// We already did an assert for that - so let it go.
|
||
|
if (Id<-1 || Id>=NextStat) {
|
||
|
// ASSERT(!"Performance logging with bad Id");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
EnterCriticalSection(&CSMeasure);
|
||
|
LARGE_INTEGER Time;
|
||
|
QUERY_PERFORMANCE_COUNTER(&Time);
|
||
|
|
||
|
// Get time in 10muSec from start - this gets the number small
|
||
|
// it's OK for nearly 6 hrs then an int overflows
|
||
|
LONGLONG Tim = (Time.QuadPart-QPStart) * UNIT / QPFreq;
|
||
|
#ifdef DEBUG
|
||
|
ASSERT(Tim>=tLast, "Time is going backwards!!"); tLast = Tim;
|
||
|
#endif
|
||
|
Log[NextLog].Time = Tim;
|
||
|
Log[NextLog].Type = NOTE;
|
||
|
Log[NextLog].Id = Id;
|
||
|
++NextLog;
|
||
|
if (NextLog==MAXLOG) {
|
||
|
NextLog = 0;
|
||
|
bFull = TRUE;
|
||
|
}
|
||
|
int t = (int)(Tim - StatBuffer[Id].Latest); // convert to delta
|
||
|
// this is now OK for nearly 6 hrs since the last Note of this quantity.
|
||
|
|
||
|
StatBuffer[Id].Latest = Tim;
|
||
|
++StatBuffer[Id].Number;
|
||
|
if (StatBuffer[Id].Number>0) {
|
||
|
if (t > StatBuffer[Id].Largest) StatBuffer[Id].Largest = t;
|
||
|
if (t < StatBuffer[Id].Smallest) StatBuffer[Id].Smallest = t;
|
||
|
StatBuffer[Id].Sum += (int)t;
|
||
|
LONGLONG lt = t;
|
||
|
StatBuffer[Id].SumSq += lt*lt;
|
||
|
}
|
||
|
LeaveCriticalSection(&CSMeasure);
|
||
|
|
||
|
} // Msr_Note
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// Msr_Integer
|
||
|
//
|
||
|
// Record the event with registered id Id. Add it to the circular Log and
|
||
|
// add (ThisTime-PreviousTime) to the statistical record StatBuffer
|
||
|
//=============================================================================
|
||
|
void WINAPI Msr_Integer(int Id, int n)
|
||
|
{
|
||
|
if (bPaused) return;
|
||
|
|
||
|
// This is performance critical. Keep all array subscripting
|
||
|
// together and with any luck the compiler will only calculate the
|
||
|
// offset once. Avoid subroutine calls unless they are definitely inline.
|
||
|
|
||
|
// An id of -1 is the standard rotten registration one.
|
||
|
// We already did an assert for that - so let it go.
|
||
|
if (Id<-1 || Id>=NextStat) {
|
||
|
// ASSERT(!"Performance logging with bad Id");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
EnterCriticalSection(&CSMeasure);
|
||
|
LARGE_INTEGER Time;
|
||
|
QUERY_PERFORMANCE_COUNTER(&Time);
|
||
|
|
||
|
// Get time in 10muSec from start - this gets the number small
|
||
|
// it's OK for nearly 6 hrs then an int overflows
|
||
|
LONGLONG Tim = (Time.QuadPart-QPStart) * UNIT / QPFreq;
|
||
|
#ifdef DEBUG
|
||
|
ASSERT(Tim>=tLast, "Time is going backwards!!"); tLast = Tim;
|
||
|
#endif
|
||
|
Log[NextLog].Time = Tim;
|
||
|
Log[NextLog].Type = INTGR;
|
||
|
Log[NextLog].Id = Id;
|
||
|
Log[NextLog].n = n;
|
||
|
++NextLog;
|
||
|
if (NextLog==MAXLOG) {
|
||
|
NextLog = 0;
|
||
|
bFull = TRUE;
|
||
|
}
|
||
|
|
||
|
// StatBuffer[Id].Latest = garbage for Intgr
|
||
|
|
||
|
if (StatBuffer[Id].Number == -1) {
|
||
|
StatBuffer[Id].Number = 0;
|
||
|
StatBuffer[Id].iType = INTGR;
|
||
|
}
|
||
|
++StatBuffer[Id].Number;
|
||
|
if (n > StatBuffer[Id].Largest) StatBuffer[Id].Largest = n;
|
||
|
if (n < StatBuffer[Id].Smallest) StatBuffer[Id].Smallest = n;
|
||
|
StatBuffer[Id].Sum += (int)n;
|
||
|
LONGLONG ln = n;
|
||
|
StatBuffer[Id].SumSq += ln*ln;
|
||
|
|
||
|
LeaveCriticalSection(&CSMeasure);
|
||
|
|
||
|
} // Msr_Integer
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// TypeName
|
||
|
//
|
||
|
// Convert the type code into readable format
|
||
|
//=============================================================================
|
||
|
const LPTSTR TypeName(int Type)
|
||
|
{
|
||
|
switch(Type){
|
||
|
case START: return "START";
|
||
|
case STOP: return "STOP ";
|
||
|
case NOTE: return "NOTE ";
|
||
|
case RESET: return "RESET";
|
||
|
case INTGR: return "INTGR";
|
||
|
case PAUSE: return "PAUSE";
|
||
|
case RUN: return "RUN ";
|
||
|
default: return "DUNNO";
|
||
|
}
|
||
|
|
||
|
} // TypeName
|
||
|
|
||
|
|
||
|
//==============================================================================
|
||
|
//
|
||
|
// Format
|
||
|
//
|
||
|
// I haven't found any way to get sprintf to format integers as
|
||
|
// 1,234.567.89 - so this does it. (that's 12 spaces)
|
||
|
// All times are in tens of microsecs - so they are formatted as
|
||
|
// n,nnn.mmm,mm - this uses 12 spaces.
|
||
|
// The result that it returns points to Buff - it doesn't allocate any storage
|
||
|
// i must be positive. Negative numbers are not handled (the pain of the floating
|
||
|
// minus sign is the reason - i.e. " -12,345" not "- 12,345"
|
||
|
//==============================================================================
|
||
|
LPTSTR Format( LPTSTR Buff, int i)
|
||
|
{
|
||
|
if (i<0) {
|
||
|
sprintf(Buff, " -. ");
|
||
|
return Buff;
|
||
|
}
|
||
|
BOOL bStarted; // TRUE means that some left part of the number has been
|
||
|
// formatted and so we must continue with zeros not spaces
|
||
|
if (i>999999999) {
|
||
|
sprintf(Buff, " ***large***");
|
||
|
return Buff;
|
||
|
}
|
||
|
|
||
|
if (i>99999999) {
|
||
|
sprintf(Buff, "%1d,", i/100000000);
|
||
|
i = i%100000000;
|
||
|
bStarted = TRUE;
|
||
|
} else {
|
||
|
sprintf(Buff, " ");
|
||
|
bStarted = FALSE;
|
||
|
}
|
||
|
|
||
|
if (bStarted) {
|
||
|
sprintf(Buff, "%s%03d.", Buff, i/100000);
|
||
|
i = i%100000;
|
||
|
} else {
|
||
|
sprintf(Buff, "%s%3d.", Buff,i/100000);
|
||
|
i = i%100000;
|
||
|
}
|
||
|
|
||
|
sprintf(Buff, "%s%03d,%02d", Buff, i/100, i%100);
|
||
|
|
||
|
return Buff;
|
||
|
} // Format
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// WriteOut
|
||
|
//
|
||
|
// If hFile==NULL then write str to debug output, else write it to file hFile
|
||
|
//
|
||
|
//=============================================================================
|
||
|
void WriteOut(HANDLE hFile, LPSTR str)
|
||
|
{
|
||
|
if (hFile==NULL) {
|
||
|
OutputDebugString(str);
|
||
|
} else {
|
||
|
DWORD dw;
|
||
|
WriteFile(hFile, str, lstrlen(str), &dw, NULL);
|
||
|
}
|
||
|
} // WriteOut
|
||
|
|
||
|
|
||
|
typedef LONGLONG longlongarray[MAXSTAT];
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// WriteLogEntry
|
||
|
//
|
||
|
// If hFile==NULL then write to debug output, else write to file hFile
|
||
|
// write the ith entry of Log in a readable format
|
||
|
//
|
||
|
//=============================================================================
|
||
|
void WriteLogEntry(HANDLE hFile, int i, longlongarray &Prev)
|
||
|
{
|
||
|
// We have the problem of printing LONGLONGs and wsprintf (26/6/95)
|
||
|
// doesn't like them - found out the hard way - Laurie.
|
||
|
char Buffer[200];
|
||
|
char s1[20];
|
||
|
char s2[20];
|
||
|
|
||
|
int Delta; // time since previous interesting incident
|
||
|
|
||
|
switch(Log[i].Type) {
|
||
|
case START:
|
||
|
Prev[Log[i].Id] = Log[i].Time;
|
||
|
Delta = -2;
|
||
|
sprintf( Buffer, "%s %5s %s : %s\r\n"
|
||
|
, Format(s1,(int)(Log[i].Time))
|
||
|
, TypeName(Log[i].Type)
|
||
|
, Format(s2, Delta)
|
||
|
, Incidents[Log[i].Id]
|
||
|
);
|
||
|
break;
|
||
|
case STOP:
|
||
|
if (Prev[Log[i].Id]==-1) {
|
||
|
Delta = -2;
|
||
|
} else {
|
||
|
Delta = (int)(Log[i].Time - Prev[Log[i].Id]);
|
||
|
}
|
||
|
Prev[Log[i].Id] = -1;
|
||
|
sprintf( Buffer, "%s %5s %s : %s\r\n"
|
||
|
, Format(s1,(int)(Log[i].Time))
|
||
|
, TypeName(Log[i].Type)
|
||
|
, Format(s2, Delta)
|
||
|
, Incidents[Log[i].Id]
|
||
|
);
|
||
|
break;
|
||
|
case NOTE:
|
||
|
if (Prev[Log[i].Id]==-1) {
|
||
|
Delta = -2;
|
||
|
} else {
|
||
|
Delta = (int)(Log[i].Time - Prev[Log[i].Id]);
|
||
|
}
|
||
|
Prev[Log[i].Id] = Log[i].Time;
|
||
|
sprintf( Buffer, "%s %5s %s : %s\r\n"
|
||
|
, Format(s1,(int)(Log[i].Time))
|
||
|
, TypeName(Log[i].Type)
|
||
|
, Format(s2, Delta)
|
||
|
, Incidents[Log[i].Id]
|
||
|
);
|
||
|
break;
|
||
|
case INTGR:
|
||
|
sprintf( Buffer, "%s %5s %12d : %s\r\n"
|
||
|
, Format(s1,(int)(Log[i].Time))
|
||
|
, TypeName(Log[i].Type)
|
||
|
, Log[i].n
|
||
|
, Incidents[Log[i].Id]
|
||
|
);
|
||
|
break;
|
||
|
case RESET: // the delta for a reset will be length of run
|
||
|
case PAUSE:
|
||
|
case RUN:
|
||
|
if ((Log[i].Id==-1)||(Prev[Log[i].Id]==-1)) {
|
||
|
Delta = (int)(Log[i].Time); // = time from start
|
||
|
} else {
|
||
|
Delta = (int)(Log[i].Time - Prev[Log[i].Id]);
|
||
|
}
|
||
|
if (Log[i].Id!=-1) Prev[Log[i].Id] = Log[i].Time;
|
||
|
sprintf( Buffer, "%s %5s %s : %s\r\n"
|
||
|
, Format(s1,(int)(Log[i].Time))
|
||
|
, TypeName(Log[i].Type)
|
||
|
, Format(s2, Delta)
|
||
|
, Incidents[Log[i].Id]
|
||
|
);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
WriteOut(hFile, Buffer);
|
||
|
|
||
|
} // WriteLogEntry
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// WriteLog
|
||
|
//
|
||
|
// Write the whole of Log out in readable format.
|
||
|
// If hFile==NULL then write to debug output, else write to hFile.
|
||
|
//=============================================================================
|
||
|
void WriteLog(HANDLE hFile)
|
||
|
{
|
||
|
//LONGLONG Prev[MAXSTAT]; // previous values found in log
|
||
|
longlongarray Prev;
|
||
|
|
||
|
char Buffer[100];
|
||
|
sprintf(Buffer, " Time (sec) Type Delta Incident_Name\r\n");
|
||
|
WriteOut(hFile, Buffer);
|
||
|
|
||
|
int i;
|
||
|
|
||
|
// initialise Prev to recognisable odd values
|
||
|
for (i = 0; i<MAXSTAT; ++i) {
|
||
|
Prev[i] = -1;
|
||
|
}
|
||
|
|
||
|
if (bFull) {
|
||
|
for(i = NextLog; i<MAXLOG; ++i) {
|
||
|
WriteLogEntry(hFile, i, Prev);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for(i = 0; i<NextLog; ++i) {
|
||
|
WriteLogEntry(hFile, i, Prev);
|
||
|
}
|
||
|
|
||
|
} // WriteLog
|
||
|
|
||
|
|
||
|
//=============================================================================
|
||
|
//
|
||
|
// WriteStats
|
||
|
//
|
||
|
// Write the whole of StatBuffer out in readable format.
|
||
|
// If hFile==NULL then write to DbgLog, else write to hFile.
|
||
|
//=============================================================================
|
||
|
void WriteStats(HANDLE hFile)
|
||
|
{
|
||
|
char Buffer[200];
|
||
|
char s1[20];
|
||
|
char s2[20];
|
||
|
char s3[20];
|
||
|
char s4[20];
|
||
|
sprintf( Buffer
|
||
|
, "Number Average StdDev Smallest Largest Incident_Name\r\n"
|
||
|
);
|
||
|
WriteOut(hFile, Buffer);
|
||
|
|
||
|
int i;
|
||
|
for (i = 0; i<NextStat; ++i) {
|
||
|
if (i==0 && StatBuffer[i].Number==0) {
|
||
|
continue; // no temp scribbles to report
|
||
|
}
|
||
|
double SumSq = (double)StatBuffer[i].SumSq;
|
||
|
double Sum = StatBuffer[i].Sum;
|
||
|
|
||
|
if (StatBuffer[i].iType==INTGR) {
|
||
|
double Average;
|
||
|
if (StatBuffer[i].Number<=0) {
|
||
|
Average = 0;
|
||
|
} else {
|
||
|
Average = (double)StatBuffer[i].Sum / (double)StatBuffer[i].Number;
|
||
|
}
|
||
|
double Std;
|
||
|
if (StatBuffer[i].Number<=1) Std = 0.0;
|
||
|
Std = sqrt( ( (double)SumSq
|
||
|
- ( (double)(Sum * Sum)
|
||
|
/ (double)StatBuffer[i].Number
|
||
|
)
|
||
|
)
|
||
|
/ ((double)StatBuffer[i].Number-1.0)
|
||
|
);
|
||
|
sprintf( Buffer
|
||
|
, "%6d %12.3f %12.3f %12d %12d : %s\r\n"
|
||
|
, StatBuffer[i].Number + (StatBuffer[i].iType==NOTE ? 1 : 0)
|
||
|
, Average
|
||
|
, Std
|
||
|
, StatBuffer[i].Smallest
|
||
|
, StatBuffer[i].Largest
|
||
|
, Incidents[i]
|
||
|
);
|
||
|
} else {
|
||
|
double StDev;
|
||
|
int Avg;
|
||
|
int Smallest;
|
||
|
int Largest;
|
||
|
|
||
|
// Calculate Standard Deviation
|
||
|
if (StatBuffer[i].Number<=1) StDev = -2;
|
||
|
else {
|
||
|
StDev = sqrt( ( SumSq
|
||
|
- ( (Sum * Sum)
|
||
|
/ StatBuffer[i].Number
|
||
|
)
|
||
|
)
|
||
|
/ (StatBuffer[i].Number-1)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// Calculate average
|
||
|
if (StatBuffer[i].Number<=0) {
|
||
|
Avg = -2;
|
||
|
} else {
|
||
|
Avg = StatBuffer[i].Sum / StatBuffer[i].Number;
|
||
|
}
|
||
|
|
||
|
// Calculate smallest and largest
|
||
|
if (StatBuffer[i].Number<=0) {
|
||
|
Smallest = -2;
|
||
|
Largest = -2;
|
||
|
} else {
|
||
|
Smallest = StatBuffer[i].Smallest;
|
||
|
Largest = StatBuffer[i].Largest;
|
||
|
}
|
||
|
sprintf( Buffer
|
||
|
, "%6d %s %s %s %s : %s\r\n"
|
||
|
, StatBuffer[i].Number + (StatBuffer[i].iType==NOTE ? 1 : 0)
|
||
|
, Format(s1, Avg )
|
||
|
, Format(s2, (int)StDev )
|
||
|
, Format(s3, Smallest )
|
||
|
, Format(s4, Largest )
|
||
|
, Incidents[i]
|
||
|
);
|
||
|
}
|
||
|
|
||
|
|
||
|
WriteOut(hFile, Buffer);
|
||
|
}
|
||
|
WriteOut(hFile, "Times such as 0.050,00 are in seconds (that was 1/20 sec) \r\n");
|
||
|
} // WriteStats
|
||
|
|
||
|
|
||
|
#if 0 // test format
|
||
|
void TestFormat(int n)
|
||
|
{
|
||
|
char Buffer[50];
|
||
|
char s1[20];
|
||
|
sprintf(Buffer, ">%s<",Format(s1,n));
|
||
|
DbgLog((LOG_TRACE, 0, Buffer));
|
||
|
} // TestFormat
|
||
|
#endif
|
||
|
|
||
|
|
||
|
|
||
|
//=====================================================================
|
||
|
//
|
||
|
// Dump
|
||
|
//
|
||
|
// Dump out all the results from Log and StatBuffer in readable format.
|
||
|
// If hFile is NULL then it uses DbgLog
|
||
|
// otherwise it prints it to that file
|
||
|
//=====================================================================
|
||
|
void Msr_Dump(HANDLE hFile)
|
||
|
{
|
||
|
EnterCriticalSection(&CSMeasure);
|
||
|
if (!bInitOk) {
|
||
|
Msr_Init(); // of course the log will be empty - never mind!
|
||
|
}
|
||
|
|
||
|
WriteLog(hFile);
|
||
|
WriteStats(hFile);
|
||
|
|
||
|
#if 0 // test Format
|
||
|
TestFormat(1);
|
||
|
TestFormat(12);
|
||
|
TestFormat(123);
|
||
|
TestFormat(1234);
|
||
|
TestFormat(12345);
|
||
|
TestFormat(123456);
|
||
|
TestFormat(1234567);
|
||
|
TestFormat(12345678);
|
||
|
TestFormat(123456789);
|
||
|
TestFormat(1234567890);
|
||
|
#endif
|
||
|
|
||
|
LeaveCriticalSection(&CSMeasure);
|
||
|
} // Msr_Dump
|
||
|
|
||
|
|
||
|
//=====================================================================
|
||
|
//
|
||
|
// DumpStats
|
||
|
//
|
||
|
// Dump out all the results from Log and StatBuffer in readable format.
|
||
|
// If hFile is NULL then it uses DbgLog
|
||
|
// otherwise it prints it to that file
|
||
|
//=====================================================================
|
||
|
void WINAPI Msr_DumpStats(HANDLE hFile)
|
||
|
{
|
||
|
EnterCriticalSection(&CSMeasure);
|
||
|
if (!bInitOk) {
|
||
|
Msr_Init(); // of course the stats will be empty - never mind!
|
||
|
}
|
||
|
WriteStats(hFile);
|
||
|
|
||
|
LeaveCriticalSection(&CSMeasure);
|
||
|
} // Msr_DumpStats
|
||
|
|
||
|
extern "C" BOOL WINAPI DllMain(HINSTANCE, ULONG, LPVOID);
|
||
|
|
||
|
BOOL WINAPI
|
||
|
DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pv)
|
||
|
{
|
||
|
UNREFERENCED_PARAMETER(pv);
|
||
|
switch (ulReason)
|
||
|
{
|
||
|
|
||
|
case DLL_PROCESS_ATTACH:
|
||
|
|
||
|
DisableThreadLibraryCalls(hInstance);
|
||
|
InitPerfCounter();
|
||
|
Msr_Init();
|
||
|
break;
|
||
|
|
||
|
case DLL_PROCESS_DETACH:
|
||
|
Msr_Terminate();
|
||
|
break;
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|