2025-04-27 07:49:33 -04:00

656 lines
23 KiB
C++

/*
** SSOPERF.CPP
**
** Perfmon Counters for the SSO Framework
**
** davidsan -- 03/01/96
*/
#pragma warning(disable: 4237) // disable "bool" reserved
#include <ssobase.h>
#include <winperf.h>
#include <iis64.h>
#include "ssoperf.h"
// Okay, this is how the perfmon gunk works. Some of this is distilled from
// the MSDN documentation on perfmon counters (in the win32 SDK and the win32
// DDK). Other parts of it are explanations of how this implementation works.
//
// Basically, the API to provide perfmon counters works like this: assuming
// everything's set up correctly in the registry, when your object is selected
// by a perfmon client, SSOPerfOpen() is called. Then SSOPerfCollect() is
// repeatedly called to provide the actual counter data.
//
// SSOPerfCollect() is given a buffer, which it's supposed to fill with a
// PERF_OBJECT_TYPE structure, followed by a set of PERF_COUNTER_DEFINITION
// structures (one per counter), which are in turn followed by a set of
// PERF_INSTANCE_DEFINITION structures, one per instance, which contain
// the actual counter data. Whew. Since this huge structure has to be
// spewed into the buffer for every SSOPerfCollect() call, we keep as much
// of it as we can pre-initialized and just ready to go. The main structure
// we use is the SPD structure, which is pre-initialized below. The only
// problem is that there's some stuff we don't know at compile time. The
// really important one is: as part of this perfmon API, every
// perfmon counter source gets two magic reg values put into its reg key
// as part of installation of the counter. The reg values are a "first
// counter" and "first help" index. At SSOPerfOpen() time, these values
// have to be added to every index in the SPD structure (there are two counter
// indices per object and two per counter).
//
// Perfmon objects can provide COUNTERS and INSTANCES. A counter is an
// abstract measurement of some quantity, like # of times Invoke() is called
// per second. An instance is an instantiation of a counter. In this code,
// we map "instance" onto "SSO method". One limitation of the perfmon API
// is that every counter must have data for every instance. This means that
// every counter in this code has to compute its value PER SSO METHOD. We
// can't add global counters without adding a second PERF_OBJECT_TYPE, which
// just isn't worth it no matter what.
//
// The only other real complication is the fact that the perfmon.exe process
// and the Gibraltar process have to share the perfmon counter data via a
// shared memory-mapped file. The way we do this is to export a set of
// pointers to arrays of counter data within the MMF. That way, once the
// Gibraltar side has called InitPerfSource(), it can just do things like
// g_rgcInvokes[instance]++;
// to set the counters. The perfmon.exe side of the MMF uses the same names
// for the variables and everything, so once InitPerfSink() is called, it
// can get the value by just accessing g_rgcInvokes[instance] directly.
// Steps to add a new counter:
// 1) add a PERF_COUNTER_DEFINITION to g_spd
// 2) increase g_cCounters
// 3) add its instance computations in Open
// 4) add entry in ssoperf.h and ssoperf.ini
// 5) add new arrays for the shared memory block, if necessary
// 6) add their computation/storage in Collect
// 7) add code in ssobase.cpp to actually touch the counter values
// 8) add setting of arrays in InitPerfSource/FInitPerfSink
#include <pshpack4.h>
typedef struct _SSO_PERF_DATA
{
PERF_OBJECT_TYPE potSSO;
PERF_COUNTER_DEFINITION pcdInvokes;
PERF_COUNTER_DEFINITION pcdInvokesTotal;
PERF_COUNTER_DEFINITION pcdGetNames;
PERF_COUNTER_DEFINITION pcdGetNamesTotal;
PERF_COUNTER_DEFINITION pcdLatency;
PERF_COUNTER_DEFINITION pcdMaxLatency;
} SPD, *PSPD;
#include <poppack.h>
const int g_cCounters = 6;
// This structure is preinitialized here, but there are some adjustments that
// have to be made in the Open routine, because at compile time we don't know
// how many instances (SSO methods) we have. Also, the Index pieces are
// relative to some values that get stored in the registry by lodctr.exe, so
// those have to be adjusted also. Here's a list of everything to adjust:
// TotalByteLength += size of instance defs (includes data)
// ObjectNameTitleIndex/ObjectHelpTitleIndex += base offset from registry
// NumInstances
// each CounterNameTitleIndex/CounterHelpTitleIndex
SPD g_spd =
{
{
sizeof(SPD), // TotalByteLength
sizeof(SPD), // DefinitionLength
sizeof(PERF_OBJECT_TYPE), // HeaderLength
SSO_PERF_OBJECT, // ObjectNameTitleIndex
0, // ObjectNameTitle
SSO_PERF_OBJECT, // ObjectHelpTitleIndex
0, // ObjectHelpTitle
PERF_DETAIL_NOVICE, // DetailLevel
g_cCounters, // NumCounters
0, // DefaultCounter
0, // NumInstances
0, // CodePage
},
{
sizeof(PERF_COUNTER_DEFINITION), // ByteLength
SSO_NUM_INVOKES, // CounterNameTitleIndex
0, // CounterNameTitle
SSO_NUM_INVOKES, // CounterHelpTitleIndex
0, // CounterHelpTitle
0, // DefaultScale
PERF_DETAIL_NOVICE, // DetailLevel
PERF_COUNTER_COUNTER, // CounterType -- means dword rate counter with per/sec display
sizeof(DWORD), // CounterSize
sizeof(DWORD), // CounterOffset
},
{
sizeof(PERF_COUNTER_DEFINITION), // ByteLength
SSO_NUM_INVOKES_TOTAL, // CounterNameTitleIndex
0, // CounterNameTitle
SSO_NUM_INVOKES_TOTAL, // CounterHelpTitleIndex
0, // CounterHelpTitle
0, // DefaultScale
PERF_DETAIL_NOVICE, // DetailLevel
PERF_COUNTER_RAWCOUNT, // CounterType -- means dword counter
sizeof(DWORD), // CounterSize
2 * sizeof(DWORD), // CounterOffset
},
{
sizeof(PERF_COUNTER_DEFINITION), // ByteLength
SSO_NUM_GETNAMES, // CounterNameTitleIndex
0, // CounterNameTitle
SSO_NUM_GETNAMES, // CounterHelpTitleIndex
0, // CounterHelpTitle
0, // DefaultScale
PERF_DETAIL_NOVICE, // DetailLevel
PERF_COUNTER_COUNTER, // CounterType -- means dword rate counter with per/sec display
sizeof(DWORD), // CounterSize
3 * sizeof(DWORD), // CounterOffset
},
{
sizeof(PERF_COUNTER_DEFINITION), // ByteLength
SSO_NUM_GETNAMES_TOTAL, // CounterNameTitleIndex
0, // CounterNameTitle
SSO_NUM_GETNAMES_TOTAL, // CounterHelpTitleIndex
0, // CounterHelpTitle
0, // DefaultScale
PERF_DETAIL_NOVICE, // DetailLevel
PERF_COUNTER_RAWCOUNT, // CounterType -- means dword counter
sizeof(DWORD), // CounterSize
4 * sizeof(DWORD), // CounterOffset
},
{
sizeof(PERF_COUNTER_DEFINITION), // ByteLength
SSO_LATENCY_INVOKES_AVG, // CounterNameTitleIndex
0, // CounterNameTitle
SSO_LATENCY_INVOKES_AVG, // CounterHelpTitleIndex
0, // CounterHelpTitle
0, // DefaultScale
PERF_DETAIL_NOVICE, // DetailLevel
PERF_COUNTER_RAWCOUNT, // CounterType -- just a number, don't munge it
sizeof(DWORD), // CounterSize
5 * sizeof(DWORD), // CounterOffset
},
{
sizeof(PERF_COUNTER_DEFINITION), // ByteLength
SSO_LATENCY_INVOKES_MAX, // CounterNameTitleIndex
0, // CounterNameTitle
SSO_LATENCY_INVOKES_MAX, // CounterHelpTitleIndex
0, // CounterHelpTitle
0, // DefaultScale
PERF_DETAIL_NOVICE, // DetailLevel
PERF_COUNTER_RAWCOUNT, // CounterType -- just a number, don't munge it
sizeof(DWORD), // CounterSize
6 * sizeof(DWORD), // CounterOffset
},
};
BOOL g_fInited = FALSE;
int g_cInstances = 0;
// add new counter arrays here
int *g_rgcInvokes = NULL;
int *g_rgcGetNames = NULL;
DWORD *g_rgctixLatencyMax = NULL;
int *g_rgcTimeSamples = NULL;
int *g_rgiTimeSampleCurrent = NULL;
DWORD *g_rgTimeSamples = NULL;
extern BOOL FInitPerfSink();
PERF_INSTANCE_DEFINITION **g_rgppid = NULL;
BOOL
_FAddInstance(OLECHAR *wszName, int *pcbInstanceData, int iInstance, int iFirstCounter)
{
int cchName, cbName;
int cbThisInstance;
void *pv;
cchName = lstrlenW(wszName) + 1;
// pad to 4-byte (2-wchar) boundary
if (cchName & 1)
cchName++;
cbName = 2 * cchName;
// allocate space for PID + name + PERF_COUNTER_BLOCK + data
cbThisInstance = sizeof(PERF_INSTANCE_DEFINITION) + cbName + sizeof(DWORD) + (sizeof(DWORD) * g_cCounters);
if (!(pv = (void *)new BYTE[cbThisInstance]))
return(FALSE);
*pcbInstanceData += cbThisInstance;
g_rgppid[iInstance] = (PERF_INSTANCE_DEFINITION *)pv;
g_rgppid[iInstance]->ByteLength = sizeof(PERF_INSTANCE_DEFINITION) + cbName;
g_rgppid[iInstance]->ParentObjectTitleIndex = iFirstCounter;
g_rgppid[iInstance]->ParentObjectInstance = iFirstCounter;
g_rgppid[iInstance]->UniqueID = PERF_NO_UNIQUE_ID;
g_rgppid[iInstance]->NameOffset = sizeof(PERF_INSTANCE_DEFINITION);
g_rgppid[iInstance]->NameLength = cbName;
lstrcpyW((WORD *)(((char *)pv) + sizeof(PERF_INSTANCE_DEFINITION)), wszName);
return(TRUE);
}
extern "C" DWORD APIENTRY
SSOPerfOpen(LPWSTR lpDeviceNames)
{
LONG lRet = ERROR_SUCCESS;
char szKey[MAX_PATH];
HKEY hkey;
DWORD dwType;
DWORD cb;
DWORD iFirstCounter;
DWORD iFirstHelp;
SSOMETHOD *psm;
int iInstance = 0;
int cbInstanceData;
if (!g_fInited)
{
// count the number of methods (instances) we have
psm = g_rgssomethod;
while (psm->wszName)
{
g_cInstances++;
psm++;
}
if (g_pfnssoDynamic)
++g_cInstances; // one for the dynamic method
g_spd.potSSO.NumInstances = g_cInstances;
if (!FInitPerfSink())
{
return ERROR_NOT_ENOUGH_MEMORY;
}
// get the magic counter offset values from the registry
wsprintf(szKey, "SYSTEM\\CurrentControlSet\\Services\\%s\\Performance", g_szSSOProgID);
lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, KEY_READ | KEY_WRITE, &hkey);
if (ERROR_SUCCESS != lRet)
goto LBail;
cb = sizeof(DWORD);
lRet = RegQueryValueEx(hkey, "First Counter", 0, &dwType, (LPBYTE)&iFirstCounter, &cb);
if (ERROR_SUCCESS != lRet || cb != sizeof(DWORD))
{
RegCloseKey(hkey);
goto LBail;
}
lRet = RegQueryValueEx(hkey, "First Help", 0, &dwType, (LPBYTE)&iFirstHelp, &cb);
if (ERROR_SUCCESS != lRet || cb != sizeof(DWORD))
{
RegCloseKey(hkey);
goto LBail;
}
// add the magic counter offset values to all our indices
g_spd.potSSO.ObjectNameTitleIndex += iFirstCounter;
g_spd.potSSO.ObjectHelpTitleIndex += iFirstHelp;
g_spd.pcdInvokes.CounterNameTitleIndex += iFirstCounter;
g_spd.pcdInvokes.CounterHelpTitleIndex += iFirstHelp;
g_spd.pcdInvokesTotal.CounterNameTitleIndex += iFirstCounter;
g_spd.pcdInvokesTotal.CounterHelpTitleIndex += iFirstHelp;
g_spd.pcdGetNames.CounterNameTitleIndex += iFirstCounter;
g_spd.pcdGetNames.CounterHelpTitleIndex += iFirstHelp;
g_spd.pcdGetNamesTotal.CounterNameTitleIndex += iFirstCounter;
g_spd.pcdGetNamesTotal.CounterHelpTitleIndex += iFirstHelp;
g_spd.pcdLatency.CounterNameTitleIndex += iFirstCounter;
g_spd.pcdLatency.CounterHelpTitleIndex += iFirstHelp;
g_spd.pcdMaxLatency.CounterNameTitleIndex += iFirstCounter;
g_spd.pcdMaxLatency.CounterHelpTitleIndex += iFirstHelp;
RegCloseKey(hkey);
// build up an array of pointers to PERF_INSTANCE_DEFINITION structures,
// one per instance.
g_rgppid = new PERF_INSTANCE_DEFINITION *[g_cInstances];
if (!g_rgppid)
{
lRet = ERROR_NOT_ENOUGH_MEMORY;
goto LBail;
}
iInstance = 0;
cbInstanceData = 0;
// dynamic method
if (g_pfnssoDynamic)
{
if (!_FAddInstance(L"Dynamic Methods", &cbInstanceData, iInstance, iFirstCounter))
{
lRet = ERROR_NOT_ENOUGH_MEMORY;
goto LBail;
}
iInstance++;
}
// static methods
psm = g_rgssomethod;
while (psm->wszName)
{
if (!_FAddInstance(psm->wszName, &cbInstanceData, iInstance, iFirstCounter))
{
lRet = ERROR_NOT_ENOUGH_MEMORY;
goto LBail;
}
psm++;
iInstance++;
}
g_spd.potSSO.TotalByteLength += cbInstanceData;
g_fInited = TRUE;
}
LBail:
return lRet;
}
extern "C" DWORD APIENTRY
SSOPerfClose()
{
return ERROR_SUCCESS;
}
const WCHAR *g_wszGlobal = L"Global";
const WCHAR *g_wszForeign = L"Foreign";
const WCHAR *g_wszCostly = L"Costly";
BOOL
FWszStartsWith(LPWSTR wsz, LPCWSTR wszPrefix)
{
WCHAR *pwc;
WCHAR *pwcPrefix;
if (!wsz)
return FALSE;
pwc = wsz;
pwcPrefix = (WCHAR *)wszPrefix;
while (*pwc && *pwcPrefix)
{
if (*pwc++ != *pwcPrefix++)
return FALSE;
}
if (!*pwc && *pwcPrefix)
return FALSE;
return TRUE;
}
BOOL
FWszNumberListContains(LPWSTR wsz, DWORD dw)
{
WCHAR *pwc;
DWORD dwCur;
if (!wsz)
return FALSE;
pwc = wsz;
dwCur = 0;
while (*pwc)
{
if (*pwc >= (WCHAR)'0' && *pwc <= (WCHAR)'9')
{
dwCur *= 10;
dwCur += *pwc - (WCHAR)'0';
}
else if (*pwc == (WCHAR)' ')
{
if (dw == dwCur)
return TRUE;
dwCur = 0;
}
else
return FALSE; // if not digit or space, bail
pwc++;
}
if (dw == dwCur)
return TRUE;
return FALSE;
}
DWORD
DwAvgLatency(int iInstance)
{
DWORD *rgSamples = &g_rgTimeSamples[iInstance * cTimeSamplesMax];
int i;
int iMax = g_rgcTimeSamples[iInstance]; // only take this out of the array once,
// because it can change in the array
// during this loop!
__int64 i64Sum = 0;
if (iMax == 0)
return 0;
for (i = 0; i < iMax; i++)
i64Sum += rgSamples[i];
return (DWORD)(unsigned __int64)(i64Sum / (__int64)iMax);
}
// In order to specify what object(s) the perfmon client wants data for, the
// API passes information in in the form of lpwszValue. Yes, that's right,
// a string. This string can be:
// L"global" -- meaning give me all the objects you have
// L"foreign" -- give me data on another computer. we ignore this.
// L"costly" -- give me everything that's hard to compute. we ignore this also.
// Or, my favorite, it can be a list of object indices, provided as a space-
// separated list of stringized numbers which we're supposed to grovel through
// to find our ObjectNameTitleIndex value.
extern "C" DWORD APIENTRY
SSOPerfCollect(LPWSTR lpwszValue, LPVOID *lppData, LPDWORD lpcbBytes, LPDWORD lpcObjectTypes)
{
int iInstance;
BYTE *pb = (BYTE *)*lppData;
if (!g_fInited)
{
*lpcbBytes = 0;
*lpcObjectTypes = 0;
return ERROR_SUCCESS; // can't return any errors from this routine, whee
}
if (FWszStartsWith(lpwszValue, g_wszForeign) || FWszStartsWith(lpwszValue, g_wszCostly))
{
// we don't handle foreign requests, and i don't know what `costly' means
*lpcbBytes = 0;
*lpcObjectTypes = 0;
return ERROR_SUCCESS; // can't return any errors from this routine, whee
}
if (!FWszStartsWith(lpwszValue, g_wszGlobal))
{
// not a global request, so it better be a list of object indices. if ours isn't
// in the list, bail out.
if (!FWszNumberListContains(lpwszValue, g_spd.potSSO.ObjectNameTitleIndex))
{
*lpcbBytes = 0;
*lpcObjectTypes = 0;
return ERROR_SUCCESS; // can't return any errors from this routine, whee
}
}
if (*lpcbBytes < g_spd.potSSO.TotalByteLength)
{
*lpcbBytes = 0;
*lpcObjectTypes = 0;
return ERROR_MORE_DATA;
}
// okay, if we got to here, it means that perfmon really wants our data
// and that we have enough room to put it in. first, copy the SPD into
// the buffer.
CopyMemory(pb, &g_spd, sizeof(g_spd));
pb += sizeof(g_spd);
// then, for each instance, put in the PERF_INSTANCE_DEFINITION and all
// the counter data.
for (iInstance = 0; iInstance < g_cInstances; iInstance++)
{
CopyMemory(pb, g_rgppid[iInstance], g_rgppid[iInstance]->ByteLength);
pb += g_rgppid[iInstance]->ByteLength;
// this is the ByteLength of the PERF_COUNTER_BLOCK
*(DWORD *)pb = sizeof(DWORD) + (g_cCounters * sizeof(DWORD));
pb += sizeof(DWORD);
// first two counters are same value with different representations
*(DWORD *)pb = g_rgcInvokes[iInstance];
pb += sizeof(DWORD);
*(DWORD *)pb = g_rgcInvokes[iInstance];
pb += sizeof(DWORD);
// second two counters are same value with different representations
*(DWORD *)pb = g_rgcGetNames[iInstance];
pb += sizeof(DWORD);
*(DWORD *)pb = g_rgcGetNames[iInstance];
pb += sizeof(DWORD);
// next: avg latency. this one is medium-hard to compute, so we don't
// do it here.
*(DWORD *)pb = DwAvgLatency(iInstance);
pb += sizeof(DWORD);
// max latency
*(DWORD *)pb = g_rgctixLatencyMax[iInstance];
pb += sizeof(DWORD);
// add other counters here!
}
*lpcbBytes = DIFF(pb - (BYTE *)*lppData);
*lppData = pb;
*lpcObjectTypes = 1;
return ERROR_SUCCESS;
}
const char *g_szMMFFormat = "%s.PerfData";
HANDLE g_hmmf = NULL;
// the shared MMF has the following chunks, in order:
// list of invokes counters (one DWORD per instance)
// list of getnames counters (one DWORD per instance)
// list of max latency values (one DWORD per instance)
// list of counts of time samples (one DWORD per instance)
// list of indices of current time samples (one DWORD per instance)
// list of arrays of time samples (one array of cTimeSamplesMax DWORDS per instance)
HANDLE
HmmfShared()
{
char szMMF[MAX_PATH];
SECURITY_DESCRIPTOR sd;
SECURITY_ATTRIBUTES sa;
int cbChunk = g_cInstances * sizeof(DWORD);
// 5 in next line is the number of one-dword-per-instance things we have
int cbMMF = (5 * cbChunk) + (cTimeSamplesMax * cbChunk);
if (g_hmmf)
return g_hmmf;
wsprintf(szMMF, g_szMMFFormat, g_szSSOProgID);
g_hmmf = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, szMMF);
if (!g_hmmf)
{
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, TRUE, (PACL)NULL, FALSE);
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = &sd;
sa.bInheritHandle = FALSE;
g_hmmf = CreateFileMapping(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0, cbMMF, szMMF);
}
return g_hmmf;
}
BOOL
FInitPerfSink()
{
char *pch;
int cbChunk = (g_cInstances * sizeof(DWORD));
if (g_rgcInvokes)
return TRUE;
if (!HmmfShared())
return FALSE;
pch = (char *)MapViewOfFile(g_hmmf,
FILE_MAP_ALL_ACCESS,
0, 0, 0);
if (!pch)
return FALSE;
g_rgcInvokes = (int *)pch;
pch += cbChunk;
g_rgcGetNames = (int *)pch;
pch += cbChunk;
g_rgctixLatencyMax = (DWORD *)pch;
pch += cbChunk;
g_rgcTimeSamples = (int *)pch;
pch += cbChunk;
g_rgiTimeSampleCurrent = (int *)pch;
pch += cbChunk;
g_rgTimeSamples = (DWORD *)pch;
return TRUE;
}
void
InitPerfSource()
{
SSOMETHOD *psm;
char *pch;
int cbChunk;
int cbMMF;
if (g_rgcInvokes)
return;
g_cInstances = (g_pfnssoDynamic ? 1 : 0);
psm = g_rgssomethod;
while (psm->wszName)
{
psm->iMethod = g_cInstances++;
psm++;
}
if (!HmmfShared())
return;
cbChunk = (g_cInstances * sizeof(DWORD));
cbMMF = (4 * cbChunk) + (cTimeSamplesMax * cbChunk);
pch = (char *)MapViewOfFile(g_hmmf,
FILE_MAP_ALL_ACCESS,
0, 0, 0);
if (!pch)
return;
g_rgcInvokes = (int *)pch;
pch += cbChunk;
g_rgcGetNames = (int *)pch;
pch += cbChunk;
g_rgctixLatencyMax = (DWORD *)pch;
pch += cbChunk;
g_rgcTimeSamples = (int *)pch;
pch += cbChunk;
g_rgiTimeSampleCurrent = (int *)pch;
pch += cbChunk;
g_rgTimeSamples = (DWORD *)pch;
FillMemory(g_rgcInvokes, g_cInstances * g_cCounters * sizeof(DWORD), 0);
}
void
FreePerfGunk()
{
if (g_rgcInvokes)
UnmapViewOfFile(g_rgcInvokes);
if (g_hmmf)
CloseHandle(g_hmmf);
}