/*++ 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 #include #include #include #include #include #include #define PERF_HEAP hLibHeap #include #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; }