mirror of
https://github.com/esphome/esphome.git
synced 2026-06-23 17:38:54 +02:00
[logger] Move task log buffer storage to BSS (#15153)
This commit is contained in:
@@ -331,9 +331,8 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
CORE.data.setdefault(CONF_LOGGER, {})[CONF_LEVEL] = level
|
CORE.data.setdefault(CONF_LOGGER, {})[CONF_LEVEL] = level
|
||||||
tx_buffer_size = config[CONF_TX_BUFFER_SIZE]
|
tx_buffer_size = config[CONF_TX_BUFFER_SIZE]
|
||||||
cg.add_define("ESPHOME_LOGGER_TX_BUFFER_SIZE", tx_buffer_size)
|
cg.add_define("ESPHOME_LOGGER_TX_BUFFER_SIZE", tx_buffer_size)
|
||||||
# Determine task log buffer size and define USE_ESPHOME_TASK_LOG_BUFFER early
|
# Determine task log buffer size. The buffer is a direct member of Logger
|
||||||
# so the constructor can allocate the buffer immediately, preventing a race
|
# (no separate heap allocation).
|
||||||
# where another task logs before the buffer is initialized.
|
|
||||||
task_log_buffer_size = 0
|
task_log_buffer_size = 0
|
||||||
if CORE.is_esp32 or CORE.is_libretiny or CORE.is_nrf52:
|
if CORE.is_esp32 or CORE.is_libretiny or CORE.is_nrf52:
|
||||||
task_log_buffer_size = config[CONF_TASK_LOG_BUFFER_SIZE]
|
task_log_buffer_size = config[CONF_TASK_LOG_BUFFER_SIZE]
|
||||||
@@ -341,16 +340,11 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
task_log_buffer_size = 64 # Fixed 64 slots for host
|
task_log_buffer_size = 64 # Fixed 64 slots for host
|
||||||
if task_log_buffer_size > 0:
|
if task_log_buffer_size > 0:
|
||||||
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
|
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
|
||||||
log = cg.new_Pvariable(
|
cg.add_define("ESPHOME_TASK_LOG_BUFFER_SIZE", task_log_buffer_size)
|
||||||
config[CONF_ID],
|
log = cg.new_Pvariable(
|
||||||
baud_rate,
|
config[CONF_ID],
|
||||||
task_log_buffer_size,
|
baud_rate,
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
log = cg.new_Pvariable(
|
|
||||||
config[CONF_ID],
|
|
||||||
baud_rate,
|
|
||||||
)
|
|
||||||
if CORE.is_esp32 or CORE.is_host:
|
if CORE.is_esp32 or CORE.is_host:
|
||||||
cg.add(log.create_pthread_key())
|
cg.add(log.create_pthread_key())
|
||||||
# set_uart_selection() must be called before pre_setup() because
|
# set_uart_selection() must be called before pre_setup() because
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li
|
|||||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
// For non-main threads/tasks, queue the message for callbacks
|
// For non-main threads/tasks, queue the message for callbacks
|
||||||
message_sent =
|
message_sent =
|
||||||
this->log_buffer_->send_message_thread_safe(level, tag, static_cast<uint16_t>(line), thread_name, format, args);
|
this->log_buffer_.send_message_thread_safe(level, tag, static_cast<uint16_t>(line), thread_name, format, args);
|
||||||
if (message_sent) {
|
if (message_sent) {
|
||||||
// Enable logger loop to process the buffered message
|
// Enable logger loop to process the buffered message
|
||||||
// This is safe to call from any context including ISRs
|
// This is safe to call from any context including ISRs
|
||||||
@@ -152,23 +152,13 @@ inline uint8_t Logger::level_for(const char *tag) {
|
|||||||
return this->current_level_;
|
return this->current_level_;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
|
||||||
Logger::Logger(uint32_t baud_rate, size_t task_log_buffer_size) : baud_rate_(baud_rate) {
|
|
||||||
#else
|
|
||||||
Logger::Logger(uint32_t baud_rate) : baud_rate_(baud_rate) {
|
Logger::Logger(uint32_t baud_rate) : baud_rate_(baud_rate) {
|
||||||
#endif
|
|
||||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
this->main_task_ = xTaskGetCurrentTaskHandle();
|
this->main_task_ = xTaskGetCurrentTaskHandle();
|
||||||
#elif defined(USE_ZEPHYR)
|
#elif defined(USE_ZEPHYR)
|
||||||
this->main_task_ = k_current_get();
|
this->main_task_ = k_current_get();
|
||||||
#elif defined(USE_HOST)
|
#elif defined(USE_HOST)
|
||||||
this->main_thread_ = pthread_self();
|
this->main_thread_ = pthread_self();
|
||||||
#endif
|
|
||||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - allocated once, never freed
|
|
||||||
this->log_buffer_ = new logger::TaskLogBuffer(task_log_buffer_size);
|
|
||||||
// Note: we don't disable loop here because the component isn't registered with App yet.
|
|
||||||
// The loop self-disables on its first iteration when it finds no messages to process.
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,16 +174,16 @@ void Logger::loop() {
|
|||||||
void Logger::process_messages_() {
|
void Logger::process_messages_() {
|
||||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
// Process any buffered messages when available
|
// Process any buffered messages when available
|
||||||
if (this->log_buffer_->has_messages()) {
|
if (this->log_buffer_.has_messages()) {
|
||||||
logger::TaskLogBuffer::LogMessage *message;
|
logger::TaskLogBuffer::LogMessage *message;
|
||||||
uint16_t text_length;
|
uint16_t text_length;
|
||||||
while (this->log_buffer_->borrow_message_main_loop(message, text_length)) {
|
while (this->log_buffer_.borrow_message_main_loop(message, text_length)) {
|
||||||
const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr;
|
const char *thread_name = message->thread_name[0] != '\0' ? message->thread_name : nullptr;
|
||||||
LogBuffer buf{this->tx_buffer_, ESPHOME_LOGGER_TX_BUFFER_SIZE};
|
LogBuffer buf{this->tx_buffer_, ESPHOME_LOGGER_TX_BUFFER_SIZE};
|
||||||
this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name,
|
this->format_buffered_message_and_notify_(message->level, message->tag, message->line, thread_name,
|
||||||
message->text_data(), text_length, buf);
|
message->text_data(), text_length, buf);
|
||||||
// Release the message to allow other tasks to use it as soon as possible
|
// Release the message to allow other tasks to use it as soon as possible
|
||||||
this->log_buffer_->release_message_main_loop();
|
this->log_buffer_.release_message_main_loop();
|
||||||
this->write_log_buffer_to_console_(buf);
|
this->write_log_buffer_to_console_(buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,13 +229,11 @@ void Logger::dump_config() {
|
|||||||
this->baud_rate_, LOG_STR_ARG(get_uart_selection_()));
|
this->baud_rate_, LOG_STR_ARG(get_uart_selection_()));
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
if (this->log_buffer_) {
|
|
||||||
#ifdef USE_HOST
|
#ifdef USE_HOST
|
||||||
ESP_LOGCONFIG(TAG, " Task Log Buffer Slots: %u", static_cast<unsigned int>(this->log_buffer_->size()));
|
ESP_LOGCONFIG(TAG, " Task Log Buffer Slots: %u", static_cast<unsigned int>(this->log_buffer_.size()));
|
||||||
#else
|
#else
|
||||||
ESP_LOGCONFIG(TAG, " Task Log Buffer Size: %u bytes", static_cast<unsigned int>(this->log_buffer_->size()));
|
ESP_LOGCONFIG(TAG, " Task Log Buffer Size: %u bytes", static_cast<unsigned int>(this->log_buffer_.size()));
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
|
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
|
||||||
|
|||||||
@@ -143,11 +143,7 @@ enum UARTSelection : uint8_t {
|
|||||||
*/
|
*/
|
||||||
class Logger final : public Component {
|
class Logger final : public Component {
|
||||||
public:
|
public:
|
||||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
|
||||||
explicit Logger(uint32_t baud_rate, size_t task_log_buffer_size);
|
|
||||||
#else
|
|
||||||
explicit Logger(uint32_t baud_rate);
|
explicit Logger(uint32_t baud_rate);
|
||||||
#endif
|
|
||||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||||
void loop() override;
|
void loop() override;
|
||||||
#endif
|
#endif
|
||||||
@@ -353,10 +349,6 @@ class Logger final : public Component {
|
|||||||
#ifdef USE_LOGGER_LEVEL_LISTENERS
|
#ifdef USE_LOGGER_LEVEL_LISTENERS
|
||||||
std::vector<LoggerLevelListener *> level_listeners_; // Log level change listeners
|
std::vector<LoggerLevelListener *> level_listeners_; // Log level change listeners
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
|
||||||
logger::TaskLogBuffer *log_buffer_{nullptr}; // Allocated once, never freed
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Group smaller types together at the end
|
// Group smaller types together at the end
|
||||||
uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
|
uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
|
||||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_ZEPHYR)
|
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_ZEPHYR)
|
||||||
@@ -374,8 +366,11 @@ class Logger final : public Component {
|
|||||||
bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
|
bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Large buffer placed last to keep frequently-accessed member offsets small
|
// Large buffers placed last to keep frequently-accessed member offsets small
|
||||||
char tx_buffer_[ESPHOME_LOGGER_TX_BUFFER_SIZE + 1]; // +1 for null terminator
|
char tx_buffer_[ESPHOME_LOGGER_TX_BUFFER_SIZE + 1]; // +1 for null terminator
|
||||||
|
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
|
logger::TaskLogBuffer log_buffer_; // Embedded in Logger (no separate heap allocation)
|
||||||
|
#endif
|
||||||
|
|
||||||
// --- get_thread_name_ overloads (per-platform) ---
|
// --- get_thread_name_ overloads (per-platform) ---
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,23 @@
|
|||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
#include "task_log_buffer_esp32.h"
|
#include "task_log_buffer_esp32.h"
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
|
|
||||||
namespace esphome::logger {
|
namespace esphome::logger {
|
||||||
|
|
||||||
TaskLogBuffer::TaskLogBuffer(size_t total_buffer_size) {
|
TaskLogBuffer::TaskLogBuffer() {
|
||||||
// Store the buffer size
|
|
||||||
this->size_ = total_buffer_size;
|
|
||||||
// Allocate memory for the ring buffer using ESPHome's RAM allocator
|
|
||||||
RAMAllocator<uint8_t> allocator;
|
|
||||||
this->storage_ = allocator.allocate(this->size_);
|
|
||||||
// Create a static ring buffer with RINGBUF_TYPE_NOSPLIT for message integrity
|
// Create a static ring buffer with RINGBUF_TYPE_NOSPLIT for message integrity
|
||||||
this->ring_buffer_ = xRingbufferCreateStatic(this->size_, RINGBUF_TYPE_NOSPLIT, this->storage_, &this->structure_);
|
// Storage is a member array (embedded in Logger), no heap allocation needed
|
||||||
|
this->ring_buffer_ =
|
||||||
|
xRingbufferCreateStatic(sizeof(this->storage_), RINGBUF_TYPE_NOSPLIT, this->storage_, &this->structure_);
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskLogBuffer::~TaskLogBuffer() {
|
TaskLogBuffer::~TaskLogBuffer() {
|
||||||
if (this->ring_buffer_ != nullptr) {
|
if (this->ring_buffer_ != nullptr) {
|
||||||
// Delete the ring buffer
|
|
||||||
vRingbufferDelete(this->ring_buffer_);
|
vRingbufferDelete(this->ring_buffer_);
|
||||||
this->ring_buffer_ = nullptr;
|
this->ring_buffer_ = nullptr;
|
||||||
|
|
||||||
// Free the allocated memory
|
|
||||||
RAMAllocator<uint8_t> allocator;
|
|
||||||
allocator.deallocate(this->storage_, this->size_);
|
|
||||||
this->storage_ = nullptr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/ringbuf.h>
|
#include <freertos/ringbuf.h>
|
||||||
@@ -47,8 +46,7 @@ class TaskLogBuffer {
|
|||||||
inline const char *text_data() const { return reinterpret_cast<const char *>(this) + sizeof(LogMessage); }
|
inline const char *text_data() const { return reinterpret_cast<const char *>(this) + sizeof(LogMessage); }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Constructor that takes a total buffer size
|
TaskLogBuffer();
|
||||||
explicit TaskLogBuffer(size_t total_buffer_size);
|
|
||||||
~TaskLogBuffer();
|
~TaskLogBuffer();
|
||||||
|
|
||||||
// NOT thread-safe - borrow a message from the ring buffer, only call from main loop
|
// NOT thread-safe - borrow a message from the ring buffer, only call from main loop
|
||||||
@@ -67,13 +65,12 @@ class TaskLogBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the total buffer size in bytes
|
// Get the total buffer size in bytes
|
||||||
inline size_t size() const { return size_; }
|
static constexpr size_t size() { return ESPHOME_TASK_LOG_BUFFER_SIZE; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RingbufHandle_t ring_buffer_{nullptr}; // FreeRTOS ring buffer handle
|
RingbufHandle_t ring_buffer_{nullptr}; // FreeRTOS ring buffer handle
|
||||||
StaticRingbuffer_t structure_; // Static structure for the ring buffer
|
StaticRingbuffer_t structure_; // Static structure for the ring buffer
|
||||||
uint8_t *storage_{nullptr}; // Pointer to allocated memory
|
uint8_t storage_[ESPHOME_TASK_LOG_BUFFER_SIZE]; // Embedded in Logger (no separate heap allocation)
|
||||||
size_t size_{0}; // Size of allocated memory
|
|
||||||
|
|
||||||
// Atomic counter for message tracking (only differences matter)
|
// Atomic counter for message tracking (only differences matter)
|
||||||
std::atomic<uint16_t> message_counter_{0}; // Incremented when messages are committed
|
std::atomic<uint16_t> message_counter_{0}; // Incremented when messages are committed
|
||||||
|
|||||||
@@ -10,22 +10,13 @@
|
|||||||
|
|
||||||
namespace esphome::logger {
|
namespace esphome::logger {
|
||||||
|
|
||||||
TaskLogBuffer::TaskLogBuffer(size_t slot_count) : slot_count_(slot_count) {
|
|
||||||
// Allocate message slots
|
|
||||||
this->slots_ = std::make_unique<LogMessage[]>(slot_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskLogBuffer::~TaskLogBuffer() {
|
|
||||||
// unique_ptr handles cleanup automatically
|
|
||||||
}
|
|
||||||
|
|
||||||
int TaskLogBuffer::acquire_write_slot_() {
|
int TaskLogBuffer::acquire_write_slot_() {
|
||||||
// Try to reserve a slot using compare-and-swap
|
// Try to reserve a slot using compare-and-swap
|
||||||
size_t current_reserve = this->reserve_index_.load(std::memory_order_relaxed);
|
size_t current_reserve = this->reserve_index_.load(std::memory_order_relaxed);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// Calculate next index (with wrap-around)
|
// Calculate next index (with wrap-around)
|
||||||
size_t next_reserve = (current_reserve + 1) % this->slot_count_;
|
size_t next_reserve = (current_reserve + 1) % ESPHOME_TASK_LOG_BUFFER_SIZE;
|
||||||
|
|
||||||
// Check if buffer would be full
|
// Check if buffer would be full
|
||||||
// Buffer is full when next write position equals read position
|
// Buffer is full when next write position equals read position
|
||||||
@@ -50,7 +41,7 @@ void TaskLogBuffer::commit_write_slot_(int slot_index) {
|
|||||||
// Try to advance the write_index if we're the next expected commit
|
// Try to advance the write_index if we're the next expected commit
|
||||||
// This ensures messages are read in order
|
// This ensures messages are read in order
|
||||||
size_t expected = slot_index;
|
size_t expected = slot_index;
|
||||||
size_t next = (slot_index + 1) % this->slot_count_;
|
size_t next = (slot_index + 1) % ESPHOME_TASK_LOG_BUFFER_SIZE;
|
||||||
|
|
||||||
// We only advance write_index if this slot is the next one expected
|
// We only advance write_index if this slot is the next one expected
|
||||||
// This handles out-of-order commits correctly
|
// This handles out-of-order commits correctly
|
||||||
@@ -63,7 +54,7 @@ void TaskLogBuffer::commit_write_slot_(int slot_index) {
|
|||||||
|
|
||||||
// Successfully advanced, check if next slot is also ready
|
// Successfully advanced, check if next slot is also ready
|
||||||
expected = next;
|
expected = next;
|
||||||
next = (next + 1) % this->slot_count_;
|
next = (next + 1) % ESPHOME_TASK_LOG_BUFFER_SIZE;
|
||||||
if (!this->slots_[expected].ready.load(std::memory_order_acquire)) {
|
if (!this->slots_[expected].ready.load(std::memory_order_acquire)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -142,7 +133,7 @@ void TaskLogBuffer::release_message_main_loop() {
|
|||||||
this->slots_[current_read].ready.store(false, std::memory_order_release);
|
this->slots_[current_read].ready.store(false, std::memory_order_release);
|
||||||
|
|
||||||
// Advance read index
|
// Advance read index
|
||||||
size_t next_read = (current_read + 1) % this->slot_count_;
|
size_t next_read = (current_read + 1) % ESPHOME_TASK_LOG_BUFFER_SIZE;
|
||||||
this->read_index_.store(next_read, std::memory_order_release);
|
this->read_index_.store(next_read, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
namespace esphome::logger {
|
namespace esphome::logger {
|
||||||
@@ -50,9 +49,6 @@ namespace esphome::logger {
|
|||||||
*/
|
*/
|
||||||
class TaskLogBuffer {
|
class TaskLogBuffer {
|
||||||
public:
|
public:
|
||||||
// Default number of message slots - host has plenty of memory
|
|
||||||
static constexpr size_t DEFAULT_SLOT_COUNT = 64;
|
|
||||||
|
|
||||||
// Structure for a log message (fixed size for lock-free operation)
|
// Structure for a log message (fixed size for lock-free operation)
|
||||||
struct LogMessage {
|
struct LogMessage {
|
||||||
// Size constants
|
// Size constants
|
||||||
@@ -74,9 +70,7 @@ class TaskLogBuffer {
|
|||||||
inline char *text_data() { return this->text; }
|
inline char *text_data() { return this->text; }
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Constructor that takes the number of message slots
|
TaskLogBuffer() = default;
|
||||||
explicit TaskLogBuffer(size_t slot_count);
|
|
||||||
~TaskLogBuffer();
|
|
||||||
|
|
||||||
// NOT thread-safe - get next message from buffer, only call from main loop
|
// NOT thread-safe - get next message from buffer, only call from main loop
|
||||||
// Returns true if a message was retrieved, false if buffer is empty
|
// Returns true if a message was retrieved, false if buffer is empty
|
||||||
@@ -96,7 +90,7 @@ class TaskLogBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the buffer size (number of slots)
|
// Get the buffer size (number of slots)
|
||||||
inline size_t size() const { return slot_count_; }
|
static constexpr size_t size() { return ESPHOME_TASK_LOG_BUFFER_SIZE; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Acquire a slot for writing (thread-safe)
|
// Acquire a slot for writing (thread-safe)
|
||||||
@@ -106,8 +100,7 @@ class TaskLogBuffer {
|
|||||||
// Commit a slot after writing (thread-safe)
|
// Commit a slot after writing (thread-safe)
|
||||||
void commit_write_slot_(int slot_index);
|
void commit_write_slot_(int slot_index);
|
||||||
|
|
||||||
std::unique_ptr<LogMessage[]> slots_; // Pre-allocated message slots
|
LogMessage slots_[ESPHOME_TASK_LOG_BUFFER_SIZE]; // Embedded in Logger (no separate heap allocation)
|
||||||
size_t slot_count_; // Number of slots
|
|
||||||
|
|
||||||
// Lock-free indices using atomics
|
// Lock-free indices using atomics
|
||||||
// - reserve_index_: Next slot to reserve (producers CAS this to claim slots)
|
// - reserve_index_: Next slot to reserve (producers CAS this to claim slots)
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
#ifdef USE_LIBRETINY
|
#ifdef USE_LIBRETINY
|
||||||
|
|
||||||
#include "task_log_buffer_libretiny.h"
|
#include "task_log_buffer_libretiny.h"
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
|
|
||||||
namespace esphome::logger {
|
namespace esphome::logger {
|
||||||
|
|
||||||
TaskLogBuffer::TaskLogBuffer(size_t total_buffer_size) {
|
TaskLogBuffer::TaskLogBuffer() {
|
||||||
this->size_ = total_buffer_size;
|
|
||||||
// Allocate memory for the circular buffer using ESPHome's RAM allocator
|
|
||||||
RAMAllocator<uint8_t> allocator;
|
|
||||||
this->storage_ = allocator.allocate(this->size_);
|
|
||||||
// Create mutex for thread-safe access
|
// Create mutex for thread-safe access
|
||||||
|
// Storage is a member array (embedded in Logger), no heap allocation needed
|
||||||
this->mutex_ = xSemaphoreCreateMutex();
|
this->mutex_ = xSemaphoreCreateMutex();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,11 +18,6 @@ TaskLogBuffer::~TaskLogBuffer() {
|
|||||||
vSemaphoreDelete(this->mutex_);
|
vSemaphoreDelete(this->mutex_);
|
||||||
this->mutex_ = nullptr;
|
this->mutex_ = nullptr;
|
||||||
}
|
}
|
||||||
if (this->storage_ != nullptr) {
|
|
||||||
RAMAllocator<uint8_t> allocator;
|
|
||||||
allocator.deallocate(this->storage_, this->size_);
|
|
||||||
this->storage_ = nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t TaskLogBuffer::available_contiguous_space() const {
|
size_t TaskLogBuffer::available_contiguous_space() const {
|
||||||
@@ -34,7 +25,7 @@ size_t TaskLogBuffer::available_contiguous_space() const {
|
|||||||
// head is ahead of or equal to tail
|
// head is ahead of or equal to tail
|
||||||
// Available space is from head to end, plus from start to tail
|
// Available space is from head to end, plus from start to tail
|
||||||
// But for contiguous, just from head to end (minus 1 to avoid head==tail ambiguity)
|
// But for contiguous, just from head to end (minus 1 to avoid head==tail ambiguity)
|
||||||
size_t space_to_end = this->size_ - this->head_;
|
size_t space_to_end = ESPHOME_TASK_LOG_BUFFER_SIZE - this->head_;
|
||||||
if (this->tail_ == 0) {
|
if (this->tail_ == 0) {
|
||||||
// Can't use the last byte or head would equal tail
|
// Can't use the last byte or head would equal tail
|
||||||
return space_to_end > 0 ? space_to_end - 1 : 0;
|
return space_to_end > 0 ? space_to_end - 1 : 0;
|
||||||
@@ -48,8 +39,8 @@ size_t TaskLogBuffer::available_contiguous_space() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &text_length) {
|
bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &text_length) {
|
||||||
// Check if buffer was initialized successfully
|
// Check if mutex was initialized successfully
|
||||||
if (this->mutex_ == nullptr || this->storage_ == nullptr) {
|
if (this->mutex_ == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +77,7 @@ void TaskLogBuffer::release_message_main_loop() {
|
|||||||
this->tail_ += this->current_message_size_;
|
this->tail_ += this->current_message_size_;
|
||||||
|
|
||||||
// Handle wrap-around if we've reached the end
|
// Handle wrap-around if we've reached the end
|
||||||
if (this->tail_ >= this->size_) {
|
if (this->tail_ >= ESPHOME_TASK_LOG_BUFFER_SIZE) {
|
||||||
this->tail_ = 0;
|
this->tail_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,9 +106,9 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
|
|||||||
// Calculate total size needed (header + text length + null terminator)
|
// Calculate total size needed (header + text length + null terminator)
|
||||||
size_t total_size = message_total_size(text_length);
|
size_t total_size = message_total_size(text_length);
|
||||||
|
|
||||||
// Check if buffer was initialized successfully
|
// Check if mutex was initialized successfully
|
||||||
if (this->mutex_ == nullptr || this->storage_ == nullptr) {
|
if (this->mutex_ == nullptr) {
|
||||||
return false; // Buffer not initialized, fall back to direct output
|
return false; // Mutex not initialized, fall back to direct output
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to acquire mutex without blocking - don't block logging tasks
|
// Try to acquire mutex without blocking - don't block logging tasks
|
||||||
@@ -185,7 +176,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
|
|||||||
this->head_ += total_size;
|
this->head_ += total_size;
|
||||||
|
|
||||||
// Handle wrap-around (shouldn't happen due to contiguous space check, but be safe)
|
// Handle wrap-around (shouldn't happen due to contiguous space check, but be safe)
|
||||||
if (this->head_ >= this->size_) {
|
if (this->head_ >= ESPHOME_TASK_LOG_BUFFER_SIZE) {
|
||||||
this->head_ = 0;
|
this->head_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,8 +59,7 @@ class TaskLogBuffer {
|
|||||||
// Valid log levels are 0-7, so 0xFF cannot be a real message
|
// Valid log levels are 0-7, so 0xFF cannot be a real message
|
||||||
static constexpr uint8_t PADDING_MARKER_LEVEL = 0xFF;
|
static constexpr uint8_t PADDING_MARKER_LEVEL = 0xFF;
|
||||||
|
|
||||||
// Constructor that takes a total buffer size
|
TaskLogBuffer();
|
||||||
explicit TaskLogBuffer(size_t total_buffer_size);
|
|
||||||
~TaskLogBuffer();
|
~TaskLogBuffer();
|
||||||
|
|
||||||
// NOT thread-safe - borrow a message from the buffer, only call from main loop
|
// NOT thread-safe - borrow a message from the buffer, only call from main loop
|
||||||
@@ -78,7 +77,7 @@ class TaskLogBuffer {
|
|||||||
inline bool HOT has_messages() const { return this->message_count_ != 0; }
|
inline bool HOT has_messages() const { return this->message_count_ != 0; }
|
||||||
|
|
||||||
// Get the total buffer size in bytes
|
// Get the total buffer size in bytes
|
||||||
inline size_t size() const { return this->size_; }
|
static constexpr size_t size() { return ESPHOME_TASK_LOG_BUFFER_SIZE; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Calculate total size needed for a message (header + text + null terminator)
|
// Calculate total size needed for a message (header + text + null terminator)
|
||||||
@@ -87,10 +86,9 @@ class TaskLogBuffer {
|
|||||||
// Calculate available contiguous space at write position
|
// Calculate available contiguous space at write position
|
||||||
size_t available_contiguous_space() const;
|
size_t available_contiguous_space() const;
|
||||||
|
|
||||||
uint8_t *storage_{nullptr}; // Pointer to allocated memory
|
uint8_t storage_[ESPHOME_TASK_LOG_BUFFER_SIZE]; // Embedded in Logger (no separate heap allocation)
|
||||||
size_t size_{0}; // Size of allocated memory
|
size_t head_{0}; // Write position
|
||||||
size_t head_{0}; // Write position
|
size_t tail_{0}; // Read position
|
||||||
size_t tail_{0}; // Read position
|
|
||||||
|
|
||||||
SemaphoreHandle_t mutex_{nullptr}; // FreeRTOS mutex for thread safety
|
SemaphoreHandle_t mutex_{nullptr}; // FreeRTOS mutex for thread safety
|
||||||
volatile uint16_t message_count_{0}; // Fast check counter (dirty read OK)
|
volatile uint16_t message_count_{0}; // Fast check counter (dirty read OK)
|
||||||
|
|||||||
@@ -17,19 +17,16 @@ static inline uint32_t get_wlen(const mpsc_pbuf_generic *item) {
|
|||||||
return total_size_in_32bit_words(reinterpret_cast<const TaskLogBuffer::LogMessage *>(item)->text_length);
|
return total_size_in_32bit_words(reinterpret_cast<const TaskLogBuffer::LogMessage *>(item)->text_length);
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskLogBuffer::TaskLogBuffer(size_t total_buffer_size) {
|
TaskLogBuffer::TaskLogBuffer() {
|
||||||
// alignment to 4 bytes
|
// Storage is a member array (embedded in Logger), no heap allocation needed
|
||||||
total_buffer_size = (total_buffer_size + 3) / sizeof(uint32_t);
|
this->mpsc_config_.buf = this->buf_storage_;
|
||||||
this->mpsc_config_.buf = new uint32_t[total_buffer_size];
|
this->mpsc_config_.size = BUF_WORD_COUNT;
|
||||||
this->mpsc_config_.size = total_buffer_size;
|
|
||||||
this->mpsc_config_.flags = MPSC_PBUF_MODE_OVERWRITE;
|
this->mpsc_config_.flags = MPSC_PBUF_MODE_OVERWRITE;
|
||||||
this->mpsc_config_.get_wlen = get_wlen,
|
this->mpsc_config_.get_wlen = get_wlen;
|
||||||
|
|
||||||
mpsc_pbuf_init(&this->log_buffer_, &this->mpsc_config_);
|
mpsc_pbuf_init(&this->log_buffer_, &this->mpsc_config_);
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskLogBuffer::~TaskLogBuffer() { delete[] this->mpsc_config_.buf; }
|
|
||||||
|
|
||||||
bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name,
|
bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name,
|
||||||
const char *format, va_list args) {
|
const char *format, va_list args) {
|
||||||
// First, calculate the exact length needed using a null buffer (no actual writing)
|
// First, calculate the exact length needed using a null buffer (no actual writing)
|
||||||
|
|||||||
@@ -33,15 +33,14 @@ class TaskLogBuffer {
|
|||||||
// Methods for accessing message contents
|
// Methods for accessing message contents
|
||||||
inline char *text_data() { return reinterpret_cast<char *>(this) + sizeof(LogMessage); }
|
inline char *text_data() { return reinterpret_cast<char *>(this) + sizeof(LogMessage); }
|
||||||
};
|
};
|
||||||
// Constructor that takes a total buffer size
|
TaskLogBuffer();
|
||||||
explicit TaskLogBuffer(size_t total_buffer_size);
|
~TaskLogBuffer() = default;
|
||||||
~TaskLogBuffer();
|
|
||||||
|
|
||||||
// Check if there are messages ready to be processed using an atomic counter for performance
|
// Check if there are messages ready to be processed using an atomic counter for performance
|
||||||
inline bool HOT has_messages() { return mpsc_pbuf_is_pending(&this->log_buffer_); }
|
inline bool HOT has_messages() { return mpsc_pbuf_is_pending(&this->log_buffer_); }
|
||||||
|
|
||||||
// Get the total buffer size in bytes
|
// Get the total buffer size in bytes
|
||||||
inline size_t size() const { return this->mpsc_config_.size * sizeof(uint32_t); }
|
static constexpr size_t size() { return BUF_WORD_COUNT * sizeof(uint32_t); }
|
||||||
|
|
||||||
// NOT thread-safe - borrow a message from the ring buffer, only call from main loop
|
// NOT thread-safe - borrow a message from the ring buffer, only call from main loop
|
||||||
bool borrow_message_main_loop(LogMessage *&message, uint16_t &text_length);
|
bool borrow_message_main_loop(LogMessage *&message, uint16_t &text_length);
|
||||||
@@ -54,6 +53,9 @@ class TaskLogBuffer {
|
|||||||
const char *format, va_list args);
|
const char *format, va_list args);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
// Round up byte size to 32-bit word count for mpsc_pbuf alignment requirement
|
||||||
|
static constexpr size_t BUF_WORD_COUNT = (ESPHOME_TASK_LOG_BUFFER_SIZE + 3) / sizeof(uint32_t);
|
||||||
|
uint32_t buf_storage_[BUF_WORD_COUNT]; // Embedded in Logger (no separate heap allocation)
|
||||||
mpsc_pbuf_buffer_config mpsc_config_{};
|
mpsc_pbuf_buffer_config mpsc_config_{};
|
||||||
mpsc_pbuf_buffer log_buffer_{};
|
mpsc_pbuf_buffer log_buffer_{};
|
||||||
const mpsc_pbuf_generic *current_token_{};
|
const mpsc_pbuf_generic *current_token_{};
|
||||||
|
|||||||
@@ -200,6 +200,7 @@
|
|||||||
#define USE_ESP32_CRASH_HANDLER
|
#define USE_ESP32_CRASH_HANDLER
|
||||||
#define USE_MQTT_IDF_ENQUEUE
|
#define USE_MQTT_IDF_ENQUEUE
|
||||||
#define USE_ESPHOME_TASK_LOG_BUFFER
|
#define USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
|
#define ESPHOME_TASK_LOG_BUFFER_SIZE 768
|
||||||
#define USE_OTA_ROLLBACK
|
#define USE_OTA_ROLLBACK
|
||||||
#define USE_ESP32_MIN_CHIP_REVISION_SET
|
#define USE_ESP32_MIN_CHIP_REVISION_SET
|
||||||
#define USE_ESP32_SRAM1_AS_IRAM
|
#define USE_ESP32_SRAM1_AS_IRAM
|
||||||
@@ -373,18 +374,23 @@
|
|||||||
#define USE_WEBSERVER
|
#define USE_WEBSERVER
|
||||||
#define USE_WEBSERVER_AUTH
|
#define USE_WEBSERVER_AUTH
|
||||||
#define USE_WEBSERVER_PORT 80 // NOLINT
|
#define USE_WEBSERVER_PORT 80 // NOLINT
|
||||||
|
#define USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
|
#define ESPHOME_TASK_LOG_BUFFER_SIZE 768
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_HOST
|
#ifdef USE_HOST
|
||||||
#define USE_HTTP_REQUEST_RESPONSE
|
#define USE_HTTP_REQUEST_RESPONSE
|
||||||
#define USE_SOCKET_IMPL_BSD_SOCKETS
|
#define USE_SOCKET_IMPL_BSD_SOCKETS
|
||||||
#define USE_SOCKET_SELECT_SUPPORT
|
#define USE_SOCKET_SELECT_SUPPORT
|
||||||
|
#define USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
|
#define ESPHOME_TASK_LOG_BUFFER_SIZE 64
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_NRF52
|
#ifdef USE_NRF52
|
||||||
#define ESPHOME_BLE_NUS_TX_RING_BUFFER_SIZE 512
|
#define ESPHOME_BLE_NUS_TX_RING_BUFFER_SIZE 512
|
||||||
#define ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE 512
|
#define ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE 512
|
||||||
#define USE_ESPHOME_TASK_LOG_BUFFER
|
#define USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
|
#define ESPHOME_TASK_LOG_BUFFER_SIZE 768
|
||||||
#define USE_LOGGER_EARLY_MESSAGE
|
#define USE_LOGGER_EARLY_MESSAGE
|
||||||
#define USE_LOGGER_UART_SELECTION_USB_CDC
|
#define USE_LOGGER_UART_SELECTION_USB_CDC
|
||||||
#define USE_LOGGER_USB_CDC
|
#define USE_LOGGER_USB_CDC
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ void setup() {
|
|||||||
|
|
||||||
// Log functions call global_logger->log_vprintf_() without a null check,
|
// Log functions call global_logger->log_vprintf_() without a null check,
|
||||||
// so we must set up a Logger before any test that triggers logging.
|
// so we must set up a Logger before any test that triggers logging.
|
||||||
static esphome::logger::Logger test_logger(0, 64);
|
static esphome::logger::Logger test_logger(0);
|
||||||
test_logger.set_log_level(ESPHOME_LOG_LEVEL);
|
test_logger.set_log_level(ESPHOME_LOG_LEVEL);
|
||||||
test_logger.pre_setup();
|
test_logger.pre_setup();
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ void original_setup() {
|
|||||||
void setup() {
|
void setup() {
|
||||||
// Log functions call global_logger->log_vprintf_() without a null check,
|
// Log functions call global_logger->log_vprintf_() without a null check,
|
||||||
// so we must set up a Logger before any test that triggers logging.
|
// so we must set up a Logger before any test that triggers logging.
|
||||||
static esphome::logger::Logger test_logger(0, 64);
|
static esphome::logger::Logger test_logger(0);
|
||||||
test_logger.set_log_level(ESPHOME_LOG_LEVEL);
|
test_logger.set_log_level(ESPHOME_LOG_LEVEL);
|
||||||
test_logger.pre_setup();
|
test_logger.pre_setup();
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ void setup() {
|
|||||||
static char name[] = "livingroom";
|
static char name[] = "livingroom";
|
||||||
static char friendly_name[] = "LivingRoom";
|
static char friendly_name[] = "LivingRoom";
|
||||||
App.pre_setup(name, sizeof(name) - 1, friendly_name, sizeof(friendly_name) - 1);
|
App.pre_setup(name, sizeof(name) - 1, friendly_name, sizeof(friendly_name) - 1);
|
||||||
auto *log = new logger::Logger(115200, 512); // NOLINT
|
auto *log = new logger::Logger(115200); // NOLINT
|
||||||
log->pre_setup();
|
log->pre_setup();
|
||||||
log->set_uart_selection(logger::UART_SELECTION_UART0);
|
log->set_uart_selection(logger::UART_SELECTION_UART0);
|
||||||
App.register_component_(log);
|
App.register_component_(log);
|
||||||
|
|||||||
Reference in New Issue
Block a user