/**
* Copyright 1998-2015 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;
/// If True console symbol parser will ask user to locate the file containing symbols.
public static bool bLoadDefaultSymbols = false;
/// File name associated with this stream.
public string FileName;
/// Array of unique names. Code has fixed indexes into it.
public List NameArray;
/// 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 stack addresses. Code has fixed indexes into it.
public List CallStackAddressArray;
/// Proper symbol parser will be created based on platform.
public ISymbolParser ConsoleSymbolParser = 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 on which these statistics were collected V3.
public EPlatformType Platform;
/// 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 )
{
Platform = Header.Platform;
PlatformName = Header.PlatformName;
NameArray = new List( (int)Header.NameTableEntries );
CallStackArray = new List( (int)Header.CallStackTableEntries );
CallStackAddressArray = new List( (int)Header.CallStackAddressTableEntries );
}
///
/// Returns index of the name, if the name doesn't exit creates a new one if bCreateIfNonExistent is true
/// NOTE: this is slow because it has to do a reverse lookup into NameArray
///
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( NameArray )
{
int NameIndex = NameArray.IndexOf( Name );
if( NameIndex == -1 && bCreateIfNonExistent )
{
NameArray.Add( Name );
NameIndex = NameArray.Count - 1;
}
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( NameArray )
{
for( int NameIndex = 0; NameIndex < NameArray.Count; NameIndex ++ )
{
if( NameArray[NameIndex].Contains( PartialName ) )
{
return NameIndex;
}
}
return -1;
}
}
/// Returns bank size in megabytes.
public static int GetMemoryBankSize( int BankIndex )
{
Debug.Assert( BankIndex >= 0 );
if( FStreamToken.Version >= 4 )
{
return BankIndex == 0 ? 512 : 0;
}
else
{
//private static int[][] MEMORY_BANK_SIZE = new int[][] { new int[] { 512, 1 }, new int[] { 180, 256 } };
//private static int[][] MEMORY_BANK_SIZE_FOR_RENDERED_GRAPHS = new int[][] { new int[] { 512 * 1024 * 1024, 1 }, new int[] { 256 * 1024 * 1024, 256 * 1024 * 1024 } };
if( GlobalInstance.Platform == EPlatformType.Xbox360 || GlobalInstance.Platform == EPlatformType.IPhone )
{
return BankIndex == 0 ? 512 : 0;
}
else if( GlobalInstance.Platform == EPlatformType.PS3 )
{
return BankIndex < 2 ? 256 : 0;
}
else if( ( GlobalInstance.Platform & EPlatformType.AnyWindows ) != EPlatformType.Unknown )
{
// Technically could be much larger, but the graph becomes unusable
// if the scale is too large.
return BankIndex == 0 ? 512 : 0;
}
else
{
throw new Exception( "Unsupported platform" );
}
}
}
/// 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 ] );
while( StreamIndex > FrameStreamIndices[ StartFrame ] && StartFrame + 1 < FrameStreamIndices.Count )
{
StartFrame++;
}
return StartFrame;
}
/// 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( ConsoleSymbolParser is IDisplayCallback )
{
( ( IDisplayCallback )ConsoleSymbolParser ).Shutdown();
}
}
}
/// 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;
};
}