From 59889a6286eed421c33880a1f931c0bc72733a95 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 23 Jun 2025 04:06:02 +0200 Subject: [PATCH] Reduce Logger memory usage by optimizing variable sizes (#9161) --- esphome/components/logger/__init__.py | 4 +- esphome/components/logger/logger.cpp | 20 ++--- esphome/components/logger/logger.h | 111 ++++++++++++++------------ esphome/core/log.cpp | 4 +- 4 files changed, 76 insertions(+), 63 deletions(-) diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 26516e1506..af62d8a73f 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -184,7 +184,9 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(Logger), cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int, - cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes, + cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.All( + cv.validate_bytes, cv.int_range(min=160, max=65535) + ), cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean, cv.SplitDefault( CONF_TASK_LOG_BUFFER_SIZE, diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 28a66b23b7..b42496af66 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -24,7 +24,7 @@ static const char *const TAG = "logger"; // - Messages are serialized through main loop for proper console output // - Fallback to emergency console logging only if ring buffer is full // - WITHOUT task log buffer: Only emergency console output, no callbacks -void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT +void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT if (level > this->level_for(tag)) return; @@ -46,8 +46,8 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char * bool message_sent = false; #ifdef USE_ESPHOME_TASK_LOG_BUFFER // 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); + message_sent = + this->log_buffer_->send_message_thread_safe(level, tag, static_cast(line), current_task, format, args); #endif // USE_ESPHOME_TASK_LOG_BUFFER // Emergency console logging for non-main tasks when ring buffer is full or disabled @@ -58,7 +58,7 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char * // Maximum size for console log messages (includes null terminator) static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144; char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety - int buffer_at = 0; // Initialize buffer position + uint16_t buffer_at = 0; // Initialize buffer position this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE); this->write_msg_(console_buffer); @@ -69,7 +69,7 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char * } #else // Implementation for all other platforms -void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT +void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT if (level > this->level_for(tag) || global_recursion_guard_) return; @@ -85,7 +85,7 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char * #ifdef USE_STORE_LOG_STR_IN_FLASH // Implementation for ESP8266 with flash string support. // Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266. -void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, +void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format, va_list args) { // NOLINT if (level > this->level_for(tag) || global_recursion_guard_) return; @@ -122,7 +122,7 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr } #endif // USE_STORE_LOG_STR_IN_FLASH -inline int Logger::level_for(const char *tag) { +inline uint8_t Logger::level_for(const char *tag) { auto it = this->log_levels_.find(tag); if (it != this->log_levels_.end()) return it->second; @@ -195,13 +195,13 @@ void Logger::loop() { #endif void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } -void Logger::set_log_level(const std::string &tag, int log_level) { this->log_levels_[tag] = log_level; } +void Logger::set_log_level(const std::string &tag, uint8_t log_level) { this->log_levels_[tag] = log_level; } #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) UARTSelection Logger::get_uart() const { return this->uart_; } #endif -void Logger::add_on_log_callback(std::function &&callback) { +void Logger::add_on_log_callback(std::function &&callback) { this->log_callback_.add(std::move(callback)); } float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; } @@ -230,7 +230,7 @@ void Logger::dump_config() { } } -void Logger::set_log_level(int level) { +void Logger::set_log_level(uint8_t level) { if (level > ESPHOME_LOG_LEVEL) { level = ESPHOME_LOG_LEVEL; ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]); diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 9f09208b66..ea82764393 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -61,7 +61,7 @@ static const char *const LOG_LEVEL_LETTERS[] = { * * Advanced configuration (pin selection, etc) is not supported. */ -enum UARTSelection { +enum UARTSelection : uint8_t { #ifdef USE_LIBRETINY UART_SELECTION_DEFAULT = 0, UART_SELECTION_UART0, @@ -129,10 +129,10 @@ class Logger : public Component { #endif /// Set the default log level for this logger. - void set_log_level(int level); + void set_log_level(uint8_t level); /// Set the log level of the specified tag. - void set_log_level(const std::string &tag, int log_level); - int get_log_level() { return this->current_level_; } + void set_log_level(const std::string &tag, uint8_t log_level); + uint8_t get_log_level() { return this->current_level_; } // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) @@ -140,19 +140,20 @@ class Logger : public Component { void pre_setup(); void dump_config() override; - inline int level_for(const char *tag); + inline uint8_t level_for(const char *tag); /// Register a callback that will be called for every log message sent - void add_on_log_callback(std::function &&callback); + void add_on_log_callback(std::function &&callback); // add a listener for log level changes - void add_listener(std::function &&callback) { this->level_callback_.add(std::move(callback)); } + void add_listener(std::function &&callback) { this->level_callback_.add(std::move(callback)); } float get_setup_priority() const override; - void log_vprintf_(int level, const char *tag, int line, const char *format, va_list args); // NOLINT + void log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args); // NOLINT #ifdef USE_STORE_LOG_STR_IN_FLASH - void log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, va_list args); // NOLINT + void log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format, + va_list args); // NOLINT #endif protected: @@ -160,8 +161,9 @@ class Logger : public Component { // Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator // It's the caller's responsibility to initialize buffer_at (typically to 0) - inline void HOT format_log_to_buffer_with_terminator_(int level, const char *tag, int line, const char *format, - va_list args, char *buffer, int *buffer_at, int buffer_size) { + inline void HOT format_log_to_buffer_with_terminator_(uint8_t level, const char *tag, int line, const char *format, + va_list args, char *buffer, uint16_t *buffer_at, + uint16_t buffer_size) { #if defined(USE_ESP32) || defined(USE_LIBRETINY) this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(), buffer, buffer_at, buffer_size); #else @@ -180,7 +182,7 @@ class Logger : public Component { } // Helper to format and send a log message to both console and callbacks - inline void HOT log_message_to_buffer_and_send_(int level, const char *tag, int line, const char *format, + inline void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // Format to tx_buffer and prepare for output this->tx_buffer_at_ = 0; // Initialize buffer position @@ -194,11 +196,12 @@ class Logger : public Component { } // Write the body of the log message to the buffer - inline void write_body_to_buffer_(const char *value, size_t length, char *buffer, int *buffer_at, int buffer_size) { + inline void write_body_to_buffer_(const char *value, size_t length, char *buffer, uint16_t *buffer_at, + uint16_t buffer_size) { // Calculate available space - const int available = buffer_size - *buffer_at; - if (available <= 0) + if (*buffer_at >= buffer_size) return; + const uint16_t available = buffer_size - *buffer_at; // Determine copy length (minimum of remaining capacity and string length) const size_t copy_len = (length < static_cast(available)) ? length : available; @@ -211,7 +214,7 @@ class Logger : public Component { } // Format string to explicit buffer with varargs - inline void printf_to_buffer_(char *buffer, int *buffer_at, int buffer_size, const char *format, ...) { + inline void printf_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, ...) { va_list arg; va_start(arg, format); this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, arg); @@ -222,41 +225,50 @@ class Logger : public Component { const char *get_uart_selection_(); #endif + // Group 4-byte aligned members first uint32_t baud_rate_; char *tx_buffer_{nullptr}; - int tx_buffer_at_{0}; - int tx_buffer_size_{0}; +#ifdef USE_ARDUINO + Stream *hw_serial_{nullptr}; +#endif +#if defined(USE_ESP32) || defined(USE_LIBRETINY) + void *main_task_ = nullptr; // Only used for thread name identification +#endif +#ifdef USE_ESP32 + // Task-specific recursion guards: + // - Main task uses a dedicated member variable for efficiency + // - Other tasks use pthread TLS with a dynamically created key via pthread_key_create + pthread_key_t log_recursion_key_; // 4 bytes +#endif +#ifdef USE_ESP_IDF + uart_port_t uart_num_; // 4 bytes (enum defaults to int size) +#endif + + // Large objects (internally aligned) + std::map log_levels_{}; + CallbackManager log_callback_{}; + CallbackManager level_callback_{}; +#ifdef USE_ESPHOME_TASK_LOG_BUFFER + std::unique_ptr log_buffer_; // Will be initialized with init_log_buffer +#endif + + // Group smaller types together at the end + uint16_t tx_buffer_at_{0}; + uint16_t tx_buffer_size_{0}; + uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE}; #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) UARTSelection uart_{UART_SELECTION_UART0}; #endif #ifdef USE_LIBRETINY UARTSelection uart_{UART_SELECTION_DEFAULT}; #endif -#ifdef USE_ARDUINO - Stream *hw_serial_{nullptr}; -#endif -#ifdef USE_ESP_IDF - uart_port_t uart_num_; -#endif - std::map log_levels_{}; - CallbackManager log_callback_{}; - int current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE}; -#ifdef USE_ESPHOME_TASK_LOG_BUFFER - std::unique_ptr log_buffer_; // Will be initialized with init_log_buffer -#endif #ifdef USE_ESP32 - // Task-specific recursion guards: - // - Main task uses a dedicated member variable for efficiency - // - Other tasks use pthread TLS with a dynamically created key via pthread_key_create bool main_task_recursion_guard_{false}; - pthread_key_t log_recursion_key_; #else bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms #endif - CallbackManager level_callback_{}; #if defined(USE_ESP32) || defined(USE_LIBRETINY) - void *main_task_ = nullptr; // Only used for thread name identification const char *HOT get_thread_name_() { TaskHandle_t current_task = xTaskGetCurrentTaskHandle(); if (current_task == main_task_) { @@ -297,11 +309,10 @@ class Logger : public Component { } #endif - inline void HOT write_header_to_buffer_(int level, const char *tag, int line, const char *thread_name, char *buffer, - int *buffer_at, int buffer_size) { + inline void HOT write_header_to_buffer_(uint8_t level, const char *tag, int line, const char *thread_name, + char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { // Format header - if (level < 0) - level = 0; + // uint8_t level is already bounded 0-255, just ensure it's <= 7 if (level > 7) level = 7; @@ -320,12 +331,12 @@ class Logger : public Component { this->printf_to_buffer_(buffer, buffer_at, buffer_size, "%s[%s][%s:%03u]: ", color, letter, tag, line); } - inline void HOT format_body_to_buffer_(char *buffer, int *buffer_at, int buffer_size, const char *format, + inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, va_list args) { // Get remaining capacity in the buffer - const int remaining = buffer_size - *buffer_at; - if (remaining <= 0) + if (*buffer_at >= buffer_size) return; + const uint16_t remaining = buffer_size - *buffer_at; const int ret = vsnprintf(buffer + *buffer_at, remaining, format, args); @@ -334,7 +345,7 @@ class Logger : public Component { } // Update buffer_at with the formatted length (handle truncation) - int formatted_len = (ret >= remaining) ? remaining : ret; + uint16_t formatted_len = (ret >= remaining) ? remaining : ret; *buffer_at += formatted_len; // Remove all trailing newlines right after formatting @@ -343,18 +354,18 @@ class Logger : public Component { } } - inline void HOT write_footer_to_buffer_(char *buffer, int *buffer_at, int buffer_size) { - static const int RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR); + inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { + static const uint16_t 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); } }; extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -class LoggerMessageTrigger : public Trigger { +class LoggerMessageTrigger : public Trigger { public: - explicit LoggerMessageTrigger(Logger *parent, int level) { + explicit LoggerMessageTrigger(Logger *parent, uint8_t level) { this->level_ = level; - parent->add_on_log_callback([this](int level, const char *tag, const char *message) { + parent->add_on_log_callback([this](uint8_t level, const char *tag, const char *message) { if (level <= this->level_) { this->trigger(level, tag, message); } @@ -362,7 +373,7 @@ class LoggerMessageTrigger : public Trigger { } protected: - int level_; + uint8_t level_; }; } // namespace logger diff --git a/esphome/core/log.cpp b/esphome/core/log.cpp index 424154d253..909319dd28 100644 --- a/esphome/core/log.cpp +++ b/esphome/core/log.cpp @@ -29,7 +29,7 @@ void HOT esp_log_vprintf_(int level, const char *tag, int line, const char *form if (log == nullptr) return; - log->log_vprintf_(level, tag, line, format, args); + log->log_vprintf_(static_cast(level), tag, line, format, args); #endif } @@ -41,7 +41,7 @@ void HOT esp_log_vprintf_(int level, const char *tag, int line, const __FlashStr if (log == nullptr) return; - log->log_vprintf_(level, tag, line, format, args); + log->log_vprintf_(static_cast(level), tag, line, format, args); #endif } #endif