Files
UnrealEngineUWP/Engine/Source/Developer/ProfilerClient/Private/ProfilerClientManager.cpp
Robert Manuszewski 97cc1e1be1 Copying //UE4/Dev-Core to //UE4/Main
==========================
MAJOR FEATURES + CHANGES
==========================

Change 2799478 on 2015/12/11 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

	Added Dev Stream custom versions.

	- Each stream now has its own custom version
	- Developers working in a stream should only modify their respective version

Change 2789867 on 2015/12/04 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

	UnrealHeaderTool plugins no longer need to be a separate plugin and don't require UnrealHeaderTool source code changes to work.

	- Merged ScriptGeneratorPlugin with ScriptPlugin
	- Introduced the concept of UHT plugin support for plugins so that UHT's source files don't need to be modified to make it work with external plugins
	- Added RuntimeNoProgram module type (module that can be used at runtime but not by program targets).
	- Fixed logic with project file path setting in the engine. It will no longer try to crate a full path from an already rooted path.

Change 2796114 on 2015/12/09 by Steve.Robb@Dev-Core

	TEnumRange - enabled ranged-based for iteration over enum values.

Change 2789843 on 2015/12/04 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream2

	More thorough way of verifying GC cluster assumptions.

Change 2794221 on 2015/12/08 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream2

	Don't merge GC clusters by default

Change 2797824 on 2015/12/10 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

	Added the option to load all symbols for stack walking in non-monolithic builds.

Change 2790539 on 2015/12/04 by Jaroslaw.Surowiec@Stream.1.JarekSurowiec

	Stats/Profiler - Better handling for live connection, using the same path as file profiles, added FStatsLoadedState to separate runtime stats state from the loaded one

Change 2794183 on 2015/12/08 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

	Always reset events when returning them to pool.

Change 2794406 on 2015/12/08 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream2

	Fixing -unversioned flag being completely ignored when cooking

Change 2794563 on 2015/12/08 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream2

	Making sure string referenced assets don't get cooked if referenced by editor-only properties.

Change 2795124 on 2015/12/08 by Jaroslaw.Surowiec@Stream.1.JarekSurowiec

	Profiler - Fixed bad data in min/max/avg inclusive times, added min/max/avg num calls, fixed event graph tooltip not displaying correct values

Change 2796208 on 2015/12/09 by Jaroslaw.Surowiec@Stream.1.JarekSurowiec

	Profiler - Remove support for multiple instances in the profiler (game thread is always visible, a few crash fixes, cleaned code a bit)

Change 2797658 on 2015/12/10 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

	Allocation verification helpers. Helps with tracking down memory stomps, freeing the same pointers multiple times.

Change 2797699 on 2015/12/10 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

	Fix incorrect asset loading in Cooked game data (by bozaro)

	PR #1844

Change 2798173 on 2015/12/10 by Steve.Robb@Dev-Core

	Migration of Fortnite to use engine's TEnumRange.

Change 2798217 on 2015/12/10 by Jaroslaw.Surowiec@Stream.1.JarekSurowiec

	PR #1331
	[Core] Added a stomp allocator that allows finding memory overruns, underruns, and read/write after free. (Contributed by Pablo Zurita)

Change 2799605 on 2015/12/11 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

	Fixing a crash when cancelling async loading caused by detaching linker from objects that had RF_NeedLoad flag set.

Change 2799849 on 2015/12/11 by Steve.Robb@Dev-Core

	Migration of Ocean to use engine's TEnumRange.

Change 2803144 on 2015/12/15 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

	Changed export tagging archive to also serialize class default objects using the normal Serialize path so that it can collect all custom versions used by exports.

Change 2803206 on 2015/12/15 by Jaroslaw.Surowiec@Stream.1.JarekSurowiec

	#jira UE-24177
	Audit ôshippingö defines in engine

Change 2804868 on 2015/12/16 by Steve.Robb@Dev-Core

	Removal of stats from MallocBinned2, to be readded later.

#lockdown Nick.Penwarden

[CL 2805158 by Robert Manuszewski in Main branch]
2015-12-16 11:52:36 -05:00

1040 lines
34 KiB
C++

// Copyright 1998-2015 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 ),
TStatId(), nullptr, ENamedThreads::GameThread
);
}
/** Called after reading all data from the file. */
virtual void PreProcessStats() override
{
FFunctionGraphTask::CreateAndDispatchWhenReady( [=]()
{
ProfilerClientManager->FinalizeLoading();
}
, 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 );
}
}
// #YRX_tag 2015-11-19 Convert to thread safe pointers?
void FProfilerClientManager::SendProfilerDataFrameToGame( FProfilerDataFrame* NewData, FStatMetaData* MetaDataPtr )
{
if (MetaDataPtr)
{
ProfilerMetaDataUpdatedDelegate.Broadcast( LoadConnection->InstanceId, *MetaDataPtr );
delete MetaDataPtr;
MetaDataPtr = nullptr;
}
if (NewData)
{
ProfilerDataDelegate.Broadcast( LoadConnection->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()
{
ProfilerLoadCompletedDelegate.Broadcast( LoadConnection->InstanceId );
delete LoadConnection->StatsReader;
LoadConnection->StatsReader = nullptr;
LoadConnection = nullptr;
RetryTime = 5.f;
}
#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