Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

798 lines
26 KiB

/*++
Copyright (c) 1996 Microsoft Corporation
Module Name:
perfcpu.c
Abstract:
This file implements an Performance Object that presents
System Processor performance object data
Created:
Bob Watson 22-Oct-1996
Revision History
--*/
//
// Include Files
//
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <assert.h>
#include <winperf.h>
#include <ntprfctr.h>
#define PERF_HEAP hLibHeap
#include <perfutil.h>
#include "perfos.h"
#include "perfosmc.h"
#include "datacpu.h"
DWORD dwCpuOpenCount = 0; // count of "Open" threads
// variables local to this module.
SYSTEM_INTERRUPT_INFORMATION *pProcessorInterruptInformation = NULL;
DWORD dwInterruptInfoBufferSize = 0;
SYSTEM_PROCESSOR_IDLE_INFORMATION *pProcessorIdleInformation = NULL;
DWORD dwProcessorIdleBufferSize = 0;
UCHAR *pProcessorBuffer = NULL;
ULONG ProcessorBufSize = 0;
BOOL bPerfCpuUseIdleData = FALSE;
BOOL bPerfCpuIdleDataTested = FALSE;
DWORD APIENTRY
OpenProcessorObject (
LPWSTR lpDeviceNames
)
/*++
Routine Description:
This routine will initialize the data structures used to pass
data back to the registry
Arguments:
Pointer to object ID of each device to be opened (PerfGen)
Return Value:
None.
--*/
{
DWORD status = ERROR_SUCCESS;
//
// Since WINLOGON is multi-threaded and will call this routine in
// order to service remote performance queries, this library
// must keep track of how many times it has been opened (i.e.
// how many threads have accessed it). the registry routines will
// limit access to the initialization routine to only one thread
// at a time so synchronization (i.e. reentrancy) should not be
// a problem
//
UNREFERENCED_PARAMETER (lpDeviceNames);
if (!dwCpuOpenCount) {
dwInterruptInfoBufferSize = (ULONG)BasicInfo.NumberOfProcessors *
sizeof (SYSTEM_INTERRUPT_INFORMATION);
pProcessorInterruptInformation = ALLOCMEM (dwInterruptInfoBufferSize);
if (pProcessorInterruptInformation == NULL) {
status = ERROR_OUTOFMEMORY;
goto OpenExitPoint;
}
ProcessorBufSize = BasicInfo.NumberOfProcessors *
sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION);
pProcessorBuffer = ALLOCMEM(ProcessorBufSize);
if (pProcessorBuffer == NULL) {
status = ERROR_OUTOFMEMORY;
goto OpenExitPoint;
}
dwProcessorIdleBufferSize = BasicInfo.NumberOfProcessors *
sizeof(SYSTEM_PROCESSOR_IDLE_INFORMATION);
pProcessorIdleInformation = ALLOCMEM(dwProcessorIdleBufferSize);
if (pProcessorIdleInformation == NULL) {
status = ERROR_OUTOFMEMORY;
goto OpenExitPoint;
}
}
dwCpuOpenCount++; // increment OPEN counter
status = ERROR_SUCCESS; // for successful exit
OpenExitPoint:
if (status == ERROR_OUTOFMEMORY) {
if (pProcessorInterruptInformation) {
FREEMEM (pProcessorInterruptInformation);
pProcessorInterruptInformation = NULL;
}
if (pProcessorBuffer) {
FREEMEM (pProcessorBuffer);
pProcessorBuffer = NULL;
}
dwInterruptInfoBufferSize = 0;
ProcessorBufSize = 0;
dwProcessorIdleBufferSize = 0;
}
return status;
}
DWORD APIENTRY
CollectProcessorObjectData (
IN OUT LPVOID *lppData,
IN OUT LPDWORD lpcbTotalBytes,
IN OUT LPDWORD lpNumObjectTypes
)
/*++
Routine Description:
This routine will return the data for the processor object
Arguments:
IN OUT LPVOID *lppData
IN: pointer to the address of the buffer to receive the completed
PerfDataBlock and subordinate structures. This routine will
append its data to the buffer starting at the point referenced
by *lppData.
OUT: points to the first byte after the data structure added by this
routine. This routine updated the value at lppdata after appending
its data.
IN OUT LPDWORD lpcbTotalBytes
IN: the address of the DWORD that tells the size in bytes of the
buffer referenced by the lppData argument
OUT: the number of bytes added by this routine is writted to the
DWORD pointed to by this argument
IN OUT LPDWORD NumObjectTypes
IN: the address of the DWORD to receive the number of objects added
by this routine
OUT: the number of objects added by this routine is writted to the
DWORD pointed to by this argument
Returns:
0 if successful, else Win 32 error code of failure
--*/
{
LONG lReturn = ERROR_SUCCESS;
DWORD TotalLen; // Length of the total return block
DWORD dwBufferSize;
DWORD dwReturnedBufferSize = 0;
PPROCESSOR_DATA_DEFINITION pProcessorDataDefinition = NULL;
PPROCESSOR_COUNTER_DATA pPCD;
PEX_PROCESSOR_DATA_DEFINITION pExProcessorDataDefinition = NULL;
PEX_PROCESSOR_COUNTER_DATA pExPCD;
PROCESSOR_COUNTER_DATA pcdTotalData;
EX_PROCESSOR_COUNTER_DATA pexcdTotalData;
PERF_INSTANCE_DEFINITION *pPerfInstanceDefinition;
ULONG CurProc;
UNICODE_STRING ProcessorName;
WCHAR ProcessorNameBuffer[512];
SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *pProcessorInformation = NULL;
SYSTEM_PROCESSOR_IDLE_INFORMATION *pProcIdleInformation = NULL;
SYSTEM_INTERRUPT_INFORMATION *pThisProcessorInterruptInformation = NULL;
NTSTATUS ntStatus;
//
// Check for sufficient space for processor data
//
#ifdef DBG
STARTTIMING;
#endif
// check for QUADWORD alignment of incoming pointer
assert (((ULONG_PTR)(*lppData) & 0x00000007) == 0);
if (!bPerfCpuIdleDataTested) {
// call this function once to see if this info is available from the system
//
// get system idle information by processor
//
dwBufferSize = dwProcessorIdleBufferSize;
ntStatus = NtQuerySystemInformation(
SystemProcessorIdleInformation,
pProcessorIdleInformation,
dwBufferSize,
&dwReturnedBufferSize
);
if (NT_SUCCESS(ntStatus)) {
bPerfCpuUseIdleData = TRUE;
} else {
memset (pProcessorIdleInformation, 0, dwProcessorIdleBufferSize);
}
bPerfCpuIdleDataTested = TRUE;
}
if (bPerfCpuUseIdleData) {
pExProcessorDataDefinition = (EX_PROCESSOR_DATA_DEFINITION *) *lppData;
TotalLen =
sizeof(EX_PROCESSOR_DATA_DEFINITION) + // object def header
((sizeof (PERF_INSTANCE_DEFINITION) + // plus an instance for
((MAX_INSTANCE_NAME + 1) * sizeof(WCHAR)) +
sizeof (PROCESSOR_COUNTER_DATA)) * // each processor and
(BasicInfo.NumberOfProcessors + 1)); // the "total" instance
TotalLen = QWORD_MULTIPLE(TotalLen);
if ( *lpcbTotalBytes < TotalLen ) {
lReturn = ERROR_MORE_DATA;
*lpcbTotalBytes = (DWORD) 0;
*lpNumObjectTypes = (DWORD) 0;
goto COLLECT_BAIL_OUT;
}
} else {
pProcessorDataDefinition = (PROCESSOR_DATA_DEFINITION *) *lppData;
TotalLen =
sizeof(PROCESSOR_DATA_DEFINITION) + // object def header
((sizeof (PERF_INSTANCE_DEFINITION) + // plus an instance for
((MAX_INSTANCE_NAME + 1) * sizeof(WCHAR)) +
sizeof (PROCESSOR_COUNTER_DATA)) * // each processor and
(BasicInfo.NumberOfProcessors + 1)); // the "total" instance
if ( *lpcbTotalBytes < TotalLen ) {
lReturn = ERROR_MORE_DATA;
*lpcbTotalBytes = (DWORD) 0;
*lpNumObjectTypes = (DWORD) 0;
goto COLLECT_BAIL_OUT;
}
}
//
// Get processor data from system
//
if ( ProcessorBufSize ) {
ntStatus = NtQuerySystemInformation(
SystemProcessorPerformanceInformation,
pProcessorBuffer,
ProcessorBufSize,
&dwReturnedBufferSize
);
if (!NT_SUCCESS(ntStatus) && (hEventLog != NULL)) {
// clear buffer & log error
ReportEvent (hEventLog,
EVENTLOG_WARNING_TYPE,
0,
PERFOS_UNABLE_QUERY_PROCSSOR_INFO,
NULL,
0,
sizeof(DWORD),
NULL,
(LPVOID)&ntStatus);
memset (pProcessorBuffer, 0, ProcessorBufSize);
}
#ifdef DBG
ENDTIMING (("PERFCPU: %d takes %I64u ms\n", __LINE__, diff));
#endif
}
//
// get system interrupt information by processor
//
dwInterruptInfoBufferSize = (ULONG)BasicInfo.NumberOfProcessors *
sizeof (SYSTEM_INTERRUPT_INFORMATION);
ntStatus = NtQuerySystemInformation(
SystemInterruptInformation,
pProcessorInterruptInformation,
dwInterruptInfoBufferSize,
&dwReturnedBufferSize
);
if (!NT_SUCCESS(ntStatus) && (hEventLog != NULL)) {
// clear buffer & log error
ReportEvent (hEventLog,
EVENTLOG_WARNING_TYPE,
0,
PERFOS_UNABLE_QUERY_INTERRUPT_INFO,
NULL,
0,
sizeof(DWORD),
NULL,
(LPVOID)&ntStatus);
memset (pProcessorInterruptInformation, 0,
(BasicInfo.NumberOfProcessors *
sizeof (SYSTEM_INTERRUPT_INFORMATION)));
}
#ifdef DBG
ENDTIMING (("PERFCPU: %d takes %I64u ms\n", __LINE__, diff));
#endif
if (bPerfCpuUseIdleData) {
//
// get system idle information by processor
//
dwBufferSize = dwProcessorIdleBufferSize;
ntStatus = NtQuerySystemInformation(
SystemProcessorIdleInformation,
pProcessorIdleInformation,
dwBufferSize,
&dwReturnedBufferSize
);
if (!NT_SUCCESS(ntStatus) && (hEventLog != NULL)) {
// it worked once before or this flag wouldn't be set
// so report the error.
ReportEvent (hEventLog,
EVENTLOG_WARNING_TYPE,
0,
PERFOS_UNABLE_QUERY_IDLE_INFO,
NULL,
0,
sizeof(DWORD),
NULL,
(LPVOID)&ntStatus);
memset (pProcessorIdleInformation, 0, dwProcessorIdleBufferSize);
}
#ifdef DBG
ENDTIMING (("PERFCPU: %d takes %I64u ms\n", __LINE__, diff));
#endif
} else {
memset (pProcessorIdleInformation, 0, dwProcessorIdleBufferSize);
}
// clear the pointers to trap unassigned ones below
pPCD = NULL;
pExPCD = NULL;
if ((!bPerfCpuUseIdleData) && (pProcessorDataDefinition != NULL)) {
// use the original format of the structure
// clear the "Total" instance
memset (&pcdTotalData, 0, sizeof (pcdTotalData));
// Define processor data block
//
memcpy (pProcessorDataDefinition,
&ProcessorDataDefinition,
sizeof(PROCESSOR_DATA_DEFINITION));
pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)
&pProcessorDataDefinition[1];
pProcessorInformation = (SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *)
pProcessorBuffer;
// point to the first processor in the returned array of interrupt
// information. data is returned as an array of structures.
pThisProcessorInterruptInformation = pProcessorInterruptInformation;
pProcIdleInformation = pProcessorIdleInformation;
for ( CurProc = 0;
CurProc < (ULONG) BasicInfo.NumberOfProcessors;
CurProc++ ) {
//
// Define processor instance 0;
// More could be defined like this
//
ProcessorName.Length = 0;
ProcessorName.MaximumLength = sizeof(ProcessorNameBuffer);
ProcessorName.Buffer = ProcessorNameBuffer;
ProcessorNameBuffer[0] = 0;
RtlIntegerToUnicodeString(CurProc, 10, &ProcessorName);
MonBuildInstanceDefinition(pPerfInstanceDefinition,
(PVOID *) &pPCD,
0,
0,
(DWORD)-1,
ProcessorNameBuffer);
// test for Quadword Alignment
assert (((ULONG_PTR)(pPCD) & 0x00000007) == 0);
//
// Format and collect processor data. While doing so,
// accumulate totals in the System Object Type data block.
// Pointers to these were initialized in QuerySystemData.
//
pPCD->CounterBlock.ByteLength = QWORD_MULTIPLE(sizeof (PROCESSOR_COUNTER_DATA));
pcdTotalData.ProcessorTime +=
pPCD->ProcessorTime =
pProcessorInformation->IdleTime.QuadPart;
pcdTotalData.UserTime +=
pPCD->UserTime =
pProcessorInformation->UserTime.QuadPart;
// kernel time is total kernel time less the time spent in the
// idle thread for that processor
pcdTotalData.KernelTime +=
pPCD->KernelTime =
pProcessorInformation->KernelTime.QuadPart -
pPCD->ProcessorTime;
pcdTotalData.Interrupts +=
pPCD->Interrupts = pProcessorInformation->InterruptCount;
pcdTotalData.DpcTime +=
pPCD->DpcTime = pProcessorInformation->DpcTime.QuadPart;
pcdTotalData.InterruptTime +=
pPCD->InterruptTime =
pProcessorInformation->InterruptTime.QuadPart;
pcdTotalData.DpcCountRate +=
pPCD->DpcCountRate =
pThisProcessorInterruptInformation->DpcCount;
pcdTotalData.DpcRate +=
pPCD->DpcRate =
pThisProcessorInterruptInformation->DpcRate;
//
// Advance to next processor
//
pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)&pPCD[1];
// point to next processor's data in return array(s)
pProcessorInformation++;
pThisProcessorInterruptInformation++;
pProcIdleInformation++;
}
// do the total instance now
ProcessorName.Length = (WORD)((lstrlenW (wszTotal) + 1) * sizeof (WCHAR));
ProcessorName.MaximumLength = (WORD)(sizeof (ProcessorNameBuffer));
lstrcpyW (ProcessorNameBuffer, wszTotal);
ProcessorName.Buffer = ProcessorNameBuffer;
MonBuildInstanceDefinition(pPerfInstanceDefinition,
(PVOID *) &pPCD,
0,
0,
(DWORD)-1,
ProcessorNameBuffer);
// define the size
pcdTotalData.CounterBlock.ByteLength = QWORD_MULTIPLE(sizeof (PROCESSOR_COUNTER_DATA));
// adjust the total values of the time fields to the number of
// processors to "normalize" the values
pcdTotalData.ProcessorTime /= BasicInfo.NumberOfProcessors;
pcdTotalData.UserTime /= BasicInfo.NumberOfProcessors;
pcdTotalData.KernelTime /= BasicInfo.NumberOfProcessors;
pcdTotalData.DpcTime /= BasicInfo.NumberOfProcessors;
pcdTotalData.InterruptTime /= BasicInfo.NumberOfProcessors;
// these fields are OK as totals
//
// pcdTotalData.Interrupts
// pcdTotalData.DpcCountRate
// pcdTotalData.DpcRate
// copy total data to buffer
memcpy (pPCD, &pcdTotalData, sizeof (pcdTotalData));
// adjust local buffer pointer
pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)&pPCD[1];
//
// Now we know how large an area we used for the
// processor definition, so we can update the offset
// to the next object definition
//
pProcessorDataDefinition->ProcessorObjectType.NumInstances =
BasicInfo.NumberOfProcessors + 1;
*lppData = (LPVOID)pPerfInstanceDefinition;
// round up buffer to the nearest QUAD WORD
*lppData = ALIGN_ON_QWORD (*lppData);
*lpcbTotalBytes =
pProcessorDataDefinition->ProcessorObjectType.TotalByteLength =
QWORD_MULTIPLE(
(DWORD)((LPBYTE) pPerfInstanceDefinition -
(LPBYTE) pProcessorDataDefinition));
}
if ((bPerfCpuUseIdleData) && (pExProcessorDataDefinition != NULL)) {
// use the new extended structure
// clear the "Total" instance
memset (&pexcdTotalData, 0, sizeof (pexcdTotalData));
// Define processor data block
//
memcpy (pExProcessorDataDefinition,
&ExProcessorDataDefinition,
sizeof(EX_PROCESSOR_DATA_DEFINITION));
pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)
&pExProcessorDataDefinition[1];
pProcessorInformation = (SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *)
pProcessorBuffer;
// point to the first processor in the returned array of interrupt
// information. data is returned as an array of structures.
pThisProcessorInterruptInformation = pProcessorInterruptInformation;
pProcIdleInformation = pProcessorIdleInformation;
for ( CurProc = 0;
CurProc < (ULONG) BasicInfo.NumberOfProcessors;
CurProc++ ) {
//
// Define processor instance 0;
// More could be defined like this
//
ProcessorName.Length = 0;
ProcessorName.MaximumLength = sizeof(ProcessorNameBuffer);
ProcessorName.Buffer = ProcessorNameBuffer;
ProcessorNameBuffer[0] = 0;
RtlIntegerToUnicodeString(CurProc, 10, &ProcessorName);
MonBuildInstanceDefinition(pPerfInstanceDefinition,
(PVOID *) &pExPCD,
0,
0,
(DWORD)-1,
ProcessorNameBuffer);
// test for Quadword Alignment
assert (((ULONG_PTR)(pExPCD) & 0x00000007) == 0);
//
// Format and collect processor data. While doing so,
// accumulate totals in the System Object Type data block.
// Pointers to these were initialized in QuerySystemData.
//
pExPCD->CounterBlock.ByteLength = QWORD_MULTIPLE(sizeof (EX_PROCESSOR_COUNTER_DATA));
pexcdTotalData.ProcessorTime +=
pExPCD->ProcessorTime =
pProcessorInformation->IdleTime.QuadPart;
pexcdTotalData.UserTime +=
pExPCD->UserTime =
pProcessorInformation->UserTime.QuadPart;
// kernel time is total kernel time less the time spent in the
// idle thread for that processor
pexcdTotalData.KernelTime +=
pExPCD->KernelTime =
pProcessorInformation->KernelTime.QuadPart -
pExPCD->ProcessorTime;
pexcdTotalData.Interrupts +=
pExPCD->Interrupts = pProcessorInformation->InterruptCount;
pexcdTotalData.DpcTime +=
pExPCD->DpcTime = pProcessorInformation->DpcTime.QuadPart;
pexcdTotalData.InterruptTime +=
pExPCD->InterruptTime =
pProcessorInformation->InterruptTime.QuadPart;
pexcdTotalData.DpcCountRate +=
pExPCD->DpcCountRate =
pThisProcessorInterruptInformation->DpcCount;
pexcdTotalData.DpcRate +=
pExPCD->DpcRate =
pThisProcessorInterruptInformation->DpcRate;
// fill in the system idle info
pexcdTotalData.IdleTime +=
pExPCD->IdleTime =
pProcIdleInformation->IdleTime;
pexcdTotalData.C1Time +=
pExPCD->C1Time =
pProcIdleInformation->C1Time;
pexcdTotalData.C2Time +=
pExPCD->C2Time =
pProcIdleInformation->C2Time;
pexcdTotalData.C3Time +=
pExPCD->C3Time =
pProcIdleInformation->C3Time;
pexcdTotalData.C1Transitions +=
pExPCD->C1Transitions =
pProcIdleInformation->C1Transitions;
pexcdTotalData.C2Transitions +=
pExPCD->C2Transitions =
pProcIdleInformation->C2Transitions;
pexcdTotalData.C3Transitions +=
pExPCD->C3Transitions =
pProcIdleInformation->C3Transitions;
//
// Advance to next processor
//
pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)&pExPCD[1];
// point to next processor's data in return array(s)
pProcessorInformation++;
pThisProcessorInterruptInformation++;
pProcIdleInformation++;
}
// do the total instance now
ProcessorName.Length = (WORD)((lstrlenW (wszTotal) + 1) * sizeof (WCHAR));
ProcessorName.MaximumLength = (WORD)(sizeof (ProcessorNameBuffer));
lstrcpyW (ProcessorNameBuffer, wszTotal);
ProcessorName.Buffer = ProcessorNameBuffer;
MonBuildInstanceDefinition(pPerfInstanceDefinition,
(PVOID *) &pExPCD,
0,
0,
(DWORD)-1,
ProcessorNameBuffer);
// define the size
pexcdTotalData.CounterBlock.ByteLength = QWORD_MULTIPLE(sizeof (EX_PROCESSOR_COUNTER_DATA));
// adjust the total values of the time fields to the number of
// processors to "normalize" the values
pexcdTotalData.ProcessorTime /= BasicInfo.NumberOfProcessors;
pexcdTotalData.UserTime /= BasicInfo.NumberOfProcessors;
pexcdTotalData.KernelTime /= BasicInfo.NumberOfProcessors;
pexcdTotalData.IdleTime /= BasicInfo.NumberOfProcessors;
pexcdTotalData.C1Time /= BasicInfo.NumberOfProcessors;
pexcdTotalData.C2Time /= BasicInfo.NumberOfProcessors;
pexcdTotalData.C3Time /= BasicInfo.NumberOfProcessors;
pexcdTotalData.DpcTime /= BasicInfo.NumberOfProcessors;
pexcdTotalData.InterruptTime /= BasicInfo.NumberOfProcessors;
// these fields are OK as totals
//
// pexcdTotalData.Interrupts
// pexcdTotalData.DpcCountRate
// pexcdTotalData.DpcRate
// copy total data to buffer
memcpy (pExPCD, &pexcdTotalData, sizeof (pexcdTotalData));
// adjust local buffer pointer
pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)&pExPCD[1];
//
// Now we know how large an area we used for the
// processor definition, so we can update the offset
// to the next object definition
//
pExProcessorDataDefinition->ProcessorObjectType.NumInstances =
BasicInfo.NumberOfProcessors + 1;
*lpcbTotalBytes =
pExProcessorDataDefinition->ProcessorObjectType.TotalByteLength =
(DWORD) QWORD_MULTIPLE(((LPBYTE) pPerfInstanceDefinition) -
(LPBYTE) pExProcessorDataDefinition);
* lppData = (LPVOID) (((LPBYTE) pExProcessorDataDefinition) + * lpcbTotalBytes);
}
if ((pExProcessorDataDefinition == NULL) && (pProcessorDataDefinition == NULL)) {
// then no data buffer found to use
lReturn = ERROR_SUCCESS;
*lpcbTotalBytes = (DWORD) 0;
*lpNumObjectTypes = (DWORD) 0;
goto COLLECT_BAIL_OUT;
}
#ifdef DBG
if (*lpcbTotalBytes > TotalLen ) {
DbgPrint ("\nPERFOS: Processor Perf Ctr. Instance Size Underestimated:");
DbgPrint ("\nPERFOS: Estimated size: %d, Actual Size: %d", TotalLen, *lpcbTotalBytes);
}
#endif
*lpNumObjectTypes = 1;
#ifdef DBG
ENDTIMING (("PERFCPU: %d takes %I64u ms total\n", __LINE__, diff));
#endif
return ERROR_SUCCESS;
COLLECT_BAIL_OUT:
#ifdef DBG
ENDTIMING (("PERFCPU: %d takes %I64u ms total\n", __LINE__, diff));
#endif
return lReturn;
}
DWORD APIENTRY
CloseProcessorObject (
)
/*++
Routine Description:
This routine closes the open handles
Arguments:
None.
Return Value:
ERROR_SUCCESS
--*/
{
if (dwCpuOpenCount > 0) {
if (!(--dwCpuOpenCount)) { // when this is the last thread...
// close stuff here
if (hLibHeap != NULL) {
if (pProcessorInterruptInformation != NULL) {
FREEMEM (pProcessorInterruptInformation);
pProcessorInterruptInformation = NULL;
}
if (pProcessorBuffer != NULL) {
FREEMEM (pProcessorBuffer);
pProcessorBuffer = NULL;
}
if (pProcessorIdleInformation != NULL) {
FREEMEM (pProcessorIdleInformation);
pProcessorIdleInformation = NULL;
}
dwInterruptInfoBufferSize = 0;
ProcessorBufSize = 0;
dwProcessorIdleBufferSize = 0;
}
}
} else {
// if the open count is 0, then these should have been deleted
assert (pProcessorBuffer == NULL);
assert (pProcessorInterruptInformation == NULL);
assert (pProcessorIdleInformation == NULL);
}
return ERROR_SUCCESS;
}