[core] fast_select stats: log details on load-bearing hit

Adds a non-inline helper note_fast_select_load_bearing_() invoked only
when the scan found data and the task notification counter was 0.
Logs: hit sequence number, lwip_sock pointer, index in
monitored_sockets_, raw rcvevent value, delay_ms, and how many other
sockets also had data at that instant.

Hot path is unchanged — the helper is out-of-line so yield_with_select_
still inlines to the same code as before for the zero-hit path.
This commit is contained in:
J. Nick Koston
2026-04-10 20:11:34 -10:00
parent 14b804f3e1
commit 8921a9dd60
2 changed files with 32 additions and 1 deletions
+28
View File
@@ -462,6 +462,34 @@ void Application::log_fast_select_scan_stats_() {
load_bearing);
}
void Application::note_fast_select_load_bearing_(struct lwip_sock *sock, uint32_t delay_ms) {
uint32_t load_bearing = fast_select_scan_load_bearing_.fetch_add(1, std::memory_order_relaxed) + 1;
// Find the socket's index in monitored_sockets_ for easier correlation with registration order.
int index = -1;
for (size_t i = 0; i < this->monitored_sockets_.size(); i++) {
if (this->monitored_sockets_[i] == sock) {
index = static_cast<int>(i);
break;
}
}
// Read the rcvevent value directly. This is the same offset-based read used by
// esphome_lwip_socket_has_data(); value > 0 means unread data is queued.
int16_t rcvevent =
*reinterpret_cast<volatile int16_t *>(reinterpret_cast<char *>(sock) + ESPHOME_LWIP_SOCK_RCVEVENT_OFFSET);
// Count how many other sockets also had data at this scan (could reveal whether it's always
// the same socket or a burst across multiple).
size_t sockets_with_data = 0;
for (struct lwip_sock *s : this->monitored_sockets_) {
if (esphome_lwip_socket_has_data(s))
sockets_with_data++;
}
ESP_LOGW(TAG,
"fast_select LOAD-BEARING hit #%" PRIu32 ": sock=%p idx=%d/%u rcvevent=%d delay_ms=%" PRIu32
" sockets_with_data=%u",
load_bearing, sock, index, static_cast<unsigned>(this->monitored_sockets_.size()), rcvevent, delay_ms,
static_cast<unsigned>(sockets_with_data));
}
bool Application::register_socket(struct lwip_sock *sock) {
// It modifies monitored_sockets_ without locking — must only be called from the main loop.
if (sock == nullptr)
+4 -1
View File
@@ -664,6 +664,8 @@ class Application {
static std::atomic<uint32_t> fast_select_scan_load_bearing_;
uint32_t fast_select_scan_stats_last_log_{0};
void log_fast_select_scan_stats_();
// Non-inline, called only on the rare load-bearing event so the hot path stays unchanged.
void note_fast_select_load_bearing_(struct lwip_sock *sock, uint32_t delay_ms);
#elif defined(USE_HOST)
std::vector<int> socket_fds_; // Vector of all monitored socket file descriptors
#endif
@@ -947,7 +949,8 @@ inline void ESPHOME_ALWAYS_INLINE Application::yield_with_select_(uint32_t delay
fast_select_scan_found_data_.fetch_add(1, std::memory_order_relaxed);
if (fast_select_notify_value_before_scan == 0) {
// Scan was load-bearing: no notification pending, so Take would have stalled.
fast_select_scan_load_bearing_.fetch_add(1, std::memory_order_relaxed);
// Delegate to a non-inline helper so the hot path stays the same size.
this->note_fast_select_load_bearing_(sock, delay_ms);
}
yield();
return;