System Monitor: an App for tasks, memory and network, like btm #11

Closed
opened 2026-10-05 09:55:00 +00:00 by twisla · 2 comments
Owner

Idea

A system monitor App in the spirit of btm or htop: live views of CPU and tasks, memory, network and storage. It has small graphs of recent history and works in release builds, not just through the Debug Console.

Why

Every milestone so far has been driven by measurements: heap floors, stack trimming, TLS dips. Today they all need a Debug Build and a laptop. Seeing them on the device makes it easier to spot problems as they happen, and it's satisfying to watch.

What could be shown

  • CPU and tasks
    • CPU load per core (from the idle tasks' runtime).
    • A task list: name, core, priority, CPU %, state, and stack high-water mark.
    • Sortable, with a graph of history.
  • Memory
    • Free heap, lowest since boot, and the largest free block (fragmentation, which is what cut pages at 8.7 KB in G1).
    • Internal and DMA-capable memory, shown separately.
    • A history graph with the Q86 floors drawn as lines (55, 40 and 20 KB).
  • Network
    • Wi-Fi: SSID, channel, signal strength over time, IP, gateway, DNS (see #7), and the VPN (#8) when it's up.
    • Open connections per service: IRC, Gemini, Debug Console, UpdateService, SSH (#2).
    • Bytes in and out, per service or overall.
  • System
    • Uptime, reset reason, version and slot (the same as the Debug Console's info).
    • CPU frequency and chip temperature.
    • Battery voltage, percentage and charging.
    • SD card usage.

What's known

  • Data that already exists.
    • printSystem and printTasks (src/platform/system_info.cpp) write text to a Print. They'd need splitting into a data model (structs) that both the Debug Console and the App use.
    • uxTaskGetSystemState gives the tasks and their runtime counters; FreeRTOS runtime statistics are on in both release and debug builds. CPU % comes from the difference between two samples.
  • Network counters are missing. CONFIG_LWIP_STATS is off. Options:
    • Turn on lwIP statistics. That costs some RAM and CPU, and it counts packets per protocol, with byte counters only via MIB2 statistics, which still needs checking.
    • Count bytes ourselves in each service's read and write path, which is cheap and gives per-service numbers.
  • History costs RAM. For example, 120 samples × 6 values × 2 bytes ≈ 1.5 KB. Sampling should run only while the App is open, or keep a small ring all the time, so the history is there when you open the App.
  • Sampling cost. One task snapshot is about 25 tasks × ~40 bytes, allocated briefly. A 1 s period is cheap, but the App itself must not distort what it measures: its own stack and buffers show up in the numbers.
  • Chip temperature comes from the ESP32-S3's built-in sensor (ESP-IDF's temperature_sensor driver). It's rough, but enough to see trends.

Questions for the design round

  1. Which layout: tabs (CPU, Memory, Network, System), like btm's panels, or one dense overview plus detail pages?
  2. Should history be kept only while the App is open, or always, in a small ring?
  3. Network counters: lwIP statistics, our own counters per service, or both?
  4. Should the task list allow any actions (none, probably), or is it read-only?
  5. Should there be a log of events next to the graphs: lowest heap reached, a Wi-Fi reconnect, a fetch refused below the floor?
  6. Should it export a snapshot to the card (CSV or text) for later comparison?
  7. Should a compact version live in the status bar or as a Launcher tile badge (#9), for example free heap?

Related

src/platform/system_info.cpp, src/services/debug_console.cpp (info, tasks), sdkconfig.defaults (CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS, CONFIG_LWIP_STATS), docs/milestones/G1.md (Q86 floors), #7, #8, #9.

## Idea A system monitor App in the spirit of `btm` or `htop`: live views of CPU and tasks, memory, network and storage. It has small graphs of recent history and works in release builds, not just through the Debug Console. ## Why Every milestone so far has been driven by measurements: heap floors, stack trimming, TLS dips. Today they all need a Debug Build and a laptop. Seeing them on the device makes it easier to spot problems as they happen, and it's satisfying to watch. ## What could be shown - **CPU and tasks** - CPU load per core (from the idle tasks' runtime). - A task list: name, core, priority, CPU %, state, and stack high-water mark. - Sortable, with a graph of history. - **Memory** - Free heap, lowest since boot, and the largest free block (fragmentation, which is what cut pages at 8.7 KB in G1). - Internal and DMA-capable memory, shown separately. - A history graph with the Q86 floors drawn as lines (55, 40 and 20 KB). - **Network** - Wi-Fi: SSID, channel, signal strength over time, IP, gateway, DNS (see #7), and the VPN (#8) when it's up. - Open connections per service: IRC, Gemini, Debug Console, UpdateService, SSH (#2). - Bytes in and out, per service or overall. - **System** - Uptime, reset reason, version and slot (the same as the Debug Console's `info`). - CPU frequency and chip temperature. - Battery voltage, percentage and charging. - SD card usage. ## What's known - **Data that already exists.** - `printSystem` and `printTasks` (`src/platform/system_info.cpp`) write text to a `Print`. They'd need splitting into a data model (structs) that both the Debug Console and the App use. - `uxTaskGetSystemState` gives the tasks and their runtime counters; FreeRTOS runtime statistics are on in both release and debug builds. CPU % comes from the difference between two samples. - **Network counters are missing.** `CONFIG_LWIP_STATS` is off. Options: - Turn on lwIP statistics. That costs some RAM and CPU, and it counts packets per protocol, with byte counters only via MIB2 statistics, which still needs checking. - Count bytes ourselves in each service's read and write path, which is cheap and gives per-service numbers. - **History costs RAM.** For example, 120 samples × 6 values × 2 bytes ≈ 1.5 KB. Sampling should run only while the App is open, or keep a small ring all the time, so the history is there when you open the App. - **Sampling cost.** One task snapshot is about 25 tasks × ~40 bytes, allocated briefly. A 1 s period is cheap, but the App itself must not distort what it measures: its own stack and buffers show up in the numbers. - **Chip temperature** comes from the ESP32-S3's built-in sensor (ESP-IDF's `temperature_sensor` driver). It's rough, but enough to see trends. ## Questions for the design round 1. Which layout: tabs (CPU, Memory, Network, System), like btm's panels, or one dense overview plus detail pages? 2. Should history be kept only while the App is open, or always, in a small ring? 3. Network counters: lwIP statistics, our own counters per service, or both? 4. Should the task list allow any actions (none, probably), or is it read-only? 5. Should there be a log of events next to the graphs: lowest heap reached, a Wi-Fi reconnect, a fetch refused below the floor? 6. Should it export a snapshot to the card (CSV or text) for later comparison? 7. Should a compact version live in the status bar or as a Launcher tile badge (#9), for example free heap? ## Related `src/platform/system_info.cpp`, `src/services/debug_console.cpp` (`info`, `tasks`), `sdkconfig.defaults` (`CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS`, `CONFIG_LWIP_STATS`), `docs/milestones/G1.md` (Q86 floors), #7, #8, #9.
twisla added this to the S1 System basics milestone 2026-10-05 20:14:06 +00:00
Author
Owner

Built on the s1 branch after a design round (docs/milestones/S1.md, Q117 to Q127), and checked on the device.

The System App, in any build, read-only, five views with Tab between them: Overview, Tasks, Memory, Network, System. It samples once a second and keeps two minutes of history only while it's open.

Decided differently from the text above

  • History only while the App is open (Q120): no always-on ring.
  • Network: bytes counted per service by a wrapper around each service's connection (IRC, Gemini, Debug Console, Updates), not lwIP statistics (Q121). net prints them.
  • No task actions, event log or export (Q126).

Found while building it

  • A task's run-time counter only moves when the task is switched out, so the main loop, which takes the samples and has core 1 to itself, read 2 %. The sampling task now gets what's left of its core. Result: the main loop uses 100 % of core 1 at rest: #40.
  • tasks on the console measured inside its own wait and showed the loop asleep. It now samples across a second of normal running.

Tested: traffic counts exact to the byte on a Gemini fetch; the Memory view shows a TLS dip through the three floors; low-stack tasks flagged (IDLE0, IDLE1, spk_task, all the framework's).

To close when s1 is merged.

Built on the `s1` branch after a design round (`docs/milestones/S1.md`, Q117 to Q127), and checked on the device. **The System App**, in any build, read-only, five views with Tab between them: Overview, Tasks, Memory, Network, System. It samples once a second and keeps two minutes of history only while it's open. **Decided differently from the text above** - History only while the App is open (Q120): no always-on ring. - Network: bytes counted per service by a wrapper around each service's connection (IRC, Gemini, Debug Console, Updates), not lwIP statistics (Q121). `net` prints them. - No task actions, event log or export (Q126). **Found while building it** - A task's run-time counter only moves when the task is switched out, so the main loop, which takes the samples and has core 1 to itself, read 2 %. The sampling task now gets what's left of its core. Result: the main loop uses 100 % of core 1 at rest: #40. - `tasks` on the console measured inside its own wait and showed the loop asleep. It now samples across a second of normal running. **Tested**: traffic counts exact to the byte on a Gemini fetch; the Memory view shows a TLS dip through the three floors; low-stack tasks flagged (`IDLE0`, `IDLE1`, `spk_task`, all the framework's). To close when `s1` is merged.
twisla added
status
ready
and removed
status
needs-design
labels 2026-10-05 23:28:28 +00:00
Author
Owner

Shipped in v0.8.0 (s1 merged into main).

Shipped in v0.8.0 (`s1` merged into `main`).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: twisla/roro9stack#11