EM_Task/CoreUObject/Private/UObject/ScriptStackTracker.cpp

141 lines
4.7 KiB
C++
Raw Permalink Normal View History

2026-02-13 16:18:33 +08:00
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
ScriptStackTracker.cpp: Stack Tracking within Unreal Engine.
=============================================================================*/
#include "UObject/ScriptStackTracker.h"
#include "UObject/Stack.h"
DEFINE_LOG_CATEGORY_STATIC(LogScriptStackTracker, Log, All);
/**
* Captures the current stack and updates stack tracking information.
*/
void FScriptStackTracker::CaptureStackTrace(const FFrame* StackFrame, int32 EntriesToIgnore /*=0*/)
{
// Avoid re-rentrancy as the code uses TArray/TMap.
if (!bAvoidCapturing && bIsEnabled)
{
// Scoped true/ false.
bAvoidCapturing = true;
// Capture callstack and create CRC.
FString StackTrace = StackFrame->GetStackTrace();
uint32 CRC = FCrc::MemCrc_DEPRECATED(*StackTrace, StackTrace.Len());
// Use index if found
int32* IndexPtr = CRCToCallStackIndexMap.Find(CRC);
if (IndexPtr)
{
// Increase stack count for existing callstack.
CallStacks[*IndexPtr].StackCount++;
}
// Encountered new call stack, add to array and set index mapping.
else
{
FCallStack NewCallStack;
NewCallStack.StackCount = 1;
NewCallStack.StackTrace = StackTrace;
int32 Index = CallStacks.Add(NewCallStack);
CRCToCallStackIndexMap.Add(CRC, Index);
}
// We're done capturing.
bAvoidCapturing = false;
}
}
/**
* Dumps capture stack trace summary to the passed in log.
*/
void FScriptStackTracker::DumpStackTraces(int32 StackThreshold, FOutputDevice& Ar)
{
// Avoid distorting results while we log them.
check(!bAvoidCapturing);
bAvoidCapturing = true;
// Make a copy as sorting causes index mismatch with TMap otherwise.
TArray<FCallStack> SortedCallStacks = CallStacks;
// Compare function, sorting callstack by stack count in descending order.
struct FCompareStackCount
{
FORCEINLINE bool operator()(const FCallStack& A, const FCallStack& B) const { return B.StackCount < A.StackCount; }
};
// Sort callstacks in descending order by stack count.
SortedCallStacks.Sort(FCompareStackCount());
// Iterate over each callstack to get total stack count.
uint64 TotalStackCount = 0;
for (int32 CallStackIndex = 0; CallStackIndex < SortedCallStacks.Num(); CallStackIndex++)
{
const FCallStack& CallStack = SortedCallStacks[CallStackIndex];
TotalStackCount += CallStack.StackCount;
}
// Calculate the number of frames we captured.
int32 FramesCaptured = 0;
if (bIsEnabled)
{
FramesCaptured = GFrameCounter - StartFrameCounter;
}
else
{
FramesCaptured = StopFrameCounter - StartFrameCounter;
}
// Log quick summary as we don't log each individual so totals in CSV won't represent real totals.
Ar.Logf(TEXT("Captured %i unique callstacks totalling %i function calls over %i frames, averaging %5.2f calls/frame"), SortedCallStacks.Num(), (int32)TotalStackCount, FramesCaptured, (float)TotalStackCount / FramesCaptured);
// Iterate over each callstack and write out info in human readable form in CSV format
for (int32 CallStackIndex = 0; CallStackIndex < SortedCallStacks.Num(); CallStackIndex++)
{
const FCallStack& CallStack = SortedCallStacks[CallStackIndex];
// Avoid log spam by only logging above threshold.
if (CallStack.StackCount > StackThreshold)
{
// First row is stack count.
FString CallStackString = FString::FromInt((int32)CallStack.StackCount);
CallStackString += LINE_TERMINATOR;
CallStackString += CallStack.StackTrace;
// Finally log with ',' prefix so "Log:" can easily be discarded as row in Excel.
Ar.Logf(TEXT(",%s"), *CallStackString);
}
}
// Done logging.
bAvoidCapturing = false;
}
/** Resets stack tracking. */
void FScriptStackTracker::ResetTracking()
{
check(!bAvoidCapturing);
CRCToCallStackIndexMap.Empty();
CallStacks.Empty();
// Reset the markers
StartFrameCounter = GFrameCounter;
StopFrameCounter = GFrameCounter;
}
/** Toggles tracking. */
void FScriptStackTracker::ToggleTracking()
{
bIsEnabled = !bIsEnabled;
// Enabled
if (bIsEnabled)
{
UE_LOG(LogScriptStackTracker, Log, TEXT("Script stack tracking is now enabled."));
StartFrameCounter = GFrameCounter;
}
// Disabled.
else
{
StopFrameCounter = GFrameCounter;
UE_LOG(LogScriptStackTracker, Log, TEXT("Script stack tracking is now disabled."));
}
}