// 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;
}
}
}