435 lines
12 KiB
C++
435 lines
12 KiB
C++
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (c) 1998, Microsoft Corp. All rights reserved.
|
|
//
|
|
// FILE
|
|
//
|
|
// perflib.cpp
|
|
//
|
|
// SYNOPSIS
|
|
//
|
|
// Defines classes for implementing a PerfMon DLL.
|
|
//
|
|
// MODIFICATION HISTORY
|
|
//
|
|
// 09/06/1998 Original version.
|
|
// 10/19/1998 Throw LONG's on error.
|
|
// 03/18/1999 Data buffer must be 8-byte aligned.
|
|
// 05/13/1999 Fix offset to first help text.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <ias.h>
|
|
#include <align.h>
|
|
#include <perflib.h>
|
|
|
|
/////////
|
|
// Application offsets into the string title database.
|
|
/////////
|
|
DWORD theFirstCounter;
|
|
DWORD theFirstHelp;
|
|
|
|
/////////
|
|
// Reads the name offsets for a given application.
|
|
/////////
|
|
LONG GetCounterOffsets(PCWSTR appName) throw ()
|
|
{
|
|
// Build the name of the performance key.
|
|
WCHAR keyPath[MAX_PATH + 1] = L"SYSTEM\\CurrentControlSet\\Services\\";
|
|
wcscat(keyPath, appName);
|
|
wcscat(keyPath, L"\\Performance");
|
|
|
|
// Open the performance key.
|
|
LONG status;
|
|
HKEY hKey;
|
|
status = RegOpenKeyExW(
|
|
HKEY_LOCAL_MACHINE,
|
|
keyPath,
|
|
0,
|
|
KEY_READ,
|
|
&hKey
|
|
);
|
|
if (status != NO_ERROR) { return status; }
|
|
|
|
|
|
// Get the first counter offset.
|
|
DWORD type, cbData = sizeof(DWORD);
|
|
status = RegQueryValueExW(
|
|
hKey,
|
|
L"First Counter",
|
|
0,
|
|
&type,
|
|
(PBYTE)&theFirstCounter,
|
|
&cbData
|
|
);
|
|
if (status != ERROR_SUCCESS) { goto close_key; };
|
|
|
|
// Make sure it's a DWORD.
|
|
if (type != REG_DWORD || cbData != sizeof(DWORD))
|
|
{
|
|
status = ERROR_BADKEY;
|
|
goto close_key;
|
|
}
|
|
|
|
// Get the first help offset.
|
|
status = RegQueryValueExW(
|
|
hKey,
|
|
L"First Help",
|
|
0,
|
|
&type,
|
|
(PBYTE)&theFirstHelp,
|
|
&cbData
|
|
);
|
|
if (status != ERROR_SUCCESS) { goto close_key; };
|
|
|
|
// Make sure it's a DWORD.
|
|
if (type != REG_DWORD || cbData != sizeof(DWORD))
|
|
{
|
|
status = ERROR_BADKEY;
|
|
goto close_key;
|
|
}
|
|
|
|
close_key:
|
|
RegCloseKey(hKey);
|
|
|
|
return status;
|
|
}
|
|
|
|
PBYTE PerfCounterBlock::collect(PBYTE first, PBYTE last)
|
|
{
|
|
PBYTE retval = first + pcb.ByteLength;
|
|
|
|
if (retval > last) { throw ERROR_MORE_DATA; }
|
|
|
|
memcpy(first, this, pcb.ByteLength);
|
|
|
|
return retval;
|
|
}
|
|
|
|
PerfCounterBlock* PerfCounterBlock::create(DWORD numDWORDs)
|
|
{
|
|
// Compute various lengths.
|
|
DWORD cntrLength = sizeof(DWORD) * numDWORDs;
|
|
DWORD byteLength = sizeof(PERF_COUNTER_BLOCK) + cntrLength;
|
|
|
|
// Allocate enough extra space for the counters.
|
|
PerfCounterBlock* pcb = new (operator new(byteLength)) PerfCounterBlock;
|
|
|
|
// Initialize the PERF_COUNTER_BLOCK.
|
|
pcb->pcb.ByteLength = byteLength;
|
|
|
|
// Initialize the counters.
|
|
memset(pcb->counters, 0, cntrLength);
|
|
|
|
return pcb;
|
|
}
|
|
|
|
PBYTE PerfInstanceDefinition::collect(PBYTE first, PBYTE last)
|
|
{
|
|
PBYTE retval = first + pid.ByteLength;
|
|
|
|
if (retval > last) { throw ERROR_MORE_DATA; }
|
|
|
|
memcpy(first, this, pid.ByteLength);
|
|
|
|
return retval;
|
|
}
|
|
|
|
PerfInstanceDefinition* PerfInstanceDefinition::create(
|
|
PCWSTR name,
|
|
LONG uniqueID
|
|
)
|
|
{
|
|
// Compute various lengths.
|
|
DWORD nameLength = name ? (wcslen(name) + 1) * sizeof(WCHAR) : 0;
|
|
DWORD byteLength = sizeof(PERF_INSTANCE_DEFINITION) + nameLength;
|
|
|
|
// Keep everything DWORD aligned.
|
|
byteLength = ROUND_UP_COUNT(byteLength, ALIGN_DWORD);
|
|
|
|
// Allocate enough extra space for the name.
|
|
PerfInstanceDefinition* pid = new (operator new(byteLength))
|
|
PerfInstanceDefinition;
|
|
|
|
// Initialize the PERF_INSTANCE_DEFINITION.
|
|
pid->pid.ByteLength = byteLength;
|
|
pid->pid.ParentObjectTitleIndex = 0;
|
|
pid->pid.ParentObjectInstance = 0;
|
|
pid->pid.UniqueID = uniqueID;
|
|
pid->pid.NameOffset = sizeof(PERF_INSTANCE_DEFINITION);
|
|
pid->pid.NameLength = nameLength;
|
|
|
|
// Initialize the name.
|
|
memcpy(pid->name, name, nameLength);
|
|
|
|
return pid;
|
|
}
|
|
|
|
PBYTE PerfInstance::collect(PBYTE first, PBYTE last)
|
|
{
|
|
return pcb->collect((pid.get() ? pid->collect(first, last) : first), last);
|
|
}
|
|
|
|
PerfObjectType::~PerfObjectType() throw ()
|
|
{
|
|
for (MyVec::iterator i = instances.begin(); i != instances.end(); ++i)
|
|
{
|
|
delete *i;
|
|
}
|
|
}
|
|
|
|
void PerfObjectType::clear() throw ()
|
|
{
|
|
// Never clear the default instance.
|
|
if (pot.NumInstances != -1)
|
|
{
|
|
for (MyVec::iterator i = instances.begin(); i != instances.end(); ++i)
|
|
{
|
|
delete *i;
|
|
}
|
|
|
|
instances.clear();
|
|
}
|
|
}
|
|
|
|
void PerfObjectType::addInstance(PCWSTR name, LONG uniqueID)
|
|
{
|
|
// Resize first. If we threw an exception in push_back, we'd leak.
|
|
instances.reserve(size() + 1);
|
|
|
|
instances.push_back(new PerfInstance(name, uniqueID, numDWORDs));
|
|
}
|
|
|
|
PBYTE PerfObjectType::collect(PBYTE first, PBYTE last)
|
|
{
|
|
// Reserve enough room for the type definition.
|
|
PBYTE retval = first + pot.DefinitionLength;
|
|
if (retval > last) { throw ERROR_MORE_DATA; }
|
|
|
|
// Give the user a chance to fill-in the data.
|
|
if (dataSource) { dataSource(*this); }
|
|
|
|
if (pot.NumInstances == -1)
|
|
{
|
|
// We always have exactly one instance.
|
|
retval = at(0).collect(retval, last);
|
|
}
|
|
else if (instances.empty())
|
|
{
|
|
// If we're empty, then we right one instance's worth of zeros.
|
|
// Otherwise, PerfMon.exe has a tendency to display garbage.
|
|
|
|
DWORD nbyte = sizeof(PERF_INSTANCE_DEFINITION) +
|
|
sizeof(PERF_COUNTER_BLOCK) +
|
|
numDWORDs * sizeof(DWORD);
|
|
|
|
if (retval + nbyte > last) { throw ERROR_MORE_DATA; }
|
|
|
|
memset(retval, 0, nbyte);
|
|
retval += nbyte;
|
|
|
|
pot.NumInstances = 0;
|
|
}
|
|
else
|
|
{
|
|
// iterate through and collect all instances.
|
|
for (size_type i = 0; i < size(); ++i)
|
|
{
|
|
retval = at(i).collect(retval, last);
|
|
}
|
|
|
|
pot.NumInstances = (DWORD)size();
|
|
}
|
|
|
|
// Now that we've collected all the data, we can finish the definition ...
|
|
retval = (PBYTE)ROUND_UP_POINTER(retval, ALIGN_QUAD);
|
|
pot.TotalByteLength = retval - first;
|
|
QueryPerformanceCounter(&pot.PerfTime);
|
|
|
|
// ... and copy in the data.
|
|
memcpy(first, &pot, pot.DefinitionLength);
|
|
|
|
return retval;
|
|
}
|
|
|
|
PerfObjectType* PerfObjectType::create(const PerfObjectTypeDef& def)
|
|
{
|
|
// Allocate a new object.
|
|
size_t counterLength = def.numCounters * sizeof(PERF_COUNTER_DEFINITION);
|
|
size_t nbyte = sizeof(PerfObjectType) + counterLength;
|
|
std::auto_ptr<PerfObjectType> po(new (operator new(nbyte)) PerfObjectType);
|
|
|
|
// Save the data source.
|
|
po->dataSource = def.dataSource;
|
|
|
|
// Fill in the PERF_COUNTER_DEFINITION structs first since we also
|
|
// need to compute the object detail level.
|
|
DWORD detailLevel = PERF_DETAIL_WIZARD;
|
|
PERF_COUNTER_DEFINITION* dst = po->pcd;
|
|
PerfCounterDef* src = def.counters;
|
|
DWORD offset = sizeof(PERF_COUNTER_BLOCK);
|
|
|
|
for (DWORD i = 0; i < def.numCounters; ++i, ++dst, ++src)
|
|
{
|
|
dst->ByteLength = sizeof(PERF_COUNTER_DEFINITION);
|
|
dst->CounterNameTitleIndex = src->nameTitleOffset + theFirstCounter;
|
|
dst->CounterNameTitle = 0;
|
|
dst->CounterHelpTitleIndex = src->nameTitleOffset + theFirstHelp;
|
|
dst->CounterHelpTitle = 0;
|
|
dst->DefaultScale = src->defaultScale;
|
|
dst->DetailLevel = src->detailLevel;
|
|
dst->CounterType = src->counterType;
|
|
dst->CounterOffset = offset;
|
|
|
|
// Compute the counter size.
|
|
switch (dst->CounterOffset & 0x300)
|
|
{
|
|
case PERF_SIZE_DWORD:
|
|
dst->CounterSize = sizeof(DWORD);
|
|
break;
|
|
|
|
case PERF_SIZE_LARGE:
|
|
dst->CounterSize = sizeof(LARGE_INTEGER);
|
|
break;
|
|
|
|
default:
|
|
dst->CounterSize = 0;
|
|
}
|
|
|
|
// Update the offset based on the size.
|
|
offset += dst->CounterSize;
|
|
|
|
// The object detail level is the minimum counter detail level.
|
|
if (dst->DetailLevel < detailLevel)
|
|
{
|
|
detailLevel = dst->DetailLevel;
|
|
}
|
|
}
|
|
|
|
// Calculate the number of DWORD's of counter data.
|
|
po->numDWORDs = (offset - sizeof(PERF_COUNTER_BLOCK)) / sizeof(DWORD);
|
|
|
|
// Fill in the PERF_OBJECT_TYPE struct.
|
|
po->pot.DefinitionLength = sizeof(PERF_OBJECT_TYPE) + counterLength;
|
|
po->pot.HeaderLength = sizeof(PERF_OBJECT_TYPE);
|
|
po->pot.ObjectNameTitleIndex = def.nameTitleOffset + theFirstCounter;
|
|
po->pot.ObjectNameTitle = 0;
|
|
po->pot.ObjectHelpTitleIndex = def.nameTitleOffset + theFirstHelp;
|
|
po->pot.ObjectHelpTitle = 0;
|
|
po->pot.DetailLevel = detailLevel;
|
|
po->pot.NumCounters = def.numCounters;
|
|
po->pot.DefaultCounter = def.defaultCounter;
|
|
po->pot.NumInstances = 0;
|
|
po->pot.CodePage = 0;
|
|
QueryPerformanceFrequency(&(po->pot.PerfFreq));
|
|
|
|
// If it doesn't support multiple instances, then it must have exactly one.
|
|
if (!def.multipleInstances)
|
|
{
|
|
po->pot.NumInstances = -1;
|
|
po->instances.reserve(1);
|
|
po->instances.push_back(new PerfInstance(po->numDWORDs));
|
|
}
|
|
|
|
return po.release();
|
|
}
|
|
|
|
PerfCollector::~PerfCollector() throw ()
|
|
{
|
|
close();
|
|
}
|
|
|
|
void PerfCollector::clear() throw ()
|
|
{
|
|
for (PerfObjectType** i = types; *i; ++i)
|
|
{
|
|
(*i)->clear();
|
|
}
|
|
}
|
|
|
|
void PerfCollector::open(const PerfCollectorDef& def)
|
|
{
|
|
// Read the registry.
|
|
LONG success = GetCounterOffsets(def.name);
|
|
if (success != NO_ERROR) { throw success; }
|
|
|
|
// Allocate a null terminated array to hold the object types.
|
|
DWORD len = def.numTypes + 1;
|
|
types = new PerfObjectType*[len];
|
|
memset(types, 0, sizeof(PerfObjectType*) * len);
|
|
|
|
// Create the various object types.
|
|
for (DWORD i = 0; i < def.numTypes; ++i)
|
|
{
|
|
types[i] = PerfObjectType::create(def.types[i]);
|
|
}
|
|
}
|
|
|
|
void PerfCollector::collect(
|
|
PCWSTR values,
|
|
PVOID& data,
|
|
DWORD& numBytes,
|
|
DWORD& numTypes
|
|
)
|
|
{
|
|
PBYTE cursor = (PBYTE)data;
|
|
PBYTE last = cursor + numBytes;
|
|
|
|
numBytes = 0;
|
|
numTypes = 0;
|
|
|
|
if (values == NULL || *values == L'\0' || !wcscmp(values, L"Global"))
|
|
{
|
|
// For global we get everything.
|
|
for (PerfObjectType** i = types; *i; ++i)
|
|
{
|
|
cursor = (*i)->collect(cursor, last);
|
|
++numTypes;
|
|
}
|
|
}
|
|
else if (wcsncmp(values, L"Foreign", 7) && wcscmp(values, L"Costly"))
|
|
{
|
|
// It's not Global, Foreign, or Costly, so we parse the tokens and
|
|
// convert them to title indices.
|
|
|
|
PWSTR endptr;
|
|
PCWSTR nptr = values;
|
|
|
|
ULONG index = wcstoul(nptr, &endptr, 10);
|
|
|
|
while (endptr != nptr)
|
|
{
|
|
// We got a valid index, so find the object ...
|
|
for (PerfObjectType** i = types; *i; ++i)
|
|
{
|
|
if ((*i)->getIndex() == index)
|
|
{
|
|
// ... and collect the data.
|
|
cursor = (*i)->collect(cursor, last);
|
|
++numTypes;
|
|
break;
|
|
}
|
|
}
|
|
|
|
index = wcstoul(nptr = endptr, &endptr, 10);
|
|
}
|
|
}
|
|
|
|
numBytes = cursor - (PBYTE)data;
|
|
data = cursor;
|
|
}
|
|
|
|
void PerfCollector::close() throw ()
|
|
{
|
|
if (types)
|
|
{
|
|
for (PerfObjectType** i = types ; *i; ++i)
|
|
{
|
|
delete *i;
|
|
}
|
|
|
|
delete[] types;
|
|
types = NULL;
|
|
}
|
|
}
|