Files
UnrealEngineUWP/Engine/Source/Programs/UnrealTraceServer/src/StoreCborServer.cpp

442 lines
10 KiB
C++
Raw Normal View History

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Pch.h"
#include "AsioIoable.h"
#include "AsioSocket.h"
#include "CborPayload.h"
#include "Recorder.h"
#include "Store.h"
#include "StoreCborServer.h"
#include "TraceRelay.h"
////////////////////////////////////////////////////////////////////////////////
class FStoreCborPeer
: public FAsioIoSink
{
public:
FStoreCborPeer(asio::ip::tcp::socket& InSocket, FStore& InStore, FRecorder& InRecorder, FStoreCborServer& InParent);
virtual ~FStoreCborPeer();
bool IsOpen() const;
void Close();
protected:
void OnPayload();
void OnSessionCount();
void OnSessionInfo();
void OnStatus();
void OnTraceCount();
void OnTraceInfo();
void OnTraceRead();
void SendError(EStatusCode StatusCode);
void SendResponse(const FPayload& Payload);
virtual void OnIoComplete(uint32 Id, int32 Size) override;
enum { OpStart, OpReadPayloadSize, OpReadPayload, OpSendResponse };
FStore& Store;
FRecorder& Recorder;
FStoreCborServer& Parent;
FAsioSocket Socket;
uint32 PayloadSize;
FResponse Response;
};
////////////////////////////////////////////////////////////////////////////////
FStoreCborPeer::FStoreCborPeer(
asio::ip::tcp::socket& InSocket,
FStore& InStore,
FRecorder& InRecorder,
FStoreCborServer& InParent)
: Store(InStore)
, Recorder(InRecorder)
, Parent(InParent)
, Socket(InSocket)
{
OnIoComplete(OpStart, 0);
}
////////////////////////////////////////////////////////////////////////////////
FStoreCborPeer::~FStoreCborPeer()
{
check(!IsOpen());
}
////////////////////////////////////////////////////////////////////////////////
bool FStoreCborPeer::IsOpen() const
{
return Socket.IsOpen();
}
////////////////////////////////////////////////////////////////////////////////
void FStoreCborPeer::Close()
{
Socket.Close();
}
////////////////////////////////////////////////////////////////////////////////
void FStoreCborPeer::OnSessionCount()
{
TPayloadBuilder<> Builder(EStatusCode::Success);
Builder.AddInteger("count", Recorder.GetSessionCount());
SendResponse(Builder.Done());
}
////////////////////////////////////////////////////////////////////////////////
void FStoreCborPeer::OnSessionInfo()
{
const FRecorder::FSession* Session = nullptr;
int32 Index = int32(Response.GetInteger("index", -1));
if (Index >= 0)
{
Session = Recorder.GetSessionInfo(Index);
}
else if (uint32 Id = uint32(Response.GetInteger("id", 0)))
{
for (int i = 0, n = Recorder.GetSessionCount(); i < n; ++i)
{
const FRecorder::FSession* Candidate = Recorder.GetSessionInfo(i);
if (Candidate->GetId() == Id)
{
Session = Candidate;
break;
}
}
}
else if (uint32 TraceId = uint32(Response.GetInteger("trace_id", 0)))
{
for (int i = 0, n = Recorder.GetSessionCount(); i < n; ++i)
{
const FRecorder::FSession* Candidate = Recorder.GetSessionInfo(i);
if (Candidate->GetTraceId() == TraceId)
{
Session = Candidate;
break;
}
}
}
if (Session == nullptr)
{
return SendError(EStatusCode::BadRequest);
}
TPayloadBuilder<> Builder(EStatusCode::Success);
Builder.AddInteger("id", Session->GetId());
Builder.AddInteger("trace_id", Session->GetTraceId());
Builder.AddInteger("ip_address", Session->GetIpAddress());
Builder.AddInteger("control_port", Session->GetControlPort());
SendResponse(Builder.Done());
}
////////////////////////////////////////////////////////////////////////////////
void FStoreCborPeer::OnStatus()
{
TPayloadBuilder<> Builder(EStatusCode::Success);
Builder.AddInteger("recorder_port", Recorder.GetPort());
Builder.AddInteger("change_serial", Store.GetChangeSerial());
UnrealTraceServer Linux/Mac implementation and more. - Forward fork's command line arguments to the daemon (9c481e3) - Added command line options to control store dir and ports (6ffd7db) - Added cxxopts (544d33a) - Treat some store paths as fs::path objects instead of char strings (7ebe3d4) - Version bump (57e3d65) - Daemon no longer needs to expect the lock file to not exist (f5625a1) - Added a timeout when waiting on a daemon (1ac42fd) - Wait for the daemon by waiting on child processes or signals (d212f29) - Completely daemonise by detaching from the parent and any terminal (b9c51ce) - Correctly respond to SIGTERM and SIGINT signals (039fd43) - Use the PID returned by the lock query as that more robust (44ff23e) - Delete orphaned lock files (d227389) - Use fcntl locks instead of flock ones. (d59bdff) - Missed a few close() calls (4e54cbc) - Keep the lock file somemore more global and make sure all users have access (fe2a578) - There is no longer a need to validate the binary path (1d76482) - Use flock() to have the daemon hold an exclusive lock on the lock file (ca54648) - Moved 3rdparty to thirdparty inline with TPS expectations (4574f3e) - Make sure FMount::Id is set (818ea56) - Removed superfluous argument to GetMountCount() (74ff594) - Allow the store to mount more than one directory to read traces from (f11459a) - Have GetStoreDir() return a string instead of a pointer to one (0456c9d) - Keep methods and members grouped (cf9f322) - Removed an unused block of code (891eefa) - Added funciton to convery fs::path to a FString (287cc70) - Aliased std::filesystem to fs as the former is very wordy (b5eac19) - Added a conversion constructor (942f8ef) - Drop down to C++17 as that matches Ubuntu LTS' current best version (46f7c02) - Deleted dummy dir-watcher impl (30b9976) - Removed directory-watcher preprocessor conditions (e36679b) - Integrated change 19143970 (75d0f6f) - Integrated change 19143744 (dad341b) - Integrated change 19070550 (3384a7d) #rb jb #rnx #preflight 6238792dbe1e4104d345181e [CL 19451192 by Martin Ridgers in ue5-main branch]
2022-03-21 09:36:32 -04:00
Builder.AddString("store_dir", *Store.GetStoreDir());
SendResponse(Builder.Done());
}
////////////////////////////////////////////////////////////////////////////////
void FStoreCborPeer::OnTraceCount()
{
TPayloadBuilder<> Builder(EStatusCode::Success);
Builder.AddInteger("count", Store.GetTraceCount());
SendResponse(Builder.Done());
}
////////////////////////////////////////////////////////////////////////////////
void FStoreCborPeer::OnTraceInfo()
{
const FStore::FTrace* Trace = nullptr;
int32 Index = int32(Response.GetInteger("index", -1));
if (Index >= 0)
{
Trace = Store.GetTraceInfo(Index);
}
else
{
uint32 Id = uint32(Response.GetInteger("id", 0));
if (Id != 0)
{
for (int i = 0, n = Store.GetTraceCount(); i < n; ++i)
{
const FStore::FTrace* Candidate = Store.GetTraceInfo(i);
if (Candidate->GetId() == Id)
{
Trace = Candidate;
break;
}
}
}
}
if (Trace == nullptr)
{
return SendError(EStatusCode::BadRequest);
}
char OutName[256];
const FStringView& Name = Trace->GetName();
uint32 NameLength = std::min(uint32(sizeof(OutName)), uint32(Name.Len()));
for (uint32 i = 0; i < NameLength; ++i)
{
OutName[i] = char(Name[i]);
}
TPayloadBuilder<> Builder(EStatusCode::Success);
Builder.AddInteger("id", Trace->GetId());
Builder.AddInteger("size", Trace->GetSize());
Builder.AddInteger("timestamp", Trace->GetTimestamp());
Builder.AddString("name", OutName, NameLength);
SendResponse(Builder.Done());
}
////////////////////////////////////////////////////////////////////////////////
void FStoreCborPeer::OnTraceRead()
{
uint32 Id = uint32(Response.GetInteger("id", 0));
if (!Id || !Store.HasTrace(Id))
{
return SendError(EStatusCode::BadRequest);
}
FTraceRelay* Relay = Parent.RelayTrace(Id);
if (Relay == nullptr)
{
return SendError(EStatusCode::InternalError);
}
TPayloadBuilder<> Builder(EStatusCode::Success);
Builder.AddInteger("port", Relay->GetPort());
SendResponse(Builder.Done());
}
////////////////////////////////////////////////////////////////////////////////
void FStoreCborPeer::OnPayload()
{
FStringView Request = Response.GetString("$request", "");
FStringView Path = Response.GetString("$path", "");
if (!Request.Len() || !Path.Len())
{
SendError(EStatusCode::BadRequest);
return;
}
static struct {
uint32 Hash;
void (FStoreCborPeer::*Func)();
} const DispatchTable[] = {
{ QuickStoreHash("v1/session/count"), &FStoreCborPeer::OnSessionCount },
{ QuickStoreHash("v1/session/info"), &FStoreCborPeer::OnSessionInfo },
{ QuickStoreHash("v1/status"), &FStoreCborPeer::OnStatus },
{ QuickStoreHash("v1/trace/count"), &FStoreCborPeer::OnTraceCount },
{ QuickStoreHash("v1/trace/info"), &FStoreCborPeer::OnTraceInfo },
{ QuickStoreHash("v1/trace/read"), &FStoreCborPeer::OnTraceRead },
};
uint32 PathHash = QuickStoreHash(Path);
for (const auto& Row : DispatchTable)
{
if (Row.Hash == PathHash)
{
return (this->*(Row.Func))();
}
}
SendError(EStatusCode::NotFound);
}
////////////////////////////////////////////////////////////////////////////////
void FStoreCborPeer::SendError(EStatusCode StatusCode)
{
TPayloadBuilder<> Builder(StatusCode);
FPayload Payload = Builder.Done();
SendResponse(Payload);
}
////////////////////////////////////////////////////////////////////////////////
void FStoreCborPeer::SendResponse(const FPayload& Payload)
{
struct {
uint32 Size;
uint8 Data[];
}* Dest;
Dest = decltype(Dest)(Response.Reserve(sizeof(*Dest) + Payload.Size));
Dest->Size = Payload.Size;
memcpy(Dest->Data, Payload.Data, Payload.Size);
Socket.Write(Dest, sizeof(*Dest) + Payload.Size, this, OpSendResponse);
}
////////////////////////////////////////////////////////////////////////////////
void FStoreCborPeer::OnIoComplete(uint32 Id, int32 Size)
{
if (Size < 0)
{
Close();
return;
}
switch (Id)
{
case OpStart:
case OpSendResponse:
Socket.Read(&PayloadSize, sizeof(uint32), this, OpReadPayloadSize);
break;
case OpReadPayloadSize:
Socket.Read(Response.Reserve(PayloadSize), PayloadSize, this, OpReadPayload);
break;
case OpReadPayload:
OnPayload();
break;
}
}
////////////////////////////////////////////////////////////////////////////////
FStoreCborServer::FStoreCborServer(
asio::io_context& IoContext,
int32 Port,
FStore& InStore,
FRecorder& InRecorder)
: FAsioTcpServer(IoContext)
, FAsioTickable(IoContext)
, Store(InStore)
, Recorder(InRecorder)
{
if (!StartServer(Port))
{
StartServer();
}
StartTick(500);
}
////////////////////////////////////////////////////////////////////////////////
FStoreCborServer::~FStoreCborServer()
{
check(!FAsioTcpServer::IsOpen());
check(!FAsioTickable::IsActive());
for (FStoreCborPeer* Peer : Peers)
{
delete Peer;
}
for (FTraceRelay* Relay : Relays)
{
delete Relay;
}
}
////////////////////////////////////////////////////////////////////////////////
void FStoreCborServer::Close()
{
FAsioTcpServer::Close();
FAsioTickable::StopTick();
for (FStoreCborPeer* Peer : Peers)
{
Peer->Close();
}
for (FTraceRelay* Relay : Relays)
{
Relay->Close();
}
}
////////////////////////////////////////////////////////////////////////////////
FStore& FStoreCborServer::GetStore() const
{
return Store;
}
////////////////////////////////////////////////////////////////////////////////
FRecorder& FStoreCborServer::GetRecorder() const
{
return Recorder;
}
////////////////////////////////////////////////////////////////////////////////
bool FStoreCborServer::OnAccept(asio::ip::tcp::socket& Socket)
{
FStoreCborPeer* Peer = new FStoreCborPeer(Socket, Store, Recorder, *this);
Peers.Add(Peer);
return true;
}
////////////////////////////////////////////////////////////////////////////////
void FStoreCborServer::OnTick()
{
// Clean up dead peers
uint32 FinalNum = 0;
for (int i = 0, n = Peers.Num(); i < n; ++i)
{
FStoreCborPeer* Peer = Peers[i];
if (Peer->IsOpen())
{
Peers[FinalNum] = Peer;
++FinalNum;
continue;
}
delete Peer;
}
Peers.SetNum(FinalNum);
// Clean up dead relays
FinalNum = 0;
for (int i = 0, n = Relays.Num(); i < n; ++i)
{
FTraceRelay* Relay = Relays[i];
if (Relay->IsOpen())
{
Relays[FinalNum] = Relay;
++FinalNum;
continue;
}
delete Relay;
}
Relays.SetNum(FinalNum);
}
////////////////////////////////////////////////////////////////////////////////
FTraceRelay* FStoreCborServer::RelayTrace(uint32 Id)
{
FAsioReadable* Input = Store.OpenTrace(Id);
if (Input == nullptr)
{
return nullptr;
}
uint32 SessionId = 0;
for (int i = 0, n = Recorder.GetSessionCount(); i < n; ++i)
{
const FRecorder::FSession* Session = Recorder.GetSessionInfo(i);
if (Session->GetTraceId() == Id)
{
SessionId = Session->GetId();
break;
}
}
asio::io_context& IoContext = FAsioTcpServer::GetIoContext();
FTraceRelay* Relay = new FTraceRelay(IoContext, Input, SessionId, Recorder);
if (Relay->GetPort() == 0)
{
delete Relay;
return nullptr;
}
Relays.Add(Relay);
return Relay;
}
/* vim: set noexpandtab : */