(* Fast Memory Manager Usage Tracker 2.00 Description: - Shows FastMM4 allocation usage - Shows VM Memory in graphical map - Free - Commit - Reserved - EXE (Red) - DLLs (Blue) - VM Dump of the whole process (2GB standard, 3GB with the /3G switch set, and 4GB under WoW64) - General Information - System memory usage - Process memory usage - 5 Largest contiguous free VM memory spaces - FastMM4 summary information Usage: - Add the FastMMUsageTracker unit - Add the ShowFastMMUsageTracker procedure to an event - FastMMUsageTracker form should not be autocreated Notes: - Consider setting the base adress of your BPLs & DLLs or use Microsoft's ReBase.exe to set third party BPLs and DLLs. Libraries that do not have to be relocated can be shared across processes, thus conserving system resources. - The first of the "Largest contiguous free VM memory spaces" gives you an indication of the largest single memory block that can be allocated. Change log: Version 2.10 (22 September 2009): - New usage tracker implemented by Hanspeter Widmer with many new features. (Thanks Hanspeter!); - Colour coding of changes in the allocation map added by Murray McGowan (red for an increase in usage, green for a decrease). (Thanks Murray!) *) unit FastMMUsageTracker; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, Grids, Buttons, ComCtrls, Menus, FastMM4; type TChunkStatusEx = ( {Items that correspond to the same entry in TChunkStatus} csExUnallocated, csExAllocated, csExReserved, csExSysAllocated, csExSysReserved, {TChunkStatusEx additional detail} csExSysExe, csExSysDLL); TMemoryMapEx = array[0..65535] of TChunkStatusEx; TfFastMMUsageTracker = class(TForm) tTimer: TTimer; bClose: TBitBtn; bUpdate: TBitBtn; ChkAutoUpdate: TCheckBox; smVMDump: TPopupMenu; smMM4Allocation: TPopupMenu; smGeneralInformation: TPopupMenu; miVMDumpCopyAlltoClipboard: TMenuItem; miGeneralInformationCopyAlltoClipboard: TMenuItem; siMM4AllocationCopyAlltoClipboard: TMenuItem; pcUsageTracker: TPageControl; tsAllocation: TTabSheet; tsVMGraph: TTabSheet; tsVMDump: TTabSheet; tsGeneralInformation: TTabSheet; mVMStatistics: TMemo; sgVMDump: TStringGrid; Label1: TLabel; Label2: TLabel; Label3: TLabel; eAddress: TEdit; eState: TEdit; eDLLName: TEdit; ChkSmallGraph: TCheckBox; sgBlockStatistics: TStringGrid; dgMemoryMap: TDrawGrid; procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure tTimerTimer(Sender: TObject); procedure FormCreate(Sender: TObject); procedure bCloseClick(Sender: TObject); procedure dgMemoryMapDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); procedure dgMemoryMapSelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean); procedure bUpdateClick(Sender: TObject); procedure ChkAutoUpdateClick(Sender: TObject); procedure ChkSmallGraphClick(Sender: TObject); procedure sgVMDumpMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure sgVMDumpMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure sgVMDumpDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); procedure miVMDumpCopyAlltoClipboardClick(Sender: TObject); procedure miGeneralInformationCopyAlltoClipboardClick(Sender: TObject); procedure siMM4AllocationCopyAlltoClipboardClick(Sender: TObject); procedure sgBlockStatisticsDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); private {The current and previous memory manager states} FMemoryManagerState, FPrevMemoryManagerState: TMemoryManagerState; FMemoryMapEx: TMemoryMapEx; AddressSpacePageCount: Integer; OR_VMDumpDownCell: TGridCoord; procedure HeaderClicked(AGrid: TStringgrid; const ACell: TGridCoord); procedure SortGrid(grid: TStringgrid; PB_Nummeric: Boolean; byColumn: Integer; ascending: Boolean); procedure UpdateGraphMetrics; public {Refreshes the display} procedure RefreshSnapShot; end; function ShowFastMMUsageTracker: TfFastMMUsageTracker; implementation uses Clipbrd, PsAPI; {$R *.dfm} const SystemBasicInformation = 0; SystemPerformanceInformation = 2; SystemTimeInformation = 3; type {To get access to protected methods} TLocalStringGrid = class(TStringGrid); TMemoryStatusEx = packed record dwLength: DWORD; dwMemoryLoad: DWORD; ullTotalPhys: Int64; ullAvailPhys: Int64; ullTotalPageFile: Int64; ullAvailPageFile: Int64; ullTotalVirtual: Int64; ullAvailVirtual: Int64; ullAvailExtendedVirtual: Int64; end; PMemoryStatusEx = ^TMemoryStatusEx; LPMEMORYSTATUSEX = PMemoryStatusEx; TP_GlobalMemoryStatusEx = function( var PR_MemStatusEx: TMemoryStatusEx): LongBool; stdcall; TSystem_Basic_Information = packed record dwUnknown1: DWORD; uKeMaximumIncrement: ULONG; uPageSize: ULONG; uMmNumberOfPhysicalPages: ULONG; uMmLowestPhysicalPage: ULONG; uMmHighestPhysicalPage: ULONG; uAllocationGranularity: ULONG; pLowestUserAddress: Pointer; pMmHighestUserAddress: Pointer; uKeActiveProcessors: ULONG; bKeNumberProcessors: Byte; bUnknown2: Byte; wUnknown3: Word; end; TSystem_Performance_Information = packed record liIdleTime: LARGE_INTEGER; dwSpare: array[0..75] of DWORD; end; TSystem_Time_Information = packed record liKeBootTime: LARGE_INTEGER; liKeSystemTime: LARGE_INTEGER; liExpTimeZoneBias: LARGE_INTEGER; uCurrentTimeZoneId: ULONG; dwReserved: DWORD; end; TP_NtQuerySystemInformation = function(InfoClass: DWORD; Buffer: Pointer; BufSize: DWORD; ReturnSize: PCardinal): DWORD; stdcall; var MP_GlobalMemoryStatusEx: TP_GlobalMemoryStatusEx = nil; MP_NtQuerySystemInformation: TP_NtQuerySystemInformation = nil; //----------------------------------------------------------------------------- // Various Global Procedures //----------------------------------------------------------------------------- function ShowFastMMUsageTracker: TfFastMMUsageTracker; begin Application.CreateForm(TfFastMMUsageTracker, Result); if Assigned(Result) then begin Result.RefreshSnapShot; Result.Show; end; end; function CardinalToStringFormatted(const ACardinal: Cardinal): string; begin Result := FormatFloat('#,##0', ACardinal); end; function Int64ToStringFormatted(const AInt64: Int64): string; begin Result := FormatFloat('#,##0', AInt64); end; function CardinalToKStringFormatted(const ACardinal: Cardinal): string; begin Result := FormatFloat('#,##0', ACardinal div 1024) + 'K'; end; function Int64ToKStringFormatted(const AInt64: Int64): string; begin Result := FormatFloat('#,##0', AInt64 div 1024) + 'K'; end; procedure CopyGridContentsToClipBoard(AStringGrid: TStringGrid); const TAB = Chr(VK_TAB); CRLF = #13#10; var LI_r, LI_c: Integer; LS_S: string; begin LS_S := ''; for LI_r := 0 to AStringGrid.RowCount - 1 do begin for LI_c := 0 to AStringGrid.ColCount - 1 do begin LS_S := LS_S + AStringGrid.Cells[LI_c, LI_r]; if LI_c < AStringGrid.ColCount - 1 then LS_S := LS_S + TAB; end; if LI_r < AStringGrid.RowCount - 1 then LS_S := LS_S + CRLF; end; ClipBoard.SetTextBuf(PChar(LS_S)); end; function LocSort(P1, P2: Pointer): Integer; begin if NativeUInt(P1) = NativeUInt(P2) then Result := 0 else begin if NativeUInt(P1) > NativeUInt(P2) then Result := -1 else Result := 1; end; end; //----------------------------------------------------------------------------- // Form TfFastMMUsageTracker //----------------------------------------------------------------------------- procedure TfFastMMUsageTracker.FormCreate(Sender: TObject); var LR_SystemInfo: TSystemInfo; begin pcUsageTracker.ActivePage := tsAllocation; GetSystemInfo(LR_SystemInfo); {Get the number of address space pages} if (Cardinal(LR_SystemInfo.lpMaximumApplicationAddress) and $80000000) = 0 then AddressSpacePageCount := 32768 else AddressSpacePageCount := 65536; {Update the graph metricx} UpdateGraphMetrics; {Set up the StringGrid columns} with sgBlockStatistics do begin Cells[0, 0] := 'Block Size'; Cells[1, 0] := '# Live Pointers'; Cells[2, 0] := 'Live Size'; Cells[3, 0] := 'Used Space'; Cells[4, 0] := 'Efficiency'; end; with sgVMDump do begin Cells[0, 0] := 'VM Block'; Cells[1, 0] := 'Size'; Cells[2, 0] := 'Type'; Cells[3, 0] := 'State'; Cells[4, 0] := 'EXE/DLL'; end; end; procedure TfFastMMUsageTracker.FormClose(Sender: TObject; var Action: TCloseAction); begin Action := caFree; end; procedure TfFastMMUsageTracker.SortGrid(grid: TStringgrid; PB_Nummeric: Boolean; byColumn: Integer; ascending: Boolean); function CompareNumeric(const S1, S2: string): Integer; var LVal1, LVal2: Integer; begin begin LVal1 := StrToInt(S1); LVal2 := StrToInt(S2); if LVal1 = LVal2 then begin Result := 0; end else begin if LVal1 > LVal2 then Result := 1 else Result := -1; end; end; end; procedure ExchangeGridRows(i, j: Integer); var k: Integer; begin for k := 0 to Grid.ColCount - 1 do Grid.Cols[k].Exchange(i, j); end; procedure QuickSortNummeric(L, R: Integer); var I, J: Integer; P: string; begin repeat I := L; J := R; P := Grid.Cells[byColumn, (L + R) shr 1]; repeat while CompareNumeric(Grid.Cells[byColumn, I], P) < 0 do Inc(I); while CompareNumeric(Grid.Cells[byColumn, J], P) > 0 do Dec(J); if I <= J then begin if I <> J then ExchangeGridRows(I, J); Inc(I); Dec(J); end; until I > J; if L < J then QuickSortNummeric(L, J); L := I; until I >= R; end; procedure QuickSortString(L, R: Integer); var I, J: Integer; P: string; begin repeat I := L; J := R; P := Grid.Cells[byColumn, (L + R) shr 1]; repeat while CompareText(Grid.Cells[byColumn, I], P) < 0 do Inc(I); while CompareText(Grid.Cells[byColumn, J], P) > 0 do Dec(J); if I <= J then begin if I <> J then ExchangeGridRows(I, J); Inc(I); Dec(J); end; until I > J; if L < J then QuickSortString(L, J); L := I; until I >= R; end; procedure InvertGrid; var i, j: Integer; begin i := Grid.Fixedrows; j := Grid.Rowcount - 1; while i < j do begin ExchangeGridRows(I, J); Inc(i); Dec(j); end; end; begin Screen.Cursor := crHourglass; Grid.Perform(WM_SETREDRAW, 0, 0); try if PB_Nummeric then QuickSortNummeric(Grid.FixedRows, Grid.Rowcount - 1) else QuickSortString(Grid.FixedRows, Grid.Rowcount - 1); if not Ascending then InvertGrid; finally Grid.Perform(WM_SETREDRAW, 1, 0); Grid.Refresh; Screen.Cursor := crDefault; end; end; procedure TfFastMMUsageTracker.HeaderClicked(AGrid: TStringgrid; const ACell: TGridCoord); var i: Integer; LNumericSort: Boolean; begin // The header cell stores a flag in the Objects property that signals the // current sort order of the grid column. A value of 0 shows no sort marker, // 1 means sorted ascending, -1 sorted descending // clear markers for i := AGrid.FixedCols to AGrid.ColCount - 1 do begin if Assigned(AGrid.Objects[i, 0]) and (i <> ACell.x) then begin AGrid.Objects[i, 0] := nil; TLocalStringGrid(AGrid).InvalidateCell(i, 0); end; end; // Sort grid on new column. If grid is currently sorted ascending on this // column we invert the sort direction, otherwise we sort it ascending. if ACell.X = 1 then LNumericSort := True else LNumericSort := False; if Integer(AGrid.Objects[ACell.x, ACell.y]) = 1 then begin SortGrid(AGrid, LNumericSort, ACell.x, False); AGrid.Objects[ACell.x, 0] := Pointer(-1); end else begin SortGrid(AGrid, LNumericSort, ACell.x, True); AGrid.Objects[ACell.x, 0] := Pointer(1); end; TLocalStringGrid(AGrid).InvalidateCell(ACell.x, ACell.y); end; procedure TfFastMMUsageTracker.UpdateGraphMetrics; begin if ChkSmallGraph.Checked then begin dgMemoryMap.DefaultColWidth := 4; dgMemoryMap.ColCount := 128; end else begin dgMemoryMap.DefaultColWidth := 8; dgMemoryMap.ColCount := 64; end; dgMemoryMap.DefaultRowHeight := dgMemoryMap.DefaultColWidth; dgMemoryMap.RowCount := AddressSpacePageCount div dgMemoryMap.ColCount; end; procedure TfFastMMUsageTracker.RefreshSnapShot; var LP_FreeVMList: TList; LU_MEM_FREE: SIZE_T; LU_MEM_COMMIT: SIZE_T; LU_MEM_RESERVE: SIZE_T; LAllocatedSize, LTotalBlocks, LTotalAllocated, LTotalReserved, LPrevAllocatedSize, LPrevTotalBlocks, LPrevTotalAllocated, LPrevTotalReserved: NativeUInt; procedure UpdateVMGraph(var AMemoryMap: TMemoryMapEx); var LInd, LIndTop, I1: Integer; LChunkState: TChunkStatusEx; LMBI: TMemoryBasicInformation; LA_Char: array[0..MAX_PATH] of Char; begin LInd := 0; repeat {If the chunk is not allocated by this MM, what is its status?} if AMemoryMap[LInd] = csExSysAllocated then begin {Get all the reserved memory blocks and windows allocated memory blocks, etc.} VirtualQuery(Pointer(LInd * 65536), LMBI, SizeOf(LMBI)); if LMBI.State = MEM_COMMIT then begin if (GetModuleFileName(DWord(LMBI.AllocationBase), LA_Char, MAX_PATH) <> 0) then begin if DWord(LMBI.AllocationBase) = SysInit.HInstance then LChunkState := csExSysExe else LChunkState := csExSysDLL; end else begin LChunkState := csExSysAllocated; end; if LMBI.RegionSize > 65536 then begin LIndTop := (Cardinal(LMBI.BaseAddress) + Cardinal(LMBI.RegionSize)) div 65536; // Fill up multiple tables for I1 := LInd to LIndTop do AMemoryMap[I1] := LChunkState; LInd := LIndTop; end else begin AMemoryMap[LInd] := LChunkState; end; end end; Inc(LInd); until LInd >= AddressSpacePageCount; end; procedure UpdateVMDump; var LP_Base: PByte; LR_Info: TMemoryBasicInformation; LU_rv: SIZE_T; LI_I: Integer; LA_Char: array[0..MAX_PATH] of Char; begin LP_Base := nil; LU_rv := VirtualQuery(LP_Base, LR_Info, sizeof(LR_Info)); LI_I := 1; while LU_rv = sizeof(LR_Info) do begin with sgVMDump do begin Cells[0, LI_I] := IntToHex(Integer(LR_Info.BaseAddress), 8); Cells[1, LI_I] := IntToStr(LR_Info.RegionSize); Cells[3, LI_I] := IntToHex(Integer(LR_Info.Protect), 8); case LR_Info.State of MEM_Commit: begin LU_MEM_COMMIT := LU_MEM_COMMIT + LR_Info.RegionSize; if (GetModuleFileName(dword(LR_Info.AllocationBase), LA_Char, MAX_PATH) <> 0) then begin if DWord(LR_Info.AllocationBase) = SysInit.HInstance then Cells[2, LI_I] := 'Exe' else Cells[2, LI_I] := 'DLL'; Cells[4, LI_I] := ExtractFileName(LA_Char); end else begin Cells[4, LI_I] := ''; Cells[2, LI_I] := 'Commited'; end; end; MEM_RESERVE: begin LU_MEM_RESERVE := LU_MEM_RESERVE + LR_Info.RegionSize; Cells[2, LI_I] := 'Reserved'; Cells[4, LI_I] := ''; end; MEM_FREE: begin LP_FreeVMList.Add(Pointer(LR_Info.RegionSize)); LU_MEM_FREE := LU_MEM_FREE + Lr_Info.RegionSize; Cells[2, LI_I] := 'Free'; Cells[4, LI_I] := ''; end; end; Inc(LP_Base, LR_Info.RegionSize); LU_rv := VirtualQuery(LP_Base, LR_Info, sizeof(LR_Info)); Inc(LI_I); end; end; sgVMDump.RowCount := LI_I; end; procedure UpdateFastMM4Data; var LInd: Integer; LU_StateLength: Cardinal; LPrevSBState, LSBState: ^TSmallBlockTypeState; procedure UpdateBlockStatistics(c, r, current, prev: Integer); var s : string; begin s := IntToStr(current); if current > prev then s := s + ' (+' + IntToStr(current - prev) + ')' else if current < prev then s := s + ' (-' + IntToStr(prev - current) + ')'; sgBlockStatistics.Cells[c, r] := s; sgBlockStatistics.Objects[c, r] := Pointer(current - prev); end; begin LU_StateLength := Length(FMemoryManagerState.SmallBlockTypeStates); {Set up the row count} sgBlockStatistics.RowCount := LU_StateLength + 4; sgBlockStatistics.Cells[0, LU_StateLength + 1] := 'Medium Blocks'; sgBlockStatistics.Cells[0, LU_StateLength + 2] := 'Large Blocks'; sgBlockStatistics.Cells[0, LU_StateLength + 3] := 'Overall'; for LInd := 0 to High(FMemoryManagerState.SmallBlockTypeStates) do begin sgBlockStatistics.Cells[0, LInd + 1] := IntToStr(FMemoryManagerState.SmallBlockTypeStates[LInd].InternalBlockSize) + '(' + IntToStr(FMemoryManagerState.SmallBlockTypeStates[LInd].UseableBlockSize) + ')'; end; {Set the texts inside the results string grid} for LInd := 0 to High(FMemoryManagerState.SmallBlockTypeStates) do begin LPrevSBState := @FPrevMemoryManagerState.SmallBlockTypeStates[LInd]; LSBState := @FMemoryManagerState.SmallBlockTypeStates[LInd]; UpdateBlockStatistics(1, LInd + 1, LSBState.AllocatedBlockCount, LPrevSBState.AllocatedBlockCount); Inc(LTotalBlocks, LSBState.AllocatedBlockCount); Inc(LPrevTotalBlocks, LPrevSBState.AllocatedBlockCount); LAllocatedSize := LSBState.AllocatedBlockCount * LSBState.UseableBlockSize; LPrevAllocatedSize := LPrevSBState.AllocatedBlockCount * LPrevSBState.UseableBlockSize; UpdateBlockStatistics(2, LInd + 1, LAllocatedSize, LPrevAllocatedSize); Inc(LTotalAllocated, LAllocatedSize); Inc(LPrevTotalAllocated, LPrevAllocatedSize); UpdateBlockStatistics(3, LInd + 1, LSBState.ReservedAddressSpace, LPrevSBState.ReservedAddressSpace); Inc(LTotalReserved, LSBState.ReservedAddressSpace); Inc(LPrevTotalReserved, LPrevSBState.ReservedAddressSpace); if LSBState.ReservedAddressSpace > 0 then sgBlockStatistics.Cells[4, LInd + 1] := FormatFloat('0.##%', LAllocatedSize / LSBState.ReservedAddressSpace * 100) else sgBlockStatistics.Cells[4, LInd + 1] := 'N/A'; end; {-----------Medium blocks---------} LInd := length(FMemoryManagerState.SmallBlockTypeStates) + 1; UpdateBlockStatistics(1, LInd, FMemoryManagerState.AllocatedMediumBlockCount, FPrevMemoryManagerState.AllocatedMediumBlockCount); Inc(LTotalBlocks, FMemoryManagerState.AllocatedMediumBlockCount); Inc(LPrevTotalBlocks, FPrevMemoryManagerState.AllocatedMediumBlockCount); UpdateBlockStatistics(2, LInd, FMemoryManagerState.TotalAllocatedMediumBlockSize, FPrevMemoryManagerState.TotalAllocatedMediumBlockSize); Inc(LTotalAllocated, FMemoryManagerState.TotalAllocatedMediumBlockSize); Inc(LPrevTotalAllocated, FPrevMemoryManagerState.TotalAllocatedMediumBlockSize); UpdateBlockStatistics(3, LInd, FMemoryManagerState.ReservedMediumBlockAddressSpace, FPrevMemoryManagerState.ReservedMediumBlockAddressSpace); Inc(LTotalReserved, FMemoryManagerState.ReservedMediumBlockAddressSpace); Inc(LPrevTotalReserved, FPrevMemoryManagerState.ReservedMediumBlockAddressSpace); if FMemoryManagerState.ReservedMediumBlockAddressSpace > 0 then sgBlockStatistics.Cells[4, LInd] := FormatFloat('0.##%', FMemoryManagerState.TotalAllocatedMediumBlockSize / FMemoryManagerState.ReservedMediumBlockAddressSpace * 100) else sgBlockStatistics.Cells[4, LInd] := 'N/A'; {----------Large blocks----------} LInd := Length(FMemoryManagerState.SmallBlockTypeStates) + 2; UpdateBlockStatistics(1, LInd, FMemoryManagerState.AllocatedLargeBlockCount, FPrevMemoryManagerState.AllocatedLargeBlockCount); Inc(LTotalBlocks, FMemoryManagerState.AllocatedLargeBlockCount); Inc(LPrevTotalBlocks, FPrevMemoryManagerState.AllocatedLargeBlockCount); UpdateBlockStatistics(2, LInd, FMemoryManagerState.TotalAllocatedLargeBlockSize, FPrevMemoryManagerState.TotalAllocatedLargeBlockSize); Inc(LTotalAllocated, FMemoryManagerState.TotalAllocatedLargeBlockSize); Inc(LPrevTotalAllocated, FPrevMemoryManagerState.TotalAllocatedLargeBlockSize); UpdateBlockStatistics(3, LInd, FMemoryManagerState.ReservedLargeBlockAddressSpace, FPrevMemoryManagerState.ReservedLargeBlockAddressSpace); Inc(LTotalReserved, FMemoryManagerState.ReservedLargeBlockAddressSpace); Inc(LPrevTotalReserved, FPrevMemoryManagerState.ReservedLargeBlockAddressSpace); if FMemoryManagerState.ReservedLargeBlockAddressSpace > 0 then sgBlockStatistics.Cells[4, LInd] := FormatFloat('0.##%', FMemoryManagerState.TotalAllocatedLargeBlockSize / FMemoryManagerState.ReservedLargeBlockAddressSpace * 100) else sgBlockStatistics.Cells[4, LInd] := 'N/A'; {-----------Overall--------------} LInd := Length(FMemoryManagerState.SmallBlockTypeStates) + 3; UpdateBlockStatistics(1, Lind, LTotalBlocks, LPrevTotalBlocks); UpdateBlockStatistics(2, Lind, LTotalAllocated, LPrevTotalAllocated); UpdateBlockStatistics(3, Lind, LTotalReserved, LPrevTotalReserved); if LTotalReserved > 0 then sgBlockStatistics.Cells[4, LInd] := FormatFloat('0.##%', LTotalAllocated / LTotalReserved * 100) else sgBlockStatistics.Cells[4, LInd] := 'N/A'; end; procedure UpdateStatisticsData; const CI_MaxFreeBlocksList = 9; var LR_SystemInfo: TSystemInfo; LR_GlobalMemoryStatus: TMemoryStatus; LR_GlobalMemoryStatusEx: TMemoryStatusEx; LR_ProcessMemoryCounters: TProcessMemoryCounters; LR_SysBaseInfo: TSystem_Basic_Information; LU_MinQuota: {$if CompilerVersion >= 23}NativeUInt{$else}Cardinal{$ifend}; LU_MaxQuota: {$if CompilerVersion >= 23}NativeUInt{$else}Cardinal{$ifend}; LI_I: Integer; LI_Max: Integer; begin mVMStatistics.Lines.BeginUpdate; try mVMStatistics.Clear; LU_MinQuota := 0; LU_MaxQuota := 0; if Assigned(MP_GlobalMemoryStatusEx) then begin ZeroMemory(@LR_GlobalMemoryStatusEx, SizeOf(TMemoryStatusEx)); LR_GlobalMemoryStatusEx.dwLength := SizeOf(TMemoryStatusEx); if not MP_GlobalMemoryStatusEx(LR_GlobalMemoryStatusEx) then begin mVMStatistics.Lines.Add('GlobalMemoryStatusEx err: ' + SysErrorMessage(GetLastError)); end; end else begin LR_GlobalMemoryStatus.dwLength := SizeOf(TMemoryStatus); GlobalMemoryStatus(LR_GlobalMemoryStatus); end; LP_FreeVMList.SortList(LocSort); GetProcessWorkingSetSize(GetCurrentProcess, LU_MinQuota, LU_MaxQuota); GetSystemInfo(LR_SystemInfo); with mVMStatistics.Lines do begin Add('System Info:'); Add('------------'); Add('Processor Count = ' + IntToStr(LR_SystemInfo.dwNumberOfProcessors)); Add('Allocation Granularity = ' + IntToStr(LR_SystemInfo.dwAllocationGranularity)); if Assigned(MP_GlobalMemoryStatusEx) then begin with LR_GlobalMemoryStatusEx do begin Add('Available Physical Memory = ' + Int64ToKStringFormatted(ullAvailPhys)); Add('Total Physical Memory = ' + Int64ToKStringFormatted(ullTotalPhys)); Add('Available Virtual Memory = ' + Int64ToKStringFormatted(ullAvailVirtual)); Add('Total Virtual Memory = ' + Int64ToKStringFormatted(ullTotalVirtual)); Add('Total Virtual Extended Memory = ' + Int64ToKStringFormatted(ullAvailExtendedVirtual)); end; end else begin with LR_GlobalMemoryStatus do begin Add('Available Physical Memory = ' + Int64ToKStringFormatted(dwAvailPhys)); Add('Total Physical Memory = ' + Int64ToKStringFormatted(dwTotalPhys)); Add('Available Virtual Memory = ' + Int64ToKStringFormatted(dwAvailVirtual)); Add('Total Virtual Memory = ' + Int64ToKStringFormatted(dwTotalVirtual)); end; end; if Assigned(MP_NtQuerySystemInformation) then begin if MP_NtQuerySystemInformation(SystemBasicInformation, @LR_SysBaseInfo, SizeOf(LR_SysBaseInfo), nil) = 0 then begin with LR_SysBaseInfo do begin Add('Maximum Increment = ' + CardinalToKStringFormatted(uKeMaximumIncrement)); Add('Page Size = ' + CardinalToKStringFormatted(uPageSize)); Add('Number of Physical Pages = ' + CardinalToKStringFormatted(uMmNumberOfPhysicalPages)); Add('Lowest Physical Page = ' + CardinalToStringFormatted(uMmLowestPhysicalPage)); Add('Highest Physical Page = ' + CardinalToKStringFormatted(uMmHighestPhysicalPage)); end; end; end; // same as GetProcessMemoryInfo & NtQuerySystemInformation (SystemBasicInformation // The working set is the amount of memory physically mapped to the process context at a given // time. Memory in the paged pool is system memory that can be transferred to the paging file // on disk (paged) when it is not being used. Memory in the nonpaged pool is system memory // that cannot be paged to disk as long as the corresponding objects are allocated. The pagefile // usage represents how much memory is set aside for the process in the system paging file. // When memory usage is too high, the virtual memory manager pages selected memory to disk. // When a thread needs a page that is not in memory, the memory manager reloads it from the // paging file. if GetProcessMemoryInfo(GetCurrentProcess, @LR_ProcessMemoryCounters, SizeOf(LR_ProcessMemoryCounters)) then begin with LR_ProcessMemoryCounters do begin Add('Page Fault Count = ' + CardinalToKStringFormatted(PageFaultCount)); Add('Peak Working Set Size = ' + Int64ToKStringFormatted(PeakWorkingSetSize)); Add('Working Set Size = ' + Int64ToKStringFormatted(WorkingSetSize)); Add('Quota Peak Paged Pool Usage = ' + Int64ToKStringFormatted(QuotaPeakPagedPoolUsage)); Add('Quota Paged Pool Usage = ' + Int64ToKStringFormatted(QuotaPagedPoolUsage)); Add('Quota Peak Non-Paged Pool Usage = ' + Int64ToKStringFormatted(QuotaPeakNonPagedPoolUsage)); Add('Quota Non-Paged Pool Usage = ' + Int64ToKStringFormatted(QuotaNonPagedPoolUsage)); Add('Pagefile Usage = ' + Int64ToKStringFormatted(PagefileUsage)); Add('Peak Pagefile Usage = ' + Int64ToKStringFormatted(PeakPagefileUsage)); end; end; Add(''); Add('Process Info: PID (' + IntToStr(GetCurrentProcessId) + ')'); Add('------------------------'); Add('Minimum Address = ' + Int64ToKStringFormatted(NativeUInt(LR_SystemInfo.lpMinimumApplicationAddress))); Add('Maximum VM Address = ' + Int64ToKStringFormatted(NativeUInt(LR_SystemInfo.lpMaximumApplicationAddress))); Add('Page Protection & Commit Size = ' + IntToStr(LR_SystemInfo.dWPageSize)); Add(''); Add('Quota info:'); Add('-----------'); Add('Minimum Quota = ' + Int64ToKStringFormatted(LU_MinQuota)); Add('Maximum Quota = ' + Int64ToKStringFormatted(LU_MaxQuota)); Add(''); Add('VM Info:'); Add('--------'); Add('Total Free = ' + Int64ToKStringFormatted(LU_MEM_FREE)); Add('Total Reserve = ' + Int64ToKStringFormatted(LU_MEM_RESERVE)); Add('Total Commit = ' + Int64ToKStringFormatted(LU_MEM_COMMIT)); if LP_FreeVMList.Count > CI_MaxFreeBlocksList then LI_Max := CI_MaxFreeBlocksList - 1 else LI_Max := LP_FreeVMList.Count - 1; for LI_I := 0 to LI_Max do begin Add('Largest Free Block ' + IntToStr(LI_I + 1) + '. = ' + Int64ToKStringFormatted(NativeUInt(LP_FreeVMList.List[LI_I]))); end; Add(''); Add('FastMM4 Info:'); Add('-------------'); Add('Total Blocks = ' + Int64ToKStringFormatted(LTotalBlocks)); Add('Total Allocated = ' + Int64ToKStringFormatted(LTotalAllocated)); Add('Total Reserved = ' + Int64ToKStringFormatted(LTotalReserved)); end; finally mVMStatistics.Lines.EndUpdate; end; end; var Save_Cursor: TCursor; begin if SizeOf(TMemoryMap) <> SizeOf(TMemoryMapEx) then begin Showmessage('Internal implementation error'); Exit; end; LU_MEM_FREE := 0; LU_MEM_COMMIT := 0; LU_MEM_RESERVE := 0; LTotalBlocks := 0; LTotalAllocated := 0; LTotalReserved := 0; LPrevTotalBlocks := 0; LPrevTotalAllocated := 0; LPrevTotalReserved := 0; // Set hourglass cursor Save_Cursor := Screen.Cursor; Screen.Cursor := crHourGlass; LP_FreeVMList := TList.Create; try // retrieve FastMM4 info GetMemoryManagerState(FMemoryManagerState); GetMemoryMap(TMemoryMap(FMemoryMapEx)); // Update FastMM4 Graph with EXE & DLL locations UpdateVMGraph(FMemoryMapEx); // VM dump UpdateVMDump; // FastMM4 data UpdateFastMM4Data; // General Information UpdateStatisticsData; // Screen updates dgMemoryMap.Invalidate; FPrevMemoryManagerState := FMemoryManagerState; finally FreeAndNil(LP_FreeVMList); Screen.Cursor := Save_Cursor; end; end; procedure TfFastMMUsageTracker.sgBlockStatisticsDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); var d: integer; y: integer; s: string; LOldColour, LColour: TColor; begin d := Integer(sgBlockStatistics.Objects[ACol, ARow]); if d <> 0 then begin LOldColour := sgBlockStatistics.Canvas.Brush.Color; if d < 0 then LColour := clLime else LColour := clRed; sgBlockStatistics.Canvas.Brush.Color := LColour; sgBlockStatistics.Canvas.Font.Color := clWindowText; s := sgBlockStatistics.Cells[ACol, ARow]; y := sgBlockStatistics.Canvas.TextHeight(s); y := ((Rect.Bottom - Rect.Top) - y) div 2; sgBlockStatistics.Canvas.TextRect(Rect, Rect.Left + 2, Rect.top + y, s); sgBlockStatistics.Canvas.Brush.Color := LOldColour; end; end; procedure TfFastMMUsageTracker.tTimerTimer(Sender: TObject); begin tTimer.Enabled := False; try RefreshSnapShot; finally tTimer.Enabled := True; end; end; procedure TfFastMMUsageTracker.bCloseClick(Sender: TObject); begin Close; end; procedure TfFastMMUsageTracker.dgMemoryMapDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); var LChunkIndex: integer; LChunkColour: TColor; begin {Get the chunk index} LChunkIndex := ARow * dgMemoryMap.ColCount + ACol; {Get the correct colour} case FMemoryMapEx[LChunkIndex] of csExAllocated: begin LChunkColour := $9090FF; end; csExReserved: begin LChunkColour := $90F090; end; csExSysAllocated: begin LChunkColour := $707070; end; csExSysExe: begin LChunkColour := clRed; end; csExSysDLL: begin LChunkColour := clBlue; end; csExSysReserved: begin LChunkColour := $C0C0C0; end else begin {ExUnallocated} LChunkColour := $FFFFFF; end; end; {Draw the chunk background} dgMemoryMap.Canvas.Brush.Color := LChunkColour; if State = [] then dgMemoryMap.Canvas.FillRect(Rect) else dgMemoryMap.Canvas.Rectangle(Rect); end; procedure TfFastMMUsageTracker.dgMemoryMapSelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean); var LChunkIndex: Cardinal; LMBI: TMemoryBasicInformation; LA_Char: array[0..MAX_PATH] of char; begin eDLLName.Text := ''; LChunkIndex := ARow * dgMemoryMap.ColCount + ACol; eAddress.Text := Format('$%0.8x', [LChunkIndex shl 16]); case FMemoryMapEx[LChunkIndex] of csExAllocated: begin eState.Text := 'FastMM Allocated'; end; csExReserved: begin eState.Text := 'FastMM Reserved'; end; csExSysAllocated: begin eState.Text := 'System Allocated'; end; csExSysExe: begin eState.Text := 'System Exe'; VirtualQuery(Pointer(LChunkIndex shl 16), LMBI, SizeOf(LMBI)); if (GetModuleFileName(dword(LMBI.AllocationBase), LA_Char, MAX_PATH) <> 0) then begin eDLLName.Text := LA_Char; end; end; csExSysDLL: begin eState.Text := 'System/User DLL'; VirtualQuery(Pointer(LChunkIndex shl 16), LMBI, SizeOf(LMBI)); if (GetModuleFileName(dword(LMBI.AllocationBase), LA_Char, MAX_PATH) <> 0) then begin eDLLName.Text := LA_Char; end; end; csExSysReserved: begin eState.Text := 'System Reserved'; end else begin {ExUnallocated} eState.Text := 'Free'; end; end; end; procedure TfFastMMUsageTracker.bUpdateClick(Sender: TObject); begin RefreshSnapShot; end; procedure TfFastMMUsageTracker.ChkAutoUpdateClick(Sender: TObject); begin tTimer.Enabled := ChkAutoUpdate.Checked; end; procedure TfFastMMUsageTracker.ChkSmallGraphClick(Sender: TObject); begin UpdateGraphMetrics; dgMemoryMap.Invalidate; dgMemoryMap.SetFocus; end; procedure TfFastMMUsageTracker.sgVMDumpMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if (Button = mbLeft) and (Shift = [ssLeft]) then begin (Sender as TStringgrid).MouseToCell(X, Y, OR_VMDumpDownCell.X, OR_VMDumpDownCell.Y); end else begin OR_VMDumpDownCell.X := 0; OR_VMDumpDownCell.Y := 0; end; end; procedure TfFastMMUsageTracker.sgVMDumpMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var p: TGridCoord; LGrid: TStringgrid; begin LGrid := Sender as TStringGrid; if (Button = mbLeft) and (Shift = []) then begin LGrid.MouseToCell(X, Y, p.X, p.Y); if CompareMem(@p, @OR_VMDumpDownCell, sizeof(p)) and (p.Y < LGrid.FixedRows) and (p.X >= LGrid.FixedCols) then begin HeaderClicked(LGrid, p); end; end; OR_VMDumpDownCell.X := 0; OR_VMDumpDownCell.Y := 0; end; procedure TfFastMMUsageTracker.sgVMDumpDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); var LGrid: TStringgrid; LMarker: Char; begin LGrid := Sender as TStringgrid; // paint the sort marker on header columns if (ACol >= LGrid.FixedCols) and (aRow = 0) then begin if Assigned(LGrid.Objects[aCol, aRow]) then begin if Integer(LGrid.Objects[aCol, aRow]) > 0 then LMarker := 't' // up wedge in Marlett font else LMarker := 'u'; // down wedge in Marlett font with LGrid.canvas do begin Font.Name := 'Marlett'; Font.Charset := SYMBOL_CHARSET; Font.Size := 12; TextOut(Rect.Right - TextWidth(LMarker), Rect.Top, LMarker); Font := LGrid.font; end; end; end; end; procedure TfFastMMUsageTracker.siMM4AllocationCopyAlltoClipboardClick(Sender: TObject); begin CopyGridContentsToClipBoard(sgBlockStatistics); end; procedure TfFastMMUsageTracker.miVMDumpCopyAlltoClipboardClick(Sender: TObject); begin CopyGridContentsToClipBoard(sgVMDump); end; procedure TfFastMMUsageTracker.miGeneralInformationCopyAlltoClipboardClick(Sender: TObject); begin with mVMStatistics do begin Lines.BeginUpdate; try SelectAll; CopyToClipboard; SelStart := 0; finally Lines.EndUpdate; end; end; end; procedure ModuleInit; begin if Win32Platform = VER_PLATFORM_WIN32_NT then begin MP_GlobalMemoryStatusEx := TP_GlobalMemoryStatusEx( GetProcAddress(GetModuleHandle(kernel32), 'GlobalMemoryStatusEx')); MP_NtQuerySystemInformation := TP_NtQuerySystemInformation( GetProcAddress(GetModuleHandle('ntdll.dll'), 'NtQuerySystemInformation')); end; end; initialization ModuleInit; end.