// Copyright Epic Games, Inc. All Rights Reserved. #include "UbaTrace.h" #include "UbaFileAccessor.h" #include "UbaProcessHandle.h" #if PLATFORM_WINDOWS #include #include #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& 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 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; } }