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
73 lines
2.7 KiB
C++
73 lines
2.7 KiB
C++
#include "task_stats.h"
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cstring>
|
|
|
|
namespace roro {
|
|
|
|
std::vector<TaskRow> taskRows(const std::vector<TaskSample>& before, uint32_t totalBefore,
|
|
const std::vector<TaskSample>& now, uint32_t totalNow) {
|
|
uint32_t elapsed = totalNow - totalBefore; // unsigned: right across one wrap
|
|
std::vector<TaskRow> rows;
|
|
rows.reserve(now.size());
|
|
for (const TaskSample& t : now) {
|
|
uint32_t ran = t.runtime; // a task that wasn't there before ran all of it since
|
|
for (const TaskSample& b : before)
|
|
if (b.id == t.id) {
|
|
ran = t.runtime - b.runtime;
|
|
break;
|
|
}
|
|
TaskRow r;
|
|
r.name = t.name;
|
|
r.core = t.core;
|
|
r.state = t.state;
|
|
r.priority = t.priority;
|
|
r.stackFree = t.stackFree;
|
|
r.permille = elapsed ? static_cast<uint16_t>(std::min<uint64_t>(1000, static_cast<uint64_t>(ran) * 1000 / elapsed)) : 0;
|
|
r.lowStack = t.stackFree < kLowStackBytes;
|
|
r.idle = std::strncmp(t.name, "IDLE", 4) == 0;
|
|
rows.push_back(r);
|
|
}
|
|
// The task that took the sample is running, and FreeRTOS counts a task's time when it's
|
|
// switched out: with its core to itself it never was, and its counter hardly moved. Give it
|
|
// what's left of its core once the idle task and the other tasks pinned there are counted.
|
|
for (TaskRow& r : rows) {
|
|
if (r.state != 0 || r.idle || r.core < 0) continue; // 0: eRunning
|
|
int others = 0;
|
|
bool idleSeen = false;
|
|
for (const TaskRow& o : rows) {
|
|
if (&o == &r || o.core != r.core) continue;
|
|
others += o.permille;
|
|
idleSeen |= o.idle;
|
|
}
|
|
if (idleSeen && elapsed && 1000 - others > r.permille) r.permille = static_cast<uint16_t>(std::max(0, 1000 - others));
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
int coreLoad(const std::vector<TaskRow>& rows, int core) {
|
|
for (const TaskRow& r : rows)
|
|
if (r.idle && r.core == core) return 100 - (r.permille + 5) / 10;
|
|
return -1;
|
|
}
|
|
|
|
void sortTasks(std::vector<TaskRow>& rows, TaskSort by) {
|
|
auto lower = [](const std::string& s) {
|
|
std::string l = s;
|
|
for (char& c : l) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
return l;
|
|
};
|
|
std::stable_sort(rows.begin(), rows.end(), [&](const TaskRow& a, const TaskRow& b) {
|
|
switch (by) {
|
|
case TaskSort::Share:
|
|
if (a.idle != b.idle) return !a.idle; // idle tasks last
|
|
return a.permille > b.permille;
|
|
case TaskSort::Stack: return a.stackFree < b.stackFree;
|
|
default: return lower(a.name) < lower(b.name);
|
|
}
|
|
});
|
|
}
|
|
|
|
} // namespace roro
|