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

268 lines
7.4 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "HAL/FileManager.h"
#include "HAL/RunnableThread.h"
#include "Misc/ScopeLock.h"
#include "Serialization/MemoryReader.h"
#include "Misc/SecureHash.h"
#include "IMessageContext.h"
#include "ProfilerServiceMessages.h"
#include "IProfilerServiceManager.h"
#include "ProfilerServiceManager.h"
FFileTransferRunnable::FFileTransferRunnable(TSharedPtr<FMessageEndpoint, ESPMode::ThreadSafe>& InMessageEndpoint)
: Runnable(nullptr)
, WorkEvent(FPlatformProcess::GetSynchEventFromPool(true))
, MessageEndpoint(InMessageEndpoint)
, StopTaskCounter(0)
{
Runnable = FRunnableThread::Create(this, TEXT("FFileTransferRunnable"), 128 * 1024, TPri_BelowNormal);
}
FFileTransferRunnable::~FFileTransferRunnable()
{
if (Runnable != nullptr)
{
Stop();
Runnable->WaitForCompletion();
delete Runnable;
Runnable = nullptr;
}
// Delete all active file readers.
for (auto It = ActiveTransfers.CreateIterator(); It; ++It)
{
FReaderAndAddress& ReaderAndAddress = It.Value();
DeleteFileReader(ReaderAndAddress);
UE_LOG(LogProfilerService, Log, TEXT("File service-client sending aborted (srv): %s"), *It.Key());
}
FPlatformProcess::ReturnSynchEventToPool(WorkEvent);
WorkEvent = nullptr;
}
bool FFileTransferRunnable::Init()
{
return true;
}
uint32 FFileTransferRunnable::Run()
{
LLM_SCOPE_BYNAME(TEXT("SessionProfiler"));
while (!ShouldStop())
{
if (WorkEvent->Wait(250))
{
FProfilerServiceFileChunk* FileChunk;
while (!ShouldStop() && SendQueue.Dequeue(FileChunk))
{
FMemoryReader MemoryReader(FileChunk->Header);
FProfilerFileChunkHeader FileChunkHeader;
MemoryReader << FileChunkHeader;
FileChunkHeader.Validate();
if (FileChunkHeader.ChunkType == EProfilerFileChunkType::SendChunk)
{
// Find the corresponding file archive reader and recipient.
FArchive* ReaderArchive = nullptr;
FMessageAddress Recipient;
{
FScopeLock Lock(&SyncActiveTransfers);
const FReaderAndAddress* ReaderAndAddress = ActiveTransfers.Find(FileChunk->Filename);
if (ReaderAndAddress)
{
ReaderArchive = ReaderAndAddress->Key;
Recipient = ReaderAndAddress->Value;
}
}
// If there is no reader and recipient is invalid, it means that the file transfer is no longer valid, because client disconnected or exited.
if (ReaderArchive && Recipient.IsValid())
{
ReadAndSetHash(FileChunk, FileChunkHeader, ReaderArchive);
if (MessageEndpoint.IsValid())
{
MessageEndpoint->Send(FileChunk, Recipient);
}
}
}
else if (FileChunkHeader.ChunkType == EProfilerFileChunkType::PrepareFile)
{
PrepareFileForSending(FileChunk);
}
}
WorkEvent->Reset();
}
}
return 0;
}
void FFileTransferRunnable::Exit()
{
}
void FFileTransferRunnable::EnqueueFileToSend(const FString& StatFilename, const FMessageAddress& RecipientAddress, const FGuid& ServiceInstanceId)
{
UE_LOG(LogProfilerService, Log, TEXT("Opening stats file for service-client sending: %s"), *StatFilename);
const int64 FileSize = IFileManager::Get().FileSize(*StatFilename);
if (FileSize < 4)
{
UE_LOG(LogProfilerService, Error, TEXT("Could not open: %s"), *StatFilename);
return;
}
FArchive* FileReader = IFileManager::Get().CreateFileReader(*StatFilename);
if (!FileReader)
{
UE_LOG(LogProfilerService, Error, TEXT("Could not open: %s"), *StatFilename);
return;
}
{
FScopeLock Lock(&SyncActiveTransfers);
check(!ActiveTransfers.Contains(StatFilename));
ActiveTransfers.Add(StatFilename, FReaderAndAddress(FileReader, RecipientAddress));
}
// This is not a real file chunk, but helper to prepare file for sending on the runnable.
EnqueueFileChunkToSend(new FProfilerServiceFileChunk(ServiceInstanceId, StatFilename, FProfilerFileChunkHeader(0, 0, FileReader->TotalSize(), EProfilerFileChunkType::PrepareFile).AsArray()), true);
}
void FFileTransferRunnable::EnqueueFileChunkToSend(FProfilerServiceFileChunk* FileChunk, bool bTriggerWorkEvent /*= false */)
{
SendQueue.Enqueue(FileChunk);
if (bTriggerWorkEvent)
{
// Trigger the runnable.
WorkEvent->Trigger();
}
}
void FFileTransferRunnable::PrepareFileForSending(FProfilerServiceFileChunk*& FileChunk)
{
// Find the corresponding file archive and recipient.
FArchive* Reader = nullptr;
FMessageAddress Recipient;
{
FScopeLock Lock(&SyncActiveTransfers);
const FReaderAndAddress& ReaderAndAddress = ActiveTransfers.FindChecked(FileChunk->Filename);
Reader = ReaderAndAddress.Key;
Recipient = ReaderAndAddress.Value;
}
int64 ChunkOffset = 0;
int64 RemainingSizeToSend = Reader->TotalSize();
while (RemainingSizeToSend > 0)
{
const int64 SizeToCopy = FMath::Min(FProfilerFileChunkHeader::DefChunkSize, RemainingSizeToSend);
EnqueueFileChunkToSend(new FProfilerServiceFileChunk(FileChunk->InstanceId, FileChunk->Filename, FProfilerFileChunkHeader(ChunkOffset, SizeToCopy, Reader->TotalSize(), EProfilerFileChunkType::SendChunk).AsArray()));
ChunkOffset += SizeToCopy;
RemainingSizeToSend -= SizeToCopy;
}
// Trigger the runnable.
WorkEvent->Trigger();
// Delete this file chunk.
delete FileChunk;
FileChunk = nullptr;
}
void FFileTransferRunnable::FinalizeFileSending(const FString& Filename)
{
FScopeLock Lock(&SyncActiveTransfers);
check(ActiveTransfers.Contains(Filename));
FReaderAndAddress ReaderAndAddress = ActiveTransfers.FindAndRemoveChecked(Filename);
DeleteFileReader(ReaderAndAddress);
UE_LOG(LogProfilerService, Log, TEXT("File service-client sent successfully : %s"), *Filename);
}
void FFileTransferRunnable::AbortFileSending(const FMessageAddress& Recipient)
{
FScopeLock Lock(&SyncActiveTransfers);
for (auto It = ActiveTransfers.CreateIterator(); It; ++It)
{
FReaderAndAddress& ReaderAndAddress = It.Value();
if (ReaderAndAddress.Value == Recipient)
{
UE_LOG(LogProfilerService, Log, TEXT("File service-client sending aborted (cl): %s"), *It.Key());
FReaderAndAddress ActiveReaderAndAddress = ActiveTransfers.FindAndRemoveChecked(It.Key());
DeleteFileReader(ActiveReaderAndAddress);
}
}
}
void FFileTransferRunnable::DeleteFileReader(FReaderAndAddress& ReaderAndAddress)
{
delete ReaderAndAddress.Key;
ReaderAndAddress.Key = nullptr;
}
void FFileTransferRunnable::ReadAndSetHash(FProfilerServiceFileChunk* FileChunk, const FProfilerFileChunkHeader& FileChunkHeader, FArchive* Reader)
{
TArray<uint8> FileChunkData;
FileChunkData.AddUninitialized(FileChunkHeader.ChunkSize);
Reader->Seek(FileChunkHeader.ChunkOffset);
Reader->Serialize(FileChunkData.GetData(), FileChunkHeader.ChunkSize);
const int32 HashSize = 20;
uint8 LocalHash[HashSize] = {0};
// 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);
FileChunk->ChunkHash.AddUninitialized(HashSize);
FMemory::Memcpy(FileChunk->ChunkHash.GetData(), LocalHash, HashSize);
// Limit transfer per second, otherwise we will probably hang the message bus.
static int64 TotalReadBytes = 0;
#ifdef _DEBUG
static const int64 NumBytesPerTick = 128 * 1024;
#else
static const int64 NumBytesPerTick = 256 * 1024;
#endif // _DEBUG
// Convert to hex.
FileChunk->HexData = FString::FromHexBlob(FileChunkData.GetData(), FileChunkData.Num());
TotalReadBytes += FileChunkHeader.ChunkSize;
if (TotalReadBytes > NumBytesPerTick)
{
FPlatformProcess::Sleep(0.1f);
TotalReadBytes = 0;
}
}