Public Access
Five views, Tab between them: Overview (each core's load, memory, traffic, battery, two minutes of load), Tasks (share of a core over the last second, lowest free stack, flagged under 512 bytes; `s` sorts), Memory (free heap against the floors of Q86), Network (bytes per service, and what's moving now), System (what `info` prints, plus battery, card, radio, GNSS). It samples once a second and keeps history only while open. The arithmetic is host-tested, including the trap found on the device: a task's run-time counter only moves when it's switched out, so the task that samples (the main loop, alone on its core) gets what's left of its core. `tasks` now samples across a second of normal running instead of inside its own wait. The main loop uses 100 % of core 1 at rest (#40). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
141 lines
5.7 KiB
C++
141 lines
5.7 KiB
C++
#include "platform/system_info.h"
|
|
|
|
#include <Arduino.h>
|
|
#include <esp_heap_caps.h>
|
|
#include <esp_ota_ops.h>
|
|
#include <esp_system.h>
|
|
#include <freertos/FreeRTOS.h>
|
|
#include <freertos/task.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "version.h"
|
|
|
|
namespace roro::system_info {
|
|
|
|
const char* resetReason() {
|
|
switch (esp_reset_reason()) {
|
|
case ESP_RST_POWERON: return "power-on";
|
|
case ESP_RST_EXT: return "reset pin";
|
|
case ESP_RST_SW: return "restart";
|
|
case ESP_RST_PANIC: return "panic";
|
|
case ESP_RST_INT_WDT: return "interrupt watchdog";
|
|
case ESP_RST_TASK_WDT: return "task watchdog";
|
|
case ESP_RST_WDT: return "watchdog";
|
|
case ESP_RST_DEEPSLEEP: return "deep sleep";
|
|
case ESP_RST_BROWNOUT: return "brownout";
|
|
case ESP_RST_SDIO: return "sdio";
|
|
case ESP_RST_USB: return "usb";
|
|
case ESP_RST_JTAG: return "jtag";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
bool resetWasCrash() {
|
|
switch (esp_reset_reason()) {
|
|
case ESP_RST_PANIC:
|
|
case ESP_RST_INT_WDT:
|
|
case ESP_RST_TASK_WDT:
|
|
case ESP_RST_WDT: return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
static const char* stateName(esp_ota_img_states_t s) {
|
|
switch (s) {
|
|
case ESP_OTA_IMG_NEW: return "new";
|
|
case ESP_OTA_IMG_PENDING_VERIFY: return "pending verify";
|
|
case ESP_OTA_IMG_VALID: return "valid";
|
|
case ESP_OTA_IMG_INVALID: return "invalid";
|
|
case ESP_OTA_IMG_ABORTED: return "aborted";
|
|
default: return "undefined";
|
|
}
|
|
}
|
|
|
|
void printSystem(Print& out) {
|
|
uint32_t s = millis() / 1000;
|
|
out.printf("firmware: %s %s\n", kProductName, versionString());
|
|
out.printf("uptime: %luh%02lum%02lus, last start: %s\n", (unsigned long)(s / 3600), (unsigned long)(s / 60 % 60),
|
|
(unsigned long)(s % 60), resetReason());
|
|
out.printf("heap: %u free, %u lowest, %u largest block\n", ESP.getFreeHeap(), ESP.getMinFreeHeap(),
|
|
(unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_8BIT));
|
|
out.printf("chip: %s rev %d, %lu MHz, %.1f C\n", ESP.getChipModel(), ESP.getChipRevision(),
|
|
(unsigned long)ESP.getCpuFreqMHz(), temperatureRead());
|
|
}
|
|
|
|
static std::string slotKey(const esp_partition_t* slot) { return std::string("ver_") + slot->label; }
|
|
|
|
void recordSlotVersion(KeyValueStore& store, const esp_partition_t* slot, const char* version) {
|
|
std::string known;
|
|
if (slot && !(store.getString(slotKey(slot).c_str(), known) && known == version))
|
|
store.putString(slotKey(slot).c_str(), version);
|
|
}
|
|
|
|
void printSlots(Print& out, KeyValueStore& store) {
|
|
const esp_partition_t* running = esp_ota_get_running_partition();
|
|
const esp_partition_t* next = esp_ota_get_boot_partition();
|
|
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, nullptr);
|
|
for (; it; it = esp_partition_next(it)) { // the last next() releases the iterator
|
|
const esp_partition_t* p = esp_partition_get(it);
|
|
esp_app_desc_t desc;
|
|
bool hasApp = esp_ota_get_partition_description(p, &desc) == ESP_OK;
|
|
std::string version = "(unknown version)";
|
|
store.getString(slotKey(p).c_str(), version);
|
|
esp_ota_img_states_t state;
|
|
bool hasState = esp_ota_get_state_partition(p, &state) == ESP_OK;
|
|
out.printf("slot %s: %s%s%s, %s\n", p->label, hasApp ? version.c_str() : "(empty)", p == running ? ", running" : "",
|
|
p == next ? ", boots next" : "", hasState ? stateName(state) : "no OTA state");
|
|
}
|
|
}
|
|
|
|
uint32_t sampleTasks(std::vector<TaskSample>& out) {
|
|
UBaseType_t n = uxTaskGetNumberOfTasks();
|
|
std::vector<TaskStatus_t> tasks(n + 4);
|
|
uint32_t total = 0;
|
|
n = uxTaskGetSystemState(tasks.data(), tasks.size(), &total);
|
|
out.clear();
|
|
out.reserve(n);
|
|
for (UBaseType_t i = 0; i < n; i++) {
|
|
const TaskStatus_t& t = tasks[i];
|
|
TaskSample s;
|
|
s.id = t.xTaskNumber;
|
|
std::snprintf(s.name, sizeof s.name, "%s", t.pcTaskName);
|
|
s.runtime = t.ulRunTimeCounter;
|
|
s.stackFree = static_cast<uint16_t>(std::min<uint32_t>(t.usStackHighWaterMark, 0xFFFF));
|
|
s.core = t.xCoreID == tskNO_AFFINITY ? -1 : static_cast<int8_t>(t.xCoreID);
|
|
s.state = static_cast<uint8_t>(t.eCurrentState);
|
|
s.priority = static_cast<uint8_t>(t.uxCurrentPriority);
|
|
out.push_back(s);
|
|
}
|
|
return total;
|
|
}
|
|
|
|
// Shares over the interval since `before`, not since boot: the counters wrap every 71 minutes.
|
|
void printTasks(Print& out, const std::vector<TaskSample>& before, uint32_t beforeTotal) {
|
|
std::vector<TaskSample> now;
|
|
uint32_t t1 = sampleTasks(now);
|
|
std::vector<TaskRow> rows = taskRows(before, beforeTotal, now, t1);
|
|
sortTasks(rows, TaskSort::Share);
|
|
out.printf("%-16s %-4s %3s %6s %6s %s\n", "task", "st", "pri", "stack", "cpu%", "core");
|
|
static const char kStates[] = "RrBSD"; // running, ready, blocked, suspended, deleted
|
|
for (const TaskRow& r : rows)
|
|
out.printf("%-16s %-4c %3u %6u %4u.%u %d%s\n", r.name.c_str(), r.state < 5 ? kStates[r.state] : '?', r.priority,
|
|
r.stackFree, r.permille / 10, r.permille % 10, r.core, r.lowStack ? " low stack" : "");
|
|
out.printf("load: core 0 %d %%, core 1 %d %%\n", coreLoad(rows, 0), coreLoad(rows, 1));
|
|
}
|
|
|
|
const char* bootOtherSlot() {
|
|
const esp_partition_t* other = esp_ota_get_next_update_partition(nullptr);
|
|
esp_app_desc_t desc;
|
|
if (!other || esp_ota_get_partition_description(other, &desc) != ESP_OK) return "the other slot is empty";
|
|
if (esp_ota_set_boot_partition(other) != ESP_OK) return "the other slot doesn't hold a valid image";
|
|
delay(200);
|
|
esp_restart();
|
|
return "";
|
|
}
|
|
|
|
} // namespace roro::system_info
|