Public Access
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:
@@ -0,0 +1,160 @@
|
||||
#include <unity.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "task_stats.h"
|
||||
|
||||
using namespace roro;
|
||||
|
||||
void setUp() {}
|
||||
void tearDown() {}
|
||||
|
||||
static TaskSample task(uint32_t id, const char* name, uint32_t runtime, uint16_t stackFree = 2000, int core = 1) {
|
||||
TaskSample t;
|
||||
t.id = id;
|
||||
std::snprintf(t.name, sizeof t.name, "%s", name);
|
||||
t.runtime = runtime;
|
||||
t.stackFree = stackFree;
|
||||
t.core = static_cast<int8_t>(core);
|
||||
t.state = 2; // blocked, unless a test says it's the one running
|
||||
return t;
|
||||
}
|
||||
|
||||
static const TaskRow* find(const std::vector<TaskRow>& rows, const char* name) {
|
||||
for (auto& r : rows)
|
||||
if (r.name == name) return &r;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Q119: a task's share is its run time over the interval between two samples, in tenths of a percent.
|
||||
void test_shares_over_the_interval() {
|
||||
std::vector<TaskSample> before = {task(1, "loopTask", 5000000), task(2, "IDLE1", 1000000), task(3, "IDLE0", 4000000, 240, 0),
|
||||
task(4, "wifi", 900000, 4000, 0)};
|
||||
std::vector<TaskSample> now = {task(1, "loopTask", 5810000), task(2, "IDLE1", 1170000), task(3, "IDLE0", 4780000, 240, 0),
|
||||
task(4, "wifi", 1090000, 4000, 0)};
|
||||
auto rows = taskRows(before, 10000000, now, 11000000); // one second later
|
||||
TEST_ASSERT_EQUAL_size_t(4, rows.size());
|
||||
TEST_ASSERT_EQUAL_UINT16(810, find(rows, "loopTask")->permille);
|
||||
TEST_ASSERT_EQUAL_UINT16(170, find(rows, "IDLE1")->permille);
|
||||
TEST_ASSERT_EQUAL_UINT16(780, find(rows, "IDLE0")->permille);
|
||||
TEST_ASSERT_EQUAL_UINT16(190, find(rows, "wifi")->permille);
|
||||
// A core's load is what its idle task didn't use.
|
||||
TEST_ASSERT_EQUAL_INT(22, coreLoad(rows, 0));
|
||||
TEST_ASSERT_EQUAL_INT(83, coreLoad(rows, 1));
|
||||
TEST_ASSERT_EQUAL_INT(-1, coreLoad(rows, 2));
|
||||
}
|
||||
|
||||
// FreeRTOS adds to a task's run time when it's switched out. The task doing the sampling is
|
||||
// running: if nothing else wanted its core, it was never switched out and its counter hardly
|
||||
// moved. Its real share is what's left of its core.
|
||||
void test_the_running_task_gets_what_is_left_of_its_core() {
|
||||
auto running = [](TaskSample t) {
|
||||
t.state = 0; // eRunning
|
||||
return t;
|
||||
};
|
||||
std::vector<TaskSample> before = {running(task(1, "loopTask", 5000000)), task(2, "IDLE1", 1000000), task(3, "radio", 100),
|
||||
task(4, "IDLE0", 4000000, 240, 0), task(5, "wifi", 900000, 4000, 0)};
|
||||
std::vector<TaskSample> now = {running(task(1, "loopTask", 5020000)), task(2, "IDLE1", 1000000), task(3, "radio", 30100),
|
||||
task(4, "IDLE0", 4980000, 240, 0), task(5, "wifi", 910000, 4000, 0)};
|
||||
auto rows = taskRows(before, 0, now, 1000000);
|
||||
TEST_ASSERT_EQUAL_UINT16(970, find(rows, "loopTask")->permille); // not the 2 % its counter says
|
||||
TEST_ASSERT_EQUAL_UINT16(30, find(rows, "radio")->permille);
|
||||
TEST_ASSERT_EQUAL_UINT16(10, find(rows, "wifi")->permille); // another core: untouched
|
||||
TEST_ASSERT_EQUAL_INT(100, coreLoad(rows, 1));
|
||||
TEST_ASSERT_EQUAL_INT(2, coreLoad(rows, 0));
|
||||
}
|
||||
|
||||
// When its counter did keep up (it was switched out often), nothing is added.
|
||||
void test_the_running_task_keeps_its_own_count_when_it_is_higher() {
|
||||
std::vector<TaskSample> before = {task(1, "loopTask", 0), task(2, "IDLE1", 0)};
|
||||
std::vector<TaskSample> now = {task(1, "loopTask", 600000), task(2, "IDLE1", 400000)};
|
||||
now[0].state = before[0].state = 0;
|
||||
auto rows = taskRows(before, 0, now, 1000000);
|
||||
TEST_ASSERT_EQUAL_UINT16(600, find(rows, "loopTask")->permille);
|
||||
}
|
||||
|
||||
// The counters are 32-bit microseconds: they wrap every 71 minutes.
|
||||
void test_counters_wrap() {
|
||||
std::vector<TaskSample> before = {task(1, "loopTask", 0xFFFFF000u)};
|
||||
std::vector<TaskSample> now = {task(1, "loopTask", 0x0007A120u - 0x1000u)}; // 500 ms of run time later
|
||||
auto rows = taskRows(before, 0xFFFF0000u, now, 0xFFFF0000u + 1000000u);
|
||||
TEST_ASSERT_EQUAL_UINT16(500, rows[0].permille);
|
||||
}
|
||||
|
||||
void test_tasks_come_and_go() {
|
||||
std::vector<TaskSample> before = {task(1, "loopTask", 100), task(7, "gemini", 50000)};
|
||||
std::vector<TaskSample> now = {task(1, "loopTask", 400100), task(9, "gemini", 30000)}; // a new gemini task: another id
|
||||
auto rows = taskRows(before, 0, now, 1000000);
|
||||
TEST_ASSERT_EQUAL_size_t(2, rows.size());
|
||||
TEST_ASSERT_EQUAL_UINT16(400, find(rows, "loopTask")->permille);
|
||||
TEST_ASSERT_EQUAL_UINT16(30, find(rows, "gemini")->permille); // all its run time is inside the interval
|
||||
}
|
||||
|
||||
void test_no_time_passed() {
|
||||
std::vector<TaskSample> one = {task(1, "loopTask", 100)};
|
||||
auto rows = taskRows(one, 500, one, 500);
|
||||
TEST_ASSERT_EQUAL_UINT16(0, rows[0].permille);
|
||||
TEST_ASSERT_EQUAL_INT(-1, coreLoad(rows, 1)); // no idle task in the sample
|
||||
}
|
||||
|
||||
// Q122: under 512 bytes of stack left is flagged.
|
||||
void test_low_stack() {
|
||||
std::vector<TaskSample> s = {task(1, "spk_task", 0, 264), task(2, "IDLE0", 0, 240, 0), task(3, "radio", 0, 2360),
|
||||
task(4, "edge", 0, 512)};
|
||||
auto rows = taskRows(s, 0, s, 1000);
|
||||
TEST_ASSERT_TRUE(find(rows, "spk_task")->lowStack);
|
||||
TEST_ASSERT_TRUE(find(rows, "IDLE0")->lowStack);
|
||||
TEST_ASSERT_FALSE(find(rows, "radio")->lowStack);
|
||||
TEST_ASSERT_FALSE(find(rows, "edge")->lowStack);
|
||||
}
|
||||
|
||||
void test_sorting() {
|
||||
std::vector<TaskSample> before = {task(1, "wifi", 0, 4000), task(2, "loopTask", 0, 2800), task(3, "IDLE0", 0, 240),
|
||||
task(4, "irc", 0, 3600)};
|
||||
std::vector<TaskSample> now = {task(1, "wifi", 190000, 4000), task(2, "loopTask", 810000, 2800), task(3, "IDLE0", 780000, 240),
|
||||
task(4, "irc", 0, 3600)};
|
||||
auto rows = taskRows(before, 0, now, 1000000);
|
||||
sortTasks(rows, TaskSort::Share); // busiest first, idle tasks last: they're what's left over
|
||||
TEST_ASSERT_EQUAL_STRING("loopTask", rows[0].name.c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("wifi", rows[1].name.c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("irc", rows[2].name.c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("IDLE0", rows[3].name.c_str());
|
||||
sortTasks(rows, TaskSort::Stack); // least stack left first
|
||||
TEST_ASSERT_EQUAL_STRING("IDLE0", rows[0].name.c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("loopTask", rows[1].name.c_str());
|
||||
sortTasks(rows, TaskSort::Name); // letters only, whatever the case
|
||||
TEST_ASSERT_EQUAL_STRING("IDLE0", rows[0].name.c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("irc", rows[1].name.c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("loopTask", rows[2].name.c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("wifi", rows[3].name.c_str());
|
||||
}
|
||||
|
||||
// Q120: two minutes of history, the oldest sample dropped first.
|
||||
void test_history() {
|
||||
History<uint8_t, 4> h;
|
||||
TEST_ASSERT_EQUAL_size_t(0, h.size());
|
||||
for (uint8_t v : {10, 20, 30}) h.push(v);
|
||||
TEST_ASSERT_EQUAL_size_t(3, h.size());
|
||||
TEST_ASSERT_EQUAL_UINT8(10, h.at(0));
|
||||
TEST_ASSERT_EQUAL_UINT8(30, h.at(2));
|
||||
for (uint8_t v : {40, 50, 60}) h.push(v);
|
||||
TEST_ASSERT_EQUAL_size_t(4, h.size());
|
||||
TEST_ASSERT_EQUAL_UINT8(30, h.at(0)); // oldest kept
|
||||
TEST_ASSERT_EQUAL_UINT8(60, h.at(3)); // newest
|
||||
h.clear();
|
||||
TEST_ASSERT_EQUAL_size_t(0, h.size());
|
||||
}
|
||||
|
||||
int main() {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_shares_over_the_interval);
|
||||
RUN_TEST(test_the_running_task_gets_what_is_left_of_its_core);
|
||||
RUN_TEST(test_the_running_task_keeps_its_own_count_when_it_is_higher);
|
||||
RUN_TEST(test_counters_wrap);
|
||||
RUN_TEST(test_tasks_come_and_go);
|
||||
RUN_TEST(test_no_time_passed);
|
||||
RUN_TEST(test_low_stack);
|
||||
RUN_TEST(test_sorting);
|
||||
RUN_TEST(test_history);
|
||||
return UNITY_END();
|
||||
}
|
||||
Reference in New Issue
Block a user