892 lines
28 KiB
C++
892 lines
28 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ProfilerRawStatsForMemory.h"
|
|
|
|
#if STATS && UE_STATS_MEMORY_PROFILER_ENABLED
|
|
|
|
#include "Stats/StatsMisc.h"
|
|
#include "ProfilingDebugging/DiagnosticTable.h"
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
Sort helpers
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
/** Sorts allocations by size. */
|
|
struct FAllocationInfoSequenceTagLess
|
|
{
|
|
FORCEINLINE bool operator()( const FAllocationInfo& A, const FAllocationInfo& B ) const
|
|
{
|
|
return A.SequenceTag < B.SequenceTag;
|
|
}
|
|
};
|
|
|
|
/** Sorts allocations by size. */
|
|
struct FAllocationInfoSizeGreater
|
|
{
|
|
FORCEINLINE bool operator()( const FAllocationInfo& A, const FAllocationInfo& B ) const
|
|
{
|
|
return B.Size < A.Size;
|
|
}
|
|
};
|
|
|
|
/** Sorts combined allocations by size. */
|
|
struct FCombinedAllocationInfoSizeGreater
|
|
{
|
|
FORCEINLINE bool operator()( const FCombinedAllocationInfo& A, const FCombinedAllocationInfo& B ) const
|
|
{
|
|
return B.Size < A.Size;
|
|
}
|
|
};
|
|
|
|
|
|
/** Sorts node allocations by size. */
|
|
struct FNodeAllocationInfoSizeGreater
|
|
{
|
|
FORCEINLINE bool operator()( const FNodeAllocationInfo& A, const FNodeAllocationInfo& B ) const
|
|
{
|
|
return B.Size < A.Size;
|
|
}
|
|
};
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
Callstack decoding/encoding
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
/** Helper struct used to manipulate stats based callstacks. */
|
|
struct FStatsCallstack
|
|
{
|
|
/** Separator. */
|
|
static const TCHAR* CallstackSeparator;
|
|
|
|
/** Encodes decoded callstack a string, to be like '45+656+6565'. */
|
|
static FString Encode( const TArray<FName>& Callstack )
|
|
{
|
|
FString Result;
|
|
for (const auto& Name : Callstack)
|
|
{
|
|
Result += TTypeToString<uint32>::ToString(Name.GetComparisonIndex().ToUnstableInt() );
|
|
Result += CallstackSeparator;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
/** Decodes encoded callstack to an array of FNames. */
|
|
static void DecodeToNames( const FName& EncodedCallstack, TArray<FName>& out_DecodedCallstack )
|
|
{
|
|
TArray<FString> DecodedCallstack;
|
|
DecodeToStrings( EncodedCallstack, DecodedCallstack );
|
|
|
|
// Convert back to FNames
|
|
for (const auto& It : DecodedCallstack)
|
|
{
|
|
uint32 NameInt = 0;
|
|
TTypeFromString<uint32>::FromString(NameInt, *It );
|
|
FNameEntryId Id = FNameEntryId::FromUnstableInt(NameInt);
|
|
const FName LongName = FName(Id, Id, 0 );
|
|
|
|
out_DecodedCallstack.Add( LongName );
|
|
}
|
|
}
|
|
|
|
/** Converts the encoded callstack into human readable callstack. */
|
|
static FString GetHumanReadable( const FName& EncodedCallstack )
|
|
{
|
|
TArray<FName> DecodedCallstack;
|
|
DecodeToNames( EncodedCallstack, DecodedCallstack );
|
|
const FString Result = GetHumanReadable( DecodedCallstack );
|
|
return Result;
|
|
}
|
|
|
|
/** Converts the encoded callstack into human readable callstack. */
|
|
static FString GetHumanReadable( const TArray<FName>& DecodedCallstack )
|
|
{
|
|
FString Result;
|
|
|
|
const int32 NumEntries = DecodedCallstack.Num();
|
|
//for (int32 Index = DecodedCallstack.Num() - 1; Index >= 0; --Index)
|
|
for (int32 Index = 0; Index < NumEntries; ++Index)
|
|
{
|
|
const FName LongName = DecodedCallstack[Index];
|
|
const FString ShortName = FStatNameAndInfo::GetShortNameFrom( LongName ).ToString();
|
|
//const FString Group = FStatNameAndInfo::GetGroupNameFrom( LongName ).ToString();
|
|
FString Desc = FStatNameAndInfo::GetDescriptionFrom( LongName );
|
|
Desc.TrimStartInline();
|
|
|
|
if (Desc.Len() == 0)
|
|
{
|
|
Result += ShortName;
|
|
}
|
|
else
|
|
{
|
|
Result += Desc;
|
|
}
|
|
|
|
if (Index != NumEntries - 1)
|
|
{
|
|
Result += TEXT( " -> " );
|
|
}
|
|
}
|
|
|
|
Result.ReplaceInline( TEXT( "STAT_" ), TEXT( "" ), ESearchCase::CaseSensitive );
|
|
return Result;
|
|
}
|
|
|
|
protected:
|
|
/** Decodes encoded callstack to an array of strings. Where each string is the index of the FName. */
|
|
static void DecodeToStrings( const FName& EncodedCallstack, TArray<FString>& out_DecodedCallstack )
|
|
{
|
|
EncodedCallstack.ToString().ParseIntoArray( out_DecodedCallstack, CallstackSeparator, true );
|
|
}
|
|
};
|
|
|
|
const TCHAR* FStatsCallstack::CallstackSeparator = TEXT( "+" );
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
Allocation info
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
FAllocationInfo::FAllocationInfo( uint64 InOldPtr, uint64 InPtr, int64 InSize, const TArray<FName>& InCallstack, uint32 InSequenceTag, EMemoryOperation InOp, bool bInHasBrokenCallstack )
|
|
: OldPtr( InOldPtr )
|
|
, Ptr( InPtr )
|
|
, Size( InSize )
|
|
, EncodedCallstack( *FStatsCallstack::Encode( InCallstack ) )
|
|
, SequenceTag( InSequenceTag )
|
|
, Op( InOp )
|
|
, bHasBrokenCallstack( bInHasBrokenCallstack )
|
|
{
|
|
|
|
}
|
|
|
|
FAllocationInfo::FAllocationInfo( const FAllocationInfo& Other )
|
|
: OldPtr( Other.OldPtr )
|
|
, Ptr( Other.Ptr )
|
|
, Size( Other.Size )
|
|
, EncodedCallstack( Other.EncodedCallstack )
|
|
, SequenceTag( Other.SequenceTag )
|
|
, Op( Other.Op )
|
|
, bHasBrokenCallstack( Other.bHasBrokenCallstack )
|
|
{
|
|
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
FNodeAllocationInfo
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
void FNodeAllocationInfo::SortBySize()
|
|
{
|
|
ChildNodes.ValueSort( FNodeAllocationInfoSizeGreater() );
|
|
for (auto& It : ChildNodes)
|
|
{
|
|
It.Value->SortBySize();
|
|
}
|
|
}
|
|
|
|
|
|
void FNodeAllocationInfo::PrepareCallstackData( const TArray<FName>& InDecodedCallstack )
|
|
{
|
|
DecodedCallstack = InDecodedCallstack;
|
|
EncodedCallstack = *FStatsCallstack::Encode( DecodedCallstack );
|
|
HumanReadableCallstack = FStatsCallstack::GetHumanReadable( DecodedCallstack );
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
FRawStatsMemoryProfiler
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
FRawStatsMemoryProfiler::FRawStatsMemoryProfiler( const TCHAR* InFilename )
|
|
: FStatsReadFile( InFilename, true )
|
|
, NumDuplicatedMemoryOperations( 0 )
|
|
, NumMemoryOperations( 0 )
|
|
, LastSequenceTagForNamedMarker( 0 )
|
|
{}
|
|
|
|
void FRawStatsMemoryProfiler::PreProcessStats()
|
|
{
|
|
Super::PreProcessStats();
|
|
|
|
// Begin marker.
|
|
Snapshots.Emplace( LastSequenceTagForNamedMarker, TEXT( "BeginSnapshot" ) );
|
|
}
|
|
|
|
|
|
void FRawStatsMemoryProfiler::PostProcessStats()
|
|
{
|
|
Super::PostProcessStats();
|
|
|
|
const double StartTime = FPlatformTime::Seconds();
|
|
|
|
if (!IsProcessingStopped())
|
|
{
|
|
SortSequenceAllocations();
|
|
|
|
// End marker.
|
|
Snapshots.Emplace( TNumericLimits<uint32>::Max(), TEXT( "EndSnapshot" ) );
|
|
|
|
// Copy snapshots.
|
|
SnapshotsToBeProcessed = Snapshots;
|
|
|
|
UE_LOG( LogStats, Log, TEXT( "NumMemoryOperations: %i" ), NumMemoryOperations );
|
|
UE_LOG( LogStats, Log, TEXT( "SequenceAllocationNum: %i" ), SequenceAllocationArray.Num() );
|
|
|
|
GenerateAllocationMap();
|
|
DumpDebugAllocations();
|
|
}
|
|
|
|
if (!IsProcessingStopped())
|
|
{
|
|
StageProgress.Set( 100 );
|
|
|
|
const double TotalTime = FPlatformTime::Seconds() - StartTime;
|
|
UE_LOG( LogStats, Log, TEXT( "Post-Processing took %.2f sec(s)" ), TotalTime );
|
|
}
|
|
else
|
|
{
|
|
UE_LOG( LogStats, Warning, TEXT( "Post-Processing stopped, abandoning" ) );
|
|
}
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::DumpDebugAllocations()
|
|
{
|
|
#if UE_BUILD_DEBUG
|
|
|
|
// Dump problematic allocations
|
|
DuplicatedAllocMap.ValueSort( FAllocationInfoSizeGreater() );
|
|
|
|
uint64 TotalDuplicatedMemory = 0;
|
|
for (const auto& It : DuplicatedAllocMap)
|
|
{
|
|
const FAllocationInfo& Alloc = It.Value;
|
|
TotalDuplicatedMemory += Alloc.Size;
|
|
}
|
|
|
|
UE_LOG( LogStats, Warning, TEXT( "Dumping duplicated alloc map" ) );
|
|
UE_LOG( LogStats, Warning, TEXT( "TotalDuplicatedMemory: %llu bytes (%.2f MB)" ), TotalDuplicatedMemory, TotalDuplicatedMemory / 1024.0f / 1024.0f );
|
|
const float MaxPctDisplayed = 0.9f;
|
|
uint64 DisplayedSoFar = 0;
|
|
for (const auto& It : DuplicatedAllocMap)
|
|
{
|
|
const FAllocationInfo& Alloc = It.Value;
|
|
const FString& AllocCallstack = It.Key;
|
|
UE_LOG( LogStats, Log, TEXT( "%lli (%.2f MB) %s" ), Alloc.Size, Alloc.Size / 1024.0f / 1024.0f, *AllocCallstack );
|
|
|
|
DisplayedSoFar += Alloc.Size;
|
|
|
|
const float CurrentPct = (float)DisplayedSoFar / (float)TotalDuplicatedMemory;
|
|
if (CurrentPct > MaxPctDisplayed)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endif // UE_BUILD_DEBUG
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::FreeDebugInformation()
|
|
{
|
|
DuplicatedAllocMap.Empty();
|
|
ZeroAllocMap.Empty();
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::GenerateAllocationMap()
|
|
{
|
|
/** Map of currently alive allocations. Ptr to AllocationInfo. */
|
|
TMap<uint64, FAllocationInfo> AllocationMap;
|
|
|
|
// Initialize the begin snapshot.
|
|
auto BeginSnapshot = SnapshotsToBeProcessed[0];
|
|
|
|
SnapshotsToBeProcessed.RemoveAt( 0 );
|
|
PrepareSnapshot( BeginSnapshot.Value, AllocationMap );
|
|
auto CurrentSnapshot = SnapshotsToBeProcessed[0];
|
|
|
|
UE_LOG( LogStats, Log, TEXT( "Generating memory operations map" ) );
|
|
|
|
const int32 NumSequenceAllocations = SequenceAllocationArray.Num();
|
|
const int32 OnePercent = FMath::Max( NumSequenceAllocations / 100, 1024 );
|
|
for (int32 AllocationIndex = 0; AllocationIndex < NumSequenceAllocations; AllocationIndex++)
|
|
{
|
|
if (AllocationIndex % OnePercent == 0)
|
|
{
|
|
UpdateGenerateMemoryMapProgress( AllocationIndex );
|
|
if (IsProcessingStopped())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
const FAllocationInfo& Alloc = SequenceAllocationArray[AllocationIndex];
|
|
|
|
// Check named marker/snapshots
|
|
if (Alloc.SequenceTag > CurrentSnapshot.Key)
|
|
{
|
|
SnapshotsToBeProcessed.RemoveAt( 0 );
|
|
PrepareSnapshot( CurrentSnapshot.Value, AllocationMap );
|
|
CurrentSnapshot = SnapshotsToBeProcessed[0];
|
|
}
|
|
|
|
if (Alloc.Op == EMemoryOperation::Alloc)
|
|
{
|
|
ProcessAlloc( Alloc, AllocationMap );
|
|
}
|
|
else if (Alloc.Op == EMemoryOperation::Realloc)
|
|
{
|
|
// Previous Alloc or Realloc
|
|
if (Alloc.OldPtr != 0)
|
|
{
|
|
ProcessFree( Alloc, AllocationMap, true );
|
|
}
|
|
|
|
#if UE_BUILD_DEBUG
|
|
if (Alloc.OldPtr == 0 && Alloc.Size == 0)
|
|
{
|
|
const FString ReallocCallstack = FStatsCallstack::GetHumanReadable( Alloc.EncodedCallstack );
|
|
UE_LOG( LogStats, VeryVerbose, TEXT( "ReallocZero: %s %i %i/%i [%i]" ), *ReallocCallstack, Alloc.Size, Alloc.OldPtr, Alloc.Ptr, Alloc.SequenceTag );
|
|
}
|
|
#endif // UE_BUILD_DEBUG
|
|
|
|
if (Alloc.Ptr != 0)
|
|
{
|
|
ProcessAlloc( Alloc, AllocationMap );
|
|
}
|
|
}
|
|
else if (Alloc.Op == EMemoryOperation::Free)
|
|
{
|
|
ProcessFree( Alloc, AllocationMap, false );
|
|
}
|
|
}
|
|
|
|
auto EndSnapshot = SnapshotsToBeProcessed[0];
|
|
SnapshotsToBeProcessed.RemoveAt( 0 );
|
|
PrepareSnapshot( EndSnapshot.Value, AllocationMap );
|
|
|
|
// We don't need the allocation map. Each snapshot has its own copy.
|
|
AllocationMap.Empty();
|
|
|
|
SnapshotNamesArray = SnapshotNamesSet.Array();
|
|
|
|
UE_LOG( LogStats, Verbose, TEXT( "NumDuplicatedMemoryOperations: %i" ), NumDuplicatedMemoryOperations );
|
|
UE_LOG( LogStats, Verbose, TEXT( "NumZeroAllocs: %i" ), NumZeroAllocs );
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::ProcessAlloc( const FAllocationInfo& AllocInfo, TMap<uint64, FAllocationInfo>& AllocationMap )
|
|
{
|
|
if( AllocInfo.Size == 0 )
|
|
{
|
|
NumZeroAllocs++;
|
|
ZeroAllocMap.Add( FStatsCallstack::GetHumanReadable( AllocInfo.EncodedCallstack ), AllocInfo );
|
|
}
|
|
|
|
const FAllocationInfo* Found = AllocationMap.Find( AllocInfo.Ptr );
|
|
if (!Found)
|
|
{
|
|
AllocationMap.Add( AllocInfo.Ptr, AllocInfo );
|
|
}
|
|
else
|
|
{
|
|
NumDuplicatedMemoryOperations++;
|
|
|
|
#if UE_BUILD_DEBUG
|
|
const FString FoundCallstack = FStatsCallstack::GetHumanReadable( Found->EncodedCallstack );
|
|
const FString AllocCallstack = FStatsCallstack::GetHumanReadable( AllocInfo.EncodedCallstack );
|
|
UE_LOG( LogStats, VeryVerbose, TEXT( "DuplicatedAlloc" ) );
|
|
UE_LOG( LogStats, VeryVerbose, TEXT( "FoundCallstack: %s [%s]" ), *FoundCallstack, Found->Op==EMemoryOperation::Alloc ? TEXT("Alloc") : TEXT("Realloc") );
|
|
UE_LOG( LogStats, VeryVerbose, TEXT( "AllocCallstack: %s [%s]" ), *AllocCallstack, AllocInfo.Op==EMemoryOperation::Alloc ? TEXT("Alloc") : TEXT("Realloc") );
|
|
UE_LOG( LogStats, VeryVerbose, TEXT( "Size: %i/%i Ptr: %llu/%llu Tag: %i/%i" ), Found->Size, AllocInfo.Size, Found->Ptr, AllocInfo.Ptr, Found->SequenceTag, AllocInfo.SequenceTag );
|
|
|
|
// Store the old pointer.
|
|
DuplicatedAllocMap.Add( FoundCallstack, *Found );
|
|
#endif // UE_BUILD_DEBUG
|
|
|
|
// Replace pointer.
|
|
AllocationMap.Add( AllocInfo.Ptr, AllocInfo );
|
|
}
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::ProcessFree( const FAllocationInfo& FreeInfo, TMap<uint64, FAllocationInfo>& AllocationMap, const bool bReallocFree )
|
|
{
|
|
// bReallocFree is not needed here, but it's easier to read the code.
|
|
const uint64 PtrToBeFreed = bReallocFree ? FreeInfo.OldPtr : FreeInfo.Ptr;
|
|
const FAllocationInfo* Found = AllocationMap.Find( PtrToBeFreed );
|
|
if (Found)
|
|
{
|
|
const bool bIsValid = FreeInfo.SequenceTag > Found->SequenceTag;
|
|
if (!bIsValid)
|
|
{
|
|
UE_LOG( LogStats, Warning, TEXT( "InvalidFree Ptr: %llu, Seq: %i/%i" ), PtrToBeFreed, FreeInfo.SequenceTag, Found->SequenceTag );
|
|
}
|
|
AllocationMap.Remove( PtrToBeFreed );
|
|
}
|
|
else
|
|
{
|
|
#if UE_BUILD_DEBUG
|
|
const FString FWACallstack = FStatsCallstack::GetHumanReadable( FreeInfo.EncodedCallstack );
|
|
UE_LOG( LogStats, VeryVerbose, TEXT( "FreeWithoutAlloc: %s, %llu" ), *FWACallstack, PtrToBeFreed );
|
|
#endif // UE_BUILD_DEBUG
|
|
}
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::UpdateGenerateMemoryMapProgress( const int32 AllocationIndex )
|
|
{
|
|
const double CurrentSeconds = FPlatformTime::Seconds();
|
|
if (CurrentSeconds > LastUpdateTime + NumSecondsBetweenUpdates)
|
|
{
|
|
const int32 PercentagePos = int32( 100.0*AllocationIndex / SequenceAllocationArray.Num() );
|
|
StageProgress.Set( PercentagePos );
|
|
UE_LOG( LogStats, Verbose, TEXT( "Processing allocations %3i%% (%10i/%10i)" ), PercentagePos, AllocationIndex, SequenceAllocationArray.Num() );
|
|
LastUpdateTime = CurrentSeconds;
|
|
}
|
|
|
|
// Abandon support.
|
|
if (bShouldStopProcessing == true)
|
|
{
|
|
SetProcessingStage( EStatsProcessingStage::SPS_Stopped );
|
|
}
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::ProcessSpecialMessageMarkerOperation( const FStatMessage& Message, const FStackState& StackState )
|
|
{
|
|
const FName RawName = Message.NameAndInfo.GetRawName();
|
|
if (RawName == FStatConstants::RAW_NamedMarker)
|
|
{
|
|
const FName NamedMarker = Message.GetValue_FName();
|
|
Snapshots.Emplace( LastSequenceTagForNamedMarker, NamedMarker );
|
|
}
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::ProcessMemoryOperation( EMemoryOperation MemOp, uint64 Ptr, uint64 NewPtr, int64 Size, uint32 SequenceTag, const FStackState& StackState )
|
|
{
|
|
if (MemOp == EMemoryOperation::Alloc)
|
|
{
|
|
NumMemoryOperations++;
|
|
|
|
// Add a new allocation.
|
|
SequenceAllocationArray.Add(
|
|
FAllocationInfo(
|
|
0,
|
|
Ptr,
|
|
Size,
|
|
StackState.Stack,
|
|
SequenceTag,
|
|
EMemoryOperation::Alloc,
|
|
StackState.bIsBrokenCallstack
|
|
) );
|
|
LastSequenceTagForNamedMarker = SequenceTag;
|
|
|
|
}
|
|
else if (MemOp == EMemoryOperation::Realloc)
|
|
{
|
|
NumMemoryOperations++;
|
|
const uint64 OldPtr = Ptr;
|
|
|
|
// Add a new realloc.
|
|
SequenceAllocationArray.Add(
|
|
FAllocationInfo(
|
|
OldPtr,
|
|
NewPtr,
|
|
Size,
|
|
StackState.Stack,
|
|
SequenceTag,
|
|
EMemoryOperation::Realloc,
|
|
StackState.bIsBrokenCallstack
|
|
) );
|
|
LastSequenceTagForNamedMarker = SequenceTag;
|
|
}
|
|
else if (MemOp == EMemoryOperation::Free)
|
|
{
|
|
NumMemoryOperations++;
|
|
|
|
// Add a new free.
|
|
SequenceAllocationArray.Add(
|
|
FAllocationInfo(
|
|
0,
|
|
Ptr,
|
|
0,
|
|
StackState.Stack,
|
|
SequenceTag,
|
|
EMemoryOperation::Free,
|
|
StackState.bIsBrokenCallstack
|
|
) );
|
|
}
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::SortSequenceAllocations()
|
|
{
|
|
FScopeLogTime SLT( TEXT( "SortSequenceAllocations" ), nullptr, FScopeLogTime::ScopeLog_Milliseconds );
|
|
|
|
// Sort all memory operation by the sequence tag, iterate through all operation and generate memory usage.
|
|
SequenceAllocationArray.Sort( FAllocationInfoSequenceTagLess() );
|
|
|
|
// Abandon support.
|
|
if (bShouldStopProcessing == true)
|
|
{
|
|
SetProcessingStage( EStatsProcessingStage::SPS_Stopped );
|
|
}
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::GenerateScopedTreeAllocations( const TMap<FName, FCombinedAllocationInfo>& ScopedAllocations, FNodeAllocationInfo& out_Root )
|
|
{
|
|
FScopeLogTime SLT( TEXT( "GenerateScopedTreeAllocations" ), nullptr, FScopeLogTime::ScopeLog_Milliseconds );
|
|
|
|
// Decode all scoped allocations, generate tree for allocations and combine them.
|
|
for (const auto& It : ScopedAllocations)
|
|
{
|
|
const FName& EncodedCallstack = It.Key;
|
|
const FCombinedAllocationInfo& CombinedAllocation = It.Value;
|
|
|
|
// Decode callstack.
|
|
TArray<FName> DecodedCallstack;
|
|
FStatsCallstack::DecodeToNames( EncodedCallstack, DecodedCallstack );
|
|
|
|
const int32 AllocationLenght = DecodedCallstack.Num();
|
|
check( DecodedCallstack.Num() > 0 );
|
|
|
|
FNodeAllocationInfo* CurrentNode = &out_Root;
|
|
// Accumulate with thread root node.
|
|
CurrentNode->Accumulate( CombinedAllocation );
|
|
|
|
// Iterate through the callstack and prepare all nodes if needed, and accumulate memory.
|
|
TArray<FName> CurrentCallstack;
|
|
const int32 NumEntries = DecodedCallstack.Num();
|
|
for (int32 Idx1 = 0; Idx1 < NumEntries; ++Idx1)
|
|
{
|
|
const FName NodeName = DecodedCallstack[Idx1];
|
|
CurrentCallstack.Add( NodeName );
|
|
|
|
FNodeAllocationInfo* Node = nullptr;
|
|
const bool bContainsNode = CurrentNode->ChildNodes.Contains( NodeName );
|
|
if (!bContainsNode)
|
|
{
|
|
Node = new FNodeAllocationInfo;
|
|
Node->Depth = Idx1;
|
|
Node->PrepareCallstackData( CurrentCallstack );
|
|
|
|
CurrentNode->ChildNodes.Add( NodeName, Node );
|
|
}
|
|
else
|
|
{
|
|
Node = CurrentNode->ChildNodes.FindChecked( NodeName );
|
|
}
|
|
|
|
// Accumulate memory usage and num allocations for all nodes in the callstack.
|
|
Node->Accumulate( CombinedAllocation );
|
|
|
|
// Move to the next node.
|
|
Node->Parent = CurrentNode;
|
|
CurrentNode = Node;
|
|
}
|
|
}
|
|
|
|
out_Root.SortBySize();
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::ProcessAndDumpUObjectAllocations( const FName SnapshotName )
|
|
{
|
|
if (!SnapshotsWithAllocationMap.Contains(SnapshotName))
|
|
{
|
|
UE_LOG( LogStats, Warning, TEXT( "Snapshot not found: %s" ), *SnapshotName.ToString() );
|
|
return;
|
|
}
|
|
|
|
const TMap<uint64, FAllocationInfo>& AllocationMap = SnapshotsWithAllocationMap.FindChecked( SnapshotName );
|
|
|
|
FScopeLogTime SLT( TEXT( "ProcessingUObjectAllocations" ), nullptr, FScopeLogTime::ScopeLog_Seconds );
|
|
UE_LOG( LogStats, Warning, TEXT( "Processing UObject allocations" ) );
|
|
|
|
const FString ReportName = FString::Printf( TEXT( "%s-Memory-UObject" ), *GetPlatformName() );
|
|
FDiagnosticTableViewer MemoryReport( *FDiagnosticTableViewer::GetUniqueTemporaryFilePath( *ReportName ), true );
|
|
|
|
// Write a row of headings for the table's columns.
|
|
MemoryReport.AddColumn( TEXT( "Size (bytes)" ) );
|
|
MemoryReport.AddColumn( TEXT( "Size (MB)" ) );
|
|
MemoryReport.AddColumn( TEXT( "Count" ) );
|
|
MemoryReport.AddColumn( TEXT( "UObject class" ) );
|
|
MemoryReport.CycleRow();
|
|
|
|
TMap<FName, FCombinedAllocationInfo> UObjectAllocations;
|
|
|
|
// To minimize number of calls to expensive DecodeCallstack.
|
|
TMap<FName, FName> UObjectCallstackToClassMapping;
|
|
|
|
uint64 NumAllocations = 0;
|
|
uint64 TotalAllocatedMemory = 0;
|
|
for (const auto& It : AllocationMap)
|
|
{
|
|
const FAllocationInfo& Alloc = It.Value;
|
|
|
|
FName UObjectClass = UObjectCallstackToClassMapping.FindRef( Alloc.EncodedCallstack );
|
|
if (UObjectClass == NAME_None)
|
|
{
|
|
TArray<FName> DecodedCallstack;
|
|
FStatsCallstack::DecodeToNames( Alloc.EncodedCallstack, DecodedCallstack );
|
|
|
|
for (int32 Index = DecodedCallstack.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
const FName LongName = DecodedCallstack[Index];
|
|
const bool bValid = UObjectRawNames.Contains( LongName );
|
|
if (bValid)
|
|
{
|
|
const FString ObjectName = FStatNameAndInfo::GetShortNameFrom( LongName ).GetPlainNameString();
|
|
UObjectClass = *ObjectName.Left( ObjectName.Find( TEXT( "//" ) ) );;
|
|
UObjectCallstackToClassMapping.Add( Alloc.EncodedCallstack, UObjectClass );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (UObjectClass != NAME_None)
|
|
{
|
|
FCombinedAllocationInfo& CombinedAllocation = UObjectAllocations.FindOrAdd( UObjectClass );
|
|
CombinedAllocation += Alloc;
|
|
|
|
TotalAllocatedMemory += Alloc.Size;
|
|
NumAllocations++;
|
|
}
|
|
}
|
|
|
|
// Dump memory to the log.
|
|
UObjectAllocations.ValueSort( FCombinedAllocationInfoSizeGreater() );
|
|
|
|
const float MaxPctDisplayed = 0.90f;
|
|
int32 CurrentIndex = 0;
|
|
uint64 DisplayedSoFar = 0;
|
|
UE_LOG( LogStats, VeryVerbose, TEXT( "Index, Size (Size MB), Count, UObject class" ) );
|
|
for (const auto& It : UObjectAllocations)
|
|
{
|
|
const FCombinedAllocationInfo& CombinedAllocation = It.Value;
|
|
const FName& UObjectClass = It.Key;
|
|
|
|
UE_LOG( LogStats, VeryVerbose, TEXT( "%2i, %llu (%.2f MB), %llu, %s" ),
|
|
CurrentIndex,
|
|
CombinedAllocation.Size,
|
|
CombinedAllocation.Size / 1024.0f / 1024.0f,
|
|
CombinedAllocation.Count,
|
|
*UObjectClass.GetPlainNameString() );
|
|
|
|
// Dump stats
|
|
MemoryReport.AddColumn( TEXT( "%llu" ), CombinedAllocation.Size );
|
|
MemoryReport.AddColumn( TEXT( "%.2f MB" ), CombinedAllocation.Size / 1024.0f / 1024.0f );
|
|
MemoryReport.AddColumn( TEXT( "%llu" ), CombinedAllocation.Count );
|
|
MemoryReport.AddColumn( *UObjectClass.GetPlainNameString() );
|
|
MemoryReport.CycleRow();
|
|
|
|
CurrentIndex++;
|
|
DisplayedSoFar += CombinedAllocation.Size;
|
|
|
|
const float CurrentPct = (float)DisplayedSoFar / (float)TotalAllocatedMemory;
|
|
if (CurrentPct > MaxPctDisplayed)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
UE_LOG( LogStats, VeryVerbose, TEXT( "Allocated memory: %llu bytes (%.2f MB)" ), TotalAllocatedMemory, TotalAllocatedMemory / 1024.0f / 1024.0f );
|
|
|
|
// Add a total row.
|
|
MemoryReport.CycleRow();
|
|
MemoryReport.CycleRow();
|
|
MemoryReport.CycleRow();
|
|
MemoryReport.AddColumn( TEXT( "%llu" ), TotalAllocatedMemory );
|
|
MemoryReport.AddColumn( TEXT( "%.2f MB" ), TotalAllocatedMemory / 1024.0f / 1024.0f );
|
|
MemoryReport.AddColumn( TEXT( "%llu" ), NumAllocations );
|
|
MemoryReport.AddColumn( TEXT( "TOTAL" ) );
|
|
MemoryReport.CycleRow();
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::DumpScopedAllocations( const TCHAR* Name, const TMap<FString, FCombinedAllocationInfo>& CombinedAllocations )
|
|
{
|
|
if (CombinedAllocations.Num() == 0)
|
|
{
|
|
UE_LOG( LogStats, Warning, TEXT( "No scoped allocations: %s" ), Name );
|
|
return;
|
|
}
|
|
|
|
FScopeLogTime SLT( TEXT( "ProcessingScopedAllocations" ), nullptr, FScopeLogTime::ScopeLog_Seconds );
|
|
UE_LOG( LogStats, Warning, TEXT( "Dumping scoped allocations: %s" ), Name );
|
|
|
|
const FString ReportName = FString::Printf( TEXT( "%s-Memory-Scoped-%s" ), *GetPlatformName(), Name );
|
|
FDiagnosticTableViewer MemoryReport( *FDiagnosticTableViewer::GetUniqueTemporaryFilePath( *ReportName ), true );
|
|
|
|
// Write a row of headings for the table's columns.
|
|
MemoryReport.AddColumn( TEXT( "Size (bytes)" ) );
|
|
MemoryReport.AddColumn( TEXT( "Size (MB)" ) );
|
|
MemoryReport.AddColumn( TEXT( "Count" ) );
|
|
MemoryReport.AddColumn( TEXT( "Callstack" ) );
|
|
MemoryReport.CycleRow();
|
|
|
|
FCombinedAllocationInfo Total;
|
|
|
|
const float MaxPctDisplayed = 0.90f;
|
|
int32 CurrentIndex = 0;
|
|
UE_LOG( LogStats, VeryVerbose, TEXT( "Index, Size (Size MB), Count, Stat desc" ) );
|
|
for (const auto& It : CombinedAllocations)
|
|
{
|
|
const FCombinedAllocationInfo& CombinedAllocation = It.Value;
|
|
//const FName& EncodedCallstack = It.Key;
|
|
const FString AllocCallstack = It.Key;// GetCallstack( EncodedCallstack );
|
|
|
|
UE_LOG( LogStats, VeryVerbose, TEXT( "%2i, %llu (%.2f MB), %llu, %s" ),
|
|
CurrentIndex,
|
|
CombinedAllocation.Size,
|
|
CombinedAllocation.Size / 1024.0f / 1024.0f,
|
|
CombinedAllocation.Count,
|
|
*AllocCallstack );
|
|
|
|
// Dump stats
|
|
MemoryReport.AddColumn( TEXT( "%llu" ), CombinedAllocation.Size );
|
|
MemoryReport.AddColumn( TEXT( "%.2f MB" ), CombinedAllocation.Size / 1024.0f / 1024.0f );
|
|
MemoryReport.AddColumn( TEXT( "%llu" ), CombinedAllocation.Count );
|
|
MemoryReport.AddColumn( *AllocCallstack );
|
|
MemoryReport.CycleRow();
|
|
|
|
CurrentIndex++;
|
|
Total += CombinedAllocation;
|
|
}
|
|
|
|
UE_LOG( LogStats, VeryVerbose, TEXT( "Allocated memory: %llu bytes (%.2f MB)" ), Total.Size, Total.SizeMB );
|
|
|
|
// Add a total row.
|
|
MemoryReport.CycleRow();
|
|
MemoryReport.CycleRow();
|
|
MemoryReport.CycleRow();
|
|
MemoryReport.AddColumn( TEXT( "%llu" ), Total.Size );
|
|
MemoryReport.AddColumn( TEXT( "%.2f MB" ), Total.SizeMB );
|
|
MemoryReport.AddColumn( TEXT( "%llu" ), Total.Count );
|
|
MemoryReport.AddColumn( TEXT( "TOTAL" ) );
|
|
MemoryReport.CycleRow();
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::GenerateScopedAllocations( const TMap<uint64, FAllocationInfo>& InAllocationMap, TMap<FName, FCombinedAllocationInfo>& out_CombinedAllocations, uint64& TotalAllocatedMemory, uint64& NumAllocations )
|
|
{
|
|
FScopeLogTime SLT( TEXT( "GenerateScopedAllocations" ), nullptr, FScopeLogTime::ScopeLog_Milliseconds );
|
|
|
|
for (const auto& It : InAllocationMap)
|
|
{
|
|
const FAllocationInfo& Alloc = It.Value;
|
|
FCombinedAllocationInfo& CombinedAllocation = out_CombinedAllocations.FindOrAdd( Alloc.EncodedCallstack );
|
|
CombinedAllocation += Alloc;
|
|
|
|
TotalAllocatedMemory += Alloc.Size;
|
|
NumAllocations++;
|
|
}
|
|
|
|
// Sort by size.
|
|
out_CombinedAllocations.ValueSort( FCombinedAllocationInfoSizeGreater() );
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::PrepareSnapshot( const FName SnapshotName, const TMap<uint64, FAllocationInfo>& InAllocationMap )
|
|
{
|
|
FScopeLogTime SLT( TEXT( "PrepareSnapshot" ), nullptr, FScopeLogTime::ScopeLog_Milliseconds );
|
|
|
|
// Make sure the snapshot name is unique.
|
|
FName UniqueSnapshotName = SnapshotName;
|
|
while (SnapshotNamesSet.Contains( UniqueSnapshotName ))
|
|
{
|
|
UniqueSnapshotName = FName( UniqueSnapshotName, UniqueSnapshotName.GetNumber() + 1 );
|
|
}
|
|
SnapshotNamesSet.Add( UniqueSnapshotName );
|
|
|
|
SnapshotsWithAllocationMap.Add( UniqueSnapshotName, InAllocationMap );
|
|
|
|
TMap<FName, FCombinedAllocationInfo> SnapshotCombinedAllocations;
|
|
uint64 TotalAllocatedMemory = 0;
|
|
uint64 NumAllocations = 0;
|
|
GenerateScopedAllocations( InAllocationMap, SnapshotCombinedAllocations, TotalAllocatedMemory, NumAllocations );
|
|
SnapshotsWithScopedAllocations.Add( UniqueSnapshotName, SnapshotCombinedAllocations );
|
|
|
|
// Decode callstacks.
|
|
// Replace encoded callstacks with human readable name. For easier debugging.
|
|
TMap<FString, FCombinedAllocationInfo> SnapshotDecodedCombinedAllocations;
|
|
for (auto& It : SnapshotCombinedAllocations)
|
|
{
|
|
const FString HumanReadableCallstack = FStatsCallstack::GetHumanReadable( It.Key );
|
|
SnapshotDecodedCombinedAllocations.Add( HumanReadableCallstack, It.Value );
|
|
}
|
|
SnapshotsWithDecodedScopedAllocations.Add( UniqueSnapshotName, SnapshotDecodedCombinedAllocations );
|
|
|
|
UE_LOG( LogStats, Warning, TEXT( "PrepareSnapshot: %s Alloc: %i Scoped: %i Total: %.2f MB" ), *UniqueSnapshotName.ToString(), InAllocationMap.Num(), SnapshotCombinedAllocations.Num(), TotalAllocatedMemory / 1024.0f / 1024.0f );
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::CompareSnapshots( const FName BeginSnaphotName, const FName EndSnaphotName, TMap<FName, FCombinedAllocationInfo>& out_Result )
|
|
{
|
|
FScopeLogTime SLT( TEXT( "CompareSnapshots" ), nullptr, FScopeLogTime::ScopeLog_Milliseconds );
|
|
|
|
const auto BeginSnaphotPtr = SnapshotsWithScopedAllocations.Find( BeginSnaphotName );
|
|
const auto EndSnapshotPtr = SnapshotsWithScopedAllocations.Find( EndSnaphotName );
|
|
if (BeginSnaphotPtr && EndSnapshotPtr)
|
|
{
|
|
// Process data.
|
|
TMap<FName, FCombinedAllocationInfo> BeginSnaphot = *BeginSnaphotPtr;
|
|
TMap<FName, FCombinedAllocationInfo> EndSnaphot = *EndSnapshotPtr;
|
|
TMap<FName, FCombinedAllocationInfo> Result;
|
|
|
|
for (const auto& It : EndSnaphot)
|
|
{
|
|
const FName Callstack = It.Key;
|
|
const FCombinedAllocationInfo EndCombinedAlloc = It.Value;
|
|
|
|
const FCombinedAllocationInfo* BeginCombinedAllocPtr = BeginSnaphot.Find( Callstack );
|
|
if (BeginCombinedAllocPtr)
|
|
{
|
|
FCombinedAllocationInfo CombinedAllocation;
|
|
CombinedAllocation += EndCombinedAlloc;
|
|
CombinedAllocation -= *BeginCombinedAllocPtr;
|
|
|
|
if (CombinedAllocation.IsAlive())
|
|
{
|
|
out_Result.Add( Callstack, CombinedAllocation );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
out_Result.Add( Callstack, EndCombinedAlloc );
|
|
}
|
|
}
|
|
|
|
// Sort by size.
|
|
out_Result.ValueSort( FCombinedAllocationInfoSizeGreater() );
|
|
}
|
|
}
|
|
|
|
void FRawStatsMemoryProfiler::CompareSnapshotsHumanReadable( const FName BeginSnaphotName, const FName EndSnaphotName, TMap<FString, FCombinedAllocationInfo>& out_Result )
|
|
{
|
|
FScopeLogTime SLT( TEXT( "CompareSnapshotsHumanReadable" ), nullptr, FScopeLogTime::ScopeLog_Milliseconds );
|
|
|
|
const auto BeginSnaphotPtr = SnapshotsWithDecodedScopedAllocations.Find( BeginSnaphotName );
|
|
const auto EndSnapshotPtr = SnapshotsWithDecodedScopedAllocations.Find( EndSnaphotName );
|
|
if (BeginSnaphotPtr && EndSnapshotPtr)
|
|
{
|
|
// Process data.
|
|
TMap<FString, FCombinedAllocationInfo> BeginSnaphot = *BeginSnaphotPtr;
|
|
TMap<FString, FCombinedAllocationInfo> EndSnaphot = *EndSnapshotPtr;
|
|
|
|
for (const auto& It : EndSnaphot)
|
|
{
|
|
const FString& Callstack = It.Key;
|
|
const FCombinedAllocationInfo EndCombinedAlloc = It.Value;
|
|
|
|
const FCombinedAllocationInfo* BeginCombinedAllocPtr = BeginSnaphot.Find( Callstack );
|
|
if (BeginCombinedAllocPtr)
|
|
{
|
|
FCombinedAllocationInfo CombinedAllocation;
|
|
CombinedAllocation += EndCombinedAlloc;
|
|
CombinedAllocation -= *BeginCombinedAllocPtr;
|
|
|
|
if (CombinedAllocation.IsAlive())
|
|
{
|
|
out_Result.Add( Callstack, CombinedAllocation );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
out_Result.Add( Callstack, EndCombinedAlloc );
|
|
}
|
|
}
|
|
|
|
// Sort by size.
|
|
out_Result.ValueSort( FCombinedAllocationInfoSizeGreater() );
|
|
}
|
|
}
|
|
|
|
#endif // STATS && UE_STATS_MEMORY_PROFILER_ENABLED
|