// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics; namespace MemoryProfiler2 { /// Helper class containing information shared for all snapshots of a given stream. public class FStreamInfo { public const ulong INVALID_STREAM_INDEX = ulong.MaxValue; /// Global public stream info so we don't need to pass it around. public static FStreamInfo GlobalInstance = null; /// File name associated with this stream. public string FileName; /// Meta-data associated with this stream. public Dictionary MetaData; /// Array of unique tags. Code has fixed indexes into it. public List TagsArray; /// Hierarchy of tags built as they are parsed. public FAllocationTagHierarchy TagHierarchy = new FAllocationTagHierarchy(); /// Array of unique names. Code has fixed indexes into it. private List InternalNameArray; private Dictionary InternalNameIndexLookupMap; public IList NameArray { get { return InternalNameArray.AsReadOnly(); } } /// Array of unique names used for script, object types, etc... (FName table from runtime). public List ScriptNameArray; /// Array of unique call stacks. Code has fixed indexes into it. public List CallStackArray; /// Array of unique call stacks. Code has fixed indexes into it. public List ModuleInfoArray; /// Array of unique call stack addresses. Code has fixed indexes into it. public List CallStackAddressArray; /// Proper symbol parser will be created based on platform. public ISymbolParser SymbolParser = null; /// Memory pool ranges for all pools. public FMemoryPoolInfoCollection MemoryPoolInfo = new FMemoryPoolInfoCollection(); /// Array of frame indices located in the stream. public List FrameStreamIndices = new List(); // 2011-Sep-08, 16:20 JarekS@TODO This should be replaced with GCurrentTime for more accurate results?? /// Array of delta time for all frames. public List DeltaTimeArray = new List(); /// List of snapshots created by parser and used by various visualizers and dumping tools. public List SnapshotList = new List(); /// Platform that was captured V4. public string PlatformName; /// Profiling options, default all options are enabled. UI is not provided yet. public ProfilingOptions CreationOptions; /// Array of script callstacks. public List ScriptCallstackArray; /// Index of UObject::ProcessInternal function in the main names array. public int ProcessInternalNameIndex; /// Index of UObject::StaticAllocateObject function in the main names array. public int StaticAllocateObjectNameIndex; /// /// Array of indices to all functions related to UObject Virtual Machine in the main names array. /// This array includes following functions: /// UObject::exec* /// UObject::CallFunction /// UObject::ProcessEvent /// public List ObjectVMFunctionIndexArray = new List( 64 ); /// Mapping from the script-function name ([Script] package.class:function) in the main names array to the index in the main callstacks array. public Dictionary ScriptCallstackMapping = new Dictionary(); /// Mapping from the script-object name ([Type] UObject::StaticAllocateObject) in the main names array to the index in the main callstacks array. public Dictionary ScriptObjectTypeMapping = new Dictionary(); /// True if some callstacks has been allocated to multiple pools. public bool bHasMultiPoolCallStacks; /// Constructor, associating this stream info with a filename. /// FileName to use for this stream public FStreamInfo( string InFileName, ProfilingOptions InCreationOptions ) { FileName = InFileName; CreationOptions = new ProfilingOptions( InCreationOptions, true ); } /// Initializes and sizes the arrays. Size is known as soon as header is serialized. public void Initialize( FProfileDataHeader Header ) { PlatformName = Header.PlatformName; MetaData = new Dictionary( (int)Header.MetaDataTableEntries ); TagsArray = new List( (int)Header.TagsTableEntries ); InternalNameArray = new List( (int)Header.NameTableEntries ); InternalNameIndexLookupMap = new Dictionary( (int)Header.NameTableEntries ); CallStackArray = new List( (int)Header.CallStackTableEntries ); CallStackAddressArray = new List( (int)Header.CallStackAddressTableEntries ); ModuleInfoArray = new List((int)Header.ModuleEntries); } /// /// Returns index of the name, if the name doesn't exit creates a new one if bCreateIfNonExistent is true /// public int GetNameIndex( string Name, bool bCreateIfNonExistent ) { // This is the only method where there should be concurrent access of NameArray, so // don't worry about locking it anywhere else. lock(InternalNameArray) { int NameIndex; if (!InternalNameIndexLookupMap.TryGetValue(Name, out NameIndex)) { NameIndex = -1; } if (NameIndex == -1 && bCreateIfNonExistent) { InternalNameArray.Add(Name); NameIndex = InternalNameArray.Count - 1; InternalNameIndexLookupMap.Add(Name, NameIndex); } return NameIndex; } } /// /// If found returns index of the name, returns –1 otherwise. /// This method uses method Contains during looking up for the name. /// NOTE: this is slow because it has to do a reverse lookup into NameArray /// /// Partial name that we want find the index. public int GetNameIndex( string PartialName ) { // This is the only method where there should be concurrent access of NameArray, so // don't worry about locking it anywhere else. lock(InternalNameArray) { for( int NameIndex = 0; NameIndex < InternalNameArray.Count; NameIndex ++ ) { if(InternalNameArray[NameIndex].Contains( PartialName ) ) { return NameIndex; } } return -1; } } /// Returns bank size in megabytes. public static int GetMemoryBankSize( int BankIndex ) { Debug.Assert( BankIndex >= 0 ); return BankIndex == 0 ? 512 : 0; } /// Returns a frame number based on the stream index. public int GetFrameNumberFromStreamIndex( int StartFrame, ulong StreamIndex ) { // Check StartFrame is in the correct range. Debug.Assert( StartFrame >= 1 && StartFrame < FrameStreamIndices.Count ); // Check StartFrame is valid. Debug.Assert( StreamIndex != INVALID_STREAM_INDEX ); // The only case where these can be equal is if they are both 0. Debug.Assert( StreamIndex >= FrameStreamIndices[ StartFrame - 1 ] ); int FoundFrame = FrameStreamIndices.BinarySearch(StartFrame, FrameStreamIndices.Count - StartFrame, StreamIndex, null); if (FoundFrame < 0) { FoundFrame = ~FoundFrame; } return FoundFrame; } /// Returns the time of the stream index. /// Stream index that we want to know the time. public float GetTimeFromStreamIndex( ulong StreamIndex ) { // Check StartFrame is valid. Debug.Assert( StreamIndex != INVALID_STREAM_INDEX ); // Check StreamIndex is in the correct range. Debug.Assert( StreamIndex >= 0 && StreamIndex < FrameStreamIndices[ FrameStreamIndices.Count - 1 ] ); int FrameNumber = GetFrameNumberFromStreamIndex( 1, StreamIndex ); float Time = GetTimeFromFrameNumber( FrameNumber ); return Time; } /// Returns the time of the frame. /// Frame number that we want to know the time. public float GetTimeFromFrameNumber( int FrameNumber ) { // Check StartFrame is in the correct range. Debug.Assert( FrameNumber >= 0 && FrameNumber < FrameStreamIndices.Count ); return DeltaTimeArray[FrameNumber]; } /// Returns the total time of the frame. /// Frame number that we want to know the total time. public float GetTotalTimeFromFrameNumber( int FrameNumber ) { // Check StartFrame is in the correct range. Debug.Assert( FrameNumber >= 0 && FrameNumber < FrameStreamIndices.Count ); float TotalTime = 0.0f; for( int FrameIndex = 0; FrameIndex < FrameNumber; FrameIndex ++ ) { TotalTime += DeltaTimeArray[ FrameIndex ]; } return TotalTime; } public void Shutdown() { if (SymbolParser != null) { SymbolParser.ShutdownSymbolService(); } } } /// Helper class for logging timing in cases scopes. /// /// Example of usage /// /// using( FScopedLogTimer ParseTiming = new FScopedLogTimer( "HistogramParser.ParseSnapshot" ) ) /// { /// code... /// } /// /// public class FScopedLogTimer : IDisposable { /// Constructor. /// Global tag to use for logging public FScopedLogTimer( string InDescription ) { Description = InDescription; Timer = new Stopwatch(); Start(); } public void Start() { Timer.Start(); } public void Stop() { Timer.Stop(); } /// Destructor, logging delta time from constructor. public void Dispose() { Debug.WriteLine( Description + " took " + ( float )Timer.ElapsedMilliseconds / 1000.0f + " seconds to finish" ); } /// Global tag to use for logging. protected string Description; /// Timer used to measure the timing. protected Stopwatch Timer; } public class FGlobalTimer { public FGlobalTimer( string InName ) { Name = InName; } public void Start() { CurrentTicks = DateTime.Now.Ticks; } public void Stop() { long ElapsedTicks = DateTime.Now.Ticks - CurrentTicks; TotalTicks += ElapsedTicks; CallsNum++; } public override string ToString() { return string.Format( "Global timer for {0} took {1} ms with {2} calls", Name, ( float )TotalTicks / 10000.0f, CallsNum ); } long CallsNum = 0; string Name; Stopwatch Timer = new Stopwatch(); long TotalTicks = 0; long CurrentTicks = 0; }; }