Files
UnrealEngine/Engine/Source/Developer/TraceServices/Private/Model/CallstacksProvider.cpp
2025-05-18 13:04:45 +08:00

218 lines
6.2 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CallstacksProvider.h"
#include "Algo/Unique.h"
#include "Containers/ArrayView.h"
#include "Misc/ScopeRWLock.h"
#include "ModuleProvider.h"
#include "TraceServices/Model/AnalysisSession.h"
#include "UObject/NameTypes.h"
namespace TraceServices
{
////////////////////////////////////////////////////////////////////////////////////////////////////
static const FResolvedSymbol GNeverResolveSymbol(ESymbolQueryResult::NotLoaded, nullptr, nullptr, nullptr, 0, EResolvedSymbolFilterStatus::NotFiltered);
static const FResolvedSymbol GNotFoundSymbol(ESymbolQueryResult::NotFound, TEXT("Unknown"), nullptr, nullptr, 0, EResolvedSymbolFilterStatus::NotFiltered);
static constexpr FStackFrame GNotFoundStackFrame = { 0, &GNotFoundSymbol };
static const FCallstack GNotFoundCallstack(&GNotFoundStackFrame, 1);
////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef TRACE_CALLSTACK_STATS
static struct FCallstackProviderStats
{
uint64 Callstacks;
uint64 Frames;
uint64 FrameCountHistogram[256];
} GCallstackStats;
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////
FCallstacksProvider::FCallstacksProvider(IAnalysisSession& InSession)
: Session(InSession)
, ModuleProvider(nullptr)
, Callstacks(InSession.GetLinearAllocator(), CallstacksPerPage)
, Frames(InSession.GetLinearAllocator(), FramesPerPage)
{
// Let the first callstack to be the default empty callstack (i.e. CallstackId == 0, "callstack not recorded").
FCallstack& FirstCallstack = Callstacks.PushBack();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FCallstacksProvider::AddCallstack(uint32 InCallstackId, const uint64* InFrames, uint8 InFrameCount)
{
if (InCallstackId == 0)
{
return;
}
#ifdef TRACE_CALLSTACK_STATS
GCallstackStats.Callstacks++;
GCallstackStats.Frames += InFrameCount;
GCallstackStats.FrameCountHistogram[InFrameCount]++;
#endif
// The module provider is created on the fly so we want to cache it
// once it's available. Note that the module provider is conditionally
// created so EditProvider() may return a null pointer.
if (!ModuleProvider)
{
ModuleProvider = Session.EditProvider<IModuleProvider>(GetModuleProviderName());
}
if (InFrameCount > 0)
{
// Make sure all the frames fit on one page by appending dummy entries.
const uint64 PageHeadroom = Frames.GetPageSize() - (Frames.Num() % Frames.GetPageSize());
if (PageHeadroom < InFrameCount)
{
FRWScopeLock WriteLock(EntriesLock, SLT_Write);
uint64 EntriesToAdd = PageHeadroom + 1; // Fill page and allocate one on next
do { Frames.PushBack(); } while (--EntriesToAdd);
}
// Append the incoming frames.
for (uint32 FrameIdx = 0; FrameIdx < InFrameCount; ++FrameIdx)
{
FStackFrame& F = Frames.PushBack();
F.Addr = InFrames[FrameIdx];
if (ModuleProvider)
{
// This will return immediately. The result will be empty if the symbol
// has not been encountered before, and resolution has been queued up.
F.Symbol = ModuleProvider->GetSymbol(InFrames[FrameIdx]);
}
else
{
F.Symbol = &GNeverResolveSymbol;
}
}
}
{
FRWScopeLock WriteLock(EntriesLock, SLT_Write);
FCallstack* Callstack = nullptr;
if (InCallstackId < Callstacks.Num())
{
Callstack = &Callstacks[InCallstackId];
}
else
{
while (InCallstackId >= Callstacks.Num())
{
Callstack = &Callstacks.PushBack();
Callstack->InitEmpty(Callstacks.Num() - 1);
}
}
check(Callstack);
check(Callstack->IsEmpty() && Callstack->GetEmptyId() == uint64(InCallstackId)); // adding same callstack id twice?
if (InFrameCount > 0)
{
check(InFrameCount <= Frames.Num());
Callstack->Init(&Frames[Frames.Num() - InFrameCount], InFrameCount);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
uint32 FCallstacksProvider::AddCallstackWithHash(uint64 InCallstackHash, const uint64* InFrames, uint8 InFrameCount)
{
if (InCallstackHash == 0)
{
return 0;
}
uint32 CallstackId;
{
FRWScopeLock WriteLock(EntriesLock, SLT_Write);
CallstackId = static_cast<uint32>(Callstacks.Num());
CallstackMap.Add(InCallstackHash, CallstackId);
}
AddCallstack(CallstackId, InFrames, InFrameCount);
return CallstackId;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
uint32 FCallstacksProvider::GetCallstackIdForHash(uint64 InCallstackHash) const
{
if (InCallstackHash == 0)
{
return 0;
}
FRWScopeLock ReadLock(EntriesLock, SLT_ReadOnly);
const uint32* CallstackIdPtr = CallstackMap.Find(InCallstackHash);
if (CallstackIdPtr)
{
return *CallstackIdPtr;
}
else
{
return 0;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const FCallstack* FCallstacksProvider::GetCallstack(uint32 CallstackId) const
{
FRWScopeLock ReadLock(EntriesLock, SLT_ReadOnly);
if (CallstackId < Callstacks.Num())
{
return &Callstacks[CallstackId];
}
else
{
return &GNotFoundCallstack;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void FCallstacksProvider::GetCallstacks(const TArrayView<uint32>& CallstackIds, FCallstack const** OutCallstacks) const
{
uint64 OutIdx(0);
check(OutCallstacks != nullptr);
FRWScopeLock ReadLock(EntriesLock, SLT_ReadOnly);
for (uint64 CallstackId : CallstackIds)
{
if (CallstackId < Callstacks.Num())
{
OutCallstacks[OutIdx] = &Callstacks[CallstackId];
}
else
{
OutCallstacks[OutIdx] = &GNotFoundCallstack;
}
OutIdx++;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
FName GetCallstacksProviderName()
{
static const FName Name("CallstacksProvider");
return Name;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const ICallstacksProvider* ReadCallstacksProvider(const IAnalysisSession& Session)
{
return Session.ReadProvider<ICallstacksProvider>(GetCallstacksProviderName());
}
////////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace TraceServices