| // 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::kAll: | |
| return "All"; | |
| case LogCategory::kNone: | |
| return "<none>"; | |
| } | |
| FX_NOTREACHED(); | |
| return "<unknown>"; | |
| } | |
| } // namespace debug_ipc |