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