// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using System; using System.IO; using System.Collections.Generic; using System.Text; using System.Reflection; using System.Diagnostics; namespace MemoryProfiler2 { /// The lower 2 bits of a pointer are piggy-bagged to store what kind of data follows it. This enum lists the possible types. public enum EProfilingPayloadType { TYPE_Malloc = 0, TYPE_Free = 1, TYPE_Realloc = 2, TYPE_Other = 3, // Don't add more than 4 values - we only have 2 bits to store this. } /// /// The the case of TYPE_Other, this enum determines the subtype of the token. /// Mirrored in FMallocProfiler.h /// public enum EProfilingPayloadSubType { // Core marker types /// Marker used to determine when malloc profiler stream has ended. SUBTYPE_EndOfStreamMarker = 0, /// Marker used to determine when we need to read data from the next file. SUBTYPE_EndOfFileMarker = 1, /// Marker used to determine when a snapshot has been added. SUBTYPE_SnapshotMarker = 2, /// Marker used to determine when a new frame has started. SUBTYPE_FrameTimeMarker = 3, /// Not used. Only for backward compatibility. Use a new snapshot marker instead. SUBTYPE_TextMarker = 4, // Marker types for periodic non-GMalloc memory status updates. Only for backward compatibility, replaced with SUBTYPE_MemoryAllocationStats /// Marker used to store the total amount of memory used by the game. SUBTYPE_TotalUsed = 5, /// Marker used to store the total amount of memory allocated from the OS. SUBTYPE_TotalAllocated = 6, /// Marker used to store the allocated in use by the application virtual memory. SUBTYPE_CPUUsed = 7, /// Marker used to store the allocated from the OS/allocator, but not used by the application. SUBTYPE_CPUSlack = 8, /// Marker used to store the alignment waste from a pooled allocator plus book keeping overhead. SUBTYPE_CPUWaste = 9, /// Marker used to store the allocated in use by the application physical memory. SUBTYPE_GPUUsed = 10, /// Marker used to store the allocated from the OS, but not used by the application. SUBTYPE_GPUSlack = 11, /// Marker used to store the alignment waste from a pooled allocator plus book keeping overhead. SUBTYPE_GPUWaste = 12, /// Marker used to store the overhead of the operating system. SUBTYPE_OSOverhead = 13, /// Marker used to store the size of loaded executable, stack, static, and global object size. SUBTYPE_ImageSize = 14, /// Version 3 // Marker types for automatic snapshots. /// Marker used to determine when engine has started the cleaning process before loading a new level. SUBTYPE_SnapshotMarker_LoadMap_Start = 21, /// Marker used to determine when a new level has started loading. SUBTYPE_SnapshotMarker_LoadMap_Mid = 22, /// Marker used to determine when a new level has been loaded. SUBTYPE_SnapshotMarker_LoadMap_End = 23, /// Marker used to determine when garbage collection has started. SUBTYPE_SnapshotMarker_GC_Start = 24, /// Marker used to determine when garbage collection has finished. SUBTYPE_SnapshotMarker_GC_End = 25, /// Marker used to determine when a new streaming level has been requested to load. SUBTYPE_SnapshotMarker_LevelStream_Start = 26, /// Marker used to determine when a previously streamed level has been made visible. SUBTYPE_SnapshotMarker_LevelStream_End = 27, /// Marker used to store a generic malloc statistics. @see FMallocProfiler::WriteMemoryAllocationStats SUBTYPE_MemoryAllocationStats = 31, /// Start licensee-specific subtypes from here. SUBTYPE_LicenseeBase = 50, /// Unknown the subtype of the token. SUBTYPE_Unknown, }; /// /// Struct used to hold platform memory stats and allocator stats. Valid only for MemoryProfiler2.FProfileDataHeader.Version >= 4. /// @see FGenericPlatformMemory::GetStatsForMallocProfiler /// @see FMalloc::GetAllocatorStats /// @todo add support for non-generic platform memory stats. /// public class FMemoryAllocationStatsV4 { // Names of generic platform memory stats. @see FGenericPlatformMemory::GetStatsForMallocProfiler / GenericPlatformMemory.cpp /// The amount of actual physical memory, in bytes. static public string PlatformTotalPhysical = "Total Physical"; /// The amount of virtual memory, in bytes. static public string PlatformTotalVirtual = "Total Virtual"; /// The size of a page, in bytes. static public string PlatformPageSize = "Page Size"; /// Approximate physical RAM in GB"; 1 on everything except PC. Used for "course tuning", like FPlatformMisc::NumberOfCores(). static public string PlatformTotalPhysicalGB = "Total Physical GB"; /// The amount of physical memory currently available, in bytes. static public string PlatformAvailablePhysical = "Available Physical"; /// The amount of virtual memory currently available, in bytes. static public string PlatformAvailableVirtual = "Available Virtual"; /// The amount of physical memory used by the process, in bytes. static public string PlatformUsedPhysical = "Used Physical"; /// The peak amount of physical memory used by the process, in bytes. static public string PlatformPeakUsedPhysical = "Peak Used Physical"; /// Total amount of virtual memory used by the process. static public string PlatformUsedVirtual = "Used Virtual"; /// The peak amount of virtual memory used by the process. static public string PlatformPeakUsedVirtual = "Peak Used Virtual"; static public string MemoryProfilingOverhead = "Memory Profiling Overhead"; // Names of malloc binned memory stats. @see FMallocBinned.GetAllocatorStats / MallocBinned.h static public string BinnedWasteCurrent = "Binned Waste Current"; static public string BinnedUsedCurrent = "Binned Used Current"; static public string BinnedSlackCurrent = "Binned Slack Current"; private Dictionary _Stats; public FMemoryAllocationStatsV4() { _Stats = new Dictionary(); } public FMemoryAllocationStatsV4( FMemoryAllocationStatsV4 Other ) { this._Stats = new Dictionary( Other._Stats ); } public Int64 this[string StatName] { get { Int64 Value = 0; _Stats.TryGetValue(StatName, out Value); return Value; } set { _Stats[StatName] = value; } } public Int64 NumStats() { return _Stats.Count; } /// Returns a difference between old and new platform memory stats. public static FMemoryAllocationStatsV4 Diff( FMemoryAllocationStatsV4 Old, FMemoryAllocationStatsV4 New ) { FMemoryAllocationStatsV4 Diff = new FMemoryAllocationStatsV4(); var Keys = Old._Stats.Keys; foreach( string KeyValue in Keys ) { Int64 OldValue = Old[KeyValue]; Int64 NewValue = New[KeyValue]; Diff[KeyValue] = NewValue - OldValue; } return Diff; } /// Creates a new copy of this class. public FMemoryAllocationStatsV4 DeepCopy() { FMemoryAllocationStatsV4 Copy = new FMemoryAllocationStatsV4(this); return Copy; } /// Converts this memory allocation statistics to its equivalent string representation. public override string ToString() { StringBuilder StrBuilder = new StringBuilder( 1024 ); foreach( KeyValuePair KeyPairValue in _Stats ) { StrBuilder.AppendLine( string.Format( " {0}: {1}", KeyPairValue.Key, MainWindow.FormatSizeString2( KeyPairValue.Value ) ) ); } return StrBuilder.ToString(); } }; /// /// Variable sized token emitted by capture code. The parsing code ReadNextToken deals with this and updates /// internal data. The calling code is responsible for only accessing member variables associated with the type. /// public class FStreamToken { // Parsing configuration /// Mask of pointer field to get a real pointer (the two LSB are type fields, and the top bits may be a pool index. public static UInt64 PointerMask = 0xFFFFFFFFFFFFFFFCUL; public const UInt64 TypeMask = 0x3UL; public static int PoolShift = 64; /// Whether to decode the script callstacks. public static bool bDecodeScriptCallstacks; /// Version of the stream, same as FProfileDataHeader.Version public static uint Version; /// Position in the stream. public ulong StreamIndex = 0; /// Type of token. public EProfilingPayloadType Type; /// Subtype of token if it's of TYPE_Other. public EProfilingPayloadSubType SubType; /// Pointer in the case of alloc / free. public UInt64 Pointer; /// Old pointer in the case of realloc. public UInt64 OldPointer; /// New pointer in the case of realloc. public UInt64 NewPointer; /// Index into callstack array. public Int32 CallStackIndex; /// Index into tags array. public Int32 TagsIndex; /// Size of allocation in alloc / realloc case. public Int32 Size; /// Payload if type is TYPE_Other. public UInt32 Payload; /// Payload data if type is TYPE_Other and subtype is SUBTYPE_SnapshotMarker or SUBTYPE_TextMarker. Index into array of unique names. public int TextIndex; /// Payload data if type is TYPE_Other and subtype is SUBTYPE_FrameTimeMarker. Current delta time in seconds. public float DeltaTime; /// Total time, increased every time DeltaTime has been read. public float TotalTime = 0.0f; /// Time between two consecutive snapshot markers. public float ElapsedTime = 0.0f; /// Memory pool. public EMemoryPool Pool; /// Platform dependent memory metrics. public List Metrics = new List(); /// A list of indices into the name table, one for each loaded level including persistent level. public List LoadedLevels = new List(); /// Generic memory allocation stats for V4. public FMemoryAllocationStatsV4 MemoryAllocationStats4 = new FMemoryAllocationStatsV4(); /// Index into script callstack array. public int ScriptCallstackIndex; /// Index into script-object type array. public int ScriptObjectTypeIndex; /// Reads a script callstack. /// Stream to serialize data from void ReadScriptCallstack( BinaryReader BinaryStream ) { if( bDecodeScriptCallstacks ) { ScriptCallstackIndex = BinaryStream.ReadUInt16(); bool bAllocatingScriptObject = ( ScriptCallstackIndex & 0x8000 ) != 0; ScriptCallstackIndex = ScriptCallstackIndex & 0x7FFF; if( ScriptCallstackIndex == 0x7FFF ) { ScriptCallstackIndex = -1; } if( bAllocatingScriptObject ) { int ScriptObjectTypeCompactedName = BinaryStream.ReadInt32(); ScriptObjectTypeIndex = ScriptObjectTypeCompactedName & 0x00FFFFFF; // Number is always 0. int ScriptObjectTypeNumber = ( ScriptObjectTypeCompactedName & 0x7FFFFFFF ) >> 24; } } } /// Reads additional data required for GCM callstacks. /// Stream to serialize data from bool ReadGCMData(BinaryReader BinaryStream, ref UInt32 UnsignedSize ) { // @see FMallocGcmProfiler.h const UInt32 GCM_MEMORY_PROFILER_ID_BIT = 0x80000000; bool bHasGCMData = false; if( ( UnsignedSize & GCM_MEMORY_PROFILER_ID_BIT ) == GCM_MEMORY_PROFILER_ID_BIT ) { // Lower five bits are EAllocationType, upper three bits are EMemoryPool byte AllocationType = BinaryStream.ReadByte(); Pool = FMemoryPoolInfo.ConvertToMemoryPoolFlag( ( EMemoryPoolSerialized )( AllocationType & 0xe0 ) ); UnsignedSize &= ~GCM_MEMORY_PROFILER_ID_BIT; bHasGCMData = true; } return bHasGCMData; } /// Reads platform dependent memory metrics. /// Stream to serialize data from void ReadMetrics(BinaryReader BinaryStream) { // Read the metrics int NumMetrics = BinaryStream.ReadByte(); for (int i = 0; i < NumMetrics; i++) { Metrics.Add(BinaryStream.ReadInt64()); } } /// Reads names of all loaded levels. /// Stream to serialize data from void ReadLoadedLevels( BinaryReader BinaryStream ) { // Read the currently loaded levels int NumLevels = BinaryStream.ReadInt16(); for( int LevelIndex = 0; LevelIndex < NumLevels; LevelIndex++ ) { int LevelNameIndex = BinaryStream.ReadInt32(); LoadedLevels.Add( LevelNameIndex ); } } /// Reads generic memory allocations stats. /// Stream to serialize data from private void ReadMemoryAllocationsStats( BinaryReader BinaryStream ) { // Read Platform Memory and Allocator Stats // @see FMallocProfiler::WriteMemoryAllocationStats int StatsNum = BinaryStream.ReadByte(); for( int StatIndex = 0; StatIndex < StatsNum; StatIndex++ ) { int NameIndex = BinaryStream.ReadInt32(); Int64 StatValue = BinaryStream.ReadInt64(); string StatName = FStreamInfo.GlobalInstance.NameArray[NameIndex]; MemoryAllocationStats4[StatName] = StatValue; } } /// Updates the token with data read from passed in stream and returns whether we've reached the end. /// Stream to serialize data from public bool ReadNextToken( BinaryReader BinaryStream ) { bool bReachedEndOfStream = false; // Initialize to defaults. SubType = EProfilingPayloadSubType.SUBTYPE_Unknown; TextIndex = -1; // Read the pointer and convert to token type by looking at lowest 2 bits. Pointers are always // 4 byte aligned so need to clear them again after the conversion. UInt64 RawPointerData = BinaryStream.ReadUInt64(); Pool = EMemoryPool.MEMPOOL_Main; Type = (EProfilingPayloadType)(RawPointerData & TypeMask); Pointer = RawPointerData & PointerMask; Metrics.Clear(); LoadedLevels.Clear(); CallStackIndex = -1; TagsIndex = -1; ScriptCallstackIndex = -1; ScriptObjectTypeIndex = -1; NewPointer = 0; OldPointer = 0; Size = -1; Payload = 0; DeltaTime = -1.0f; // Serialize based on token type. switch( Type ) { // Malloc case EProfilingPayloadType.TYPE_Malloc: { // Get the call stack index. CallStackIndex = BinaryStream.ReadInt32(); // Get the tags index. if (Version >= 7) { TagsIndex = BinaryStream.ReadInt32(); } // Get the size of an allocation. UInt32 UnsignedSize = BinaryStream.ReadUInt32(); // Read GCM data if any. bool bHasGCMData = ReadGCMData( BinaryStream, ref UnsignedSize ); // If GCM doesn't exist read script callstack. if( bHasGCMData == false ) { ReadScriptCallstack( BinaryStream ); } Size = ( int )UnsignedSize; break; } // Free case EProfilingPayloadType.TYPE_Free: { break; } // Realloc case EProfilingPayloadType.TYPE_Realloc: { OldPointer = Pointer; NewPointer = BinaryStream.ReadUInt64(); CallStackIndex = BinaryStream.ReadInt32(); // Get the tags index. if (Version >= 7) { TagsIndex = BinaryStream.ReadInt32(); } UInt32 UnsignedSize = BinaryStream.ReadUInt32(); bool bHasGCMData = ReadGCMData( BinaryStream, ref UnsignedSize ); if( bHasGCMData == false ) { ReadScriptCallstack( BinaryStream ); } Size = ( int )UnsignedSize; break; } // Other case EProfilingPayloadType.TYPE_Other: { SubType = ( EProfilingPayloadSubType )BinaryStream.ReadInt32(); Payload = BinaryStream.ReadUInt32(); // Read subtype. switch( SubType ) { // End of stream! case EProfilingPayloadSubType.SUBTYPE_EndOfStreamMarker: { ReadMemoryAllocationsStats( BinaryStream ); ReadLoadedLevels( BinaryStream ); bReachedEndOfStream = true; break; } case EProfilingPayloadSubType.SUBTYPE_EndOfFileMarker: { break; } case EProfilingPayloadSubType.SUBTYPE_SnapshotMarker_LoadMap_Start: case EProfilingPayloadSubType.SUBTYPE_SnapshotMarker_LoadMap_Mid: case EProfilingPayloadSubType.SUBTYPE_SnapshotMarker_LoadMap_End: case EProfilingPayloadSubType.SUBTYPE_SnapshotMarker_GC_Start: case EProfilingPayloadSubType.SUBTYPE_SnapshotMarker_GC_End: case EProfilingPayloadSubType.SUBTYPE_SnapshotMarker_LevelStream_Start: case EProfilingPayloadSubType.SUBTYPE_SnapshotMarker_LevelStream_End: case EProfilingPayloadSubType.SUBTYPE_SnapshotMarker: { TextIndex = ( int )Payload; ReadMemoryAllocationsStats( BinaryStream ); ReadLoadedLevels( BinaryStream ); break; } case EProfilingPayloadSubType.SUBTYPE_FrameTimeMarker: { DeltaTime = BitConverter.ToSingle( System.BitConverter.GetBytes( Payload ), 0 ); TotalTime += DeltaTime; ElapsedTime += DeltaTime; break; } case EProfilingPayloadSubType.SUBTYPE_TextMarker: { TextIndex = ( int )Payload; break; } case EProfilingPayloadSubType.SUBTYPE_MemoryAllocationStats: { ReadMemoryAllocationsStats( BinaryStream ); break; } case EProfilingPayloadSubType.SUBTYPE_TotalUsed: case EProfilingPayloadSubType.SUBTYPE_TotalAllocated: case EProfilingPayloadSubType.SUBTYPE_CPUUsed: case EProfilingPayloadSubType.SUBTYPE_CPUSlack: case EProfilingPayloadSubType.SUBTYPE_CPUWaste: case EProfilingPayloadSubType.SUBTYPE_GPUUsed: case EProfilingPayloadSubType.SUBTYPE_GPUSlack: case EProfilingPayloadSubType.SUBTYPE_GPUWaste: case EProfilingPayloadSubType.SUBTYPE_ImageSize: case EProfilingPayloadSubType.SUBTYPE_OSOverhead: { break; } default: { throw new InvalidDataException(); } } break; } } return !bReachedEndOfStream; } } }