// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
using System;
using System.IO;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Xml.Serialization;
namespace MemoryProfiler2
{
public class FCallStackFunctionFilter
{
public enum EFilterMode
{
SubString,
StartsWith,
EndsWith,
RegEx,
}
public FCallStackFunctionFilter()
{
FilterMode = EFilterMode.SubString;
}
public FCallStackFunctionFilter(string InFilterPattern, EFilterMode InFilterMode)
{
FilterPattern = InFilterPattern;
FilterMode = InFilterMode;
CompileExpression();
}
void CompileExpression()
{
if (FilterMode == EFilterMode.RegEx)
{
CompiledRegex = new Regex(FilterPattern, RegexOptions.Compiled | RegexOptions.CultureInvariant);
}
else
{
CompiledRegex = null;
}
}
public bool EvaluateExpression(string ToEvaluate)
{
switch (FilterMode)
{
case EFilterMode.SubString:
return ToEvaluate.Contains(FilterPattern);
case EFilterMode.StartsWith:
return ToEvaluate.StartsWith(FilterPattern);
case EFilterMode.EndsWith:
return ToEvaluate.EndsWith(FilterPattern);
case EFilterMode.RegEx:
return CompiledRegex.Match(ToEvaluate).Success;
}
return false;
}
[XmlIgnore]
[Browsable(false)]
public string Name
{
get { return FilterMode.ToString() + " " + FilterPattern; }
}
string _FilterPattern;
[Description("The filter pattern to match.")]
[XmlAttribute]
public string FilterPattern
{
get
{
return _FilterPattern;
}
set
{
_FilterPattern = value;
CompileExpression();
}
}
EFilterMode _FilterMode;
[Description("How to process the filter pattern.")]
[XmlAttribute]
public EFilterMode FilterMode
{
get
{
return _FilterMode;
}
set
{
_FilterMode = value;
CompileExpression();
}
}
[XmlIgnore]
[Browsable(false)]
Regex CompiledRegex;
}
/// Encapsulates callstack information.
public class FCallStack
{
/// CRC of callstack pointers.
private Int32 CRC;
/// Callstack as indices into address list, from top to bottom.
public List AddressIndices;
/// First entry in the callstack that is *not* a container.
public int FirstNonContainer;
/// The class group that this callstack is associated with.
public ClassGroup Group;
/// Whether this callstack is truncated.
public bool bIsTruncated;
/// Memory pool that this callstack belongs to.
public EMemoryPool MemoryPool = EMemoryPool.MEMPOOL_None;
/// Maximum amount of memory that has been allocated in this callstack.
public long MaxSize;
/// Current amount of memory that is allocated in this callstack.
public long LatestSize;
/// The last processed stream index
private ulong LastStreamIndex = ulong.MaxValue;
/// Array of graph points used to draw a timeline graph in the callstack history view.
public List SizeGraphPoints;
/// Array of allocations that have been allocated and then freed.
public List CompleteLifecycles;
/// Array of allocations that are still in memory.
public Dictionary IncompleteLifecycles;
///
/// Reference to the original callstack.
/// Any callstacks that differ only by script callstack should set this field to the original FCallStack they were copied from.
///
public FCallStack Original;
/// Array of virtual callstacks. Callstacks with decoded script callstack or script object type.
public List Children = new List();
/// Indices of virtual callstacks into the array of unique callstacks.
public List ChildIndices = new List();
/// Script callstack. May be null if there is no associated script callstack.
public FScriptCallStack ScriptCallStack;
/// Script object type. Occurs when a script object is allocated using StaticAllocateObject method.
public FScriptObjectType ScriptObjectType;
/// Default constructor.
private FCallStack()
{
AddressIndices = new List();
SizeGraphPoints = FStreamInfo.GlobalInstance.CreationOptions.GenerateSizeGraphsCheckBox.Checked ? new List() : null;
CompleteLifecycles = FStreamInfo.GlobalInstance.CreationOptions.KeepLifecyclesCheckBox.Checked ? new List() : null;
IncompleteLifecycles = new Dictionary();
}
/// Serializing constructor.
/// Stream to serialize data from
public FCallStack(BinaryReader BinaryStream)
: this()
{
// Read CRC of original callstack.
CRC = BinaryStream.ReadInt32();
// Read call stack address indices and parse into arrays.
int AddressIndex = BinaryStream.ReadInt32();
while (AddressIndex >= 0)
{
AddressIndices.Add(AddressIndex);
AddressIndex = BinaryStream.ReadInt32();
}
// Normal callstacks are -1 terminated, whereof truncated ones are -2 terminated.
if (AddressIndex == -2)
{
bIsTruncated = true;
}
else
{
bIsTruncated = false;
}
FirstNonContainer = AddressIndices.Count - 1;
// We added bottom to top but prefer top bottom for hierarchical view.
AddressIndices.Reverse();
}
/// Based on original callstack initializes a new callstack with decoded script callstack and script object type.
public FCallStack(FCallStack InOriginal, FScriptCallStack InScriptCallStack, FScriptObjectType InScriptObjectType, int InCallStackIndex)
: this()
{
Debug.Assert(InOriginal != null);
Debug.Assert(ScriptCallStack == null || ScriptCallStack.Frames.Length > 0);
Original = InOriginal;
ScriptCallStack = InScriptCallStack;
ScriptObjectType = InScriptObjectType;
Original.Children.Add(this);
Original.ChildIndices.Add(InCallStackIndex);
CRC = Original.CRC;
AddressIndices = new List(Original.AddressIndices);
FirstNonContainer = AddressIndices.Count - 1;
bIsTruncated = Original.bIsTruncated;
// If there is a script call stack, rename functions
if (ScriptCallStack != null)
{
int ScriptFrameIndex = 0;
for (int AddressIndex = AddressIndices.Count - 1; AddressIndex >= 0; AddressIndex--)
{
int FunctionNameIndex = FStreamInfo.GlobalInstance.CallStackAddressArray[AddressIndices[AddressIndex]].FunctionIndex;
if (FunctionNameIndex == FStreamInfo.GlobalInstance.ProcessInternalNameIndex)
{
AddressIndices[AddressIndex] = ScriptCallStack.Frames[ScriptFrameIndex].CallStackAddressIndex;
ScriptFrameIndex++;
if (ScriptFrameIndex >= ScriptCallStack.Frames.Length)
{
break;
}
}
}
}
// If the call stack has a script type allocation, replace the StaticAllocateObject call with the appropriate type-tagged one
if (ScriptObjectType != null)
{
for (int AddressIndex = AddressIndices.Count - 1; AddressIndex >= 0; AddressIndex--)
{
int FunctionNameIndex = FStreamInfo.GlobalInstance.CallStackAddressArray[AddressIndices[AddressIndex]].FunctionIndex;
if (FunctionNameIndex == FStreamInfo.GlobalInstance.StaticAllocateObjectNameIndex)
{
AddressIndices[AddressIndex] = ScriptObjectType.CallStackAddressIndex;
break;
}
}
}
}
/// Array of common names used to find the first non templated argument in the callstack.
static private List CommonNames = new List()
{
"operator new<",
"operator<<",
">::",
"FString::operator=",
"FStringNoInit::operator=",
"FString::FString",
"FBestFitAllocator::",
"FHeapAllocator::",
"appMalloc",
"appRealloc"
};
/// Find the first non templated argument in the callstack.
public void EvaluateFirstNonContainer()
{
for (int AddressIndex = AddressIndices.Count - 1; AddressIndex > 0; AddressIndex--)
{
bool bIsContainer = false;
string FunctionName = FStreamInfo.GlobalInstance.NameArray[FStreamInfo.GlobalInstance.CallStackAddressArray[AddressIndices[AddressIndex]].FunctionIndex];
// See if the function name is one of the common set to ignore
foreach (string CommonName in CommonNames)
{
if (FunctionName.Contains(CommonName))
{
bIsContainer = true;
break;
}
}
// if none are templates - we're good!
if (!bIsContainer)
{
FirstNonContainer = AddressIndex;
break;
}
}
}
/// Compares two callstacks for sorting.
/// First callstack to compare
/// Second callstack to compare
public static int Compare(FCallStack A, FCallStack B)
{
// Not all callstacks have the same depth. Figure out min for comparision.
int MinSize = Math.Min(A.AddressIndices.Count, B.AddressIndices.Count);
// Iterate over matching size and compare.
for (int i = 0; i < MinSize; i++)
{
// Sort by address
if (A.AddressIndices[i] > B.AddressIndices[i])
{
return 1;
}
else if (A.AddressIndices[i] < B.AddressIndices[i])
{
return -1;
}
}
// If we got here it means that the subset of addresses matches. In theory this means
// that the callstacks should have the same size as you can't have the same address
// doing the same thing, but let's simply be thorough and handle this case if the
// stackwalker isn't 100% accurate.
// Matching length means matching callstacks.
if (A.AddressIndices.Count == B.AddressIndices.Count)
{
return 0;
}
// Sort by additional length.
else
{
return A.AddressIndices.Count > B.AddressIndices.Count ? 1 : -1;
}
}
/// Adds callstack information into the listview.
public void AddToListView(ListView CallStackListView, bool bShowFromBottomUp)
{
for (int AdressIndex = 0; AdressIndex < AddressIndices.Count; AdressIndex++)
{
// Handle iterating over addresses in reverse order.
int AddressIndexIndex = bShowFromBottomUp ? AddressIndices.Count - 1 - AdressIndex : AdressIndex;
FCallStackAddress Address = FStreamInfo.GlobalInstance.CallStackAddressArray[AddressIndices[AddressIndexIndex]];
string[] Row = new string[]
{
FStreamInfo.GlobalInstance.NameArray[Address.FunctionIndex],
FStreamInfo.GlobalInstance.NameArray[Address.FilenameIndex],
Address.LineNumber.ToString()
};
CallStackListView.Items.Add(new ListViewItem(Row));
}
}
/// Removes entries related to allocation or the malloc profilers.
public void TrimAllocatorEntries(List AllocatorFunctionFilters)
{
if (AllocatorFunctionFilters.Count == 0)
{
return;
}
bool bFoundAllocator = false;
for (int AddressIndex = AddressIndices.Count - 1; AddressIndex >= 0; AddressIndex--)
{
string FunctionName = FStreamInfo.GlobalInstance.NameArray[FStreamInfo.GlobalInstance.CallStackAddressArray[AddressIndices[AddressIndex]].FunctionIndex];
bool bFunctionIsAllocator = false;
foreach (var AllocatorFunctionFilter in AllocatorFunctionFilters)
{
if (AllocatorFunctionFilter.EvaluateExpression(FunctionName))
{
bFunctionIsAllocator = true;
break;
}
}
if (bFunctionIsAllocator)
{
bFoundAllocator = true;
}
else if (bFoundAllocator)
{
AddressIndices.RemoveRange(AddressIndex + 1, AddressIndices.Count - AddressIndex - 1);
break;
}
}
}
/// Removes all functions related to UObject Virtual Machine.
public void FilterOutObjectVMFunctions()
{
for (int AddressIndex = AddressIndices.Count - 1; AddressIndex >= 0; AddressIndex--)
{
int FunctionIndex = FStreamInfo.GlobalInstance.CallStackAddressArray[AddressIndices[AddressIndex]].FunctionIndex;
string FunctionName = FStreamInfo.GlobalInstance.NameArray[FunctionIndex];
// Works only on PS3.
bool bIsActorExecFunction = FunctionName.Contains("::exec") && FunctionName.Contains("FFrame&, void*");
bool bIsVMFunction = FStreamInfo.GlobalInstance.ObjectVMFunctionIndexArray.Contains(FunctionIndex) || bIsActorExecFunction;
if (bIsVMFunction)
{
AddressIndices.RemoveAt(AddressIndex);
}
}
}
///
/// Filters this callstack based on passed in filter. This can either be an inclusion or exclusion.
/// An inclusion means the test will pass if any of the addresses in the callstack match the inclusion
/// filter passed in.
///
/// Filter to use
/// TRUE if callstack passes filter, FALSE otherwise
public bool PassesTextFilterTest(string FilterText)
{
// Check whether any of the addresses in the call graph match the filter.
bool bIsMatch = false;
foreach (int AddressIndex in AddressIndices)
{
string FunctionName = FStreamInfo.GlobalInstance.NameArray[FStreamInfo.GlobalInstance.CallStackAddressArray[AddressIndex].FunctionIndex];
bIsMatch = FunctionName.ToUpperInvariant().Contains(FilterText);
if (bIsMatch)
{
break;
}
}
return bIsMatch;
}
/// Filter callstacks in based on the text filter AND the class filter.
private bool FilterIn(string FilterInText, List ClassGroups, EMemoryPool MemoryPoolFilter)
{
// Check against memory pool filter
if ((MemoryPoolFilter & MemoryPool) != EMemoryPool.MEMPOOL_None)
{
// Check against the simple text filter
if ((FilterInText.Length == 0) || PassesTextFilterTest(FilterInText))
{
// Check against any active classes
if (ClassGroups.Contains(Group))
{
return (true);
}
}
}
return (false);
}
/// Filter callstacks out based on the text filter AND the class filter.
private bool FilterOut(string FilterInText, List ClassGroups, EMemoryPool MemoryPoolFilter)
{
if ((MemoryPoolFilter & MemoryPool) != EMemoryPool.MEMPOOL_None)
{
// This callstack is in the selected pool, so filter it out
return false;
}
// Check against the simple text filter
if ((FilterInText.Length > 0) && PassesTextFilterTest(FilterInText))
{
// Found match, we do not want this callstack
return (false);
}
// Check against any active classes
if (ClassGroups.Contains(Group))
{
return (false);
}
return (true);
}
/// Runs all the current filters on this callstack.
public bool RunFilters(string FilterInText, List ClassGroups, bool bFilterInClasses, EMemoryPool MemoryPoolFilter)
{
// Create a list of active groups
List ActiveGroups = new List();
foreach (ClassGroup Group in ClassGroups)
{
if (Group.bFilter)
{
ActiveGroups.Add(Group);
}
}
// Filter groups in or out
if (bFilterInClasses)
{
return FilterIn(FilterInText, ActiveGroups, MemoryPoolFilter);
}
else
{
return FilterOut(FilterInText, ActiveGroups, MemoryPoolFilter);
}
}
/// Processes malloc operation for this callstack and updates lifecycles if needed.
public void ProcessMalloc(FStreamToken StreamToken, ref FAllocationLifecycle NewLifecycle)
{
// Initialize a new lifecycle and add it to the array of incomplete lifecycles.
NewLifecycle.Malloc(StreamToken, null, null);
IncompleteLifecycles.Add(NewLifecycle.LatestPointer, NewLifecycle);
NewLifecycle = null;
LatestSize += StreamToken.Size;
if (LatestSize > MaxSize)
{
MaxSize = LatestSize;
}
if (SizeGraphPoints != null)
{
Debug.Assert(SizeGraphPoints.Count == 0 || SizeGraphPoints[SizeGraphPoints.Count - 1].StreamIndex != StreamToken.StreamIndex);
SizeGraphPoints.Add(new FSizeGraphPoint(StreamToken.StreamIndex, StreamToken.Size, false));
}
}
/// Processes free operation for this callstack and updates lifecycles if needed.
public FAllocationLifecycle ProcessFree(FStreamToken StreamToken)
{
int SizeChange = 0;
FAllocationLifecycle Result = null;
FAllocationLifecycle Lifecycle;
if (IncompleteLifecycles.TryGetValue(StreamToken.Pointer, out Lifecycle))
{
SizeChange = -Lifecycle.CurrentSize;
Lifecycle.Free(StreamToken);
if (FStreamInfo.GlobalInstance.CreationOptions.KeepLifecyclesCheckBox.Checked)
{
CompleteLifecycles.Add(Lifecycle);
}
IncompleteLifecycles.Remove(StreamToken.Pointer);
Result = Lifecycle;
}
else
{
// this should be caught by the stream parser, but an extra check doesn't hurt
Debug.WriteLine("Free without malloc! StreamIndex = " + StreamToken.StreamIndex);
}
LatestSize += SizeChange;
// it's possible that this point was already added to the graph via realloc chain propagation
if (SizeGraphPoints != null && (SizeGraphPoints.Count == 0 || SizeGraphPoints[SizeGraphPoints.Count - 1].StreamIndex != StreamToken.StreamIndex))
{
SizeGraphPoints.Add(new FSizeGraphPoint(StreamToken.StreamIndex, SizeChange, false));
}
return Result;
}
/// Processes realloc operation for this callstack and updates lifecycles if needed.
public FAllocationLifecycle ProcessRealloc(FStreamToken StreamToken, ref FAllocationLifecycle NewLifecycle, FCallStack PreviousCallStack, FAllocationLifecycle PreviousLifecycle)
{
FAllocationLifecycle Result = null;
int SizeChange = 0;
bool bFreshRealloc = true;
FAllocationLifecycle Lifecycle;
if (IncompleteLifecycles.TryGetValue(StreamToken.OldPointer, out Lifecycle))
{
IncompleteLifecycles.Remove(StreamToken.OldPointer);
Lifecycle.Realloc(StreamToken, this, out SizeChange);
if (Lifecycle.bIsComplete)
{
if (FStreamInfo.GlobalInstance.CreationOptions.KeepLifecyclesCheckBox.Checked)
{
CompleteLifecycles.Add(Lifecycle);
}
}
else
{
IncompleteLifecycles.Add(Lifecycle.LatestPointer, Lifecycle);
}
bFreshRealloc = false;
Result = Lifecycle;
}
else
{
Debug.Assert(NewLifecycle != null);
NewLifecycle.Malloc(StreamToken, PreviousCallStack, PreviousLifecycle);
Result = NewLifecycle;
NewLifecycle = null;
IncompleteLifecycles.Add(Result.LatestPointer, Result);
bFreshRealloc = true;
SizeChange = StreamToken.Size;
}
LatestSize += SizeChange;
if (LatestSize > MaxSize)
{
MaxSize = LatestSize;
}
// it's possible that this point was already added to the graph via realloc chain propagation
if (SizeGraphPoints != null && (SizeGraphPoints.Count == 0 || SizeGraphPoints[SizeGraphPoints.Count - 1].StreamIndex != StreamToken.StreamIndex))
{
SizeGraphPoints.Add(new FSizeGraphPoint(StreamToken.StreamIndex, SizeChange, bFreshRealloc));
}
return Result;
}
public void PropagateSizeGraphPoint(FAllocationLifecycle Lifecycle, ulong StreamIndex, int SizeChange)
{
#if NOT_ENABLED
if (SizeGraphPoints == null)
{
return;
}
PropagateSizeGraphPointInner(StreamIndex, SizeChange);
FCallStack PreviousCallStack = Lifecycle.AllocEvent.PreviousCallStack;
FAllocationLifecycle PreviousLifecycle = Lifecycle.AllocEvent.PreviousLifecycle;
while (PreviousLifecycle != null)
{
if (PreviousCallStack.SizeGraphPoints == null)
{
break;
}
PreviousCallStack.PropagateSizeGraphPointInner(StreamIndex, SizeChange);
PreviousCallStack = PreviousLifecycle.AllocEvent.PreviousCallStack;
PreviousLifecycle = PreviousLifecycle.AllocEvent.PreviousLifecycle;
}
#endif
}
private void PropagateSizeGraphPointInner(ulong StreamIndex, int SizeChange)
{
if (LastStreamIndex == StreamIndex)
{
FSizeGraphPoint LastPoint = SizeGraphPoints[SizeGraphPoints.Count - 1];
LatestSize += SizeChange;
if (LatestSize > MaxSize)
{
MaxSize = LatestSize;
}
SizeGraphPoints[SizeGraphPoints.Count - 1] = new FSizeGraphPoint(StreamIndex, LastPoint.SizeChange + SizeChange, false);
}
else
{
LatestSize += SizeChange;
if (LatestSize > MaxSize)
{
MaxSize = LatestSize;
}
SizeGraphPoints.Add(new FSizeGraphPoint(StreamIndex, SizeChange, false));
LastStreamIndex = StreamIndex;
}
}
};
///
/// Represents a single allocation, so size can't be greater than 2GB.
/// There are a lot of these objects, so keeping the size in 32-bits
/// saves a significant amount of memory.
///
public class FAllocationLifecycle
{
/// Currently allocated pointer.
public ulong LatestPointer;
/// Allocation event.
public FAllocationEvent AllocEvent;
/// Reallocation events.
public List ReallocsEvents;
/// Position in the stream when the allocation has been freed.
public ulong FreeStreamIndex = FStreamInfo.INVALID_STREAM_INDEX;
/// True if allocation has been freed.
public bool bIsComplete;
/// Returns the current size of this allocation.
public int CurrentSize
{
get
{
if (bIsComplete)
{
return 0;
}
else if (ReallocsEvents == null)
{
return AllocEvent.Size;
}
else
{
return ReallocsEvents[ReallocsEvents.Count - 1].NewSize;
}
}
}
/// Returns the peak size of this allocation.
public int PeakSize
{
get
{
int Result = AllocEvent.Size;
if (ReallocsEvents != null)
{
for (int i = 0; i < ReallocsEvents.Count; i++)
{
if (ReallocsEvents[i].NewSize > Result)
{
Result = ReallocsEvents[i].NewSize;
}
}
}
return Result;
}
}
/// Empty constructor.
public FAllocationLifecycle()
{
}
/// Processes malloc operation for this allocation lifecycle.
public void Malloc(FStreamToken StreamToken, FCallStack PreviousCallStack, FAllocationLifecycle PreviousLifecycle)
{
AllocEvent = new FAllocationEvent(StreamToken, PreviousCallStack, PreviousLifecycle);
LatestPointer = AllocEvent.Pointer;
// if PreviousCallStack != null, initial allocation was made by another callstack
if (PreviousCallStack != null)
{
PreviousCallStack.PropagateSizeGraphPoint(PreviousLifecycle, StreamToken.StreamIndex, StreamToken.Size);
}
}
/// Processes free operation for this allocation lifecycle.
public void Free(FStreamToken StreamToken)
{
if (AllocEvent.PreviousCallStack != null)
{
AllocEvent.PreviousCallStack.PropagateSizeGraphPoint(AllocEvent.PreviousLifecycle, StreamToken.StreamIndex, -CurrentSize);
}
FreeStreamIndex = StreamToken.StreamIndex;
bIsComplete = true;
}
/// Processes realloc operation for this allocation lifecycle.
public void Realloc(FStreamToken StreamToken, FCallStack InitialCallStack, out int SizeChange)
{
// reallocs that are really frees should be handled by free()
Debug.Assert(StreamToken.Size > 0);
int InitialSize = CurrentSize;
LatestPointer = StreamToken.NewPointer;
FCallStack ReallocCallStack = FStreamInfo.GlobalInstance.CallStackArray[StreamToken.CallStackIndex];
if (ReallocsEvents == null)
{
ReallocsEvents = new List();
ReallocsEvents.Capacity = 1;
}
ReallocsEvents.Add(new FReallocationEvent(StreamToken, ReallocCallStack));
if (ReallocCallStack != InitialCallStack)
{
// pointer has been realloced by a different callstack
// it hasn't been freed, but it won't be tracked by this lifecycle object anymore,
// so mark this object complete
SizeChange = -InitialSize;
bIsComplete = true;
}
else
{
SizeChange = StreamToken.Size - InitialSize;
}
if (AllocEvent.PreviousCallStack != null)
{
AllocEvent.PreviousCallStack.PropagateSizeGraphPoint(AllocEvent.PreviousLifecycle, StreamToken.StreamIndex, SizeChange);
}
}
///
/// Size is returned as a uint instead of an int, because it is
/// frequently added to the returned pointer and for some reason
/// you can't add a ulong and an int.
///
public ulong GetPointerAtStreamIndex(ulong StreamIndex, out uint Size)
{
if (StreamIndex < AllocEvent.StreamIndex || (FreeStreamIndex != FStreamInfo.INVALID_STREAM_INDEX && StreamIndex > FreeStreamIndex))
{
// StreamIndex was before initial allocation or after final free
Size = 0;
return 0;
}
else if (ReallocsEvents != null)
{
if (StreamIndex > ReallocsEvents[ReallocsEvents.Count - 1].StreamIndex)
{
// StreamIndex is after last realloc
if (FreeStreamIndex == FStreamInfo.INVALID_STREAM_INDEX && bIsComplete)
{
// lifecycle was ended by a realloc in another callstack, and StreamIndex is after that
Size = 0;
return 0;
}
else
{
// lifecycle is still active, so return latest size
Size = (uint)ReallocsEvents[ReallocsEvents.Count - 1].NewSize;
return LatestPointer;
}
}
else if (StreamIndex < ReallocsEvents[0].StreamIndex)
{
// StreamIndex is before first realloc
Size = (uint)AllocEvent.Size;
return AllocEvent.Pointer;
}
else
{
for (int EventIndex = 1; EventIndex < ReallocsEvents.Count; EventIndex++)
{
if (StreamIndex < ReallocsEvents[EventIndex].StreamIndex)
{
Size = (uint)ReallocsEvents[EventIndex - 1].NewSize;
return ReallocsEvents[EventIndex - 1].NewPointer;
}
}
// should never happen
Debug.Assert(false, "Unhandled case");
Size = 0;
return 0;
}
}
else
{
// StreamIndex is between malloc and free, and there were no reallocs
Size = (uint)AllocEvent.Size;
return AllocEvent.Pointer;
}
}
}
/// Encapsulates allocation event information.
public struct FAllocationEvent
{
/// Position in the stream.
public ulong StreamIndex;
/// Pointer of allocation.
public ulong Pointer;
/// Size of allocation.
public int Size;
/// Previous callstack used for this allocation, happens after reallocation.
public FCallStack PreviousCallStack;
/// Previous allocation livecycle used for this allocation, happens after reallocation.
public FAllocationLifecycle PreviousLifecycle;
/// Constructor.
public FAllocationEvent(FStreamToken StreamToken, FCallStack InPreviousCallStack, FAllocationLifecycle InPreviousLifecycle)
{
StreamIndex = StreamToken.StreamIndex;
Pointer = StreamToken.Type == EProfilingPayloadType.TYPE_Realloc ? StreamToken.NewPointer : StreamToken.Pointer;
Size = StreamToken.Size;
PreviousCallStack = InPreviousCallStack;
PreviousLifecycle = InPreviousLifecycle;
}
}
/// Encapsulates reallocation event information.
public struct FReallocationEvent
{
/// Position in the stream.
public ulong StreamIndex;
/// New pointer of reallocation.
public ulong NewPointer;
/// New size of reallocation.
public int NewSize;
/// Constructor.
public FReallocationEvent(FStreamToken StreamToken, FCallStack InCallstack)
{
StreamIndex = StreamToken.StreamIndex;
NewPointer = StreamToken.NewPointer;
NewSize = StreamToken.Size;
}
}
///
/// Encapsulates history of allocation for callstack.
/// IMPORTANT: don't change this to a class, it's far more efficient as a struct.
///
public struct FSizeGraphPoint
{
/// Position in the stream.
public ulong StreamIndex;
/// Change of allocation size.
public int SizeChange;
/// True if allocation comes from realloc operation.
public bool bFreshRealloc;
public FSizeGraphPoint(ulong InStreamIndex, int InSizeChange, bool bInFreshRealloc)
{
StreamIndex = InStreamIndex;
SizeChange = InSizeChange;
bFreshRealloc = bInFreshRealloc;
}
}
}