From b0d9ffc6a1daedf3259f06fc1c5e12c830fd1a1e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 20 Jun 2025 22:53:12 +0200 Subject: [PATCH] Reduce logger CPU usage by disabling loop when buffer is empty --- esphome/components/logger/logger.cpp | 15 ++++++++++++++- esphome/components/logger/logger.h | 22 +++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 28a66b23b7..783f58af18 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -48,6 +48,11 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char * // For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered message_sent = this->log_buffer_->send_message_thread_safe(static_cast(level), tag, static_cast(line), current_task, format, args); + if (message_sent) { + // Enable logger loop to process the buffered message + // This is safe to call from any context including ISRs + this->enable_loop_soon_any_context(); + } #endif // USE_ESPHOME_TASK_LOG_BUFFER // Emergency console logging for non-main tasks when ring buffer is full or disabled @@ -139,10 +144,14 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate #ifdef USE_ESPHOME_TASK_LOG_BUFFER void Logger::init_log_buffer(size_t total_buffer_size) { this->log_buffer_ = esphome::make_unique(total_buffer_size); + + // Start with loop disabled when using task buffer (unless using USB CDC) + // The loop will be enabled automatically when messages arrive + this->disable_loop_when_buffer_empty_(); } #endif -#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESP32) +#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESPHOME_TASK_LOG_BUFFER) void Logger::loop() { #if defined(USE_LOGGER_USB_CDC) && defined(USE_ARDUINO) if (this->uart_ == UART_SELECTION_USB_CDC) { @@ -189,6 +198,10 @@ void Logger::loop() { this->write_msg_(this->tx_buffer_); } } + } else { + // No messages to process, disable loop if appropriate + // This reduces overhead when there's no async logging activity + this->disable_loop_when_buffer_empty_(); } #endif } diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 9f09208b66..ac46139ecc 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -107,7 +107,7 @@ class Logger : public Component { #ifdef USE_ESPHOME_TASK_LOG_BUFFER void init_log_buffer(size_t total_buffer_size); #endif -#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESP32) +#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESPHOME_TASK_LOG_BUFFER) void loop() override; #endif /// Manually set the baud rate for serial, set to 0 to disable. @@ -347,6 +347,26 @@ class Logger : public Component { static const int RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR); this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size); } + +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + // Disable loop when task buffer is empty (with USB CDC check) + inline void disable_loop_when_buffer_empty_() { + // Thread safety note: This is safe even if another task calls enable_loop_soon_any_context() + // concurrently. If that happens between our check and disable_loop(), the enable request + // will be processed on the next main loop iteration since: + // - disable_loop() takes effect immediately + // - enable_loop_soon_any_context() sets a pending flag that's checked at loop start +#if defined(USE_LOGGER_USB_CDC) && defined(USE_ARDUINO) + // Only disable if not using USB CDC (which needs loop for connection detection) + if (this->uart_ != UART_SELECTION_USB_CDC) { + this->disable_loop(); + } +#else + // No USB CDC support, always safe to disable + this->disable_loop(); +#endif + } +#endif }; extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)