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

824 lines
21 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UbaLogger.h"
#include "UbaBinaryReaderWriter.h"
#include "UbaFileAccessor.h"
#include "UbaPlatform.h"
#include "UbaStringBuffer.h"
#include "UbaThread.h"
#include "UbaTimer.h"
#if PLATFORM_WINDOWS
#include <io.h>
#include <dbghelp.h>
#define Fputs fputws
#else
#define Fputs fputs
#define _vsnwprintf_s(buffer,capacity,count,format,args) vsnprintf(buffer, capacity, format, args) // TODO: This will overflow
#define _vscwprintf(format, args) vsnprintf(0, 0, format, args)
#endif
namespace uba
{
CustomAssertHandler* g_assertHandler;
void ParseCallstack(StringBufferBase& out, BinaryReader& reader)
{
StringView searchPaths[3];
StringBuffer<512> currentModuleDir;
LoggerWithWriter logger(g_nullLogWriter);
GetDirectoryOfCurrentModule(logger, currentModuleDir);
StringBuffer<512> alternativePath;
u32 searchPathIndex = 0;
if (GetAlternativeUbaPath(logger, alternativePath, currentModuleDir, IsWindows && IsArmBinary))
searchPaths[searchPathIndex++] = alternativePath;
searchPaths[searchPathIndex] = currentModuleDir;
tchar executable[512] = { 0 };
#if !PLATFORM_WINDOWS
readlink("/proc/self/exe", executable, sizeof_array(executable));
#endif
ParseCallstackInfo(out, reader, executable, searchPaths);
}
ANALYSIS_NORETURN void UbaAssert(const tchar* text, const char* file, u32 line, const char* expr, bool allowTerminate, u32 terminateCode, void* context, u32 skipCallstackCount)
{
static ReaderWriterLock& assertLock = *new ReaderWriterLock(); // Leak to prevent asan annoyances during shutdown when asserts happen
SCOPED_WRITE_LOCK(assertLock, lock);
static u8* writerMem = new u8[4096];
BinaryWriter writer(writerMem, 0, 4096);
WriteCallstackInfo(writer, 2, context);
static auto& sb = *new StringBuffer<16*1024>();
WriteAssertInfo(sb.Clear(), text, file, line, expr, context);
BinaryReader reader(writerMem, 0, writer.GetPosition());
ParseCallstack(sb, reader);
if (g_assertHandler)
{
g_assertHandler(sb.data);
return;
}
Fputs(sb.data, stdout);
Fputs(TC("\n"), stdout);
fflush(stdout);
#if PLATFORM_WINDOWS
#if UBA_ASSERT_MESSAGEBOX
int ret = MessageBoxW(GetConsoleWindow(), sb.data, TC("Assert"), MB_ABORTRETRYIGNORE);
if (ret != IDABORT)
{
if (ret == IDRETRY)
DebugBreak();
return;
}
SetFocus(GetConsoleWindow());
SetActiveWindow(GetConsoleWindow());
#else
if (IsDebuggerPresent())
DebugBreak();
#endif
// *(int*)nullptr = 42; // Use this to force a crashdump instead of exiting
if (allowTerminate)
ExitProcess(terminateCode);
#else
if (allowTerminate)
_Exit(-1);
#endif
}
void SetCustomAssertHandler(CustomAssertHandler* handler)
{
g_assertHandler = handler;
}
ANALYSIS_NORETURN void FatalError(u32 code, const tchar* format, ...)
{
va_list arg;
va_start(arg, format);
tchar buffer[1024];
int count = Tvsprintf_s(buffer, 1024, format, arg);
if (count <= 0)
TStrcpy_s(buffer, 1024, format);
va_end(arg);
#if PLATFORM_WINDOWS
wprintf(TC("UBA FATAL ERROR %u: %s\n"), code, buffer);
fflush(stdout);
if (IsDebuggerPresent())
DebugBreak();
ExitProcess(code);
#else
printf(TC("UBA FATAL ERROR %u: %s\n"), code, buffer);
fflush(stdout);
kill(getpid(), SIGKILL);
#endif
}
Logger& Logger::Info(const tchar* format, ...)
{
va_list arg;
va_start(arg, format);
LogArg(LogEntryType_Info, format, arg);
va_end(arg);
return *this;
}
Logger& Logger::Detail(const tchar* format, ...)
{
va_list arg;
va_start(arg, format);
LogArg(LogEntryType_Detail, format, arg);
va_end(arg);
return *this;
}
Logger& Logger::Debug(const tchar* format, ...)
{
va_list arg;
va_start(arg, format);
LogArg(LogEntryType_Debug, format, arg);
va_end(arg);
return *this;
}
bool Logger::Warning(const tchar* format, ...)
{
va_list arg;
va_start(arg, format);
LogArg(LogEntryType_Warning, format, arg);
va_end(arg);
return false;
}
bool Logger::Error(const tchar* format, ...)
{
va_list arg;
va_start(arg, format);
LogArg(LogEntryType_Error, format, arg);
va_end(arg);
return false;
}
void Logger::Logf(LogEntryType type, const tchar* format, ...)
{
va_list arg;
va_start(arg, format);
LogArg(type, format, arg);
va_end(arg);
}
void Logger::LogArg(LogEntryType type, const tchar* format, va_list& args)
{
#if !PLATFORM_WINDOWS
constexpr size_t _TRUNCATE = (size_t)-1;
va_list args2;
va_copy(args2, args);
auto g = MakeGuard([&]() { va_end(args2); });
#endif
tchar buffer[2048];
int len = _vsnwprintf_s(buffer, sizeof_array(buffer), _TRUNCATE, format, args);
if (len >= 0 && len < sizeof_array(buffer))
{
Log(type, buffer, u32(len));
return;
}
#if PLATFORM_WINDOWS
len = _vscwprintf(format, args);
if (len < 0)
{
Error(TC("LogArg failed. bad format"));
return;
}
va_list& newArgs = args;
#else
va_list& newArgs = args2;
#endif
Vector<tchar> buf;
buf.resize(len + 1);
len = _vsnwprintf_s(buf.data(), buf.size(), buf.size(), format, newArgs);
if (len < 0)
{
Error(TC("LogArg failed. bad format"));
return;
}
Log(type, buf.data(), u32(len));
}
void Logger::Log(LogEntryType type, const StringView& str)
{
Log(type, str.data, str.count);
}
LoggerWithWriter::LoggerWithWriter(LogWriter& writer, const tchar* prefix)
: m_writer(writer)
, m_prefix(prefix)
, m_prefixLen(prefix ? u32(TStrlen(prefix)) : 0)
{
}
void FilteredLogWriter::Log(LogEntryType type, const tchar* str, u32 strLen, const tchar* prefix, u32 prefixLen)
{
if (type > m_level)
return;
m_writer.Log(type, str, strLen, prefix, prefixLen);
}
class ConsoleLogWriter : public LogWriter
{
public:
ConsoleLogWriter();
virtual void BeginScope() override;
virtual void EndScope() override;
virtual void Log(LogEntryType type, const tchar* str, u32 strLen, const tchar* prefix = nullptr, u32 prefixLen = 0) override;
private:
void LogNoLock(LogEntryType type, const tchar* str, u32 strLen, const tchar* prefix, u32 prefixLen);
Futex m_lock;
#if PLATFORM_WINDOWS
HANDLE m_stdout = 0;
u32 m_defaultAttributes = 0;
#endif
};
UBA_API LogWriter& g_consoleLogWriter = *new ConsoleLogWriter(); // Leak to prevent asan annoyances during shutdown when asserts happen
thread_local u32 t_consoleLogScopeCount = 0;
class NullLogWriter : public LogWriter
{
public:
virtual void BeginScope() override {}
virtual void EndScope() override {}
virtual void Log(LogEntryType type, const tchar* str, u32 strLen, const tchar* prefix = nullptr, u32 prefixLen = 0) override {}
} g_nullLogWriterImpl;
UBA_API LogWriter& g_nullLogWriter = g_nullLogWriterImpl;
ConsoleLogWriter::ConsoleLogWriter()
{
#if PLATFORM_WINDOWS
if (_isatty(_fileno(stdout)))
{
m_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(m_stdout, &csbi);
m_defaultAttributes = csbi.wAttributes;
}
#endif
}
void ConsoleLogWriter::BeginScope()
{
if (!t_consoleLogScopeCount++)
m_lock.Enter();
}
void ConsoleLogWriter::EndScope()
{
if (--t_consoleLogScopeCount)
return;
#if PLATFORM_WINDOWS
if (!m_stdout)
#endif
fflush(stdout);
m_lock.Leave();
}
void ConsoleLogWriter::Log(LogEntryType type, const tchar* str, u32 strLen, const tchar* prefix, u32 prefixLen)
{
if (t_consoleLogScopeCount)
return LogNoLock(type, str, strLen, prefix, prefixLen);
SCOPED_FUTEX(m_lock, lock);
LogNoLock(type, str, strLen, prefix, prefixLen);
#if PLATFORM_WINDOWS
if (!m_stdout)
#endif
fflush(stdout);
}
void ConsoleLogWriter::LogNoLock(LogEntryType type, const tchar* str, u32 strLen, const tchar* prefix, u32 prefixLen)
{
#if PLATFORM_WINDOWS
if (!m_stdout)
{
if (prefixLen)
{
Fputs(prefix, stdout);
Fputs(TC(" - "), stdout);
}
_putws(str);
}
else
{
if (prefixLen)
{
WriteConsoleW(m_stdout, prefix, prefixLen, NULL, NULL);
WriteConsoleW(m_stdout, TC(" - "), 3, NULL, NULL);
}
switch (type)
{
case LogEntryType_Warning:
SetConsoleTextAttribute(m_stdout, FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY);
WriteConsoleW(m_stdout, str, strLen, NULL, NULL);
SetConsoleTextAttribute(m_stdout, (WORD)m_defaultAttributes);
break;
case LogEntryType_Error:
SetConsoleTextAttribute(m_stdout, FOREGROUND_RED | FOREGROUND_INTENSITY);
WriteConsoleW(m_stdout, str, strLen, NULL, NULL);
SetConsoleTextAttribute(m_stdout, (WORD)m_defaultAttributes);
break;
default:
WriteConsoleW(m_stdout, str, strLen, NULL, NULL);
break;
}
WriteConsoleW(m_stdout, TC("\r\n"), 2, NULL, NULL);
}
#else
if (prefixLen)
{
Fputs(prefix, stdout);
Fputs(TC(" - "), stdout);
}
Fputs(str, stdout);
Fputs(TC("\n"), stdout);
#endif
}
LastErrorToText::LastErrorToText() : LastErrorToText(GetLastError())
{
}
LastErrorToText::LastErrorToText(u32 lastError)
{
#if PLATFORM_WINDOWS
size_t size = ::FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, lastError, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), data, capacity, NULL);
if (!size)
AppendValue(lastError);
else
Resize(size - 2);
#else
Append(strerror(int(lastError)));
#endif
}
BytesToText::BytesToText(u64 bytes)
{
if (bytes < 1000)
TSprintf_s(str, 32, TC("%ub"), u32(bytes));
else if (bytes < 1000 * 1000)
TSprintf_s(str, 32, TC("%.1fkb"), double(bytes) / 1000ull);
else if (bytes < 1000ull * 1000 * 1000)
TSprintf_s(str, 32, TC("%.1fmb"), double(bytes) / (1000ull * 1000));
else if (bytes < 1000ull * 1000 * 1000 * 1000)
TSprintf_s(str, 32, TC("%.1fgb"), double(bytes) / (1000ull * 1000 * 1000));
else
TSprintf_s(str, 32, TC("%.1ftb"), double(bytes) / (1000ull * 1000 * 1000 * 1000));
}
CountToText::CountToText(u64 count)
{
if (count < 1000)
TSprintf_s(str, 32, TC("%u"), u32(count));
else if (count < 1000 * 1000)
TSprintf_s(str, 32, TC("%.1fk"), double(count) / 1000ull);
else if (count < 1000ull * 1000 * 1000)
TSprintf_s(str, 32, TC("%.1fm"), double(count) / (1000ull * 1000));
else if (count < 1000ull * 1000 * 1000 * 1000)
TSprintf_s(str, 32, TC("%.1fg"), double(count) / (1000ull * 1000 * 1000));
else
TSprintf_s(str, 32, TC("%.1ft"), double(count) / (1000ull * 1000 * 1000 * 1000));
}
#if UBA_DEBUG_LOGGER
thread_local u32 t_debugLogScopeCount = 0;
class DebugLogWriter : public LogWriter
{
public:
virtual void BeginScope() override
{
if (!m_file)
return;
if (!t_debugLogScopeCount++)
m_logLock.Enter();
}
virtual void EndScope() override
{
if (!m_file)
return;
if (--t_debugLogScopeCount)
return;
//
m_logLock.Leave();
}
virtual void Log(LogEntryType type, const tchar* str, u32 strLen, const tchar* prefix = nullptr, u32 prefixLen = 0) override
{
if (!m_file)
return;
if (t_debugLogScopeCount)
return LogNoLock(type, str, strLen, prefix, prefixLen);
SCOPED_FUTEX(m_logLock, lock);
LogNoLock(type, str, strLen, prefix, prefixLen);
}
void LogNoLock(LogEntryType type, const tchar* str, u32 strLen, const tchar* prefix, u32 prefixLen)
{
#if PLATFORM_WINDOWS
char buffer[512];
size_t destLen;
if (wcstombs_s(&destLen, buffer, sizeof(buffer), str, sizeof(buffer)-2) != 0)
{
strcpy_s(buffer, sizeof(buffer), "BAD_STRING\n");
destLen = 11;
}
else
{
buffer[destLen-1] = '\r'; // replace null terminator with \r
buffer[destLen++] = '\n';
}
m_file->Write(buffer, destLen);
#else
m_file->Write(str, strLen);
#endif
}
TString m_fileName;
FileAccessor* m_file = nullptr;
Futex m_logLock;
};
Logger* StartDebugLogger(Logger& outerLogger, const tchar* fileName)
{
auto debugWriter = new DebugLogWriter();
debugWriter->m_fileName = fileName;
auto fa = new FileAccessor(outerLogger, debugWriter->m_fileName.c_str());
if (!fa->CreateWrite())
{
delete fa;
return new LoggerWithWriter(g_nullLogWriter);
}
#if PLATFORM_WINDOWS
unsigned char utf8BOM[] = { 0xef,0xbb,0xbf };
fa->Write(utf8BOM, sizeof(utf8BOM));
#endif
debugWriter->m_file = fa;
return new LoggerWithWriter(*debugWriter);
}
Logger* StopDebugLogger(Logger* logger)
{
auto debugLogger = (LoggerWithWriter*)logger;
if (&debugLogger->m_writer != &g_nullLogWriter)
{
auto debugWriter = (DebugLogWriter*)&debugLogger->m_writer;
if (debugWriter->m_file)
{
debugWriter->m_file->Close();
delete debugWriter->m_file;
debugWriter->m_file = nullptr;
}
delete debugWriter;
}
delete debugLogger;
return nullptr;
}
#endif
#if UBA_TRACK_CONTENTION
List<ContentionTracker>& GetContentionTrackerList();
#endif
void ParseCallstackInfo(StringBufferBase& out, BinaryReader& reader, const tchar* executable, const StringView* searchPaths)
{
#if PLATFORM_WINDOWS
bool isRunningWine = reader.ReadBool();
#else
bool isRunningWine = false;
#endif
struct CallstackEntry { u64 moduleIndex; u64 memoryOffset; };
u64 callstackCount = reader.Read7BitEncoded();
Vector<CallstackEntry> entries(callstackCount);
for (auto& entry : entries)
{
entry.moduleIndex = reader.Read7BitEncoded();
entry.memoryOffset = reader.Read7BitEncoded();
}
struct ModuleEntry { u64 start; u64 size; TString name; bool handled; UnorderedMap<u64, TString> symbols; };
u64 moduleCount = reader.Read7BitEncoded();
Vector<ModuleEntry> modules(moduleCount);
for (auto& mod : modules)
{
mod.start = reader.Read7BitEncoded();
mod.size = reader.Read7BitEncoded();
mod.name = reader.ReadString();
mod.handled = false;
}
out.Append(TCV("\n CALLSTACK")).Append(isRunningWine ? TCV(" (Wine)") : TCV("")).Append(':');
if (entries.empty())
{
out.Append(TCV("\n <No entries available>"));
return;
}
#if PLATFORM_WINDOWS
static u64 processHandleCounter = 45234523;
auto processHandle = (HANDLE)processHandleCounter++; // TODO: Reuse handle based on a bunch of criterias?
StringBuffer<> searchPathString;
for (auto it=searchPaths; it->count; ++it)
{
if (it != searchPaths)
searchPathString.Append(';');
searchPathString.Append(*it);
}
if (SymInitializeW(processHandle, searchPathString.data, FALSE))
{
SymSetOptions(SYMOPT_LOAD_LINES);
for (auto& mod : modules)
{
if (mod.name.empty())
{
mod.name = TC("<Unknown>");
continue;
}
auto res = SymLoadModuleExW(processHandle, NULL, mod.name.c_str(), NULL, mod.start, (DWORD)mod.size, NULL, 0);
if (res != 0 || GetLastError() == ERROR_SUCCESS)
{
mod.handled = true;
continue;
}
// Try something more?
}
}
auto symCleanup = MakeGuard([processHandle]() { SymCleanup(processHandle); });
for (auto& entry : entries)
{
out.Append(TCV("\n "));
if (entry.moduleIndex == ~0u)
{
out.Append(TCV("<Unknown>"));
continue;
}
auto& mod = modules[entry.moduleIndex];
if (mod.handled)
{
u8 buffer[1024];
auto& info = *(SYMBOL_INFOW*)buffer;
memset(&info, 0, sizeof(info));
info.SizeOfStruct = sizeof(info);
info.MaxNameLen = (sizeof(buffer) - sizeof(info))/sizeof(tchar);
DWORD64 displacement2 = 0;
bool gotSymbol = false;
if (SymFromAddrW(processHandle, mod.start + entry.memoryOffset, &displacement2, &info))
{
out.Appendf(TC("%s"), info.Name);
gotSymbol = true;
}
auto& line = *(IMAGEHLP_LINEW64*)buffer;
line.SizeOfStruct = sizeof(line);
DWORD displacement = 0;
bool gotLine = false;
if (SymGetLineFromAddrW64(processHandle, mod.start + entry.memoryOffset, &displacement, &line))
{
auto fileName = line.FileName;
if (auto lastSlash = TStrrchr(fileName, '\\'))
fileName = lastSlash + 1;
if (gotSymbol)
out.Append(TCV(" ("));
out.Appendf(TC("%s:%u"), fileName, line.LineNumber);
if (gotSymbol)
out.Append(TCV(")"));
gotLine = true;
}
if (gotSymbol || gotLine)
continue;
}
out.Appendf(TC("%s: +0x%llx"), mod.name.c_str(), entry.memoryOffset);
}
#else
for (auto& mod : modules)
{
if (mod.handled)
continue;
if (mod.name.empty())
mod.name = executable;
StringView name = StringView(mod.name).GetFileName();
for (auto it=searchPaths; it->count; ++it)
{
struct stat attr;
StringBuffer<512> buf;
buf.Clear().Append(*it).EnsureEndsWithSlash().Append(name);
if (stat(buf.data, &attr) == -1)
continue;
mod.name = buf.data;
break;
}
mod.handled = true;
}
for (u64 i=0; i!=modules.size(); ++i)
{
auto& mod = modules[i];
StringBuffer<1024> cmd;
#if PLATFORM_LINUX
cmd.Append("addr2line");
#else
cmd.Appendf("atos -o %s --offset", mod.name.c_str());
#endif
Vector<u64> memoryOffsets;
for (auto& entry : entries)
if (entry.moduleIndex == i)
{
cmd.Appendf(" 0x%x", entry.memoryOffset);
memoryOffsets.push_back(entry.memoryOffset);
}
#if PLATFORM_LINUX
cmd.Append(" -f -C -p -e ").Append(mod.name);
#endif
cmd.Append(" 2>/dev/null");
fflush(stdout);
u32 memoryOffsetIndex = 0;
if (FILE* fp = popen(cmd.data, "r"))
{
auto cg = MakeGuard([&]() { pclose(fp); });
u32 index = 0;
u32 countBeforeCallstack = out.count;
errno = 0;
StringBuffer<1024> str;
while (true)
{
if (!fgets(str.data, str.capacity, fp))
break;
str.count = TStrlen(str.data);
if (str.EndsWith("\n"))
str.Resize(str.count - 1);
mod.symbols.try_emplace(memoryOffsets[memoryOffsetIndex++], str.ToString());
}
}
}
u32 skipIndex = 0; // remove signal handler logic
for (u64 i=0; i!=entries.size() && !skipIndex; ++i)
if (entries[i].moduleIndex != ~0u)
if (modules[entries[i].moduleIndex].symbols[entries[i].memoryOffset].find("__restore_rt") != -1)
skipIndex = i + 1;
for (auto& entry : entries)
{
if (skipIndex > 0)
{
--skipIndex;
continue;
}
if (entry.moduleIndex == ~0u)
{
out.Appendf(TC("\n <Unknown>: 0x%llx"), entry.memoryOffset);
continue;
}
auto& mod = modules[entry.moduleIndex];
auto pg = MakeGuard([&]() { out.Appendf(TC("\n %s: 0x%llx"), mod.name.c_str(), entry.memoryOffset); });
StringBuffer<1024> str(mod.symbols[entry.memoryOffset]);
if (!str.count || str[0] == ':' || str[0] == '?')
continue;
char* fileName = str.data;
pg.Cancel();
if (out.capacity - out.count < strlen(fileName) + 5)
break;
out.Appendf(TC("\n %s"), fileName);
}
#endif
}
void PrintContentionSummary(Logger& logger)
{
#if UBA_TRACK_CONTENTION
logger.Info(TC(" ------- Contention summary -------"));
List<ContentionTracker*> list;
for (auto& ct : GetContentionTrackerList())
if (TimeToMs(ct.time) > 1)
list.push_back(&ct);
list.sort([](const ContentionTracker* a, const ContentionTracker* b)
{
if (a->time != b->time)
return a->time > b->time;
return a < b;
});
for (auto& ct : list)
{
StringBuffer<512> fn;
fn.Append(ct->file);
StringBuffer<256> s;
s.Append(TCV(" ")).AppendFileName(fn.data).Append(':').AppendValue(ct->line).Append(TCV(" - ")).AppendValue(ct->count).Append(TCV(" (")).Append(TimeToText(ct->time).str).Append(')');
logger.Info(s.data);
ct->count = 0;
ct->time = 0;
}
#endif
}
void TraverseAllCallstacks(const Function<void(const CallstackInfo&)>& func, const TraverseThreadErrorFunc& errorFunc)
{
UnorderedMap<CasKey, CallstackInfo> callstacks;
TraverseAllThreads([&](u32 tid, void** callstack, u32 callstackCount, const tchar* desc)
{
StackBinaryWriter<4096> stackWriter;
u32* stackSize = (u32*)stackWriter.AllocWrite(sizeof(u32));
WriteCallstackInfo(stackWriter, callstack, callstackCount);
*stackSize = u32(stackWriter.GetPosition() - 4);
CasKeyHasher hasher;
hasher.Update(stackWriter.GetData(), stackWriter.GetPosition());
auto insres = callstacks.try_emplace(ToCasKey(hasher, false));
CallstackInfo& cs = insres.first->second;
cs.threadIds.push_back(tid);
if (!insres.second)
return;
if (desc)
cs.desc = desc;
cs.data.resize(stackWriter.GetPosition());
memcpy(cs.data.data(), stackWriter.GetData(), cs.data.size());
}, errorFunc);
for (auto& kv : callstacks)
{
CallstackInfo& cs = kv.second;
StringBuffer<1024> temp;
if (!cs.desc.empty())
temp.Append(cs.desc).Append(TCV(" - "));
temp.Append(TCV("Thread Ids: "));
for (u32 tid : cs.threadIds)
{
if (temp.count >= temp.capacity - 10)
{
temp.Append(TCV("..."));
break;
}
temp.AppendValue(tid).Append(' ');
}
cs.desc = temp.data;
func(cs);
}
}
void PrintAllCallstacks(Logger& logger)
{
TraverseAllCallstacks([&](const CallstackInfo& cs)
{
BinaryReader stackReader(cs.data.data(), 0, cs.data.size());
stackReader.Skip(4); // Skip the size
static auto& sb = *new StringBuffer<16*1024>();
ParseCallstack(sb.Clear(), stackReader);
logger.Info(TC("%s%s"), cs.desc.c_str(), sb.data);
},
[&](const StringView& error)
{
logger.Info(error.data);
});
}
LogStallScope::LogStallScope(Logger& l, LogEntryType t, u64 ts, const tchar* m) : logger(l), type(t), timeSeconds(ts), timeStart(GetTime()), messageFormat(m) {}
LogStallScope::~LogStallScope() { Leave(); }
void LogStallScope::Leave()
{
if (!timeStart)
return;
u64 delta = GetTime() - timeStart;
if (delta > MsToTime(timeSeconds*1000))
logger.Logf(type, messageFormat, TimeToText(delta).str);
timeStart = 0;
}
}