ESP32: add CPUUsageTracker – core-0 load averages (1/5/15 min)

Track CPU utilisation on core 0 using a FreeRTOS tick hook that
increments per-tick idle/busy counters, sampled every 5s by an
esp_timer callback.  Exponential moving averages with Linux-style
time constants (1 / 5 / 15 min) are maintained in software:

  DECAY1  = exp(-5/60)  ≈ 0.9200  (1-minute window)
  DECAY5  = exp(-5/300) ≈ 0.9835  (5-minute window)
  DECAY15 = exp(-5/900) ≈ 0.9945  (15-minute window)

Core 1 is excluded intentionally: it runs the Arduino loopTask at
100% load for LoRa packet processing, so its figure is always 1.0
and carries no diagnostic value.  All other tasks are pinned to
core 0 by task_pinning.c, so core-0 load reflects the true system
utilisation.

New files:
  arch/esp32/CPUUsageTracker.h   – class declaration
  arch/esp32/CPUUsageTracker.cpp – tick hook + esp_timer sampling

Integration:
  MyMesh::begin() calls _cpu_tracker.begin() on ESP32 builds.
  formatStatsReply() emits "load_avg":[<1m>,<5m>,<15m>] in the
  compact stats JSON payload.
  formatWebStatsSummaryJson() adds the same field to the web-panel
  stats endpoint under core.load_avg.
  The web-panel HUD gains a "Load Avg" metric tile showing all
  three values side-by-side; the core-metrics grid is widened from
  4 to 5 columns.  The tile is rendered conditionally so older
  firmware (or non-ESP32 builds) that omit the field degrade
  gracefully.
  CPUUsageTracker.cpp is added to the esp32_base build_src_filter
  in platformio.ini.
Этот коммит содержится в:
Valentin V. Bartenev
2026-05-09 17:26:27 +03:00
родитель ce510738d5
Коммит a6f01d544e
6 изменённых файлов: 114 добавлений и 3 удалений
+51
Просмотреть файл
@@ -0,0 +1,51 @@
#ifdef ESP32
#include "CPUUsageTracker.h"
#include "esp_freertos_hooks.h"
volatile uint32_t CPUUsageTracker::s_idle_ticks = 0;
volatile uint32_t CPUUsageTracker::s_busy_ticks = 0;
TaskHandle_t CPUUsageTracker::s_idle_handle = nullptr;
void IRAM_ATTR CPUUsageTracker::s_tick_hook() { // ← was CPUsageTracker (typo)
if (xTaskGetCurrentTaskHandle() == s_idle_handle) {
s_idle_ticks++;
} else {
s_busy_ticks++;
}
}
void CPUUsageTracker::s_sample_cb(void* arg) {
static_cast<CPUUsageTracker*>(arg)->_onSample(); // ← was CpuUsageTracker (old name)
}
void CPUUsageTracker::_onSample() {
uint32_t busy = s_busy_ticks;
uint32_t idle = s_idle_ticks;
uint32_t db = busy - _last_busy;
uint32_t di = idle - _last_idle;
_last_busy = busy;
_last_idle = idle;
uint32_t total = db + di;
float sample = (total > 0) ? (float)db / total : 0.0f;
_avg1 = DECAY1 * _avg1 + (1.0f - DECAY1) * sample;
_avg5 = DECAY5 * _avg5 + (1.0f - DECAY5) * sample;
_avg15 = DECAY15 * _avg15 + (1.0f - DECAY15) * sample;
}
void CPUUsageTracker::begin() {
s_idle_handle = xTaskGetIdleTaskHandleForCPU(0);
esp_register_freertos_tick_hook_for_cpu(s_tick_hook, 0);
esp_timer_create_args_t args = {
.callback = s_sample_cb,
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "cpu_avg"
};
esp_timer_create(&args, &_timer);
esp_timer_start_periodic(_timer, 5000000ULL); // 5 seconds
}
#endif