Files
UnrealEngineUWP/Engine/Source/Developer/ProfilerClient/Private/ProfilerClientManager.cpp
Robert Manuszewski f4fb4b8596 Copying //UE4/Dev-Core to //UE4/Dev-Main (Source: //UE4/Dev-Core @ 2996057)
==========================
MAJOR FEATURES + CHANGES
==========================

Change 2975196 on 2016/05/12 by Robert.Manuszewski

	Garbage Collector will no longer be responsible for generating class token stream, instead the token stream will be generated on startup or when a class has finished loading.

	- This way we can avoid very long GC times after new blueprints have been loaded.
	- Temporarily enabled CLASS_TokenStreamAssembled check in development builds (for testing purposes)

Change 2993960 on 2016/05/30 by Robert.Manuszewski

	Fixing leaked linkers created by blocking load requests during async loading.

Change 2959398 on 2016/04/28 by Steve.Robb

	TMap references are strong and cannot be nulled by pending kill.  This makes references in values strong too, even though we only really care about keys, which will corrupt the map when nulled.

	#jira UE-20828

Change 2960723 on 2016/04/29 by Graeme.Thornton

	Fix for texture asset import data being ignored when async loaded

Change 2960938 on 2016/04/29 by Robert.Manuszewski

	Nulling out sql db handle after closing it.

Change 2967127 on 2016/05/05 by Steve.Robb

	Move constructors explicitly disabled in generated code.

Change 2967143 on 2016/05/05 by Steve.Robb

	Static analysis fixes:

	warning C6326: Potential comparison of a constant with another constant.

Change 2967164 on 2016/05/05 by Steve.Robb

	Static analysis fixes:

	warning C6011: Dereferencing NULL pointer

Change 2968650 on 2016/05/06 by Steve.Robb

	Fix for HotReload copying module manager.

Change 2968915 on 2016/05/06 by Robert.Manuszewski

	Fixing spelling of SetImageIntegrityStatus function name.

Change 2970406 on 2016/05/09 by Steve.Robb

	Static analysis fixes:

	Function uses '...' bytes of stack:  exceeds /analyze:stacksize '81940'.  Consider moving some data to heap.

Change 2970419 on 2016/05/09 by Steve.Robb

	Static analysis fixes:

	warning C6326: Potential comparison of a constant with another constant.
	warning C6011: Dereferencing NULL pointer '...'.
	warning C6385: Reading invalid data from '...':  the readable size is '...' bytes, but '...' bytes may be read.
	warning C6386: Buffer overrun while writing to '...':  the writable size is '...' bytes, but '...' bytes might be written.

Change 2970431 on 2016/05/09 by Steve.Robb

	Static analysis fixes:

	warning C6299: Explicitly comparing a bit field to a Boolean type will yield unexpected results.

Change 2972032 on 2016/05/10 by Steven.Hutton

	Workflow fixes to bugg / crashgroup filtering. Filters should now correctly persist across queries.

Change 2972085 on 2016/05/10 by Steve.Robb

	Const-correctness fix for FLogCategoryBase::IsSuppressed.

Change 2972087 on 2016/05/10 by Steve.Robb

	ELogVerbosity moved into its own header.

Change 2972090 on 2016/05/10 by Steve.Robb

	Redundant ensure removed.

Change 2972103 on 2016/05/10 by Steve.Robb

	Removal of redundant use of USING_CODE_ANALYSIS.

Change 2972139 on 2016/05/10 by Steve.Robb

	Fix for ensure macros throwing C6326 warnings during static analysis.

Change 2972147 on 2016/05/10 by Steve.Robb

	Fix for UE_LOG_ACTIVE macro throwing C6326 warnings during static analysis.

Change 2972162 on 2016/05/10 by Steve.Robb

	SCOPE_CYCLE_COUNTER_GUARD removed.

Change 2972168 on 2016/05/10 by Steve.Robb

	Compile error fix for logOrEnsureNanError in static analysis builds.

Change 2973084 on 2016/05/10 by Chris.Wood

	Crash Report Server performance tweak

Change 2974030 on 2016/05/11 by Steve.Robb

	Fix for IPropertyHandle::SetValue - used to take a non-const reference to a const UObject*, now it takes const references to both non-const and const UObject*.

Change 2974053 on 2016/05/11 by Steve.Robb

	Static analysis fixes:

	warning C6326: Potential comparison of a constant with another constant.

Change 2974191 on 2016/05/11 by Steve.Robb

	Fix for template instantiation error in VS2013.

Change 2975298 on 2016/05/12 by Steve.Robb

	Static analysis fixes:

	warning C6236: (<expression> || <non-zero constant>) is always a non-zero constant.

Change 2975318 on 2016/05/12 by Steve.Robb

	Fix for hot reload info being reported as warnings.

	#jira UE-30586

Change 2975447 on 2016/05/12 by Steve.Robb

	Static analysis fixes:

	warning C6235: (<non-zero constant> || <expression>) is always a non-zero constant.
	warning C6239: (<non-zero constant> && <expression>) always evaluates to the result of <expression>.  Did you intend to use the bitwise-and operator?
	warning C6240: (<expression> && <non-zero constant>) always evaluates to the result of <expression>.  Did you intend to use the bitwise-and operator?
	warning C6285: (<non-zero constant> || <non-zero constant>) is always a non-zero constant.  Did you intend to use the bitwise-and operator?
	warning C6286: (<non-zero constant> || <expression>) is always a non-zero constant.  <expression> is never evaluated and might have side effects.
	warning C6289: Incorrect operator:  mutual exclusion over || is always a non-zero constant.  Did you intend to use && instead?
	warning C6316: Incorrect operator:  tested expression is constant and non-zero.  Use bitwise-and to determine whether bits are set.

Change 2975478 on 2016/05/12 by Steve.Robb

	Static analysis fixes for lots of redundant <zero constant> and <non-zero constant> warnings.

Change 2975538 on 2016/05/12 by Steve.Robb

	Static analysis fixes:

	warning C6011: Dereferencing NULL pointer 'StaticResource'

Change 2976640 on 2016/05/13 by Robert.Manuszewski

	Fixing crashes caused by token stream generation changes. Making sure the token stream gets re-generated when a class gets re-linked.

	#jira UE-30675

Change 2978320 on 2016/05/16 by Steve.Robb

	Fix for static analysis warnings in XNA headers.

Change 2978329 on 2016/05/16 by Steve.Robb

	Static analysis fixes:

	warning C6334: sizeof operator applied to an expression with an operator might yield unexpected results:  Parentheses can be used to disambiguate certain usages.

Change 2980222 on 2016/05/17 by Steve.Robb

	Static analysis fixes:

	warning C6011: Dereferencing NULL pointer 'X'.
	warning C28182: Dereferencing NULL pointer. 'X' contains the same NULL value as 'Y' did.

Change 2980458 on 2016/05/17 by Chris.Wood

	Attempt to fix crash report submission problems from CRP to CR website
	[UE-30257] - Crashreports are sometimes missing file attachments

	Passing crash GUID so that website can easily check for duplicates in future
	Increased request timeout for AddCrash to be longer than website database timeout
	Logging retries for future visibility
	CRP v.1.1.6

Change 2980639 on 2016/05/17 by Steve.Robb

	Static analysis fixes:

	warning C6011: Dereferencing NULL pointer 'X'.
	warning C28182: Dereferencing NULL pointer. 'X' contains the same NULL value as 'Y' did.

Change 2981750 on 2016/05/18 by Steve.Robb

	check()s in ContainerAllocationPolicies.h changed to checkSlow()s, as they only exist to check that the container has been written correctly.

Change 2982106 on 2016/05/18 by John.Mahoney

	Fixed a crash caused by loading two stat capture files simultaneously in the profiler.
	If the user tries to load a capture file while another load is in progress, the previous load is now cancelled and cleaned up before proceeding with the new load.
	Made the delegates in FNewStatsReader explicitly specify which profiler instance they are loading data for, instead of relying on the current value of LoadConnection->InstanceId.
	This also fixes a crash that occurs when selecting a different capture file in the "Stats dump browser" pane of the profiler (after using Load Folder) while another file is still loading.
	Cleaned up some weak pointer usage in the profiler window.

	#jira UE-30741

Change 2983366 on 2016/05/19 by Steven.Hutton

	Changes for passing crash type directly from CRP to CRW.

Change 2983394 on 2016/05/19 by Steven.Hutton

	Minor changes to add crash with more error reporting

Change 2984685 on 2016/05/20 by Robert.Manuszewski

	Merging //UE4/Dev-Main @ 2984626 to Dev-Core (//UE4/Dev-Core)

Change 2985143 on 2016/05/20 by Steve.Robb

	Missing semi-colons.

Change 2986463 on 2016/05/23 by Steve.Robb

	CopyTemp added to make it clear that you want to make a copy (rather than a move, or an accidental copy) at the call site of a function taking rvalue refs.

Change 2986475 on 2016/05/23 by Steve.Robb

	Static analysis fixes:

	warning C6313: Incorrect operator:  zero-valued flag cannot be tested with bitwise-and.

Change 2986476 on 2016/05/23 by Steve.Robb

	Static analysis fixes:

	warning C6313: Incorrect operator:  zero-valued flag cannot be tested with bitwise-and.

Change 2986480 on 2016/05/23 by Steve.Robb

	Static analysis fixes:

	warning C6326: Potential comparison of a constant with another constant

Change 2986515 on 2016/05/23 by Steve.Robb

	Static analysis fixes:

	warning C6340: Mismatch on sign: 'X' passed as _Param_(N) when some unsigned type is required in call to 'Func'

Change 2986680 on 2016/05/23 by Steve.Robb

	Static analysis fixes:

	warning C6386: Buffer overrun while writing to 'Ptr':  the writable size is 'X' bytes, but 'Y' bytes might be written.
	warning C6387: 'Ptr' could be '0':  this does not adhere to the specification for the function 'Func'
	warning C6031: Return value ignored: 'snprintf'.
	warning C6340: Mismatch on sign: 'const unsigned int' passed as _Param_(4) when some signed type is required in call to 'snprintf'.

Change 2986865 on 2016/05/23 by Robert.Manuszewski

	Removing redundand AddReferencedObjects functions

Change 2987968 on 2016/05/24 by Robert.Manuszewski

	Removing redundant UPROPERTY macros from intrinsic classes.

Change 2987979 on 2016/05/24 by Steve.Robb

	Optimization of some FString and FPaths operations to produce fewer temporaries.

Change 2988297 on 2016/05/24 by Steve.Robb

	Static analysis fixes:

	warning C6287: Redundant code:  the left and right sub-expressions are identical.

Change 2988430 on 2016/05/24 by Steve.Robb

	Static analysis fixes:

	warning C6385: Reading invalid data from 'var':  the readable size is 'X' bytes, but 'Y' bytes may be read.

Change 2988461 on 2016/05/24 by Steve.Robb

	Static analysis fixes:

	warning C6235: (<non-zero constant> || <expression>) is always a non-zero constant.
	warning C6239: (<non-zero constant> && <expression>) always evaluates to the result of <expression>.
	warning C6240: (<expression> && <non-zero constant>) always evaluates to the result of <expression>.

Change 2988464 on 2016/05/24 by Steve.Robb

	Static analysis fixes:

	warning C6262: Function uses 'X' bytes of stack:  exceeds /analyze:stacksize 'Y'.  Consider moving some data to heap.

Change 2988494 on 2016/05/24 by Steve.Robb

	Static analysis fixes:

	warning C6237: (<zero> && <expression>) is always zero.  <expression> is never evaluated and might have side effects.

Change 2989411 on 2016/05/25 by Robert.Manuszewski

	Splitting GC cluster index and intenral object flags to allow more UObjects in editor builds.

Change 2989429 on 2016/05/25 by Steve.Robb

	Static analysis fixes:

	warning C6387: '_Param_(X)' could be '0':  this does not adhere to the specification for the function 'Func'.

Change 2989982 on 2016/05/25 by Steve.Robb

	Static analysis fixes:

	warning C6001: Using uninitialized memory 'LODPlanesMin'.

Change 2990018 on 2016/05/25 by Steve.Robb

	Static analysis fixes:

	warning C6386: Buffer overrun while writing to 'X'

Change 2990077 on 2016/05/25 by Steve.Robb

	Static analysis fixes:

	warning C6240: (<expression> && <non-zero constant>) always evaluates to the result of <expression>.
	warning C6011: Dereferencing NULL pointer 'Ptr'.

Change 2990114 on 2016/05/25 by Steve.Robb

	Static analysis fixes:

	warning C6336: Arithmetic operator has precedence over question operator, use parentheses to clarify intent.

Change 2990125 on 2016/05/25 by Steve.Robb

	Static analysis fixes:

	warning C6239: (<non-zero constant> && <expression>) always evaluates to the result of <expression>.

Change 2990162 on 2016/05/25 by Steve.Robb

	Static analysis fixes:

	warning C6294: Ill-defined for-loop:  initial condition does not satisfy test.  Loop body not executed.

Change 2990193 on 2016/05/25 by Steve.Robb

	Static analysis fixes:

	warning C28182: Dereferencing NULL pointer. 'type' contains the same NULL value as 'type->base_type' did.
	warning C6011: Dereferencing NULL pointer 'Semantic'.

Change 2991006 on 2016/05/26 by Steve.Robb

	Static analysis fixes:

	warning C28113: Accessing a local variable dummy via an Interlocked function:  This is an unusual usage which could be reconsidered.

Change 2991012 on 2016/05/26 by Steve.Robb

	Static analysis fixes:

	warning C6031: Return value ignored: 'InitializeCriticalSectionAndSpinCount'.

Change 2991013 on 2016/05/26 by Steve.Robb

	Static analysis fixes:

	warning C6287: Redundant code:  the left and right sub-expressions are identical.

Change 2991016 on 2016/05/26 by Steve.Robb

	Static analysis fixes:

	warning C6236: (<expression> || <non-zero constant>) is always a non-zero constant.

Change 2991017 on 2016/05/26 by Steve.Robb

	Static analysis fixes:

	warning C6326: Potential comparison of a constant with another constant.

Change 2991019 on 2016/05/26 by Steve.Robb

	Static analysis fixes:

	warning C6292: Ill-defined for-loop:  counts up from maximum.

Change 2991023 on 2016/05/26 by Steve.Robb

	Static analysis fixes:

	warning C6322: Empty _except block.
	warning C28251: Inconsistent annotation for 'WinMain': this instance has no annotations.

Change 2991070 on 2016/05/26 by Steve.Robb

	Static analysis fixes:

	warning C28182: Dereferencing NULL pointer. 'Ptr1' contains the same NULL value as 'Ptr2' did.

Change 2991416 on 2016/05/26 by Steve.Robb

	Static analysis fixes:

	warning C6011: Dereferencing NULL pointer 'Ptr'.

Change 2992738 on 2016/05/27 by Steve.Robb

	Revert changes to FString::MatchesWildcard.

Change 2992916 on 2016/05/27 by Steve.Robb

	Static analysis fixes:

	warning C6011: Dereferencing NULL pointer 'Ptr'.

Change 2992960 on 2016/05/27 by Chris.Wood

	Optimized P4 access in Crash Report Process and MinidumpDiagostics.

Change 2992964 on 2016/05/27 by Steve.Robb

	Static analysis fixes:

	warning C6011: Dereferencing NULL pointer 'Ptr'.

Change 2993956 on 2016/05/30 by Robert.Manuszewski

	Fixing a crash after adding a new C++ class in the editor - made sure new classes have the token stream assembled after hot-reload.

	#jira UE-31309

Change 2993977 on 2016/05/30 by Robert.Manuszewski

	Don't wait for all packages to finish loading before PostLoading those which already have.

Change 2994206 on 2016/05/31 by Robert.Manuszewski

	PR #2429: Three bug fixes required for script support to work properly (Contributed by pluranium)

#lockdown Nick.Penwarden

[CL 2996251 by Robert Manuszewski in Main branch]
2016-06-01 12:08:56 -04:00

1060 lines
34 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
ProfilerClientModule.cpp: Implements the FProfilerClientModule class.
=============================================================================*/
#include "ProfilerClientPrivatePCH.h"
#include "ISessionInstanceInfo.h"
#include "SecureHash.h"
#include "StatsData.h"
#include "StatsFile.h"
DEFINE_LOG_CATEGORY_STATIC( LogProfilerClient, Log, All );
DECLARE_CYCLE_STAT( TEXT("HandleDataReceived"), STAT_PC_HandleDataReceived, STATGROUP_Profiler );
DECLARE_CYCLE_STAT( TEXT("ReadStatMessages"), STAT_PC_ReadStatMessages, STATGROUP_Profiler );
DECLARE_CYCLE_STAT( TEXT("AddStatMessages"), STAT_PC_AddStatMessages, STATGROUP_Profiler );
DECLARE_CYCLE_STAT( TEXT("GenerateDataFrame"), STAT_PC_GenerateDataFrame, STATGROUP_Profiler );
DECLARE_CYCLE_STAT( TEXT("AddStatFName"), STAT_PC_AddStatFName, STATGROUP_Profiler );
DECLARE_CYCLE_STAT( TEXT("AddGroupFName"), STAT_PC_AddGroupFName, STATGROUP_Profiler );
DECLARE_CYCLE_STAT( TEXT("GenerateCycleGraph"), STAT_PC_GenerateCycleGraph, STATGROUP_Profiler );
DECLARE_CYCLE_STAT( TEXT("GenerateAccumulator"),STAT_PC_GenerateAccumulator, STATGROUP_Profiler );
DECLARE_CYCLE_STAT( TEXT("FindOrAddStat"), STAT_PC_FindOrAddStat, STATGROUP_Profiler );
DECLARE_CYCLE_STAT( TEXT("FindOrAddThread"), STAT_PC_FindOrAddThread, STATGROUP_Profiler );
/* FProfilerClientManager structors
*****************************************************************************/
FProfilerClientManager::FProfilerClientManager( const IMessageBusRef& InMessageBus )
{
#if STATS
MessageBus = InMessageBus;
MessageEndpoint = FMessageEndpoint::Builder("FProfilerClientModule", InMessageBus)
.Handling<FProfilerServiceAuthorize>(this, &FProfilerClientManager::HandleServiceAuthorizeMessage)
.Handling<FProfilerServiceData2>(this, &FProfilerClientManager::HandleProfilerServiceData2Message)
.Handling<FProfilerServicePreviewAck>(this, &FProfilerClientManager::HandleServicePreviewAckMessage)
.Handling<FProfilerServiceFileChunk>(this, &FProfilerClientManager::HandleServiceFileChunk)
.Handling<FProfilerServicePing>(this, &FProfilerClientManager::HandleServicePingMessage);
if (MessageEndpoint.IsValid())
{
OnShutdownMessageBusDelegateHandle = InMessageBus->OnShutdown().AddRaw( this, &FProfilerClientManager::HandleMessageBusShutdown );
MessageEndpoint->Subscribe<FProfilerServicePing>();
}
TickDelegate = FTickerDelegate::CreateRaw(this, &FProfilerClientManager::HandleTicker);
MessageDelegate = FTickerDelegate::CreateRaw(this, &FProfilerClientManager::HandleMessagesTicker);
LastPingTime = FDateTime::Now();
RetryTime = 5.f;
LoadConnection = nullptr;
MessageDelegateHandle = FTicker::GetCoreTicker().AddTicker(MessageDelegate, 0.1f);
#endif
}
FProfilerClientManager::~FProfilerClientManager()
{
#if STATS
Shutdown();
Unsubscribe();
if (MessageBus.IsValid())
{
MessageBus->OnShutdown().Remove( OnShutdownMessageBusDelegateHandle );
}
LoadConnection = nullptr;
#endif
}
/* IProfilerClient interface
*****************************************************************************/
void FProfilerClientManager::Subscribe( const FGuid& Session )
{
#if STATS
FGuid OldSessionId = ActiveSessionId;
PendingSessionId = Session;
if (MessageEndpoint.IsValid())
{
if (OldSessionId.IsValid())
{
TArray<FGuid> Instances;
Connections.GenerateKeyArray(Instances);
for (int32 i = 0; i < Instances.Num(); ++i)
{
MessageEndpoint->Publish(new FProfilerServiceUnsubscribe(OldSessionId, Instances[i]), EMessageScope::Network);
// fire the disconnection delegate
ProfilerClientDisconnectedDelegate.Broadcast(ActiveSessionId, Instances[i]);
}
ActiveSessionId.Invalidate();
}
ActiveSessionId = PendingSessionId;
}
Connections.Reset();
UE_LOG( LogProfilerClient, Log, TEXT( "Subscribe Session: %s" ), *Session.ToString() );
#endif
}
void FProfilerClientManager::Track( const FGuid& Instance )
{
#if STATS
if (MessageEndpoint.IsValid() && ActiveSessionId.IsValid() && !PendingInstances.Contains(Instance))
{
PendingInstances.Add(Instance);
MessageEndpoint->Publish(new FProfilerServiceSubscribe(ActiveSessionId, Instance), EMessageScope::Network);
RetryTime = 5.f;
TickDelegateHandle = FTicker::GetCoreTicker().AddTicker(TickDelegate, RetryTime);
UE_LOG( LogProfilerClient, Verbose, TEXT( "Track Session: %s, Instance: %s" ), *ActiveSessionId.ToString(), *Instance.ToString() );
}
#endif
}
void FProfilerClientManager::Untrack( const FGuid& Instance )
{
#if STATS
if (MessageEndpoint.IsValid() && ActiveSessionId.IsValid())
{
MessageEndpoint->Publish(new FProfilerServiceUnsubscribe(ActiveSessionId, Instance), EMessageScope::Network);
Connections.Remove(Instance);
// fire the disconnection delegate
ProfilerClientDisconnectedDelegate.Broadcast(ActiveSessionId, Instance);
UE_LOG( LogProfilerClient, Verbose, TEXT( "Untrack Session: %s, Instance: %s" ), *ActiveSessionId.ToString(), *Instance.ToString() );
}
#endif
}
void FProfilerClientManager::Unsubscribe()
{
#if STATS
PendingSessionId.Invalidate();
Subscribe(PendingSessionId);
#endif
}
void FProfilerClientManager::SetCaptureState( const bool bRequestedCaptureState, const FGuid& InstanceId /*= FGuid()*/ )
{
#if STATS
if (MessageEndpoint.IsValid() && ActiveSessionId.IsValid())
{
if( !InstanceId.IsValid() )
{
TArray<FMessageAddress> Instances;
for (auto It = Connections.CreateConstIterator(); It; ++It)
{
Instances.Add( It.Value().ProfilerServiceAddress );
}
MessageEndpoint->Send(new FProfilerServiceCapture(bRequestedCaptureState), Instances);
UE_LOG( LogProfilerClient, Verbose, TEXT( "SetCaptureState Session: %s, Instance: %s, State: %i" ), *ActiveSessionId.ToString(), *InstanceId.ToString(), (int32)bRequestedCaptureState );
}
else
{
const FMessageAddress* MessageAddress = &Connections.Find(InstanceId)->ProfilerServiceAddress;
if( MessageAddress )
{
MessageEndpoint->Send(new FProfilerServiceCapture(bRequestedCaptureState), *MessageAddress);
}
UE_LOG( LogProfilerClient, Verbose, TEXT( "SetCaptureState Session: %s, Instance: %s, State: %i" ), *ActiveSessionId.ToString(), *InstanceId.ToString(), (int32)bRequestedCaptureState );
}
}
#endif
}
void FProfilerClientManager::SetPreviewState( const bool bRequestedPreviewState, const FGuid& InstanceId /*= FGuid()*/ )
{
#if STATS
if (MessageEndpoint.IsValid() && ActiveSessionId.IsValid())
{
if( !InstanceId.IsValid() )
{
TArray<FMessageAddress> Instances;
for (auto It = Connections.CreateConstIterator(); It; ++It)
{
Instances.Add( It.Value().ProfilerServiceAddress );
}
MessageEndpoint->Send(new FProfilerServicePreview(bRequestedPreviewState), Instances);
UE_LOG( LogProfilerClient, Verbose, TEXT( "SetPreviewState Session: %s, Instance: %s, State: %i" ), *ActiveSessionId.ToString(), *InstanceId.ToString(), (int32)bRequestedPreviewState );
}
else
{
const FMessageAddress* MessageAddress = &Connections.Find(InstanceId)->ProfilerServiceAddress;
if( MessageAddress )
{
MessageEndpoint->Send(new FProfilerServicePreview(bRequestedPreviewState), *MessageAddress);
}
UE_LOG( LogProfilerClient, Verbose, TEXT( "SetPreviewState Session: %s, Instance: %s, State: %i" ), *ActiveSessionId.ToString(), *InstanceId.ToString(), (int32)bRequestedPreviewState );
}
}
#endif
}
/*-----------------------------------------------------------------------------
New read test, still temporary, but around 4x faster
-----------------------------------------------------------------------------*/
class FNewStatsReader : public FStatsReadFile
{
friend struct FStatsReader<FNewStatsReader>;
typedef FStatsReadFile Super;
public:
/** Initialize. */
void Initialize( FProfilerClientManager* InProfilerClientManager, FServiceConnection* InLoadConnection )
{
ProfilerClientManager = InProfilerClientManager;
LoadConnection = InLoadConnection;
}
protected:
/** Initialization constructor. */
FNewStatsReader( const TCHAR* InFilename )
: FStatsReadFile( InFilename, false )
, ProfilerClientManager( nullptr )
, LoadConnection( nullptr )
{
// Keep only the last frame.
SetHistoryFrames( 1 );
}
/** Called every each frame has been read from the file. */
virtual void ReadStatsFrame( const TArray<FStatMessage>& CondensedMessages, const int64 Frame ) override
{
SCOPE_CYCLE_COUNTER( STAT_PC_GenerateDataFrame );
FProfilerDataFrame& DataFrame = LoadConnection->CurrentData;
DataFrame.Frame = Frame;
DataFrame.FrameStart = 0.0;
DataFrame.CountAccumulators.Reset();
DataFrame.CycleGraphs.Reset();
DataFrame.FloatAccumulators.Reset();
DataFrame.MetaDataUpdated = false;
// Get the stat stack root and the non frame stats.
FRawStatStackNode Stack;
TArray<FStatMessage> NonFrameStats;
State.UncondenseStackStats( CondensedMessages, Stack, nullptr, &NonFrameStats );
LoadConnection->GenerateCycleGraphs( Stack, DataFrame.CycleGraphs );
LoadConnection->GenerateAccumulators( NonFrameStats, DataFrame.CountAccumulators, DataFrame.FloatAccumulators );
// Create a copy of the stats metadata.
FStatMetaData* MetaDataPtr = nullptr;
if (DataFrame.MetaDataUpdated)
{
MetaDataPtr = new FStatMetaData();
*MetaDataPtr = LoadConnection->StatMetaData;
}
// Create a copy of the stats data.
FProfilerDataFrame* DataFramePtr = new FProfilerDataFrame();
*DataFramePtr = DataFrame;
// Send to game thread.
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady
(
FSimpleDelegateGraphTask::FDelegate::CreateRaw( ProfilerClientManager, &FProfilerClientManager::SendProfilerDataFrameToGame, DataFramePtr, MetaDataPtr, LoadConnection->InstanceId ),
TStatId(), nullptr, ENamedThreads::GameThread
);
}
/** Called after reading all data from the file. */
virtual void PreProcessStats() override
{
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady
(
FSimpleDelegateGraphTask::FDelegate::CreateRaw( ProfilerClientManager, &FProfilerClientManager::FinalizeLoading, LoadConnection->InstanceId ),
TStatId(), NULL, ENamedThreads::GameThread
);
}
FProfilerClientManager* ProfilerClientManager;
FServiceConnection* LoadConnection;
};
void FServiceConnection::LoadCapture( const FString& DataFilepath, FProfilerClientManager* ProfilerClientManager )
{
StatsReader = FStatsReader<FNewStatsReader>::Create( *DataFilepath );
if (StatsReader)
{
StatsReader->Initialize( ProfilerClientManager, this );
StatsReader->ReadAndProcessAsynchronously();
}
}
void FProfilerClientManager::LoadCapture( const FString& DataFilepath, const FGuid& ProfileId )
{
#if STATS
// Start an async load.
LoadConnection = &Connections.FindOrAdd(ProfileId);
LoadConnection->InstanceId = ProfileId;
LoadConnection->StatMetaData.SecondsPerCycle = FPlatformTime::GetSecondsPerCycle(); // fix this by adding a message which specifies this
ProfilerLoadStartedDelegate.Broadcast( ProfileId );
LoadConnection->LoadCapture( DataFilepath, this );
RetryTime = 0.05f;
TickDelegateHandle = FTicker::GetCoreTicker().AddTicker(TickDelegate, RetryTime);
#endif
}
void FProfilerClientManager::RequestLastCapturedFile( const FGuid& InstanceId /*= FGuid()*/ )
{
#if STATS
if (MessageEndpoint.IsValid() && ActiveSessionId.IsValid())
{
if( !InstanceId.IsValid() )
{
TArray<FMessageAddress> Instances;
for (auto It = Connections.CreateConstIterator(); It; ++It)
{
Instances.Add( It.Value().ProfilerServiceAddress );
}
MessageEndpoint->Send(new FProfilerServiceRequest(EProfilerRequestType::PRT_SendLastCapturedFile), Instances);
}
else
{
const FMessageAddress* MessageAddress = &Connections.Find(InstanceId)->ProfilerServiceAddress;
if( MessageAddress )
{
MessageEndpoint->Send(new FProfilerServiceRequest(EProfilerRequestType::PRT_SendLastCapturedFile), *MessageAddress);
}
}
}
#endif
}
/* FProfilerClientManager event handlers
*****************************************************************************/
void FProfilerClientManager::HandleMessageBusShutdown()
{
#if STATS
Shutdown();
MessageEndpoint.Reset();
MessageBus.Reset();
#endif
}
void FProfilerClientManager::HandleServiceAuthorizeMessage( const FProfilerServiceAuthorize& Message, const IMessageContextRef& Context )
{
#if STATS
if (ActiveSessionId == Message.SessionId && PendingInstances.Contains(Message.InstanceId))
{
PendingInstances.Remove(Message.InstanceId);
FServiceConnection& Connection = Connections.FindOrAdd(Message.InstanceId);
Connection.Initialize(Message, Context);
// Fire a meta data update message
//ProfilerMetaDataUpdatedDelegate.Broadcast(Message.InstanceId);
// Fire the client connection event
ProfilerClientConnectedDelegate.Broadcast(ActiveSessionId, Message.InstanceId);
UE_LOG( LogProfilerClient, Verbose, TEXT( "Authorize SessionId: %s, InstanceId: %s" ), *Message.SessionId.ToString(), *Message.InstanceId.ToString() );
}
#endif
}
/*-----------------------------------------------------------------------------
FServiceConnection
-----------------------------------------------------------------------------*/
FServiceConnection::FServiceConnection()
: StatsReader( nullptr )
{
}
FServiceConnection::FServiceConnection( const FServiceConnection& InConnnection )
{
}
FServiceConnection::~FServiceConnection()
{
if (StatsReader)
{
StatsReader->RequestStop();
while (StatsReader->IsBusy())
{
FPlatformProcess::Sleep( 2.0f );
UE_LOG( LogStats, Log, TEXT( "RequestStop: Stage: %s / %3i%%" ), *StatsReader->GetProcessingStageAsString(), StatsReader->GetStageProgress() );
}
delete StatsReader;
StatsReader = nullptr;
}
for (const auto& It : ReceivedData)
{
delete It.Value;
}
}
void FServiceConnection::Initialize( const FProfilerServiceAuthorize& Message, const IMessageContextRef& Context )
{
#if STATS
ProfilerServiceAddress = Context->GetSender();
InstanceId = Message.InstanceId;
CurrentData.Frame = 0;
#endif
}
bool FProfilerClientManager::CheckHashAndWrite( const FProfilerServiceFileChunk& FileChunk, const FProfilerFileChunkHeader& FileChunkHeader, FArchive* Writer )
{
#if STATS
const int32 HashSize = 20;
uint8 LocalHash[HashSize]={0};
// De-hex string into TArray<uint8>
TArray<uint8> FileChunkData;
const int32 DataLength = FileChunk.HexData.Len() / 2;
FileChunkData.Reset( DataLength );
FileChunkData.AddUninitialized( DataLength );
FString::ToHexBlob( FileChunk.HexData, FileChunkData.GetData(), DataLength );
// Hash file chunk data.
FSHA1 Sha;
Sha.Update( FileChunkData.GetData(), FileChunkHeader.ChunkSize );
// Hash file chunk header.
Sha.Update( FileChunk.Header.GetData(), FileChunk.Header.Num() );
Sha.Final();
Sha.GetHash( LocalHash );
const int32 MemDiff = FMemory::Memcmp( FileChunk.ChunkHash.GetData(), LocalHash, HashSize );
bool bResult = false;
if( MemDiff == 0 )
{
// Write the data to the archive.
Writer->Seek( FileChunkHeader.ChunkOffset );
Writer->Serialize( (void*)FileChunkData.GetData(), FileChunkHeader.ChunkSize );
bResult = true;
}
return bResult;
#else
return false;
#endif
}
void FProfilerClientManager::HandleServiceFileChunk( const FProfilerServiceFileChunk& FileChunk, const IMessageContextRef& Context )
{
#if STATS
const TCHAR* StrTmp = TEXT(".tmp");
// Read file chunk header.
FMemoryReader Reader( FileChunk.Header );
FProfilerFileChunkHeader FileChunkHeader;
Reader << FileChunkHeader;
FileChunkHeader.Validate();
const bool bValidFileChunk = !FailedTransfer.Contains( FileChunk.Filename );
if (ActiveSessionId.IsValid() && Connections.Find(FileChunk.InstanceId) != nullptr && bValidFileChunk)
{
FReceivedFileInfo* ReceivedFileInfo = ActiveTransfers.Find( FileChunk.Filename );
if( !ReceivedFileInfo )
{
const FString PathName = FPaths::ProfilingDir() + TEXT("UnrealStats/Received/");
const FString StatFilepath = PathName + FileChunk.Filename + StrTmp;
UE_LOG( LogProfilerClient, Log, TEXT( "Opening stats file for service-client sending: %s" ), *StatFilepath );
FArchive* FileWriter = IFileManager::Get().CreateFileWriter(*StatFilepath);
if( !FileWriter )
{
UE_LOG( LogProfilerClient, Error, TEXT( "Could not open: %s" ), *StatFilepath );
return;
}
ReceivedFileInfo = &ActiveTransfers.Add( FileChunk.Filename, FReceivedFileInfo(FileWriter,0,StatFilepath) );
ProfilerFileTransferDelegate.Broadcast( FileChunk.Filename, ReceivedFileInfo->Progress, FileChunkHeader.FileSize );
}
const bool bSimulateBadFileChunk = true;//FMath::Rand() % 10 != 0;
const bool bSuccess = CheckHashAndWrite( FileChunk, FileChunkHeader, ReceivedFileInfo->FileWriter ) && bSimulateBadFileChunk;
if( bSuccess )
{
ReceivedFileInfo->Progress += FileChunkHeader.ChunkSize;
ReceivedFileInfo->Update();
if( ReceivedFileInfo->Progress == FileChunkHeader.FileSize )
{
// File has been successfully sent, so send this information to the profiler service.
if( MessageEndpoint.IsValid() )
{
MessageEndpoint->Send( new FProfilerServiceFileChunk( FGuid(),FileChunk.Filename,FProfilerFileChunkHeader(0,0,0,EProfilerFileChunkType::FinalizeFile).AsArray() ), Context->GetSender() );
ProfilerFileTransferDelegate.Broadcast( FileChunk.Filename, ReceivedFileInfo->Progress, FileChunkHeader.FileSize );
}
// Delete the file writer.
delete ReceivedFileInfo->FileWriter;
ReceivedFileInfo->FileWriter = nullptr;
// Rename the stats file.
IFileManager::Get().Move( *ReceivedFileInfo->DestFilepath.Replace( StrTmp, TEXT("") ), *ReceivedFileInfo->DestFilepath );
ActiveTransfers.Remove( FileChunk.Filename );
UE_LOG( LogProfilerClient, Log, TEXT( "File service-client received successfully: %s" ), *FileChunk.Filename );
}
else
{
ProfilerFileTransferDelegate.Broadcast( FileChunk.Filename, ReceivedFileInfo->Progress, FileChunkHeader.FileSize );
}
}
else
{
// This chunk is a bad chunk, so ask for resending it.
if( MessageEndpoint.IsValid() )
{
MessageEndpoint->Send( new FProfilerServiceFileChunk(FileChunk,FProfilerServiceFileChunk::FNullTag()), Context->GetSender() );
UE_LOG( LogProfilerClient, Log, TEXT( "Received a bad chunk of file, resending: %5i, %6u, %10u, %s" ), FileChunk.HexData.Len(), ReceivedFileInfo->Progress, FileChunkHeader.FileSize, *FileChunk.Filename );
}
}
}
#endif
}
void FProfilerClientManager::HandleServicePingMessage( const FProfilerServicePing& Message, const IMessageContextRef& Context )
{
#if STATS
if (MessageEndpoint.IsValid())
{
TArray<FMessageAddress> Instances;
for (auto It = Connections.CreateConstIterator(); It; ++It)
{
Instances.Add(It.Value().ProfilerServiceAddress);
}
MessageEndpoint->Send(new FProfilerServicePong(), Instances);
UE_LOG( LogProfilerClient, Verbose, TEXT( "Ping GetSender: %s" ), *Context->GetSender().ToString() );
}
#endif
}
bool FProfilerClientManager::HandleTicker( float DeltaTime )
{
#if STATS
if (PendingInstances.Num() > 0 && FDateTime::Now() > LastPingTime + DeltaTime)
{
TArray<FGuid> Instances;
Instances.Append(PendingInstances);
PendingInstances.Reset();
for (int32 i = 0; i < Instances.Num(); ++i)
{
Track(Instances[i]);
}
LastPingTime = FDateTime::Now();
}
#endif
return false;
}
bool FProfilerClientManager::HandleMessagesTicker( float DeltaTime )
{
#if STATS
for (auto It = Connections.CreateIterator(); It; ++It)
{
FServiceConnection& Connection = It.Value();
TArray<int64> Frames;
Connection.ReceivedData.GenerateKeyArray( Frames );
Frames.Sort();
// MessageBus sends all data in out of order fashion.
// We buffer frame to make sure that all frames are received in the proper order.
const int32 NUM_BUFFERED_FRAMES = 15;
for( int32 Index = 0; Index < Frames.Num(); Index++ )
{
if (Connection.ReceivedData.Num() < NUM_BUFFERED_FRAMES)
{
break;
}
//FScopeLogTime SLT( "HandleMessagesTicker" );
const int64 FrameNum = Frames[Index];
const TArray<uint8>* const Data = Connection.ReceivedData.FindChecked( FrameNum );
FStatsReadStream& Stream = Connection.Stream;
// Read all messages from the uncompressed buffer.
FMemoryReader MemoryReader( *Data, true );
while (MemoryReader.Tell() < MemoryReader.TotalSize())
{
// Read the message.
FStatMessage Message( Stream.ReadMessage( MemoryReader ) );
new (Connection.PendingStatMessagesMessages)FStatMessage( Message );
}
// Adds a new from from the pending messages, the pending messages will be removed after the call.
Connection.CurrentThreadState.ProcessMetaDataAndLeaveDataOnly( Connection.PendingStatMessagesMessages );
Connection.CurrentThreadState.AddFrameFromCondensedMessages( Connection.PendingStatMessagesMessages );
UE_LOG( LogProfilerClient, VeryVerbose, TEXT( "Frame=%i/%i, FNamesIndexMap=%i, CurrentMetadataSize=%i" ), FrameNum, Frames.Num(), Connection.Stream.FNamesIndexMap.Num(), Connection.CurrentThreadState.ShortNameToLongName.Num() );
// create an old format data frame from the data
Connection.GenerateProfilerDataFrame();
// Fire a meta data update message
if (Connection.CurrentData.MetaDataUpdated)
{
ProfilerMetaDataUpdatedDelegate.Broadcast( Connection.InstanceId, Connection.StatMetaData );
}
// send the data out
ProfilerDataDelegate.Broadcast( Connection.InstanceId, Connection.CurrentData );
delete Data;
Connection.ReceivedData.Remove( FrameNum );
}
}
// Remove any active transfer that timed out.
for( auto It = ActiveTransfers.CreateIterator(); It; ++It )
{
FReceivedFileInfo& ReceivedFileInfo = It.Value();
const FString& Filename = It.Key();
if( ReceivedFileInfo.IsTimedOut() )
{
UE_LOG( LogProfilerClient, Log, TEXT( "File service-client timed out, aborted: %s" ), *Filename );
FailedTransfer.Add( Filename );
delete ReceivedFileInfo.FileWriter;
ReceivedFileInfo.FileWriter = nullptr;
IFileManager::Get().Delete( *ReceivedFileInfo.DestFilepath );
ProfilerFileTransferDelegate.Broadcast( Filename, -1, -1 );
It.RemoveCurrent();
}
}
#endif
return true;
}
void FProfilerClientManager::HandleServicePreviewAckMessage( const FProfilerServicePreviewAck& Message, const IMessageContextRef& Context )
{
#if STATS
if (ActiveSessionId.IsValid() && Connections.Find(Message.InstanceId) != nullptr)
{
FServiceConnection& Connection = *Connections.Find(Message.InstanceId);
UE_LOG( LogProfilerClient, Verbose, TEXT( "PreviewAck InstanceId: %s, GetSender: %s" ), *Message.InstanceId.ToString(), *Context->GetSender().ToString() );
}
#endif
}
void FProfilerClientManager::HandleProfilerServiceData2Message( const FProfilerServiceData2& Message, const IMessageContextRef& Context )
{
#if STATS
SCOPE_CYCLE_COUNTER(STAT_PC_HandleDataReceived);
if (ActiveSessionId.IsValid() && Connections.Find(Message.InstanceId) != nullptr)
{
// Create a temporary profiler data and prepare all data.
FProfilerServiceData2* ToProcess = new FProfilerServiceData2( Message.InstanceId, Message.Frame, Message.HexData, Message.CompressedSize, Message.UncompressedSize );
// Decompression and decoding is done on the task graph.
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady
(
FSimpleDelegateGraphTask::FDelegate::CreateRaw( this, &FProfilerClientManager::DecompressDataAndSendToGame, ToProcess ),
TStatId()
);
}
#endif
}
void FProfilerClientManager::DecompressDataAndSendToGame( FProfilerServiceData2* ToProcess )
{
DECLARE_SCOPE_CYCLE_COUNTER( TEXT( "FProfilerClientManager::DecompressDataAndSendToGame" ), STAT_FProfilerClientManager_DecompressDataAndSendToGame, STATGROUP_Profiler );
// De-hex string into TArray<uint8>
TArray<uint8> CompressedData;
CompressedData.Reset( ToProcess->CompressedSize );
CompressedData.AddUninitialized( ToProcess->CompressedSize );
FString::ToHexBlob( ToProcess->HexData, CompressedData.GetData(), ToProcess->CompressedSize );
// Decompress data.
TArray<uint8> UncompressedData;
UncompressedData.Reset( ToProcess->UncompressedSize );
UncompressedData.AddUninitialized( ToProcess->UncompressedSize );
bool bResult = FCompression::UncompressMemory( COMPRESS_ZLIB, UncompressedData.GetData(), ToProcess->UncompressedSize, CompressedData.GetData(), ToProcess->CompressedSize );
check( bResult );
// Send to the game thread. Connections is not thread-safe, so we cannot add the data here.
TArray<uint8>* DateToGame = new TArray<uint8>( MoveTemp( UncompressedData ) );
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady
(
FSimpleDelegateGraphTask::FDelegate::CreateRaw( this, &FProfilerClientManager::SendDataToGame, DateToGame, ToProcess->Frame, ToProcess->InstanceId ),
TStatId(), nullptr, ENamedThreads::GameThread
);
delete ToProcess;
}
void FProfilerClientManager::SendDataToGame( TArray<uint8>* DataToGame, int64 Frame, const FGuid InstanceId )
{
if (ActiveSessionId.IsValid() && Connections.Find( InstanceId ) != nullptr)
{
FServiceConnection& Connection = *Connections.Find( InstanceId );
// Add the message to the connections queue.
UE_LOG( LogProfilerClient, VeryVerbose, TEXT( "Frame: %i, UncompressedSize: %i, InstanceId: %s" ), Frame, DataToGame->Num(), *InstanceId.ToString() );
Connection.ReceivedData.Add( Frame, DataToGame );
}
}
void FProfilerClientManager::SendProfilerDataFrameToGame( FProfilerDataFrame* NewData, FStatMetaData* MetaDataPtr, const FGuid InstanceId )
{
if (Connections.Find(InstanceId) != nullptr)
{
if (MetaDataPtr)
{
ProfilerMetaDataUpdatedDelegate.Broadcast( InstanceId, *MetaDataPtr );
delete MetaDataPtr;
MetaDataPtr = nullptr;
}
if (NewData)
{
ProfilerDataDelegate.Broadcast( InstanceId, *NewData );
delete NewData;
NewData = nullptr;
}
}
}
void FProfilerClientManager::Shutdown()
{
// Delete all active file writers and remove temporary files.
for (auto It = ActiveTransfers.CreateIterator(); It; ++It)
{
FReceivedFileInfo& ReceivedFileInfo = It.Value();
delete ReceivedFileInfo.FileWriter;
ReceivedFileInfo.FileWriter = nullptr;
IFileManager::Get().Delete( *ReceivedFileInfo.DestFilepath );
UE_LOG( LogProfilerClient, Log, TEXT( "File service-client transfer aborted: %s" ), *It.Key() );
}
FTicker::GetCoreTicker().RemoveTicker( MessageDelegateHandle );
FTicker::GetCoreTicker().RemoveTicker( TickDelegateHandle );
}
void FProfilerClientManager::FinalizeLoading(const FGuid InstanceId)
{
if (Connections.Find(InstanceId) != nullptr)
{
ProfilerLoadCompletedDelegate.Broadcast(InstanceId);
LoadConnection = &Connections.FindChecked(InstanceId);
delete LoadConnection->StatsReader;
LoadConnection->StatsReader = nullptr;
LoadConnection = nullptr;
Connections.Remove(InstanceId);
RetryTime = 5.f;
}
}
void FProfilerClientManager::CancelLoading(const FGuid InstanceId)
{
if (Connections.Find(InstanceId) != nullptr)
{
ProfilerLoadCancelledDelegate.Broadcast(InstanceId);
LoadConnection = &Connections.FindChecked(InstanceId);
delete LoadConnection->StatsReader;
LoadConnection->StatsReader = nullptr;
LoadConnection = nullptr;
Connections.Remove(InstanceId);
}
}
#if STATS
int32 FServiceConnection::FindOrAddStat( const FStatNameAndInfo& StatNameAndInfo, uint32 StatType)
{
SCOPE_CYCLE_COUNTER(STAT_PC_FindOrAddStat);
const FName LongName = StatNameAndInfo.GetRawName();
int32* const StatIDPtr = LongNameToStatID.Find( LongName );
int32 StatID = StatIDPtr != nullptr ? *StatIDPtr : -1;
if (!StatIDPtr)
{
// meta data has been updated
CurrentData.MetaDataUpdated = true;
const FName StatName = StatNameAndInfo.GetShortName();
FName GroupName = StatNameAndInfo.GetGroupName();
const FString Description = StatNameAndInfo.GetDescription();
// do some special stats first
if (StatName == TEXT("STAT_FrameTime"))
{
StatID = LongNameToStatID.Add(LongName, 2);
}
else if (StatName == FStatConstants::NAME_ThreadRoot)
{
StatID = LongNameToStatID.Add(LongName, 1);
GroupName = TEXT( "NoGroup" );
}
else
{
StatID = LongNameToStatID.Add(LongName, LongNameToStatID.Num()+10);
}
check(StatID != -1);
// add a new stat description to the meta data
FStatDescription StatDescription;
StatDescription.ID = StatID;
StatDescription.Name = !Description.IsEmpty() ? Description : StatName.ToString();
if( StatDescription.Name.Contains( TEXT("STAT_") ) )
{
StatDescription.Name = StatDescription.Name.RightChop(FString(TEXT("STAT_")).Len());
}
StatDescription.StatType = StatType;
if( GroupName == NAME_None && Stream.Header.Version == EStatMagicNoHeader::NO_VERSION )
{
// @todo Add more ways to group the stats.
const int32 Thread_Pos = StatDescription.Name.Find( TEXT("Thread_") );
const int32 _0Pos = StatDescription.Name.Find( TEXT("_0") );
const bool bIsThread = Thread_Pos != INDEX_NONE && _0Pos > Thread_Pos;
// Add a special group for all threads.
if( bIsThread )
{
GroupName = TEXT("Threads");
}
// Add a special group for all objects.
else
{
GroupName = TEXT("Objects");
}
}
int32* const GroupIDPtr = GroupNameArray.Find( GroupName );
int32 GroupID = GroupIDPtr != nullptr ? *GroupIDPtr : -1;
if( !GroupIDPtr )
{
// add a new group description to the meta data
GroupID = GroupNameArray.Add(GroupName, GroupNameArray.Num()+10);
check( GroupID != -1 );
FStatGroupDescription GroupDescription;
GroupDescription.ID = GroupID;
GroupDescription.Name = GroupName.ToString();
GroupDescription.Name.RemoveFromStart(TEXT("STATGROUP_"));
// add to the meta data
StatMetaData.GroupDescriptions.Add(GroupDescription.ID, GroupDescription);
}
StatDescription.GroupID = GroupID;
StatMetaData.StatDescriptions.Add(StatDescription.ID, StatDescription);
}
// return the stat id
return StatID;
}
int32 FServiceConnection::FindOrAddThread(const FStatNameAndInfo& Thread)
{
SCOPE_CYCLE_COUNTER(STAT_PC_FindOrAddThread);
// The description of a thread group contains the thread id
const FString Desc = Thread.GetDescription();
const uint32 ThreadID = FStatsUtils::ParseThreadID( Desc );
const FName ShortName = Thread.GetShortName();
// add to the meta data
const int32 OldNum = StatMetaData.ThreadDescriptions.Num();
StatMetaData.ThreadDescriptions.Add( ThreadID, ShortName.ToString() );
const int32 NewNum = StatMetaData.ThreadDescriptions.Num();
// meta data has been updated
CurrentData.MetaDataUpdated = CurrentData.MetaDataUpdated || OldNum != NewNum;
return ThreadID;
}
void FServiceConnection::GenerateAccumulators(TArray<FStatMessage>& Stats, TArray<FProfilerCountAccumulator>& CountAccumulators, TArray<FProfilerFloatAccumulator>& FloatAccumulators)
{
SCOPE_CYCLE_COUNTER(STAT_PC_GenerateAccumulator)
for (int32 Index = 0; Index < Stats.Num(); ++Index)
{
const FStatMessage& StatMessage = Stats[Index];
uint32 StatType = STATTYPE_Error;
if (StatMessage.NameAndInfo.GetField<EStatDataType>() == EStatDataType::ST_int64)
{
if (StatMessage.NameAndInfo.GetFlag( EStatMetaFlags::IsCycle ))
{
StatType = STATTYPE_CycleCounter;
}
else if (StatMessage.NameAndInfo.GetFlag( EStatMetaFlags::IsMemory ))
{
StatType = STATTYPE_MemoryCounter;
}
else
{
StatType = STATTYPE_AccumulatorDWORD;
}
}
else if (StatMessage.NameAndInfo.GetField<EStatDataType>() == EStatDataType::ST_double)
{
StatType = STATTYPE_AccumulatorFLOAT;
}
if (StatType != STATTYPE_Error)
{
const int32 StatId = FindOrAddStat( StatMessage.NameAndInfo, StatType );
if (StatMessage.NameAndInfo.GetField<EStatDataType>() == EStatDataType::ST_int64)
{
// add a count accumulator
FProfilerCountAccumulator Data;
Data.StatId = StatId;
Data.Value = StatMessage.GetValue_int64();
CountAccumulators.Add( Data );
}
else if (StatMessage.NameAndInfo.GetField<EStatDataType>() == EStatDataType::ST_double)
{
// add a float accumulator
FProfilerFloatAccumulator Data;
Data.StatId = StatId;
Data.Value = StatMessage.GetValue_double();
FloatAccumulators.Add( Data );
const FName StatName = StatMessage.NameAndInfo.GetRawName();
if (StatName == FStatConstants::RAW_SecondsPerCycle)
{
StatMetaData.SecondsPerCycle = StatMessage.GetValue_double();
}
}
}
}
}
void FServiceConnection::CreateGraphRecursively(const FRawStatStackNode* Root, FProfilerCycleGraph& Graph, uint32 InStartCycles)
{
Graph.FrameStart = InStartCycles;
Graph.StatId = FindOrAddStat(Root->Meta.NameAndInfo, STATTYPE_CycleCounter);
// add the data
if (Root->Meta.NameAndInfo.GetField<EStatDataType>() == EStatDataType::ST_int64)
{
if (Root->Meta.NameAndInfo.GetFlag(EStatMetaFlags::IsPackedCCAndDuration))
{
Graph.CallsPerFrame = FromPackedCallCountDuration_CallCount(Root->Meta.GetValue_int64());
Graph.Value = FromPackedCallCountDuration_Duration(Root->Meta.GetValue_int64());
}
else
{
Graph.CallsPerFrame = 1;
Graph.Value = Root->Meta.GetValue_int64();
}
}
uint32 ChildStartCycles = InStartCycles;
TArray<FRawStatStackNode*> ChildArray;
Root->Children.GenerateValueArray(ChildArray);
ChildArray.Sort( FStatDurationComparer<FRawStatStackNode>() );
for( int32 Index = 0; Index < ChildArray.Num(); ++Index )
{
const FRawStatStackNode* ChildStat = ChildArray[Index];
// create the child graph
FProfilerCycleGraph ChildGraph;
ChildGraph.ThreadId = Graph.ThreadId;
CreateGraphRecursively(ChildStat, ChildGraph, ChildStartCycles);
// add to the graph
Graph.Children.Add(ChildGraph);
// update the start cycles
ChildStartCycles += ChildGraph.Value;
}
}
void FServiceConnection::GenerateCycleGraphs(const FRawStatStackNode& Root, TMap<uint32, FProfilerCycleGraph>& CycleGraphs)
{
SCOPE_CYCLE_COUNTER(STAT_PC_GenerateCycleGraph);
// Initialize the root stat.
FindOrAddStat(Root.Meta.NameAndInfo, STATTYPE_CycleCounter);
// get the cycle graph from each child of the stack root
TArray<FRawStatStackNode*> ChildArray;
Root.Children.GenerateValueArray(ChildArray);
for (int32 Index = 0; Index < ChildArray.Num(); ++Index)
{
FRawStatStackNode* ThreadNode = ChildArray[Index];
FProfilerCycleGraph Graph;
// determine the thread id
Graph.ThreadId = FindOrAddThread(ThreadNode->Meta.NameAndInfo);
// create the thread graph
CreateGraphRecursively(ThreadNode, Graph, 0);
// add to the map
CycleGraphs.Add(Graph.ThreadId, Graph);
}
}
void FServiceConnection::GenerateProfilerDataFrame()
{
SCOPE_CYCLE_COUNTER( STAT_PC_GenerateDataFrame );
FProfilerDataFrame& DataFrame = CurrentData;
DataFrame.Frame = CurrentThreadState.CurrentGameFrame;
DataFrame.FrameStart = 0.0;
DataFrame.CountAccumulators.Reset();
DataFrame.CycleGraphs.Reset();
DataFrame.FloatAccumulators.Reset();
DataFrame.MetaDataUpdated = false;
// get the stat stack root and the non frame stats
FRawStatStackNode Stack;
TArray<FStatMessage> NonFrameStats;
CurrentThreadState.UncondenseStackStats( CurrentThreadState.CurrentGameFrame, Stack, nullptr, &NonFrameStats );
// cycle graphs
GenerateCycleGraphs( Stack, DataFrame.CycleGraphs );
// accumulators
GenerateAccumulators( NonFrameStats, DataFrame.CountAccumulators, DataFrame.FloatAccumulators );
}
#endif