Files
roro9stack/lib/system/src/task_stats.h
T
twislaandClaude Opus 5.5 e36aa50922 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
2026-10-06 01:28:26 +02:00

66 lines
2.2 KiB
C++

#pragma once
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>
namespace roro {
// One task as FreeRTOS reports it at one moment.
struct TaskSample {
uint32_t id = 0; // unique per task: a new task with an old name has another
char name[17] = {};
uint32_t runtime = 0; // microseconds on a CPU since it started; 32 bits, so it wraps every 71 minutes
uint16_t stackFree = 0; // the least it ever had left, bytes
int8_t core = -1; // -1: not pinned
uint8_t state = 0; // eTaskState
uint8_t priority = 0;
};
// What the System App and `tasks` show for a task (S1, Q119, Q122).
struct TaskRow {
std::string name;
int core = -1;
uint8_t state = 0, priority = 0;
uint16_t stackFree = 0;
uint16_t permille = 0; // share of one core over the interval, in tenths of a percent
bool lowStack = false;
bool idle = false; // a core's idle task: what's left over
};
constexpr uint16_t kLowStackBytes = 512;
// Shares from two samples: each task's run time between them over the time that passed. The
// 32-bit counters may have wrapped once in between. The task that took the samples (the one
// running) gets what's left of its core: see the .cpp.
std::vector<TaskRow> taskRows(const std::vector<TaskSample>& before, uint32_t totalBefore,
const std::vector<TaskSample>& now, uint32_t totalNow);
// A core's load in percent: what its idle task didn't use. -1 without that task in the rows.
int coreLoad(const std::vector<TaskRow>& rows, int core);
enum class TaskSort : uint8_t { Share, Stack, Name };
void sortTasks(std::vector<TaskRow>& rows, TaskSort by);
// The last N samples, oldest first (Q120).
template <typename T, size_t N>
class History {
public:
void push(T value) {
values_[(first_ + size_) % N] = value;
if (size_ < N) size_++;
else first_ = (first_ + 1) % N;
}
size_t size() const { return size_; }
T at(size_t i) const { return values_[(first_ + i) % N]; } // 0: the oldest kept
void clear() { first_ = size_ = 0; }
private:
T values_[N] = {};
size_t first_ = 0, size_ = 0;
};
} // namespace roro