This commit is contained in:
J. Nick Koston
2026-01-05 14:31:38 -10:00
parent 0d2c48a55a
commit c64514acdc
3 changed files with 182 additions and 5 deletions

View File

@@ -100,6 +100,7 @@ CONF_INITIAL_LEVEL = "initial_level"
CONF_LOGGER_ID = "logger_id"
CONF_RUNTIME_TAG_LEVELS = "runtime_tag_levels"
CONF_TASK_LOG_BUFFER_SIZE = "task_log_buffer_size"
CONF_TASK_LOG_BUFFER_SLOTS = "task_log_buffer_slots"
UART_SELECTION_ESP32 = {
VARIANT_ESP32: [UART0, UART1, UART2],
@@ -227,12 +228,25 @@ CONFIG_SCHEMA = cv.All(
cv.SplitDefault(
CONF_TASK_LOG_BUFFER_SIZE,
esp32=768, # Default: 768 bytes (~5-6 messages with 70-byte text plus thread names)
host=64, # Default: 64 slots (host uses slot count, not byte size)
): cv.All(
cv.only_on([PLATFORM_ESP32, PLATFORM_HOST]),
cv.only_on_esp32,
cv.validate_bytes,
cv.Any(
cv.int_(0), # Disabled
cv.int_range(min=4, max=32768),
cv.int_range(
min=640, # Min: ~4-5 messages with 70-byte text plus thread names
max=32768, # Max: Depends on message sizes, typically ~300 messages with default size
),
),
),
cv.SplitDefault(
CONF_TASK_LOG_BUFFER_SLOTS,
host=64, # Default: 64 message slots for host platform
): cv.All(
cv.only_on(PLATFORM_HOST),
cv.Any(
cv.int_(0), # Disabled
cv.int_range(min=4, max=256), # 4-256 message slots
),
),
cv.SplitDefault(
@@ -302,12 +316,18 @@ async def to_code(config):
baud_rate,
config[CONF_TX_BUFFER_SIZE],
)
if CORE.is_esp32 or CORE.is_host:
if CORE.is_esp32:
cg.add(log.create_pthread_key())
task_log_buffer_size = config.get(CONF_TASK_LOG_BUFFER_SIZE, 0)
if task_log_buffer_size > 0:
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
cg.add(log.init_log_buffer(task_log_buffer_size))
elif CORE.is_host:
cg.add(log.create_pthread_key())
task_log_buffer_slots = config.get(CONF_TASK_LOG_BUFFER_SLOTS, 0)
if task_log_buffer_slots > 0:
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
cg.add(log.init_log_buffer(task_log_buffer_slots))
cg.add(log.set_log_level(initial_level))
if CONF_HARDWARE_UART in config:

View File

@@ -292,7 +292,7 @@ void Logger::dump_config() {
#endif
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
if (this->log_buffer_) {
ESP_LOGCONFIG(TAG, " Task Log Buffer Size: %u", this->log_buffer_->size());
ESP_LOGCONFIG(TAG, " Task Log Buffer Size: %u", static_cast<unsigned int>(this->log_buffer_->size()));
}
#endif

View File

@@ -0,0 +1,157 @@
#ifdef USE_HOST
#include "task_log_buffer_host.h"
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
#include "esphome/core/log.h"
#include <algorithm>
#include <cstdio>
namespace esphome::logger {
TaskLogBufferHost::TaskLogBufferHost(size_t slot_count) : slot_count_(slot_count) {
// Allocate message slots
this->slots_ = std::make_unique<LogMessage[]>(slot_count);
}
TaskLogBufferHost::~TaskLogBufferHost() {
// unique_ptr handles cleanup automatically
}
int TaskLogBufferHost::acquire_write_slot_() {
// Try to reserve a slot using compare-and-swap
size_t current_reserve = this->reserve_index_.load(std::memory_order_relaxed);
while (true) {
// Calculate next index (with wrap-around)
size_t next_reserve = (current_reserve + 1) % this->slot_count_;
// Check if buffer would be full
// Buffer is full when next write position equals read position
size_t current_read = this->read_index_.load(std::memory_order_acquire);
if (next_reserve == current_read) {
return -1; // Buffer full
}
// Try to claim this slot
if (this->reserve_index_.compare_exchange_weak(current_reserve, next_reserve, std::memory_order_acq_rel,
std::memory_order_relaxed)) {
return static_cast<int>(current_reserve);
}
// If CAS failed, current_reserve was updated, retry with new value
}
}
void TaskLogBufferHost::commit_write_slot_(int slot_index) {
// Mark the slot as ready for reading
this->slots_[slot_index].ready.store(true, std::memory_order_release);
// Try to advance the write_index if we're the next expected commit
// This ensures messages are read in order
size_t expected = slot_index;
size_t next = (slot_index + 1) % this->slot_count_;
// We only advance write_index if this slot is the next one expected
// This handles out-of-order commits correctly
while (true) {
if (!this->write_index_.compare_exchange_weak(expected, next, std::memory_order_release,
std::memory_order_relaxed)) {
// Someone else advanced it or we're not next in line, that's fine
break;
}
// Successfully advanced, check if next slot is also ready
expected = next;
next = (next + 1) % this->slot_count_;
if (!this->slots_[expected].ready.load(std::memory_order_acquire)) {
break;
}
}
}
bool TaskLogBufferHost::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *format,
va_list args) {
// Acquire a slot
int slot_index = this->acquire_write_slot_();
if (slot_index < 0) {
return false; // Buffer full
}
LogMessage &msg = this->slots_[slot_index];
// Fill in the message header
msg.level = level;
msg.tag = tag;
msg.line = line;
// Get thread name using pthread
char thread_name_buf[LogMessage::MAX_THREAD_NAME_SIZE];
// pthread_getname_np works the same on Linux and macOS
if (pthread_getname_np(pthread_self(), thread_name_buf, sizeof(thread_name_buf)) == 0) {
strncpy(msg.thread_name, thread_name_buf, sizeof(msg.thread_name) - 1);
msg.thread_name[sizeof(msg.thread_name) - 1] = '\0';
} else {
msg.thread_name[0] = '\0';
}
// Format the message text
int ret = vsnprintf(msg.text, sizeof(msg.text), format, args);
if (ret < 0) {
// Formatting error - still commit the slot but with empty text
msg.text[0] = '\0';
msg.text_length = 0;
} else {
msg.text_length = static_cast<uint16_t>(std::min(static_cast<size_t>(ret), sizeof(msg.text) - 1));
}
// Remove trailing newlines
while (msg.text_length > 0 && msg.text[msg.text_length - 1] == '\n') {
msg.text_length--;
}
msg.text[msg.text_length] = '\0';
// Commit the slot
this->commit_write_slot_(slot_index);
return true;
}
bool TaskLogBufferHost::get_message_main_loop(LogMessage **message) {
if (message == nullptr) {
return false;
}
size_t current_read = this->read_index_.load(std::memory_order_relaxed);
size_t current_write = this->write_index_.load(std::memory_order_acquire);
// Check if buffer is empty
if (current_read == current_write) {
return false;
}
// Check if the slot is ready (should always be true if write_index advanced)
LogMessage &msg = this->slots_[current_read];
if (!msg.ready.load(std::memory_order_acquire)) {
return false;
}
*message = &msg;
return true;
}
void TaskLogBufferHost::release_message_main_loop() {
size_t current_read = this->read_index_.load(std::memory_order_relaxed);
// Clear the ready flag
this->slots_[current_read].ready.store(false, std::memory_order_release);
// Advance read index
size_t next_read = (current_read + 1) % this->slot_count_;
this->read_index_.store(next_read, std::memory_order_release);
}
} // namespace esphome::logger
#endif // USE_ESPHOME_TASK_LOG_BUFFER
#endif // USE_HOST