S1 #11: the System App: tasks, memory, network and system, live

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
This commit is contained in:
2026-10-06 01:28:26 +02:00
co-authored by Claude Opus 5.5
parent 70bb2a4137
commit e36aa50922
10 changed files with 812 additions and 12 deletions
+23 -2
View File
@@ -14,6 +14,7 @@
#include "apps/launcher_app.h"
#include "apps/lora_scanner_app.h"
#include "apps/settings_app.h"
#include "apps/system_app.h"
#include "apps/wifi_tools_app.h"
#include "apps/setup_app.h"
#include "event_bus.h"
@@ -196,6 +197,8 @@ void setup() {
apps->registerApp({"gnss", "GNSS", false, new GnssApp(*gnssService, settings)});
apps->registerApp({"gemini", "Gemini", false, new GeminiApp(*geminiService)});
apps->registerApp({"lora", "LoRa Scanner", false, new LoraScannerApp(*radioService, *loraCapture, settings, *clockService)});
apps->registerApp({"system", "System", false,
new SystemApp(*wifi, *battery, *storageService, *radioService, *gnssService, nvs)});
apps->registerApp({"settings", "Settings", false,
new SettingsApp({settings, bus, *apps, *battery, *storageService, *clockService, *wifi, *savedNetworks, *update})});
apps->registerApp({"demo", "Widget demo", true, new DemoApp(bus)});
@@ -382,9 +385,21 @@ static void ipTrialStep() {
}
#endif
// `tasks`: the first sample, and when to take the second and print.
static std::vector<TaskSample> tasksBefore;
static uint32_t tasksTotal = 0, tasksDueMs = 0;
static bool tasksPending = false;
static void tasksStep() {
if (!tasksPending || static_cast<int32_t>(millis() - tasksDueMs) < 0) return;
tasksPending = false;
system_info::printTasks(console, tasksBefore, tasksTotal);
std::vector<TaskSample>().swap(tasksBefore);
}
static const char* const kHelp =
"info firmware, uptime, memory, Wi-Fi, app slots\n"
"tasks FreeRTOS tasks: state, priority, free stack, CPU\n" "net bytes each network service has read and written since boot\n"
"tasks FreeRTOS tasks over the next second: state, priority, free stack, CPU share\n" "net bytes each network service has read and written since boot\n"
"reboot restart\n"
"boot other restart into the other app slot (manual Rollback)\n"
"log level <0-5> ESP-IDF log level (0 none ... 5 verbose)\n"
@@ -433,7 +448,11 @@ static void runCommand(String line) {
console.printf("update: %s\n", update->onProbation() ? "on probation" : "confirmed");
system_info::printSlots(console, nvs);
}
if (line == "tasks") system_info::printTasks(console);
if (line == "tasks") { // sampled now, printed a second later by tasksStep(): the loop must run in between
tasksTotal = system_info::sampleTasks(tasksBefore);
tasksDueMs = millis() + 1000;
tasksPending = true;
}
if (line == "net") { // bytes each service has read and written since boot (S1, Q121)
for (int i = 0; i < static_cast<int>(net::User::Count); i++) {
net::Traffic t = net::traffic(static_cast<net::User>(i));
@@ -739,6 +758,7 @@ static void loopSafeMode() {
uint32_t now = millis();
serialCommands();
remoteCommands();
tasksStep();
services.tick(now);
bus.dispatch();
noteStableOnce(now);
@@ -770,6 +790,7 @@ void loop() {
serialCommands();
remoteCommands();
tasksStep();
#ifdef RORO_DEBUG
ipTrialStep();
#endif