Files
UnrealEngine/Engine/Source/Programs/UnrealBuildAccelerator/Common/Private/UbaTrace.cpp
2025-05-18 13:04:45 +08:00

640 lines
17 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UbaTrace.h"
#include "UbaFileAccessor.h"
#include "UbaProcessHandle.h"
#if PLATFORM_WINDOWS
#include <tlhelp32.h>
#include <psapi.h>
#endif
namespace uba
{
constexpr u64 TraceMessageMaxSize = 256 * 1024;
Trace::Trace(LogWriter& logWriter)
: m_logger(logWriter)
, m_channel(m_logger)
{
}
Trace::~Trace()
{
FreeMemory();
}
struct Trace::WriterScope : ScopedFutex, BinaryWriter
{
WriterScope(Trace& trace) : ScopedFutex(trace.m_memoryLock), BinaryWriter(trace.m_memoryBegin, trace.m_memoryPos, trace.m_memoryCapacity), m_trace(trace)
{
EnsureMemory(TraceMessageMaxSize);
}
~WriterScope()
{
if (!m_isValid)
return;
m_trace.m_memoryPos = GetPosition();
*(u32*)m_trace.m_memoryBegin = u32(m_trace.m_memoryPos);
}
bool IsValid() { return m_isValid; }
WriterScope(const WriterScope&) = delete;
void operator=(const WriterScope&) = delete;
bool EnsureMemory(u64 size)
{
if (!m_isValid)
return false;
m_trace.m_memoryPos = GetPosition();
m_isValid = m_trace.EnsureMemory(size);
return m_isValid;
}
Trace& m_trace;
bool m_isValid = true;
};
bool Trace::StartWrite(const tchar* namedTrace, u64 traceMemCapacity)
{
m_memoryCapacity = traceMemCapacity;
m_memoryHandle = uba::CreateMemoryMappingW(m_logger, PAGE_READWRITE|SEC_RESERVE, m_memoryCapacity, namedTrace, TC("Trace"));
if (!m_memoryHandle.IsValid())
return false;
if (GetLastError() != ERROR_ALREADY_EXISTS)
m_memoryBegin = MapViewOfFile(m_logger, m_memoryHandle, FILE_MAP_WRITE, 0, m_memoryCapacity);
if (!m_memoryBegin)
{
m_logger.Warning(TC("Failed to map view of trace mapping '%s' (%s)"), namedTrace, LastErrorToText().data);
CloseFileMapping(m_logger, m_memoryHandle, TC("Trace"));
m_memoryHandle = {};
return false;
}
m_memoryPos = 0;
m_startTime = GetTime();
u64 systemStartTimeUs = GetSystemTimeUs();
{
WriterScope writer(*this);
if (!writer.IsValid())
return false;
writer.AllocWrite(4);
writer.WriteU32(TraceVersion);
writer.WriteU32(GetCurrentProcessId());
writer.Write7BitEncoded(systemStartTimeUs);
writer.Write7BitEncoded(GetFrequency());
writer.Write7BitEncoded(m_startTime);
}
if (namedTrace && m_channel.Init())
{
m_namedTrace = namedTrace;
m_channel.Write(namedTrace);
}
return true;
}
bool Trace::Write(const tchar* writeFileName, bool writeSummary)
{
if (!m_memoryBegin)
return true;
if (!writeFileName || !*writeFileName)
return true;
FileAccessor traceFile(m_logger, writeFileName);
if (!traceFile.CreateWrite(false, DefaultAttributes(), 0, nullptr))
return false;
ScopedFutex lock(m_memoryLock);
u64 fileSize = m_memoryPos;
if (!traceFile.Write(m_memoryBegin, fileSize))
return false;
lock.Leave();
if (writeSummary)
{
StackBinaryWriter<32> summaryWriter;
summaryWriter.WriteByte(TraceType_Summary);
summaryWriter.Write7BitEncoded(GetTime() - m_startTime);
if (!traceFile.Write(summaryWriter.GetData(), summaryWriter.GetPosition()))
return false;
fileSize += summaryWriter.GetPosition();
}
if (!traceFile.Close())
return false;
m_logger.Info(TC("Trace written to file %s with size %s"), writeFileName, BytesToText(fileSize).str);
return true;
}
bool Trace::StopWrite(const tchar* writeFileName)
{
if (!m_memoryBegin)
return true;
auto g = MakeGuard([this]() { FreeMemory(); });
if (!m_namedTrace.empty())
m_channel.Write(TC(""), m_namedTrace.c_str());
{
WriterScope writer(*this);
if (!writer.IsValid())
return false;
writer.WriteByte(TraceType_Summary);
writer.Write7BitEncoded(GetTime() - m_startTime);
}
return Write(writeFileName, false); // Don't write summary, already included in memory
}
u32 Trace::TrackWorkStart(const StringView& desc, const Color& color)
{
u32 workId = m_workCounter++;
WorkBegin(workId, desc, color);
return workId;
}
void Trace::TrackWorkHint(u32 id, const StringView& hint, u64 startTime)
{
WorkHint(id, hint, startTime);
}
void Trace::TrackWorkEnd(u32 id)
{
WorkEnd(id);
}
void Trace::FreeMemory()
{
if (m_memoryBegin)
{
UnmapViewOfFile(m_logger, m_memoryBegin, m_memoryCapacity, TC("Trace"));
m_memoryBegin = nullptr;
}
if (m_memoryHandle.IsValid())
{
CloseFileMapping(m_logger, m_memoryHandle, TC("Trace"));
m_memoryHandle = {};
}
}
bool Trace::EnsureMemory(u64 size)
{
if (!m_memoryBegin)
return false;
u64 committedMemoryNeeded = AlignUp(m_memoryPos + size, 64*1024);
if (m_memoryCommitted >= committedMemoryNeeded)
return true;
if (MapViewCommit(m_memoryBegin + m_memoryCommitted, committedMemoryNeeded - m_memoryCommitted))
{
m_memoryCommitted = committedMemoryNeeded;
return true;
}
FreeMemory();
return m_logger.Warning(TC("Failed to commit memory for trace (Pos: %llu Capacity: %llu, Already Committed: %llu, Needed: %llu): %s"), m_memoryPos, m_memoryCapacity, m_memoryCommitted, committedMemoryNeeded, LastErrorToText().data);
}
u32 Trace::AddString(const StringView& string)
{
if (!m_memoryBegin)
return 0;
SCOPED_FUTEX(m_stringsLock, lock);
auto insres = m_strings.try_emplace(ToStringKeyNoCheck(string.data, string.count));
if (insres.second)
{
insres.first->second = u32(m_strings.size() - 1);
WriterScope writer(*this);
if (!writer.IsValid())
return 0;
writer.WriteByte(TraceType_String);
writer.WriteString(string);
}
return insres.first->second;
}
#define BEGIN_TRACE_ENTRY(x) \
BEGIN_TRACE_ENTRY_IMPL(x, true)
#define BEGIN_TRACE_ENTRY_NOTIME(x) \
BEGIN_TRACE_ENTRY_IMPL(x, false)
#define BEGIN_TRACE_ENTRY_IMPL(x, writeTime) \
if (!m_memoryBegin) \
return; \
u64 time = GetTime() - m_startTime; \
WriterScope writer(*this); \
if (!writer.IsValid()) \
return; \
writer.WriteByte(TraceType_##x); \
if (writeTime) \
writer.Write7BitEncoded(time);
void Trace::SessionAdded(u32 sessionId, u32 clientId, const StringView& name, const StringView& info)
{
BEGIN_TRACE_ENTRY(SessionAdded);
writer.WriteString(name);
writer.WriteString(info);
writer.Write7BitEncoded(clientId);
writer.WriteU32(sessionId);
}
void Trace::SessionUpdate(u32 sessionId, u32 connectionCount, u64 send, u64 recv, u64 lastPing, u64 memAvail, u64 memTotal, float cpuLoad)
{
BEGIN_TRACE_ENTRY(SessionUpdate);
writer.Write7BitEncoded(sessionId);
writer.Write7BitEncoded(connectionCount);
writer.Write7BitEncoded(send);
writer.Write7BitEncoded(recv);
writer.Write7BitEncoded(lastPing);
writer.Write7BitEncoded(memAvail);
writer.Write7BitEncoded(memTotal);
writer.WriteU32(*(u32*)&cpuLoad);
}
void Trace::SessionNotification(u32 sessionId, const tchar* text)
{
BEGIN_TRACE_ENTRY(SessionNotification);
writer.WriteU32(sessionId);
writer.WriteString(text);
}
void Trace::SessionSummary(u32 sessionId, const u8* data, u64 dataSize)
{
BEGIN_TRACE_ENTRY(SessionSummary);
writer.WriteU32(sessionId);
writer.WriteBytes(data, dataSize);
}
void Trace::SessionDisconnect(u32 sessionId)
{
BEGIN_TRACE_ENTRY(SessionDisconnect);
writer.WriteU32(sessionId);
}
void Trace::ProcessAdded(u32 sessionId, u32 processId, const StringView& description, const StringView& breadcrumbs)
{
BEGIN_TRACE_ENTRY(ProcessAdded);
writer.WriteU32(sessionId);
writer.WriteU32(processId);
writer.WriteString(description);
writer.WriteLongString(breadcrumbs, 20);
}
void Trace::ProcessEnvironmentUpdated(u32 processId, const StringView& reason, const u8* data, u64 dataSize, const StringView& breadcrumbs)
{
BEGIN_TRACE_ENTRY(ProcessEnvironmentUpdated);
writer.WriteU32(processId);
writer.WriteString(reason);
writer.WriteBytes(data, dataSize);
writer.WriteLongString(breadcrumbs, 20);
}
void Trace::ProcessExited(u32 processId, u32 exitCode, const u8* data, u64 dataSize, const Vector<ProcessLogLine>& logLines)
{
BEGIN_TRACE_ENTRY(ProcessExited);
writer.WriteU32(processId);
writer.WriteU32(exitCode);
writer.WriteBytes(data, dataSize);
u32 lineCounter = 0;
for (auto& line : logLines)
{
if (lineCounter++ == 100) // We don't want to write the entire error in the trace stream to blow the entire buffer
break;
if (!writer.EnsureMemory(2 + (line.text.size()+2)*sizeof(tchar)))
return;
writer.WriteByte(line.type);
writer.WriteString(line.text);
}
writer.WriteByte(255);
}
void Trace::ProcessReturned(u32 processId, const StringView& reason)
{
BEGIN_TRACE_ENTRY(ProcessReturned);
writer.WriteU32(processId);
writer.WriteString(reason);
}
void Trace::ProcessAddBreadcrumbs(u32 processId, const StringView& breadcrumbs, bool deleteOld)
{
BEGIN_TRACE_ENTRY(ProcessBreadcrumbs);
writer.WriteU32(processId);
writer.WriteLongString(breadcrumbs, 20);
writer.WriteBool(deleteOld);
}
void Trace::ProxyCreated(u32 clientId, const tchar* proxyName)
{
BEGIN_TRACE_ENTRY(ProxyCreated);
writer.Write7BitEncoded(clientId);
writer.WriteString(proxyName);
}
void Trace::ProxyUsed(u32 clientId, const tchar* proxyName)
{
BEGIN_TRACE_ENTRY(ProxyUsed);
writer.Write7BitEncoded(clientId);
writer.WriteString(proxyName);
}
void Trace::FileFetchLight(u32 clientId, const CasKey& key, u64 fileSize)
{
BEGIN_TRACE_ENTRY(FileFetchLight);
writer.Write7BitEncoded(clientId);
writer.Write7BitEncoded(fileSize);
}
void Trace::FileFetchBegin(u32 clientId, const CasKey& key, const StringView& hint)
{
u32 stringIndex = AddString(hint);
BEGIN_TRACE_ENTRY(FileFetchBegin);
writer.Write7BitEncoded(clientId);
writer.WriteCasKey(key);
writer.Write7BitEncoded(stringIndex);
}
void Trace::FileFetchSize(u32 clientId, const CasKey& key, u64 fileSize)
{
BEGIN_TRACE_ENTRY(FileFetchSize);
writer.Write7BitEncoded(clientId);
writer.WriteCasKey(key);
writer.Write7BitEncoded(fileSize);
}
void Trace::FileFetchEnd(u32 clientId, const CasKey& key)
{
BEGIN_TRACE_ENTRY(FileFetchEnd);
writer.Write7BitEncoded(clientId);
writer.WriteCasKey(key);
}
void Trace::FileStoreBegin(u32 clientId, const CasKey& key, u64 size, const StringView& hint, bool detailed)
{
if (detailed)
{
u32 stringIndex = AddString(hint);
BEGIN_TRACE_ENTRY(FileStoreBegin);
writer.Write7BitEncoded(clientId);
writer.WriteCasKey(key);
writer.Write7BitEncoded(size);
writer.Write7BitEncoded(stringIndex);
}
else
{
BEGIN_TRACE_ENTRY(FileStoreLight);
writer.Write7BitEncoded(clientId);
writer.Write7BitEncoded(size);
}
}
void Trace::FileStoreEnd(u32 clientId, const CasKey& key)
{
BEGIN_TRACE_ENTRY(FileStoreEnd);
writer.Write7BitEncoded(clientId);
writer.WriteCasKey(key);
}
void Trace::WorkBegin(u32 workIndex, const StringView& desc, const Color& color)
{
u32 stringIndex = AddString(desc);
BEGIN_TRACE_ENTRY(WorkBegin);
writer.Write7BitEncoded(workIndex);
writer.Write7BitEncoded(stringIndex);
writer.WriteU32(color);
}
void Trace::WorkHint(u32 workIndex, const StringView& hint, u64 startTime)
{
u32 stringIndex = AddString(hint);
BEGIN_TRACE_ENTRY(WorkHint);
writer.Write7BitEncoded(workIndex);
writer.Write7BitEncoded(stringIndex);
writer.Write7BitEncoded(startTime ? (startTime - m_startTime) : 0);
}
void Trace::WorkEnd(u32 workIndex)
{
BEGIN_TRACE_ENTRY(WorkEnd);
writer.Write7BitEncoded(workIndex);
}
void Trace::ProgressUpdate(u32 processesTotal, u32 processesDone, u32 errorCount)
{
BEGIN_TRACE_ENTRY(ProgressUpdate);
writer.Write7BitEncoded(processesTotal);
writer.Write7BitEncoded(processesDone);
writer.Write7BitEncoded(errorCount);
}
void Trace::StatusUpdate(u32 statusRow, u32 statusColumn, const tchar* statusText, LogEntryType statusType, const tchar* statusLink)
{
BEGIN_TRACE_ENTRY(StatusUpdate);
writer.Write7BitEncoded(statusRow);
writer.Write7BitEncoded(statusColumn);
writer.WriteString(statusText);
writer.WriteByte(statusType);
writer.WriteString(statusLink ? statusLink : TC(""));
}
void Trace::DriveUpdate(const tchar drive, u8 busyPercent, u32 readCount, u64 readBytes, u32 writeCount, u64 writeBytes)
{
BEGIN_TRACE_ENTRY_NOTIME(DriveUpdate);
writer.WriteByte((u8)drive);
writer.WriteByte(busyPercent);
writer.Write7BitEncoded(readCount);
writer.Write7BitEncoded(readBytes);
writer.Write7BitEncoded(writeCount);
writer.Write7BitEncoded(writeBytes);
}
void Trace::RemoteExecutionDisabled()
{
BEGIN_TRACE_ENTRY(RemoteExecutionDisabled);
}
void Trace::CacheBeginFetch(u32 fetchId, const tchar* description)
{
BEGIN_TRACE_ENTRY(CacheBeginFetch);
writer.Write7BitEncoded(fetchId);
writer.WriteString(description);
}
void Trace::CacheEndFetch(u32 fetchId, bool success, const u8* data, u64 dataSize)
{
BEGIN_TRACE_ENTRY(CacheEndFetch);
writer.Write7BitEncoded(fetchId);
writer.WriteBool(success);
writer.WriteBytes(data, dataSize);
}
void Trace::CacheBeginWrite(u32 processId)
{
BEGIN_TRACE_ENTRY(CacheBeginWrite);
writer.Write7BitEncoded(processId);
}
void Trace::CacheEndWrite(u32 processId, bool success, u64 bytesSent)
{
BEGIN_TRACE_ENTRY(CacheEndWrite);
writer.Write7BitEncoded(processId);
writer.WriteBool(success);
writer.Write7BitEncoded(bytesSent);
}
TraceChannel::TraceChannel(Logger& logger) : m_logger(logger)
{
}
bool TraceChannel::Init(const tchar* channelName)
{
#if PLATFORM_WINDOWS
StringBuffer<245> channelMutex;
channelMutex.Append(TCV("Uba")).Append(channelName).Append(TCV("Channel"));
m_memHandle = uba::CreateMemoryMappingW(m_logger, PAGE_READWRITE, 256, channelName, TC("TraceChannel"));
if (!m_memHandle.IsValid())
{
m_logger.Error(TC("Failed to create file mapping %s for trace channel (%s)"), channelName, LastErrorToText().data);
return false;
}
bool isCreator = GetLastError() != ERROR_ALREADY_EXISTS;
auto mhg = MakeGuard([&]() { CloseFileMapping(m_logger, m_memHandle, TC("TraceChannel")); m_memHandle = {}; });
m_mem = MapViewOfFile(m_logger, m_memHandle, FILE_MAP_WRITE, 0, 256);
if (!m_mem)
{
m_logger.Error(TC("Failed to map file mapping for uba trace channel"));
return false;
}
if (isCreator)
*(tchar*)m_mem = 0;
auto mg = MakeGuard([&]() { UnmapViewOfFile(m_logger, m_mem, 256, channelMutex.data); m_mem = nullptr; });
channelMutex.Append(channelName).Append(TCV("Mutex"));
m_mutex = CreateMutexW(false, channelMutex.data);
if (!m_mutex)
return false;
mg.Cancel();
mhg.Cancel();
#endif
return true;
}
TraceChannel::~TraceChannel()
{
#if PLATFORM_WINDOWS
if (m_mem)
UnmapViewOfFile(m_logger, m_mem, 256, TC("TraceChannel"));
if (m_memHandle.IsValid())
CloseFileMapping(m_logger, m_memHandle, TC("TraceChannel"));
CloseMutex(m_mutex);
#endif
}
bool TraceChannel::Write(const tchar* traceName, const tchar* ifMatching)
{
#if PLATFORM_WINDOWS
WaitForSingleObject((HANDLE)m_mutex, INFINITE);
auto g = MakeGuard([this]() { ReleaseMutex(m_mutex); });
if (ifMatching)
if (!Equals((tchar*)m_mem, ifMatching))
return true;
TStrcpy_s((tchar*)m_mem, 256, traceName);
#endif
return true;
}
bool TraceChannel::Read(StringBufferBase& outTraceName)
{
#if PLATFORM_WINDOWS
WaitForSingleObject((HANDLE)m_mutex, INFINITE);
outTraceName.Append((tchar*)m_mem);
ReleaseMutex(m_mutex);
#endif
return true;
}
static OwnerInfo InternalGetOwnerInfo()
{
static tchar buffer[260];
*buffer = 0;
OwnerInfo info { buffer, 0 };
StringBuffer<32> ownerPidStr;
ownerPidStr.count = GetEnvironmentVariableW(TC("UBA_OWNER_PID"), ownerPidStr.data, ownerPidStr.capacity);
if (ownerPidStr.count)
{
GetEnvironmentVariableW(TC("UBA_OWNER_ID"), buffer, sizeof_array(buffer));
ownerPidStr.Parse(info.pid);
return info;
}
#if PLATFORM_WINDOWS
HANDLE snapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshotHandle == INVALID_HANDLE_VALUE)
return info;
PROCESSENTRY32 pe = { 0 };
pe.dwSize = sizeof(PROCESSENTRY32);
UnorderedMap<u32, u32> pidToParent;
if (Process32First(snapshotHandle, &pe))
{
do
{
pidToParent[pe.th32ProcessID] = pe.th32ParentProcessID;
}
while (Process32Next(snapshotHandle, &pe));
}
CloseHandle(snapshotHandle);
u32 pid = ::GetCurrentProcessId();
while (true)
{
auto findIt = pidToParent.find(pid);
if (findIt == pidToParent.end())
break;
pid = findIt->second;
pidToParent.erase(findIt);
HANDLE parentHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (parentHandle == NULL)
break;
tchar moduleName[260];
DWORD len = GetModuleFileNameExW(parentHandle, 0, moduleName, MAX_PATH);
CloseHandle(parentHandle);
if (!len)
break;
if (!Contains(moduleName, L"devenv.exe"))
continue;
TStrcpy_s(buffer, MAX_PATH, L"vs");
info.pid = pid;
break;
}
#endif
return info;
}
const OwnerInfo& GetOwnerInfo()
{
static OwnerInfo info = InternalGetOwnerInfo();
return info;
}
}