/*++ Copyright (c) 1996 Microsoft Corporation Module Name: perfheap.c Abstract: This file implements an Performance Object that presents Heap performance object data Created: Adrian Marinescu 9-Mar-2000 Revision History: --*/ // // Include Files // #include #include #include #include #include #include #include #include #include "perfsprc.h" #include "perfmsg.h" #include "dataheap.h" // // Redefinition for heap data // #define MAX_HEAP_COUNT 200 #define HEAP_MAXIMUM_FREELISTS 128 #define HEAP_MAXIMUM_SEGMENTS 64 #define HEAP_OP_COUNT 2 #define HEAP_OP_ALLOC 0 #define HEAP_OP_FREE 1 typedef struct _HEAP_ENTRY { USHORT Size; USHORT PreviousSize; UCHAR SegmentIndex; UCHAR Flags; UCHAR UnusedBytes; UCHAR SmallTagIndex; #if defined(_WIN64) ULONGLONG Reserved1; #endif } HEAP_ENTRY, *PHEAP_ENTRY; typedef struct _HEAP_SEGMENT { HEAP_ENTRY Entry; ULONG Signature; ULONG Flags; struct _HEAP *Heap; SIZE_T LargestUnCommittedRange; PVOID BaseAddress; ULONG NumberOfPages; PHEAP_ENTRY FirstEntry; PHEAP_ENTRY LastValidEntry; ULONG NumberOfUnCommittedPages; ULONG NumberOfUnCommittedRanges; PVOID UnCommittedRanges; USHORT AllocatorBackTraceIndex; USHORT Reserved; PHEAP_ENTRY LastEntryInSegment; } HEAP_SEGMENT, *PHEAP_SEGMENT; typedef struct _HEAP { HEAP_ENTRY Entry; ULONG Signature; ULONG Flags; ULONG ForceFlags; ULONG VirtualMemoryThreshold; SIZE_T SegmentReserve; SIZE_T SegmentCommit; SIZE_T DeCommitFreeBlockThreshold; SIZE_T DeCommitTotalFreeThreshold; SIZE_T TotalFreeSize; SIZE_T MaximumAllocationSize; USHORT ProcessHeapsListIndex; USHORT HeaderValidateLength; PVOID HeaderValidateCopy; USHORT NextAvailableTagIndex; USHORT MaximumTagIndex; PVOID TagEntries; PVOID UCRSegments; PVOID UnusedUnCommittedRanges; ULONG AlignRound; ULONG AlignMask; LIST_ENTRY VirtualAllocdBlocks; PHEAP_SEGMENT Segments[ HEAP_MAXIMUM_SEGMENTS ]; union { ULONG FreeListsInUseUlong[ HEAP_MAXIMUM_FREELISTS / 32 ]; UCHAR FreeListsInUseBytes[ HEAP_MAXIMUM_FREELISTS / 8 ]; } u; USHORT FreeListsInUseTerminate; USHORT AllocatorBackTraceIndex; ULONG NonDedicatedListLength; PVOID LargeBlocksIndex; PVOID PseudoTagEntries; LIST_ENTRY FreeLists[ HEAP_MAXIMUM_FREELISTS ]; PVOID LockVariable; PVOID CommitRoutine; PVOID Lookaside; ULONG LookasideLockCount; } HEAP, *PHEAP; typedef struct _HEAP_PERF_DATA { UINT64 CountFrequence; UINT64 OperationTime[HEAP_OP_COUNT]; // // The data bellow are only for sampling // ULONG Sequence; UINT64 TempTime[HEAP_OP_COUNT]; ULONG TempCount[HEAP_OP_COUNT]; } HEAP_PERF_DATA, *PHEAP_PERF_DATA; // // The heap index structure // typedef struct _HEAP_INDEX { ULONG ArraySize; ULONG VirtualMemorySize; HEAP_PERF_DATA PerfData; union { PULONG FreeListsInUseUlong; PUCHAR FreeListsInUseBytes; } u; PVOID *FreeListHints; } HEAP_INDEX, *PHEAP_INDEX; typedef struct _HEAP_LOOKASIDE { SLIST_HEADER ListHead; USHORT Depth; USHORT MaximumDepth; ULONG TotalAllocates; ULONG AllocateMisses; ULONG TotalFrees; ULONG FreeMisses; ULONG LastTotalAllocates; ULONG LastAllocateMisses; ULONG Counters[2]; } HEAP_LOOKASIDE, *PHEAP_LOOKASIDE; // // Local variables // static HEAP_LOOKASIDE LookasideBuffer[HEAP_MAXIMUM_FREELISTS]; static DWORD PageSize = 0; // // Implementation for heap query function // BOOLEAN ReadHeapData ( IN HANDLE hProcess, IN ULONG HeapNumber, IN PHEAP Heap, OUT PHEAP_COUNTER_DATA pHCD ) /*++ Routine Description: The routine loads into the given heap couter structure the data from the heap structure Arguments: hProcess - The process containing the heap Heap - the heap address pPerfInstanceDefinition - Performance instance definition data pHCD - Counter data Returns: Returns TRUE if query succeeds. --*/ { HEAP_SEGMENT CrtSegment; HEAP CrtHeap; ULONG SegmentIndex; RTL_CRITICAL_SECTION CriticalSection; HEAP_INDEX HeapIndex; ULONG i; // // Read the heap structure from the process address space // if (!ReadProcessMemory(hProcess, Heap, &CrtHeap, sizeof(CrtHeap), NULL)) { return FALSE; } // // We won't display data for heaps w/o index. // if ((CrtHeap.LargeBlocksIndex == NULL) && (HeapNumber != 0)) { // // We are not handling small heaps // return FALSE; } pHCD->FreeSpace = CrtHeap.TotalFreeSize; pHCD->FreeListLength = CrtHeap.NonDedicatedListLength; pHCD->CommittedBytes = 0; pHCD->ReservedBytes = 0; pHCD->VirtualBytes = 0; pHCD->UncommitedRangesLength = 0; // // Walking the heap segments and get the virtual address counters // for (SegmentIndex = 0; SegmentIndex < HEAP_MAXIMUM_SEGMENTS; SegmentIndex++) { if ((CrtHeap.Segments[SegmentIndex] == NULL) || !ReadProcessMemory(hProcess, CrtHeap.Segments[SegmentIndex], &CrtSegment, sizeof(CrtSegment), NULL)) { break; } pHCD->ReservedBytes += CrtSegment.NumberOfPages * PageSize; pHCD->CommittedBytes += (CrtSegment.NumberOfPages - CrtSegment.NumberOfUnCommittedPages) * PageSize; pHCD->VirtualBytes += CrtSegment.NumberOfPages * PageSize - CrtSegment.LargestUnCommittedRange; pHCD->UncommitedRangesLength += CrtSegment.NumberOfUnCommittedRanges; } if (pHCD->CommittedBytes == 0) { pHCD->CommittedBytes = 1; } if (pHCD->VirtualBytes == 0) { pHCD->VirtualBytes = 1; } // // Compute the heap fragmentation counters // pHCD->BlockFragmentation = (ULONG)(pHCD->FreeSpace * 100 / pHCD->CommittedBytes); pHCD->VAFragmentation =(ULONG)(((pHCD->VirtualBytes - pHCD->CommittedBytes)*100)/pHCD->VirtualBytes); // // Read the lock contention // pHCD->LockContention = 0; if (ReadProcessMemory(hProcess, CrtHeap.LockVariable, &CriticalSection, sizeof(CriticalSection), NULL)) { RTL_CRITICAL_SECTION_DEBUG DebugInfo; if (ReadProcessMemory(hProcess, CriticalSection.DebugInfo, &DebugInfo, sizeof(DebugInfo), NULL)) { pHCD->LockContention = DebugInfo.ContentionCount; } } // // Walk the lookaside to count the blocks // pHCD->LookasideAllocs = 0; pHCD->LookasideFrees = 0; pHCD->LookasideBlocks = 0; pHCD->LargestLookasideDepth = 0; pHCD->SmallAllocs = 0; pHCD->SmallFrees = 0; pHCD->MedAllocs = 0; pHCD->MedFrees = 0; pHCD->LargeAllocs = 0; pHCD->LargeFrees = 0; if (ReadProcessMemory(hProcess, CrtHeap.Lookaside, &LookasideBuffer, sizeof(LookasideBuffer), NULL)) { for (i = 0; i < HEAP_MAXIMUM_FREELISTS; i++) { pHCD->SmallAllocs += LookasideBuffer[i].TotalAllocates; pHCD->SmallFrees += LookasideBuffer[i].TotalFrees; pHCD->LookasideAllocs += LookasideBuffer[i].TotalAllocates - LookasideBuffer[i].AllocateMisses; pHCD->LookasideFrees += LookasideBuffer[i].TotalFrees - LookasideBuffer[i].FreeMisses; if (LookasideBuffer[i].Depth > pHCD->LargestLookasideDepth) { pHCD->LargestLookasideDepth = LookasideBuffer[i].Depth; } if (i == 0) { } else if (i < 8) { pHCD->MedAllocs += LookasideBuffer[i].Counters[0]; pHCD->MedFrees += LookasideBuffer[i].Counters[1]; } else { pHCD->LargeAllocs += LookasideBuffer[i].Counters[0]; pHCD->LargeFrees += LookasideBuffer[i].Counters[1]; } } } pHCD->LookasideBlocks = pHCD->LookasideFrees - pHCD->LookasideAllocs; // // Calculate the totals // pHCD->TotalAllocs = pHCD->SmallAllocs + pHCD->MedAllocs + pHCD->LargeAllocs; pHCD->TotalFrees = pHCD->SmallFrees + pHCD->MedFrees + pHCD->LargeFrees; // // Set the difference between allocs and frees // pHCD->DiffOperations = pHCD->TotalAllocs - pHCD->TotalFrees; pHCD->AllocTime = 0; pHCD->AllocTime = 0; // // Determine the alloc/free rates // if (ReadProcessMemory(hProcess, CrtHeap.LargeBlocksIndex, &HeapIndex, sizeof(HeapIndex), NULL)) { if (HeapIndex.PerfData.OperationTime[0]) { pHCD->AllocTime = HeapIndex.PerfData.CountFrequence / HeapIndex.PerfData.OperationTime[0]; } if (HeapIndex.PerfData.OperationTime[1]) { pHCD->FreeTime = HeapIndex.PerfData.CountFrequence / HeapIndex.PerfData.OperationTime[1]; } } return TRUE; } DWORD APIENTRY CollectHeapObjectData ( IN OUT LPVOID *lppData, IN OUT LPDWORD lpcbTotalBytes, IN OUT LPDWORD lpNumObjectTypes ) /*++ Routine Description: This routine will return the data for the heap 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 PHEAP_DATA_DEFINITION pHeapDataDefinition; PPERF_INSTANCE_DEFINITION pPerfInstanceDefinition; PHEAP_COUNTER_DATA pHCD; PSYSTEM_PROCESS_INFORMATION ProcessInfo; ULONG ProcessNumber; ULONG NumHeapInstances; ULONG HeapNumber; ULONG ProcessBufferOffset; UNICODE_STRING HeapName; WCHAR HeapNameBuffer[MAX_THREAD_NAME_LENGTH+1]; BOOL bMoreProcesses = FALSE; HeapName.Length = HeapName.MaximumLength = (MAX_THREAD_NAME_LENGTH + 1) * sizeof(WCHAR); HeapName.Buffer = HeapNameBuffer; pHeapDataDefinition = (HEAP_DATA_DEFINITION *) *lppData; // // Get the page size from the system // if (!PageSize) { SYSTEM_INFO SystemInfo; GetSystemInfo(&SystemInfo); PageSize = SystemInfo.dwPageSize; } // // Check for sufficient space for Thread object type definition // TotalLen = sizeof(HEAP_DATA_DEFINITION) + sizeof(PERF_INSTANCE_DEFINITION) + sizeof(HEAP_COUNTER_DATA); if ( *lpcbTotalBytes < TotalLen ) { *lpcbTotalBytes = (DWORD) 0; *lpNumObjectTypes = (DWORD) 0; return ERROR_MORE_DATA; } // // Define the heap data block // memcpy(pHeapDataDefinition, &HeapDataDefinition, sizeof(HEAP_DATA_DEFINITION)); pHeapDataDefinition->HeapObjectType.PerfTime = PerfTime; ProcessBufferOffset = 0; // // Now collect data for each process // ProcessNumber = 0; NumHeapInstances = 0; ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)pProcessBuffer; pPerfInstanceDefinition = (PPERF_INSTANCE_DEFINITION)&pHeapDataDefinition[1]; TotalLen = sizeof(HEAP_DATA_DEFINITION); if (ProcessInfo) { if (ProcessInfo->NextEntryOffset != 0) { bMoreProcesses = TRUE; } } while ( bMoreProcesses && (ProcessInfo != NULL)) { HANDLE hProcess; NTSTATUS Status; PROCESS_BASIC_INFORMATION BasicInfo; // // Get a handle to the process. // hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, (DWORD)(ULONGLONG)ProcessInfo->UniqueProcessId ); if ( hProcess ) { // // Get the process PEB // Status = NtQueryInformationProcess( hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL ); if ( NT_SUCCESS(Status) ) { ULONG NumberOfHeaps; PVOID ProcessHeaps[MAX_HEAP_COUNT]; PVOID HeapBuffer; PPEB Peb; Peb = BasicInfo.PebBaseAddress; // // Read the heaps from the process PEB // if (!ReadProcessMemory(hProcess, &Peb->NumberOfHeaps, &NumberOfHeaps, sizeof(NumberOfHeaps), NULL)) { goto READERROR; } // // Limit the number of heaps to be read // if (NumberOfHeaps > MAX_HEAP_COUNT) { NumberOfHeaps = MAX_HEAP_COUNT; } if (!ReadProcessMemory(hProcess, &Peb->ProcessHeaps, &HeapBuffer, sizeof(HeapBuffer), NULL)) { goto READERROR; } if (!ReadProcessMemory(hProcess, HeapBuffer, &ProcessHeaps, NumberOfHeaps * sizeof(PVOID), NULL)) { goto READERROR; } // // Loop through the heaps and retireve the data // for (HeapNumber = 0; HeapNumber < NumberOfHeaps; HeapNumber++) { TotalLen += sizeof(PERF_INSTANCE_DEFINITION) + (MAX_THREAD_NAME_LENGTH+1+sizeof(DWORD))* sizeof(WCHAR) + sizeof (HEAP_COUNTER_DATA); if ( *lpcbTotalBytes < TotalLen ) { *lpcbTotalBytes = (DWORD) 0; *lpNumObjectTypes = (DWORD) 0; CloseHandle( hProcess ); return ERROR_MORE_DATA; } // // Build the monitor instance based on the process name and // heap address // RtlIntegerToUnicodeString( (ULONG)(ULONGLONG)ProcessHeaps[HeapNumber], 16, &HeapName); MonBuildInstanceDefinition(pPerfInstanceDefinition, (PVOID *) &pHCD, PROCESS_OBJECT_TITLE_INDEX, ProcessNumber, (DWORD)-1, HeapName.Buffer); pHCD->CounterBlock.ByteLength = sizeof(HEAP_COUNTER_DATA); // // Get the data from the heap // if (ReadHeapData ( hProcess, HeapNumber, (PHEAP)ProcessHeaps[HeapNumber], pHCD) ) { pPerfInstanceDefinition = (PERF_INSTANCE_DEFINITION *)&pHCD[1]; NumHeapInstances++; } } } READERROR: CloseHandle( hProcess ); } ProcessNumber++; // // Move to the next process, if any // if (ProcessInfo->NextEntryOffset == 0) { bMoreProcesses = FALSE; continue; } ProcessBufferOffset += ProcessInfo->NextEntryOffset; ProcessInfo = (PSYSTEM_PROCESS_INFORMATION) &pProcessBuffer[ProcessBufferOffset]; } // Note number of heap instances pHeapDataDefinition->HeapObjectType.NumInstances = NumHeapInstances; // // Now we know how large an area we used for the // heap definition, so we can update the offset // to the next object definition // *lpcbTotalBytes = pHeapDataDefinition->HeapObjectType.TotalByteLength = (DWORD)((PCHAR) pPerfInstanceDefinition - (PCHAR) pHeapDataDefinition); #if DBG if (*lpcbTotalBytes > TotalLen ) { DbgPrint ("\nPERFPROC: Heap Perf Ctr. Instance Size Underestimated:"); DbgPrint ("\nPERFPROC: Estimated size: %d, Actual Size: %d", TotalLen, *lpcbTotalBytes); } #endif *lppData = (LPVOID)pPerfInstanceDefinition; *lpNumObjectTypes = 1; return lReturn; }