/* * Copyright (c) Advanced Micro Devices, Inc., or its affiliates. * * SPDX-License-Identifier: MIT */ #include "top.hpp" #include "utils/debug.hpp" #include "os/os.hpp" #if !defined(AMD_LOG_LEVEL) #include "utils/flags.hpp" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #else #include #include #endif // _WIN32 namespace amd { FILE* outFile = stderr; void truncate_log_file(); static void TruncateLogFileFlushPath(); static void crashFlushCallback(); // ================================================================================================ // Async logging infrastructure // ================================================================================================ static std::atomic crashHandlersInstalled_{false}; struct LogEntry { LogLevel level; //!< Log severity level const char* file; //!< Source file name int line; //!< Source line number std::string message; //!< Formatted log message uint64_t timestamp; //!< Timestamp in microseconds uint32_t pid; //!< Process ID uint32_t tid; //!< Thread ID (hash) uint64_t duration; //!< Duration in microseconds (0 if not a duration log) bool hasDuration; //!< True if this is a duration log entry std::atomic valid; //!< Valid flag for lock-free synchronization LogEntry() : level(LOG_NONE), file(""), line(0), timestamp(0), pid(0), duration(0), hasDuration(false), valid(false) {} }; class AsyncLogger { public: AsyncLogger() : buffer_(kBufferSize) { bool useAsync = !flagIsDefault(AMD_LOG_ASYNC) ? AMD_LOG_ASYNC : !flagIsDefault(AMD_LOG_LEVEL_FILE); if (useAsync) { enable(true); } } ~AsyncLogger() { enable(false); stop(); } void start() { if (!running_.load(std::memory_order_relaxed)) { running_.store(true, std::memory_order_relaxed); enabled_.store(true, std::memory_order_relaxed); workerThread_ = std::thread(&AsyncLogger::workerLoop, this); } } void stop() { if (running_.load(std::memory_order_relaxed)) { running_.store(false, std::memory_order_relaxed); flushCV_.notify_all(); if (workerThread_.joinable()) { workerThread_.join(); } } } void enable(bool enable) { enabled_.store(enable, std::memory_order_relaxed); if (enable && !running_.load(std::memory_order_relaxed)) { start(); } if (enable) { bool expected = false; if (crashHandlersInstalled_.compare_exchange_strong(expected, true, std::memory_order_acq_rel)) { if (!Os::installExceptionHandlers(crashFlushCallback)) { crashHandlersInstalled_.store(false, std::memory_order_release); } } } else { bool expected = true; if (crashHandlersInstalled_.compare_exchange_strong(expected, false, std::memory_order_acq_rel)) { Os::uninstallExceptionHandlers(); } } } bool isEnabled() const { return enabled_.load(std::memory_order_relaxed); } void log(LogLevel level, const char* file, int line, const char* message, uint64_t timestamp, uint64_t duration = 0, bool hasDuration = false) { if (!enabled_.load(std::memory_order_relaxed)) { return; // Fall back to sync logging } size_t currentWrite = writeIndex_.fetch_add(1, std::memory_order_release); size_t currentRead = readIndex_.load(std::memory_order_acquire); // Check if buffer is full while (currentWrite - currentRead >= kBufferSize) { flush(); currentRead = readIndex_.load(std::memory_order_acquire); } // Write to buffer (lock-free) LogEntry& entry = buffer_[currentWrite % kBufferSize]; entry.level = level; entry.file = file ? file : ""; entry.line = line; entry.message = message ? message : ""; entry.timestamp = timestamp; entry.pid = Os::getProcessId(); entry.tid = static_cast( std::hash{}(std::this_thread::get_id()) & 0xFFFFF); entry.duration = duration; entry.hasDuration = hasDuration; entry.valid.store(true, std::memory_order_release); } void flush() { if (enabled_.load(std::memory_order_relaxed)) { flushCV_.notify_all(); } } void flushInCurrentThread() { if (enabled_.load(std::memory_order_relaxed)) { flushPending(); } } void FlushOnCrash() noexcept { if (!enabled_.load(std::memory_order_relaxed)) { return; } try { flushPending(); } catch (...) { } } private: static constexpr size_t kBufferSize = 16 * 1024;//!< Circular buffer size static constexpr size_t kFlushIntervalMs = 1; //!< Flush interval in milliseconds std::vector buffer_; //!< Circular buffer of log entries std::atomic writeIndex_{0}; //!< Write position in circular buffer std::atomic readIndex_{0}; //!< Read position in circular buffer std::atomic running_{false}; //!< Worker thread running flag std::atomic enabled_{false}; //!< Async logging enabled flag std::thread workerThread_; //!< Background worker thread for flushing std::mutex flushMutex_; //!< Mutex for flush condition variable std::condition_variable flushCV_; //!< Condition variable for worker wakeup void workerLoop() { while (running_.load(std::memory_order_relaxed)) { std::unique_lock lock(flushMutex_); flushCV_.wait_for(lock, std::chrono::milliseconds(kFlushIntervalMs), [this] { return !running_.load(std::memory_order_relaxed); }); if (running_.load(std::memory_order_relaxed)) { flushPending(); } } // Final flush on shutdown flushPending(); } void flushPending() { TruncateLogFileFlushPath(); size_t currentRead = readIndex_.load(std::memory_order_acquire); size_t currentWrite = writeIndex_.load(std::memory_order_acquire); size_t writeCount = 0; while (currentRead != currentWrite) { LogEntry& entry = buffer_[currentRead % kBufferSize]; // Wait for valid flag while (!entry.valid.load(std::memory_order_acquire)) { std::this_thread::yield(); } writeToFile(entry); entry.valid.store(false, std::memory_order_release); currentRead++; readIndex_.store(currentRead, std::memory_order_release); writeCount++; if (writeCount % 1024 == 0) { fflush(outFile); } currentWrite = writeIndex_.load(std::memory_order_acquire); } fflush(outFile); } void writeToFile(const LogEntry& entry) { char pidtid[64] = ""; if (AMD_LOG_LEVEL >= 4) { snprintf(pidtid, sizeof(pidtid), "[pid:%u tid: 0x%05x]", entry.pid, entry.tid); } if (entry.hasDuration) { fprintf(outFile, ":%d:%-25s:%-4d: %010" PRIu64 " us: %s %s: duration: %" PRIu64 " us\n", entry.level, entry.file, entry.line, entry.timestamp, pidtid, entry.message.c_str(), entry.duration); } else { fprintf(outFile, ":%d:%-25s:%-4d: %010" PRIu64 " us: %s %s\n", entry.level, entry.file, entry.line, entry.timestamp, pidtid, entry.message.c_str()); } } }; // ================================================================================================ static AsyncLogger& getAsyncLogger(); static void crashFlushCallback() { getAsyncLogger().FlushOnCrash(); } static AsyncLogger& getAsyncLogger() { static AsyncLogger instance; return instance; } // ================================================================================================ static void TruncateLogFileFlushPath() { if (outFile == stderr) { return; } const size_t maxLogSize = AMD_LOG_LEVEL_SIZE * Mi; fflush(outFile); if (fseek(outFile, 0, SEEK_END) != 0) { return; } long size = ftell(outFile); if (size < 0 || static_cast(size) <= maxLogSize) { return; } #ifdef _WIN32 int fd = _fileno(outFile); if (fd < 0 || _chsize_s(fd, 0) != 0) { #else int fd = fileno(outFile); if (fd < 0 || ftruncate(fd, 0) != 0) { #endif return; } fseek(outFile, 0, SEEK_SET); } // ================================================================================================ void truncate_log_file() { if (outFile != stderr) { fseek(outFile, 0, SEEK_END); long size = ftell(outFile); const size_t maxLogSize = AMD_LOG_LEVEL_SIZE * Mi; if (size > maxLogSize) { if (nullptr == freopen(NULL, "w", outFile)) { outFile = stderr; } } } } // ================================================================================================ void report_warning(const char* message) { truncate_log_file(); fprintf(outFile, "Warning: %s\n", message); } // ================================================================================================ void log_entry(LogLevel level, const char* file, int line, const char* message) { if (level == LOG_NONE) { return; } truncate_log_file(); fprintf(outFile, ":%d:%s:%d: %s\n", level, file, line, message); fflush(outFile); } // ================================================================================================ void log_timestamped(LogLevel level, const char* file, int line, const char* message) { static bool gotstart = false; // not thread-safe, but not scary if fails static uint64_t start; if (!gotstart) { start = Os::timeNanos(); gotstart = true; } uint64_t time = Os::timeNanos() - start; if (level == LOG_NONE) { return; } truncate_log_file(); fprintf(outFile, ":% 2d:%15s:% 5d: (%010lld) us %s\n", level, file, line, time / 1000ULL, message); fflush(outFile); } // ================================================================================================ void log_printf(LogLevel level, const char* file, int line, const char* format, ...) { va_list ap; char message[4096]; va_start(ap, format); vsnprintf(message, sizeof(message), format, ap); va_end(ap); uint64_t timeUs = Os::timeNanos() / 1000ULL; // Try async logging first if (getAsyncLogger().isEnabled()) { getAsyncLogger().log(level, file, line, message, timeUs); return; } // Fall back to sync logging char pidtid[64] = ""; if (AMD_LOG_LEVEL >= 4) { size_t tid_hash = std::hash{}(std::this_thread::get_id()); snprintf(pidtid, sizeof(pidtid), "[pid:%u tid: 0x%05zx]", Os::getProcessId(), tid_hash & 0xFFFFF); } truncate_log_file(); fprintf(outFile, ":%d:%-25s:%-4d: %010" PRIu64 " us: %s %s\n", level, file, line, timeUs, pidtid, message); fflush(outFile); } // ================================================================================================ void log_printf(LogLevel level, const char* file, int line, uint64_t* start, const char* format, ...) { va_list ap; char message[4096]; va_start(ap, format); vsnprintf(message, sizeof(message), format, ap); va_end(ap); uint64_t timeUs = Os::timeNanos() / 1000ULL; bool isStartLog = (start == 0 || *start == 0); uint64_t duration = isStartLog ? 0 : (timeUs - *start); // Try async logging first if (getAsyncLogger().isEnabled()) { getAsyncLogger().log(level, file, line, message, timeUs, duration, !isStartLog); if (start != 0 && *start == 0) { *start = timeUs; } return; } // Fall back to sync logging char pidtid[64] = ""; if (AMD_LOG_LEVEL >= 4) { size_t tid_hash = std::hash{}(std::this_thread::get_id()); snprintf(pidtid, sizeof(pidtid), "[pid:%u tid: 0x%05zx]", Os::getProcessId(), tid_hash & 0xFFFFF); } truncate_log_file(); if (isStartLog) { fprintf(outFile, ":%d:%-25s:%-4d: %010" PRIu64 " us: %s %s\n", level, file, line, timeUs, pidtid, message); } else { fprintf(outFile, ":%d:%-25s:%-4d: %010" PRIu64 " us: %s %s: duration: %" PRIu64 " us\n", level, file, line, timeUs, pidtid, message, duration); } fflush(outFile); if (start != 0 && *start == 0) { *start = timeUs; } } // ================================================================================================ void EnableAsyncLogging(bool enable) { getAsyncLogger().enable(enable); } // ================================================================================================ bool IsAsyncLoggingEnabled() { return getAsyncLogger().isEnabled(); } // ================================================================================================ void FlushAsyncLogs() { getAsyncLogger().flush(); } // ================================================================================================ void FlushAsyncLogsInCurrentThread() { getAsyncLogger().flushInCurrentThread(); } } // namespace amd