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

995 lines
32 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ProfilerSession.h"
#if STATS
#include "ProfilerFPSAnalyzer.h"
#include "ProfilerDataProvider.h"
/*-----------------------------------------------------------------------------
FProfilerStat, FProfilerGroup
-----------------------------------------------------------------------------*/
FProfilerStat FProfilerStat::Default;
FProfilerGroup FProfilerGroup::Default;
FProfilerStat::FProfilerStat( const uint32 InStatID /*= 0 */ )
: _Name( TEXT("(Stat-Default)") )
, _OwningGroupPtr( FProfilerGroup::GetDefaultPtr() )
, _ID( InStatID )
, _Type( EProfilerSampleTypes::InvalidOrMax )
{}
/*-----------------------------------------------------------------------------
FProfilerSession
-----------------------------------------------------------------------------*/
FProfilerSession::FProfilerSession( EProfilerSessionTypes InSessionType, const TSharedPtr<ISessionInstanceInfo>& InSessionInstanceInfo, FGuid InSessionInstanceID, FString InDataFilepath )
: bRequestStatMetadataUpdate( false )
, bLastPacket( false )
, StatMetaDataSize( 0 )
, OnTick( FTickerDelegate::CreateRaw( this, &FProfilerSession::HandleTicker ) )
, DataProvider( MakeShareable( new FArrayDataProvider() ) )
, StatMetaData( MakeShareable( new FProfilerStatMetaData() ) )
, EventGraphDataCurrent( nullptr )
, CreationTime( FDateTime::Now() )
, SessionType( InSessionType )
, SessionInstanceInfo( InSessionInstanceInfo )
, SessionInstanceID( InSessionInstanceID )
, DataFilepath( InDataFilepath )
, NumFrames( 0 )
, NumFramesProcessed( 0 )
, bDataPreviewing( false )
, bDataCapturing( false )
, bHasAllProfilerData( false )
, FPSAnalyzer( MakeShareable( new FFPSAnalyzer( 5, 0, 90 ) ) )
{}
FProfilerSession::FProfilerSession( const TSharedPtr<ISessionInstanceInfo>& InSessionInstanceInfo )
: bRequestStatMetadataUpdate( false )
, bLastPacket( false )
, StatMetaDataSize( 0 )
, OnTick( FTickerDelegate::CreateRaw( this, &FProfilerSession::HandleTicker ) )
, DataProvider( MakeShareable( new FArrayDataProvider() ) )
, StatMetaData( MakeShareable( new FProfilerStatMetaData() ) )
, EventGraphDataCurrent( nullptr )
, CreationTime( FDateTime::Now() )
, SessionType( EProfilerSessionTypes::Live )
, SessionInstanceInfo( InSessionInstanceInfo )
, SessionInstanceID( InSessionInstanceInfo->GetInstanceId() )
, DataFilepath( TEXT("") )
, NumFrames( 0 )
, NumFramesProcessed( 0 )
, bDataPreviewing( false )
, bDataCapturing( false )
, bHasAllProfilerData( false )
, FPSAnalyzer( MakeShareable( new FFPSAnalyzer( 5, 0, 90 ) ) )
{
// Randomize creation time to test loading profiler captures with different creation time and different amount of data.
CreationTime = FDateTime::Now() += FTimespan( 0, 0, FMath::RandRange( 2, 8 ) );
OnTickHandle = FTSTicker::GetCoreTicker().AddTicker( OnTick );
}
FProfilerSession::FProfilerSession( const FString& InDataFilepath )
: bRequestStatMetadataUpdate( false )
, bLastPacket( false )
, StatMetaDataSize( 0 )
, OnTick( FTickerDelegate::CreateRaw( this, &FProfilerSession::HandleTicker ) )
, DataProvider( MakeShareable( new FArrayDataProvider() ) )
, StatMetaData( MakeShareable( new FProfilerStatMetaData() ) )
, EventGraphDataCurrent( nullptr )
, CreationTime( FDateTime::Now() )
, SessionType( EProfilerSessionTypes::StatsFile )
, SessionInstanceInfo( nullptr )
, SessionInstanceID( FGuid::NewGuid() )
, DataFilepath( InDataFilepath.Replace( *FStatConstants::StatsFileExtension, TEXT( "" ) ) )
, NumFrames( 0 )
, NumFramesProcessed( 0 )
, bDataPreviewing( false )
, bDataCapturing( false )
, bHasAllProfilerData( false )
, FPSAnalyzer( MakeShareable( new FFPSAnalyzer( 5, 0, 90 ) ) )
{
// Randomize creation time to test loading profiler captures with different creation time and different amount of data.
CreationTime = FDateTime::Now() += FTimespan( 0, 0, FMath::RandRange( 2, 8 ) );
OnTickHandle = FTSTicker::GetCoreTicker().AddTicker( OnTick );
}
FProfilerSession::~FProfilerSession()
{
FTSTicker::GetCoreTicker().RemoveTicker( OnTickHandle );
}
const TSharedRef<FEventGraphData, ESPMode::ThreadSafe> FProfilerSession::GetEventGraphDataTotal() const
{
return EventGraphDataTotal.ToSharedRef();
}
const TSharedRef<FEventGraphData, ESPMode::ThreadSafe> FProfilerSession::GetEventGraphDataMaximum() const
{
return EventGraphDataMaximum.ToSharedRef();
}
const TSharedRef<FEventGraphData, ESPMode::ThreadSafe> FProfilerSession::GetEventGraphDataAverage() const
{
return EventGraphDataAverage.ToSharedRef();
}
TSharedRef<const FGraphDataSource> FProfilerSession::CreateGraphDataSource( const uint32 InStatID )
{
FGraphDataSource* GraphDataSource = new FGraphDataSource( AsShared(), InStatID );
return MakeShareable( GraphDataSource );
}
void FProfilerSession::UpdateAggregatedStats( const uint32 FrameIndex )
{
static FTotalTimeAndCount TimeAndCount( 0.0f, 0 );
PROFILER_SCOPE_LOG_TIME( TEXT( "2 FProfilerSession::UpdateAggregatedStats" ), &TimeAndCount );
const FIntPoint& IndicesForFrame = DataProvider->GetSamplesIndicesForFrame( FrameIndex );
const uint32 SampleStartIndex = IndicesForFrame.X;
const uint32 SampleEndIndex = IndicesForFrame.Y;
// Aggregate counters etc.
const FProfilerSampleArray& Collection = DataProvider->GetCollection();
for( uint32 SampleIndex = SampleStartIndex; SampleIndex < SampleEndIndex; SampleIndex++ )
{
const FProfilerSample& ProfilerSample = Collection[ SampleIndex ];
// Skip hierarchical samples to ignore misleading recursion which would be counted twice etc.
if (ProfilerSample.Type() == EProfilerSampleTypes::HierarchicalTime)
{
continue;
}
const uint32 StatID = ProfilerSample.StatID();
FProfilerAggregatedStat* AggregatedStat = AggregatedStats.Find( StatID );
if( !AggregatedStat )
{
const FProfilerStat& ProfilerStat = GetMetaData()->GetStatByID( StatID );
AggregatedStat = &AggregatedStats.Add( ProfilerSample.StatID(), FProfilerAggregatedStat( ProfilerStat.Name(), ProfilerStat.OwningGroup().Name(), ProfilerSample.Type() ) );
}
AggregatedStat->Aggregate( ProfilerSample, StatMetaData );
}
// Aggregate hierarchical stat.
const TMap<uint32, FInclusiveTime>& InclusiveAggregates = GetInclusiveAggregateStackStats( FrameIndex );
for (const auto& It : InclusiveAggregates)
{
const uint32 StatID = It.Key;
const FInclusiveTime InclusiveTime = It.Value;
const FProfilerStat& ProfilerStat = GetMetaData()->GetStatByID( StatID );
FProfilerAggregatedStat* AggregatedStat = AggregatedStats.Find( StatID );
if (!AggregatedStat)
{
AggregatedStat = &AggregatedStats.Add( StatID, FProfilerAggregatedStat( ProfilerStat.Name(), ProfilerStat.OwningGroup().Name(), ProfilerStat.Type() ) );
}
FProfilerSample MinimalSample( 0, 0, 0, InclusiveTime.DurationCycles, InclusiveTime.CallCount );
AggregatedStat->Aggregate( MinimalSample, StatMetaData );
}
for( auto It = AggregatedStats.CreateIterator(); It; ++It )
{
FProfilerAggregatedStat& AggregatedStat = It.Value();
AggregatedStat.Advance();
}
}
FEventGraphData* FProfilerSession::CombineEventGraphs( const uint32 FrameStartIndex, const uint32 FrameEndIndex )
{
FEventGraphData* EventGraphData = new FEventGraphData( this, FrameStartIndex );
for (uint32 FrameIndex = FrameStartIndex + 1; FrameIndex < FrameEndIndex; ++FrameIndex)
{
// Create a temporary event graph data for the specified frame.
const FEventGraphData CurrentEventGraphData( this, FrameIndex );
EventGraphData->Combine( CurrentEventGraphData );
}
return EventGraphData;
}
void FProfilerSession::CombineEventGraphsTask( const uint32 FrameStartIndex, const uint32 FrameEndIndex )
{
FEventGraphData* SubEventGraph = CombineEventGraphs( FrameStartIndex, FrameEndIndex );
CombinedSubEventGraphsLFL.Push( SubEventGraph );
}
FEventGraphContainer FProfilerSession::CreateEventGraphData( const uint32 FrameStartIndex, const uint32 FrameEndIndex )
{
static FTotalTimeAndCount Current( 0.0f, 0 );
SCOPE_LOG_TIME_FUNC_WITH_GLOBAL( &Current );
const uint32 TotalNumFrames = FrameEndIndex - FrameStartIndex + 1;
enum
{
// Minimum number of frames to combine per task
MIN_NUM_FRAMES_PER_TASK = 8
};
FEventGraphData* EventGraphData = nullptr;
static const bool bUseTaskGraph = true;
if (!bUseTaskGraph)
{
EventGraphData = CombineEventGraphs( FrameStartIndex, FrameEndIndex );
}
else
{
uint32 NumWorkerThreads = (uint32)FTaskGraphInterface::Get().GetNumWorkerThreads();
uint32 NumFramesPerTask = TotalNumFrames / (NumWorkerThreads + 1);
// Find the best configuration to utilize all worker threads.
while (NumFramesPerTask < MIN_NUM_FRAMES_PER_TASK)
{
NumWorkerThreads--;
NumFramesPerTask = TotalNumFrames / (NumWorkerThreads + 1);
if (NumWorkerThreads <= 0)
{
break;
}
}
UE_LOG( LogStats, Verbose, TEXT( "NumFrames: %u, NumWorkerThreads: %u, NumFramesPerTask: %u" ), TotalNumFrames, NumWorkerThreads, NumFramesPerTask );
uint32 NumRemainingFrames = TotalNumFrames;
uint32 MyFrameStartIndex = FrameStartIndex;
FGraphEventArray CompletionEvents;
// Don't run parallel code if not really needed.
if (NumFramesPerTask >= MIN_NUM_FRAMES_PER_TASK)
{
for (uint32 ThreadIndex = 0; ThreadIndex < NumWorkerThreads; ++ThreadIndex)
{
CompletionEvents.Add(FSimpleDelegateGraphTask::CreateAndDispatchWhenReady
(
FSimpleDelegateGraphTask::FDelegate::CreateRaw( this, &FProfilerSession::CombineEventGraphsTask, MyFrameStartIndex, MyFrameStartIndex + NumFramesPerTask ),
TStatId()
));
NumRemainingFrames -= NumFramesPerTask;
MyFrameStartIndex += NumFramesPerTask;
}
}
// Final job for remaining frames.
FEventGraphData* FinalSubEventGraph = CombineEventGraphs( MyFrameStartIndex, MyFrameStartIndex + NumRemainingFrames );
// Wait for results.
FTaskGraphInterface::Get().WaitUntilTasksComplete( CompletionEvents );
// Combine with sub event graphs.
TArray<FEventGraphData*> CombinedSubEventGraphs;
CombinedSubEventGraphsLFL.PopAll( CombinedSubEventGraphs );
for (const auto& It : CombinedSubEventGraphs)
{
FEventGraphData * const SubEventGraph = It;
FinalSubEventGraph->Combine( *SubEventGraph );
delete SubEventGraph;
}
EventGraphData = FinalSubEventGraph;
}
check( EventGraphData );
EventGraphData->Finalize( FrameStartIndex, FrameEndIndex + 1 );
TSharedRef<FEventGraphData, ESPMode::ThreadSafe> Total = MakeShareable( EventGraphData );
TSharedRef<FEventGraphData, ESPMode::ThreadSafe> Average = Total->DuplicateAsRef();
Average->SetAsAverage();
TSharedRef<FEventGraphData, ESPMode::ThreadSafe> Maximum = Total->DuplicateAsRef();
Maximum->SetAsMaximim();
FEventGraphContainer EventGraphContainer( FrameStartIndex, FrameEndIndex + 1, Average, Maximum, Total );
return EventGraphContainer;
}
void FProfilerSession::EventGraphCombine( const FEventGraphData* Current, const uint32 InNumFrames )
{
if (EventGraphDataTotal.IsValid())
{
EventGraphDataTotal->Combine( *Current );
}
else
{
EventGraphDataTotal = MakeShareable( new FEventGraphData( *Current ) );
}
if (InNumFrames > 0)
{
UpdateAllEventGraphs( InNumFrames );
}
}
void FProfilerSession::UpdateAllEventGraphs( const uint32 InNumFrames )
{
EventGraphDataTotal->Finalize( 0, DataProvider->GetNumFrames() );
EventGraphDataAverage = EventGraphDataTotal->DuplicateAsRef();
EventGraphDataAverage->SetAsAverage();
EventGraphDataMaximum = EventGraphDataTotal->DuplicateAsRef();
EventGraphDataMaximum->SetAsMaximim();
}
void FProfilerSession::UpdateAggregatedEventGraphData( const uint32 FrameIndex )
{
static FTotalTimeAndCount TimeAndCount( 0.0f, 0 );
PROFILER_SCOPE_LOG_TIME( TEXT( "3 FProfilerSession::UpdateAggregatedEventGraphData" ), &TimeAndCount );
CompletionSyncAggregatedEventGraphData();
// Create a temporary event graph data for the specified frame.
delete EventGraphDataCurrent;
EventGraphDataCurrent = new FEventGraphData( this, FrameIndex );
const uint32 NumFramesLocal = SessionType == EProfilerSessionTypes::Live ? DataProvider->GetNumFrames() : 0;
static const bool bUseTaskGraph = true;
if( bUseTaskGraph )
{
DECLARE_CYCLE_STAT(TEXT("FSimpleDelegateGraphTask.EventGraphData.GraphCombine"),
STAT_FSimpleDelegateGraphTask_EventGraphData_GraphCombine,
STATGROUP_TaskGraphTasks);
CompletionSync = FSimpleDelegateGraphTask::CreateAndDispatchWhenReady
(
FSimpleDelegateGraphTask::FDelegate::CreateRaw( this, &FProfilerSession::EventGraphCombine, EventGraphDataCurrent, NumFramesLocal ),
GET_STATID( STAT_FSimpleDelegateGraphTask_EventGraphData_GraphCombine )
);
}
else
{
EventGraphCombine( EventGraphDataCurrent, NumFramesLocal );
}
}
void FProfilerSession::CompletionSyncAggregatedEventGraphData()
{
if (CompletionSync.GetReference() && !CompletionSync->IsComplete())
{
static FTotalTimeAndCount JoinTasksTimeAndCont( 0.0f, 0 );
PROFILER_SCOPE_LOG_TIME( TEXT( "4 FProfilerSession::CombineJoinAndContinue" ), &JoinTasksTimeAndCont );
FTaskGraphInterface::Get().WaitUntilTaskCompletes( CompletionSync, ENamedThreads::GameThread );
}
}
enum
{
ProfilerThreadRoot = 1
};
bool FProfilerSession::HandleTicker( float DeltaTime )
{
static double ProcessingTime = 1.0;
ProcessingTime -= DeltaTime;
static int32 NumFramesProcessedLastTime = 0;
if (ProcessingTime < 0.0)
{
UE_LOG( LogStats, Verbose, TEXT( "NumFramesProcessedLastTime: %4i / %4i" ), NumFramesProcessedLastTime, FrameToProcess.Num() );
ProcessingTime = 1.0;
NumFramesProcessedLastTime = 0;
}
// Limit processing per frame. For a live preview this must be severely limited so we can respond to the pings from the game in time
const double TimeLimitMS = ( bDataPreviewing && SessionType == EProfilerSessionTypes::Live ) ? 25.0 : 250.0;
const double TimeLimit = TimeLimitMS / 1000.0;
double Seconds = 0;
for( int32 It = 0; It < FrameToProcess.Num(); It++ )
{
if( Seconds > TimeLimit )
{
break;
}
// Update metadata if needed
if (bRequestStatMetadataUpdate)
{
StatMetaData->Update(ClientStatMetadata);
bRequestStatMetadataUpdate = false;
}
static FTotalTimeAndCount Current(0.0f, 0);
PROFILER_SCOPE_LOG_TIME( TEXT( "1 FProfilerSession::HandleTicker" ), &Current );
NumFramesProcessedLastTime++;
NumFramesProcessed++;
FSimpleScopeSecondsCounter SecondsCounter(Seconds);
const uint32 TargetFrame = FrameToProcess[0];
FrameToProcess.RemoveAt( 0 );
FProfilerDataFrame& CurrentProfilerData = FrameToProfilerDataMapping.FindChecked( TargetFrame );
TMap<uint32, float> ThreadMS;
// Preprocess the hierarchical samples for the specified frame.
const TMap<uint32, FProfilerCycleGraph>& CycleGraphs = CurrentProfilerData.CycleGraphs;
// Add a root sample for this frame.
const uint32 FrameRootSampleIndex = DataProvider->AddHierarchicalSample( 0, StatMetaData->GetStatByID( 1 ).OwningGroup().ID(), ProfilerThreadRoot, 0, 0 );
uint32 GameThreadCycles = 0;
uint32 MaxThreadCycles = 0;
TMap<uint32, FInclusiveTime> StatIDToInclusiveTime;
for( auto ThreadIt = CycleGraphs.CreateConstIterator(); ThreadIt; ++ThreadIt )
{
const uint32 ThreadID = ThreadIt.Key();
const FProfilerCycleGraph& ThreadGraph = ThreadIt.Value();
// Calculate total time for this thread.
FInclusiveTime ThreadDuration;
ThreadDuration.CallCount = 1;
for( int32 Index = 0; Index < ThreadGraph.Children.Num(); Index++ )
{
ThreadDuration.DurationCycles += ThreadGraph.Children[Index].Value;
}
if (ThreadDuration.DurationCycles > 0)
{
// Check for game thread.
const FString GameThreadName = FName( NAME_GameThread ).GetPlainNameString();
const bool bGameThreadFound = StatMetaData->GetThreadDescriptions().FindChecked( ThreadID ).Contains( GameThreadName );
if( bGameThreadFound )
{
GameThreadCycles = ThreadDuration.DurationCycles;
}
// Add a root sample for each thread.
const uint32& StatThreadID = StatMetaData->ThreadIDtoStatID.FindChecked( ThreadID );
const uint32 ThreadRootSampleIndex = DataProvider->AddHierarchicalSample
(
StatThreadID,
StatMetaData->GetStatByID(StatThreadID).OwningGroup().ID(),
StatThreadID,
ThreadDuration.DurationCycles, 1,
FrameRootSampleIndex
);
ThreadMS.FindOrAdd( ThreadID ) = static_cast<float>(StatMetaData->ConvertCyclesToMS( ThreadDuration.DurationCycles ));
// Recursively add children and parent to the root samples.
for( int32 Index = 0; Index < ThreadGraph.Children.Num(); Index++ )
{
const FProfilerCycleGraph& CycleGraph = ThreadGraph.Children[Index];
const uint32 ChildDurationCycles = CycleGraph.Value;
if (ChildDurationCycles > 0.0)
{
PopulateHierarchy_Recurrent( StatThreadID, CycleGraph, ChildDurationCycles, ThreadRootSampleIndex, StatIDToInclusiveTime );
}
}
StatIDToInclusiveTime.Add( StatThreadID, ThreadDuration );
MaxThreadCycles = FMath::Max( MaxThreadCycles, ThreadDuration.DurationCycles );
}
}
InclusiveAggregateStackStats.Add( MoveTemp( StatIDToInclusiveTime ) );
// Fix the root stat time.
FProfilerSampleArray& MutableCollection = const_cast<FProfilerSampleArray&>(DataProvider->GetCollection());
MutableCollection[FrameRootSampleIndex].SetDurationCycles( GameThreadCycles != 0 ? GameThreadCycles : MaxThreadCycles );
// Update FPS analyzer.
const float GameThreadTimeMS = static_cast<float>(StatMetaData->ConvertCyclesToMS( GameThreadCycles ));
FPSAnalyzer->AddSample( GameThreadTimeMS > 0.0f ? 1000.0f / GameThreadTimeMS : 0.0f );
// Process the non-hierarchical samples for the specified frame.
{
// Process integer counters.
for( int32 Index = 0; Index < CurrentProfilerData.CountAccumulators.Num(); Index++ )
{
const FProfilerCountAccumulator& IntCounter = CurrentProfilerData.CountAccumulators[Index];
const EProfilerSampleTypes::Type ProfilerSampleType = StatMetaData->GetSampleTypeForStatID( IntCounter.StatId );
DataProvider->AddCounterSample( StatMetaData->GetStatByID(IntCounter.StatId).OwningGroup().ID(), IntCounter.StatId, (double)IntCounter.Value, ProfilerSampleType );
}
// Process floating point counters.
for( int32 Index = 0; Index < CurrentProfilerData.FloatAccumulators.Num(); Index++ )
{
const FProfilerFloatAccumulator& FloatCounter = CurrentProfilerData.FloatAccumulators[Index];
DataProvider->AddCounterSample( StatMetaData->GetStatByID(FloatCounter.StatId).OwningGroup().ID(), FloatCounter.StatId, (double)FloatCounter.Value, EProfilerSampleTypes::NumberFloat );
}
}
// Advance frame
const uint32 DataProviderFrameIndex = DataProvider->GetNumFrames();
DataProvider->AdvanceFrame( static_cast<float>(StatMetaData->ConvertCyclesToMS( MaxThreadCycles )) );
// Update aggregated stats
UpdateAggregatedStats( DataProviderFrameIndex );
// Update aggregated events - NOTE: This may update the metadata and set bRequestStatMetadataUpdate = true
UpdateAggregatedEventGraphData( DataProviderFrameIndex );
// Update mini-view.
OnAddThreadTime.ExecuteIfBound( DataProviderFrameIndex, ThreadMS, StatMetaData );
FrameToProfilerDataMapping.Remove( TargetFrame );
}
if( SessionType == EProfilerSessionTypes::StatsFile )
{
if( FrameToProcess.Num() == 0 && bHasAllProfilerData )
{
CompletionSyncAggregatedEventGraphData();
// Advance event graphs.
if (DataProvider->GetNumFrames() == 0)
{
EventGraphDataTotal = MakeShareable(new FEventGraphData());
}
UpdateAllEventGraphs( DataProvider->GetNumFrames() );
// Broadcast that a capture file has been fully processed.
OnCaptureFileProcessed.ExecuteIfBound( GetInstanceID() );
// Disable tick method as we no longer need to tick.
return false;
}
}
return true;
}
void FProfilerSession::PopulateHierarchy_Recurrent
(
const uint32 StatThreadID,
const FProfilerCycleGraph& ParentGraph,
const uint32 ParentDurationCycles,
const uint32 ParentSampleIndex,
TMap<uint32, FInclusiveTime>& StatIDToInclusiveTime
)
{
const TSharedRef<FProfilerStatMetaData> MetaData = GetMetaData();
#if UE_BUILD_DEBUG
const FName& ParentStatName = MetaData->GetStatByID( ParentGraph.StatId ).Name();
const FName& ParentGroupName = MetaData->GetStatByID( ParentGraph.StatId ).OwningGroup().Name();
#endif // UE_BUILD_DEBUG
{
FInclusiveTime& InclusiveTime = StatIDToInclusiveTime.FindOrAdd( ParentGraph.StatId );
InclusiveTime.Recursion++;
}
const uint32 SampleIndex = DataProvider->AddHierarchicalSample
(
StatThreadID,
MetaData->GetStatByID(ParentGraph.StatId).OwningGroup().ID(),
ParentGraph.StatId,
ParentDurationCycles, ParentGraph.CallsPerFrame,
ParentSampleIndex
);
uint32 ChildrenDurationCycles = 0;
for( int32 DataIndex = 0; DataIndex < ParentGraph.Children.Num(); DataIndex++ )
{
const FProfilerCycleGraph& ChildCyclesCounter = ParentGraph.Children[DataIndex];
const uint32 ChildDurationCycles = ChildCyclesCounter.Value;
if (ChildDurationCycles > 0.0)
{
PopulateHierarchy_Recurrent( StatThreadID, ChildCyclesCounter, ChildDurationCycles, SampleIndex, StatIDToInclusiveTime );
}
ChildrenDurationCycles += ChildDurationCycles;
}
if (ParentDurationCycles > ChildrenDurationCycles && ParentGraph.Children.Num() > 0)
{
const uint32 SelfTimeCycles = ParentDurationCycles - ChildrenDurationCycles;
// Create a fake stat that represents this profiler sample's exclusive time.
// This is required if we want to create correct combined event graphs later.
DataProvider->AddHierarchicalSample
(
StatThreadID,
MetaData->GetStatByID(0).OwningGroup().ID(),
0, // @see FProfilerStatMetaData.Update, 0 means "Self"
SelfTimeCycles, 1,
SampleIndex
);
}
{
FInclusiveTime& InclusiveTime = StatIDToInclusiveTime.FindChecked( ParentGraph.StatId );
InclusiveTime.Recursion--;
if (InclusiveTime.Recursion == 0)
{
InclusiveTime.DurationCycles += ParentDurationCycles;
InclusiveTime.CallCount++;
}
}
}
void FProfilerSession::LoadComplete()
{
bHasAllProfilerData = true;
}
void FProfilerSession::SetNumberOfFrames( int32 InNumFrames )
{
NumFrames = InNumFrames;
InclusiveAggregateStackStats.Reserve( NumFrames );
AggregatedStats.Reserve( 4096 );
FrameToProfilerDataMapping.Reserve( 256 );
}
float FProfilerSession::GetProgress() const
{
if (NumFrames > 0)
{
return (float)NumFramesProcessed / (float)NumFrames;
}
return 0.0f;
}
const SIZE_T FProfilerSession::GetMemoryUsage() const
{
SIZE_T MemoryUsage = 0;
MemoryUsage += DataProvider->GetMemoryUsage();
MemoryUsage += StatMetaData->GetMemoryUsage();
MemoryUsage += AggregatedStats.GetAllocatedSize();
MemoryUsage += InclusiveAggregateStackStats.GetAllocatedSize();
for (const auto& It : InclusiveAggregateStackStats)
{
MemoryUsage += It.GetAllocatedSize();
}
MemoryUsage += FPSAnalyzer->GetMemoryUsage();
return MemoryUsage;
}
void FProfilerSession::UpdateProfilerData( const FProfilerDataFrame& Content )
{
FrameToProfilerDataMapping.FindOrAdd( Content.Frame ) = Content;
FrameToProcess.Add( Content.Frame );
}
void FProfilerSession::UpdateMetadata( const FStatMetaData& InClientStatMetaData )
{
const uint32 NewStatMetaDataSize = InClientStatMetaData.GetMetaDataSize();
if( NewStatMetaDataSize != StatMetaDataSize )
{
ClientStatMetadata = InClientStatMetaData;
bRequestStatMetadataUpdate = true;
StatMetaDataSize = NewStatMetaDataSize;
}
}
void FProfilerStatMetaData::Update( const FStatMetaData& ClientStatMetaData )
{
PROFILER_SCOPE_LOG_TIME( TEXT( "FProfilerStatMetaData.Update" ), nullptr );
// Iterate through all thread descriptions.
ThreadDescriptions.Append( ClientStatMetaData.ThreadDescriptions );
// Initialize fake stat for Self.
const uint32 NoGroupID = 0;
InitializeGroup( NoGroupID, "NoGroup" );
InitializeStat( 0, NoGroupID, TEXT( "Self" ), STATTYPE_CycleCounter );
InitializeStat( ProfilerThreadRoot, NoGroupID, FStatConstants::NAME_ThreadRoot.GetPlainNameString(), STATTYPE_CycleCounter, FStatConstants::NAME_ThreadRoot );
// Iterate through all stat group descriptions.
for( auto It = ClientStatMetaData.GroupDescriptions.CreateConstIterator(); It; ++It )
{
const FStatGroupDescription& GroupDesc = It.Value();
InitializeGroup( GroupDesc.ID, GroupDesc.Name );
}
// Iterate through all stat descriptions.
for( auto It = ClientStatMetaData.StatDescriptions.CreateConstIterator(); It; ++It )
{
const FStatDescription& StatDesc = It.Value();
InitializeStat( StatDesc.ID, StatDesc.GroupID, StatDesc.Name, (EStatType)StatDesc.StatType );
}
SecondsPerCycle = ClientStatMetaData.SecondsPerCycle;
}
void FProfilerStatMetaData::UpdateFromStatsState( const FStatsThreadState& StatsThreadStats )
{
TMap<FName, int32> GroupFNameIDs;
for( auto It = StatsThreadStats.Threads.CreateConstIterator(); It; ++It )
{
ThreadDescriptions.Add( It.Key(), It.Value().ToString() );
}
const uint32 NoGroupID = 0;
const uint32 ThreadGroupID = 1;
// Special groups.
InitializeGroup( NoGroupID, "NoGroup" );
// Self must be 0.
InitializeStat( 0, NoGroupID, TEXT( "Self" ), STATTYPE_CycleCounter );
// ThreadRoot must be 1.
InitializeStat( 1, NoGroupID, FStatConstants::NAME_ThreadRoot.GetPlainNameString(), STATTYPE_CycleCounter, FStatConstants::NAME_ThreadRoot );
int32 UniqueID = 15;
TArray<FName> GroupFNames;
StatsThreadStats.Groups.MultiFind( NAME_Groups, GroupFNames );
for( const auto& GroupFName : GroupFNames )
{
UniqueID++;
InitializeGroup( UniqueID, GroupFName.ToString() );
GroupFNameIDs.Add( GroupFName, UniqueID );
}
for( auto It = StatsThreadStats.ShortNameToLongName.CreateConstIterator(); It; ++It )
{
const FStatMessage& LongName = It.Value();
const FName GroupName = LongName.NameAndInfo.GetGroupName();
if( GroupName == NAME_Groups )
{
continue;
}
const int32 GroupID = GroupFNameIDs.FindChecked( GroupName );
const FName StatName = It.Key();
UniqueID++;
EStatType StatType = STATTYPE_Error;
if( LongName.NameAndInfo.GetField<EStatDataType>() == EStatDataType::ST_int64 )
{
if( LongName.NameAndInfo.GetFlag( EStatMetaFlags::IsCycle ) )
{
StatType = STATTYPE_CycleCounter;
}
else if( LongName.NameAndInfo.GetFlag( EStatMetaFlags::IsMemory ) )
{
StatType = STATTYPE_MemoryCounter;
}
else
{
StatType = STATTYPE_AccumulatorDWORD;
}
}
else if( LongName.NameAndInfo.GetField<EStatDataType>() == EStatDataType::ST_double )
{
StatType = STATTYPE_AccumulatorFLOAT;
}
else if( LongName.NameAndInfo.GetField<EStatDataType>() == EStatDataType::ST_Ptr )
{
// Not supported at this moment.
continue;
}
check( StatType != STATTYPE_Error );
int32 StatID = UniqueID;
// Some hackery.
if( StatName == TEXT( "STAT_FrameTime" ) )
{
StatID = 2;
}
const FString Description = LongName.NameAndInfo.GetDescription();
const FString StatDesc = !Description.IsEmpty() ? Description : StatName.ToString();
InitializeStat( StatID, GroupID, StatDesc, StatType, StatName );
// Setup thread id to stat id.
if( GroupName == FStatConstants::NAME_ThreadGroup )
{
uint32 ThreadID = 0;
for( auto ThreadsIt = StatsThreadStats.Threads.CreateConstIterator(); ThreadsIt; ++ThreadsIt )
{
if (ThreadsIt.Value() == StatName)
{
ThreadID = ThreadsIt.Key();
break;
}
}
ThreadIDtoStatID.Add( ThreadID, StatID );
// Game thread is always NAME_GameThread
if( StatName == NAME_GameThread )
{
GameThreadID = ThreadID;
}
// Rendering thread may be "Rendering thread" or NAME_RenderThread with an index
else if( StatName.GetPlainNameString().Contains( FName(NAME_RenderThread).GetPlainNameString() ) )
{
RenderThreadIDs.AddUnique( ThreadID );
}
else if( StatName.GetPlainNameString().Contains( TEXT( "RenderingThread" ) ) )
{
RenderThreadIDs.AddUnique( ThreadID );
}
}
}
}
/*-----------------------------------------------------------------------------
FProfilerAggregatedStat
-----------------------------------------------------------------------------*/
FProfilerAggregatedStat::FProfilerAggregatedStat( const FName& InStatName, const FName& InGroupName, EProfilerSampleTypes::Type InStatType ) : _StatName( InStatName )
, _GroupName( InGroupName )
, _ValueOneFrame( 0.0f )
, _ValueAllFrames( 0.0f )
, _MinValueAllFrames( FLT_MAX )
, _MaxValueAllFrames( FLT_MIN )
, _NumCallsAllFrames( 0 )
, _NumCallsOneFrame( 0 )
, _MinNumCallsAllFrames( MAX_uint32 )
, _MaxNumCallsAllFrames( 0 )
, _NumFrames( 0 )
, _NumFramesWithCall( 0 )
, StatType( InStatType )
{
}
FString FProfilerAggregatedStat::ToString() const
{
FString FormattedValueStr;
if (StatType == EProfilerSampleTypes::HierarchicalTime)
{
FormattedValueStr = FString::Printf( TEXT( "{Value Min:%.3f Avg:%.3f Max:%.3f (MS) / Calls (%.1f%%) Min:%.1f Avg:%.1f Max:%.1f}" ),
MinValue(), AvgValue(), MaxValue(),
FramesWithCallPct(), MinNumCalls(), AvgNumCalls(), MaxNumCalls() );
}
else if (StatType == EProfilerSampleTypes::Memory)
{
FormattedValueStr = FString::Printf( TEXT( "{Min:%.2f Avg:%.2f Max:%.2f (KB)}" ), MinValue(), AvgValue(), MaxValue() );
}
else if (StatType == EProfilerSampleTypes::NumberInt)
{
FormattedValueStr = FString::Printf( TEXT( "{Min:%.2f Avg:%.2f Max:%.2f}" ), MinValue(), AvgValue(), MaxValue() );
}
else if (StatType == EProfilerSampleTypes::NumberFloat)
{
FormattedValueStr = FString::Printf( TEXT( "{Min:%.2f Avg:%.2f Max:%.2f}" ), MinValue(), AvgValue(), MaxValue() );
}
else
{
check( 0 );
}
return FormattedValueStr;
}
FString FProfilerAggregatedStat::GetFormattedValue( const Type ValueType ) const
{
check( ValueType < Type::EInvalidOrMax );
const double ValueArray[Type::EInvalidOrMax] =
{
AvgValue(),
MinValue(),
MaxValue(),
AvgNumCalls(),
MinNumCalls(),
MaxNumCalls(),
FramesWithCallPct(),
};
FString FormatValueStr;
if (StatType == EProfilerSampleTypes::HierarchicalTime)
{
if (ValueType == EMinValue || ValueType == EAvgValue || ValueType == EMaxValue)
{
FormatValueStr = FString::Printf( TEXT( "%.3f (MS)" ), ValueArray[ValueType] );
}
else if (ValueType == EFramesWithCallPct)
{
FormatValueStr = FString::Printf( TEXT( "%.1f%%" ), ValueArray[EFramesWithCallPct] );
}
else
{
FormatValueStr = FString::Printf( TEXT( "%.1f" ), ValueArray[ValueType] );
}
}
else if (StatType == EProfilerSampleTypes::Memory)
{
FormatValueStr = FString::Printf( TEXT( "%.2f (KB)" ), ValueArray[ValueType] );
}
else if (StatType == EProfilerSampleTypes::NumberInt)
{
FormatValueStr = FString::Printf( TEXT( "%.2f" ), ValueArray[ValueType] );
}
else if (StatType == EProfilerSampleTypes::NumberFloat)
{
FormatValueStr = FString::Printf( TEXT( "%.2f" ), ValueArray[ValueType] );
}
else
{
check( 0 );
}
return FormatValueStr;
}
void FProfilerAggregatedStat::Advance()
{
_NumFrames++;
_NumCallsAllFrames += _NumCallsOneFrame;
_ValueAllFrames += _ValueOneFrame;
// Calculate new extreme values.
if (StatType == EProfilerSampleTypes::HierarchicalTime)
{
// Update the extreme values only if this stat has been called at least once.
if (_NumCallsOneFrame > 0)
{
_NumFramesWithCall++;
}
{
_MinValueAllFrames = FMath::Min<double>( _MinValueAllFrames, _ValueOneFrame );
_MaxValueAllFrames = FMath::Max<double>( _MaxValueAllFrames, _ValueOneFrame );
_MinNumCallsAllFrames = FMath::Min<uint32>( _MinNumCallsAllFrames, _NumCallsOneFrame );
_MaxNumCallsAllFrames = FMath::Max<uint32>( _MaxNumCallsAllFrames, _NumCallsOneFrame );
}
}
else
{
_MinValueAllFrames = FMath::Min<double>( _MinValueAllFrames, _ValueOneFrame );
_MaxValueAllFrames = FMath::Max<double>( _MaxValueAllFrames, _ValueOneFrame );
}
_ValueOneFrame = 0.0f;
_NumCallsOneFrame = 0;
}
void FProfilerAggregatedStat::Aggregate(const FProfilerSample& Sample, const TSharedRef<FProfilerStatMetaData>& Metadata)
{
double TypedValue = 0.0;
// Determine whether to we are reading a time hierarchical sample or not.
if (Sample.Type() == EProfilerSampleTypes::HierarchicalTime)
{
TypedValue = Metadata->ConvertCyclesToMS( Sample.GetDurationCycles() );
_NumCallsOneFrame += Sample.GetCallCount();
}
else
{
TypedValue = Sample.GetDoubleValue();
if (Sample.Type() == EProfilerSampleTypes::Memory)
{
// @TODO: Remove later
TypedValue *= 1.0f / 1024.0f;
}
}
_ValueOneFrame += TypedValue;
}
#endif // STATS