mirror of
https://github.com/esphome/esphome.git
synced 2026-02-11 12:07:34 -07:00
153 lines
4.7 KiB
C++
153 lines
4.7 KiB
C++
#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 {
|
|
|
|
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_() {
|
|
// 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 TaskLogBuffer::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 TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name,
|
|
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;
|
|
|
|
// Store the thread name now to avoid crashes if thread exits before processing
|
|
if (thread_name != nullptr) {
|
|
strncpy(msg.thread_name, thread_name, 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 TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &text_length) {
|
|
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;
|
|
text_length = msg.text_length;
|
|
return true;
|
|
}
|
|
|
|
void TaskLogBuffer::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
|