blob: 50c7fdcdad8cf426cad99a7af52f57ad3e1473a3 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/debug/shared/logging/debug.h"
#include <lib/syslog/cpp/macros.h>
#include <chrono>
#include <mutex>
#include <set>
#include "src/developer/debug/shared/logging/logging.h"
#include "src/lib/files/path.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace debug_ipc {
namespace {
bool kDebugMode = false;
// This marks the moment SetDebugMode was called.
std::chrono::steady_clock::time_point kStartTime = std::chrono::steady_clock::now();
std::set<LogCategory>& GetLogCategories() {
static std::set<LogCategory> categories = {LogCategory::kAll};
return categories;
}
} // namespace
bool IsDebugModeActive() { return kDebugMode; }
void SetDebugMode(bool activate) { kDebugMode = activate; }
double SecondsSinceStart() {
return std::chrono::duration<double>(std::chrono::steady_clock::now() - kStartTime).count();
}
const std::set<LogCategory>& GetActiveLogCategories() { return GetLogCategories(); }
void SetLogCategories(std::initializer_list<LogCategory> categories) {
auto& active_categories = GetLogCategories();
active_categories.clear();
for (LogCategory category : categories) {
active_categories.insert(category);
}
}
bool IsLogCategoryActive(LogCategory category) {
if (!IsDebugModeActive())
return false;
if (category == LogCategory::kAll)
return true;
const auto& active_categories = GetLogCategories();
if (active_categories.count(LogCategory::kAll) > 0)
return true;
return active_categories.count(category) > 0;
}
// Log Tree ----------------------------------------------------------------------------------------
// Log tree works by pusing a log statement when the logging is done but waiting until that block
// is done to pop it out of the stack. By waiting until the whole stack is popped, we can flush the
// logs with a correct log context.
//
// This has several drawbacks though, such as additional context is needed to reconstruct logs in
// case of a crash (backpointer to the log statement object) and that depending on the stack, log
// statements might not appear for a while.
namespace {
// Logs are aranged in a tree that permits going back to the parent.
struct LogEntry {
LogCategory category = LogCategory::kNone;
FileLineFunction location;
std::string msg;
double start_time = 0;
double end_time = 0;
std::vector<std::unique_ptr<LogEntry>> children;
LogEntry* parent = nullptr;
// If a statement is valid, it means that it has not been flushed out of the stack, thus the
// statement is still valid in memory. We can use this backpointer to flush the statement.
LogStatement* statement = nullptr;
};
inline bool Valid(const LogEntry& entry) { return entry.location.is_valid(); }
LogEntry gRootSlot;
LogEntry* gCurrentSlot = nullptr;
std::mutex gLogMutex;
// Output is dd:hh:mm:ss.<ms>. dd is only showen when non-zero.
std::string SecondsToTimeString(double ds) {
int total_secs = static_cast<int>(ds);
int s = total_secs % 60;
int total_min = total_secs / 60;
int m = total_min % 60;
int total_hours = total_min / 60;
int h = total_hours % 24;
int d = total_hours / 24;
int ms = static_cast<int>((ds - total_secs) * 1000);
// We don't want to add days if it's 0, as it adds noise and it will be rare to have them.
if (d == 0) {
return fxl::StringPrintf("%02d:%02d:%02d.%03d", h, m, s, ms);
} else {
return fxl::StringPrintf("%02d:%02d:%02d:%02d.%03d", d, h, m, s, ms);
}
}
// Output is <sec>.<ms> (eg. 32.453).
std::string DurationToString(double start, double end) {
double diff = end - start;
int s = static_cast<int>(diff);
int ms = static_cast<int>((diff - s) * 1000000);
return fxl::StringPrintf("%03d.%06ds", s, ms);
}
// Format is (depending on whether |entry.location| is valid or not:
// [<time>][<category>]<indent><log msg> (location invalid).
// [<time>][<category>]<indent>[<function>][<file:line>] <log msg>
std::string LogEntryToStr(const LogEntry& entry, int indent) {
auto start_time_str = SecondsToTimeString(entry.start_time);
const char* cat_str = LogCategoryToString(entry.category);
// No location is only timing information.
if (!entry.location.is_valid()) {
return fxl::StringPrintf("[%s][%10s]%*s%s", start_time_str.c_str(), cat_str, indent, "",
entry.msg.c_str());
}
auto duration_str = DurationToString(entry.start_time, entry.end_time);
auto file = files::GetBaseName(entry.location.file());
int line = entry.location.line();
const char* function = entry.location.function().c_str();
return fxl::StringPrintf("[%s][%s][%10s]%*s[%s:%d][%s] %s", start_time_str.c_str(),
duration_str.c_str(), cat_str, indent, "", file.c_str(), line, function,
entry.msg.c_str());
}
// Goes over the logging tree recursively and correctly indents the log messages into |logs|.
void UnwindLogTree(const LogEntry& entry, std::vector<std::string>* logs, int indent = 0) {
logs->emplace_back(LogEntryToStr(entry, indent));
for (auto& child : entry.children) {
UnwindLogTree(*child, logs, indent + 2);
}
}
// If the log entry is not filled, it means that it's still in the stack. We use the backpointer it
// was to the log statement that generated it to fill it. This normally happens then |PopLogEntry|
// is called, but an exception handler that calls |FlushLogEntries| can also make this happen.
void FillInLogEntryFromStatement(LogEntry* entry) {
if (Valid(*entry))
return;
if (!entry->statement)
return;
// Refill the slot with the log statement.
entry->category = entry->statement->category();
entry->location = entry->statement->origin();
entry->msg = entry->statement->GetMsg();
entry->start_time = entry->statement->start_time();
entry->end_time = SecondsSinceStart();
entry->statement = nullptr;
}
void TraverseLogTree(LogEntry* entry, std::vector<std::string>* logs, int indent = 0) {
FillInLogEntryFromStatement(entry);
logs->emplace_back(LogEntryToStr(*entry, indent));
for (auto& child_entry : entry->children) {
TraverseLogTree(child_entry.get(), logs, indent + 2);
}
}
} // namespace
void PushLogEntry(LogStatement* statement) {
std::lock_guard<std::mutex> lock(gLogMutex);
// No slot means that it's a new message tree. Use the root.
if (!gCurrentSlot) {
gRootSlot = {};
gCurrentSlot = &gRootSlot;
} else {
// Push a child.
gCurrentSlot->children.push_back(std::make_unique<LogEntry>());
auto& child = gCurrentSlot->children.back();
child->parent = gCurrentSlot;
gCurrentSlot = child.get();
}
gCurrentSlot->statement = statement;
}
void PopLogEntry(LogCategory category, const FileLineFunction& location, const std::string& msg,
double start_time, double end_time) {
std::vector<std::string> logs;
{
std::lock_guard<std::mutex> lock(gLogMutex);
// Set the message that's going away to the child.
gCurrentSlot->category = category;
gCurrentSlot->location = location;
gCurrentSlot->msg = msg;
gCurrentSlot->start_time = start_time;
gCurrentSlot->end_time = end_time;
gCurrentSlot->statement = nullptr;
// While there is still a parent, we're not at the root.
if (gCurrentSlot->parent) {
gCurrentSlot = gCurrentSlot->parent;
return;
}
// We tried to pop the root, that means this logging tree is done and we're going to output it
// and reset everything.
UnwindLogTree(*gCurrentSlot, &logs);
gCurrentSlot = nullptr;
}
for (auto& log : logs) {
fprintf(stderr, "\r%s\r\n", log.c_str());
}
fflush(stderr);
}
void FlushLogEntries() {
std::vector<std::string> logs;
{
if (!gLogMutex.try_lock())
return;
TraverseLogTree(&gRootSlot, &logs);
gLogMutex.unlock();
}
for (auto& log : logs) {
fprintf(stderr, "\rLOG: %s\r\n", log.c_str());
}
fflush(stderr);
}
const char* LogCategoryToString(LogCategory category) {
switch (category) {
case LogCategory::kAgent:
return "Agent";
case LogCategory::kArchArm64:
return "arm64";
case LogCategory::kArchx64:
return "x64";
case LogCategory::kBreakpoint:
return "Breakpoint";
case LogCategory::kJob:
return "Job";
case LogCategory::kMessageLoop:
return "Loop";
case LogCategory::kProcess:
return "Process";
case LogCategory::kRemoteAPI:
return "DebugAPI";
case LogCategory::kSession:
return "Session";
case LogCategory::kSetting:
return "Setting";
case LogCategory::kTest:
return "Test";
case LogCategory::kTiming:
return "Timing";
case LogCategory::kThread:
return "Thread";
case LogCategory::kWatchpoint:
return "Watchpoint";
case LogCategory::kWorkerPool:
return "WorkerPool";
case LogCategory::kDebugAdapter:
return "DebugAdapter";
case LogCategory::kAll:
return "All";
case LogCategory::kNone:
return "<none>";
}
FX_NOTREACHED();
return "<unknown>";
}
} // namespace debug_ipc