Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cli: top #3707

Merged
merged 9 commits into from
Jun 13, 2024
86 changes: 59 additions & 27 deletions applications/services/cli/cli_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -382,37 +382,69 @@ void cli_command_led(Cli* cli, FuriString* args, void* context) {
furi_record_close(RECORD_NOTIFICATION);
}

void cli_command_ps(Cli* cli, FuriString* args, void* context) {
static void cli_command_top(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
UNUSED(args);
UNUSED(context);

const uint8_t threads_num_max = 32;
FuriThreadId threads_ids[threads_num_max];
uint32_t thread_num = furi_thread_enumerate(threads_ids, threads_num_max);
printf(
"%-17s %-20s %-5s %-13s %-6s %-8s %s\r\n",
"AppID",
"Name",
"Prio",
"Stack start",
"Heap",
"Stack",
"Stack min free");
for(uint8_t i = 0; i < thread_num; i++) {
TaskControlBlock* tcb = (TaskControlBlock*)threads_ids[i];
size_t thread_heap = memmgr_heap_get_thread_memory(threads_ids[i]);
int interval = 1000;
args_read_int_and_trim(args, &interval);

FuriThreadList* thread_list = furi_thread_list_alloc();
while(!cli_cmd_interrupt_received(cli)) {
uint32_t tick = furi_get_tick();
furi_thread_enumerate(thread_list);

if(interval) printf("\e[2J\e[0;0f"); // Clear display and return to 0

uint32_t uptime = tick / furi_kernel_get_tick_frequency();
printf(
"Threads: %zu, Uptime: %luh%lum%lus\r\n",
furi_thread_list_size(thread_list),
uptime / 60 / 60,
uptime / 60 % 60,
uptime % 60);

printf(
"%-17s %-20s %-5d 0x%-11lx %-6zu %-8lu %-8lu\r\n",
furi_thread_get_appid(threads_ids[i]),
furi_thread_get_name(threads_ids[i]),
furi_thread_get_priority(threads_ids[i]),
(uint32_t)tcb->pxStack,
thread_heap == MEMMGR_HEAP_UNKNOWN ? 0u : thread_heap,
(uint32_t)(tcb->pxEndOfStack - tcb->pxStack + 1) * sizeof(StackType_t),
furi_thread_get_stack_space(threads_ids[i]));
"Heap: total %zu, free %zu, minimum %zu, max block %zu\r\n\r\n",
memmgr_get_total_heap(),
memmgr_get_free_heap(),
memmgr_get_minimum_free_heap(),
memmgr_heap_get_max_free_block());

printf(
"%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\r\n",
"AppID",
"Name",
"State",
"Prio",
"Stack start",
"Stack",
"Stack Min",
"Heap",
"CPU");

for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) {
const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i);
printf(
"%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\r\n",
item->app_id,
item->name,
item->state,
item->priority,
item->stack_address,
item->stack_size,
item->stack_min_free,
item->heap,
(double)item->cpu);
}

if(interval > 0) {
furi_delay_ms(interval);
} else {
break;
}
}
printf("\r\nTotal: %lu", thread_num);
furi_thread_list_free(thread_list);
}

void cli_command_free(Cli* cli, FuriString* args, void* context) {
Expand Down Expand Up @@ -472,7 +504,7 @@ void cli_commands_init(Cli* cli) {
cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL);
cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL);
cli_add_command(cli, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL);
cli_add_command(cli, "ps", CliCommandFlagParallelSafe, cli_command_ps, NULL);
cli_add_command(cli, "top", CliCommandFlagParallelSafe, cli_command_top, NULL);
cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL);
cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL);

Expand Down
2 changes: 1 addition & 1 deletion documentation/AppManifests.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Only two parameters are mandatory: **appid** and **apptype**. Others are optiona
- **requires**: list of application IDs to include in the build configuration when the current application is referenced in the list of applications to build.
- **conflicts**: list of application IDs with which the current application conflicts. If any of them is found in the constructed application list, `fbt` will abort the firmware build process.
- **provides**: functionally identical to **_requires_** field.
- **stack_size**: stack size in bytes to allocate for an application on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. _Note: you can use `ps` and `free` CLI commands to profile your app's memory usage._
- **stack_size**: stack size in bytes to allocate for an application on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. _Note: you can use `top` and `free` CLI commands to profile your app's memory usage._
- **icon**: animated icon name from built-in assets to be used when building the app as a part of the firmware.
- **order**: order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. _Used for ordering startup hooks and menu entries._
- **sdk_headers**: list of C header files from this app's code to include in API definitions for external applications.
Expand Down
79 changes: 60 additions & 19 deletions furi/core/thread.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "thread.h"
#include "thread_list.h"
#include "kernel.h"
#include "memmgr.h"
#include "memmgr_heap.h"
Expand All @@ -13,6 +14,8 @@
#include <stdint.h>
#include <task.h>

#include <task_control_block.h>

#define TAG "FuriThread"

#define THREAD_NOTIFY_INDEX (1) // Index 0 is used for stream buffers
Expand Down Expand Up @@ -547,33 +550,71 @@ uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeo
return rflags;
}

uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_item_count) {
uint32_t i, count;
TaskStatus_t* task;
static const char* furi_thread_state_name(eTaskState state) {
switch(state) {
case eRunning:
return "Running";
case eReady:
return "Ready";
case eBlocked:
return "Blocked";
case eSuspended:
return "Suspended";
case eDeleted:
return "Deleted";
case eInvalid:
return "Invalid";
default:
return "?";
}
}

if(FURI_IS_IRQ_MODE() || (thread_array == NULL) || (array_item_count == 0U)) {
count = 0U;
} else {
vTaskSuspendAll();
bool furi_thread_enumerate(FuriThreadList* thread_list) {
furi_check(thread_list);
furi_check(!FURI_IS_IRQ_MODE());

count = uxTaskGetNumberOfTasks();
task = pvPortMalloc(count * sizeof(TaskStatus_t));
configRUN_TIME_COUNTER_TYPE total_run_time;
bool result = false;

if(task != NULL) {
count = uxTaskGetSystemState(task, count, &total_run_time);
vTaskSuspendAll();
do {
uint32_t tick = furi_get_tick();
uint32_t count = uxTaskGetNumberOfTasks();

for(i = 0U; (i < count) && (i < array_item_count); i++) {
thread_array[i] = (FuriThreadId)task[i].xHandle;
}
count = i;
TaskStatus_t* task = pvPortMalloc(count * sizeof(TaskStatus_t));

if(!task) break;

configRUN_TIME_COUNTER_TYPE total_run_time;
count = uxTaskGetSystemState(task, count, &total_run_time);
for(uint32_t i = 0U; i < count; i++) {
TaskControlBlock* tcb = (TaskControlBlock*)task[i].xHandle;

FuriThreadListItem* item =
furi_thread_list_get_or_insert(thread_list, (FuriThread*)task[i].xHandle);

item->thread = (FuriThreadId)task[i].xHandle;
item->app_id = furi_thread_get_appid(item->thread);
item->name = task[i].pcTaskName;
item->priority = task[i].uxCurrentPriority;
item->stack_address = (uint32_t)tcb->pxStack;
size_t thread_heap = memmgr_heap_get_thread_memory(item->thread);
item->heap = thread_heap == MEMMGR_HEAP_UNKNOWN ? 0u : thread_heap;
item->stack_size = (tcb->pxEndOfStack - tcb->pxStack + 1) * sizeof(StackType_t);
item->stack_min_free = furi_thread_get_stack_space(item->thread);
item->state = furi_thread_state_name(task[i].eCurrentState);
item->counter_previous = item->counter_current;
item->counter_current = task[i].ulRunTimeCounter;
item->tick = tick;
}
(void)xTaskResumeAll();

vPortFree(task);
}
furi_thread_list_process(thread_list, total_run_time, tick);

result = true;
} while(false);
(void)xTaskResumeAll();

return count;
return result;
}

const char* furi_thread_get_name(FuriThreadId thread_id) {
Expand Down
15 changes: 9 additions & 6 deletions furi/core/thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ typedef enum {
*/
typedef struct FuriThread FuriThread;

/** FuriThreadList type */
typedef struct FuriThreadList FuriThreadList;

/**
* @brief Unique thread identifier type (used by the OS kernel).
*/
Expand Down Expand Up @@ -379,13 +382,13 @@ uint32_t furi_thread_flags_get(void);
uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout);

/**
* @brief Enumerate all threads.
*
* @param[out] thread_array pointer to the output array (must be properly allocated)
* @param[in] array_item_count output array capacity in elements (NOT bytes)
* @return total thread count (array_item_count or less)
* @brief Enumerate all threads.
*
* @param[out] thread_list pointer to the FuriThreadList container
*
* @return true on success, false otherwise
*/
uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_item_count);
bool furi_thread_enumerate(FuriThreadList* thread_list);

/**
* @brief Get the name of a thread based on its unique identifier.
Expand Down
110 changes: 110 additions & 0 deletions furi/core/thread_list.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#include "thread_list.h"
#include "check.h"

#include <m-array.h>
#include <m-dict.h>

ARRAY_DEF(FuriThreadListItemArray, FuriThreadListItem*, M_PTR_OPLIST) // NOLINT

#define M_OPL_FuriThreadListItemArray_t() ARRAY_OPLIST(FuriThreadListItemArray, M_PTR_OPLIST)

DICT_DEF2(
FuriThreadListItemDict,
uint32_t,
M_DEFAULT_OPLIST,
FuriThreadListItem*,
M_PTR_OPLIST) // NOLINT

#define M_OPL_FuriThreadListItemDict_t() \
DICT_OPLIST(FuriThreadListItemDict, M_DEFAULT_OPLIST, M_PTR_OPLIST)

struct FuriThreadList {
FuriThreadListItemArray_t items;
FuriThreadListItemDict_t search;
uint32_t runtime_previous;
uint32_t runtime_current;
};

FuriThreadList* furi_thread_list_alloc(void) {
FuriThreadList* instance = malloc(sizeof(FuriThreadList));

FuriThreadListItemArray_init(instance->items);
FuriThreadListItemDict_init(instance->search);

return instance;
}

void furi_thread_list_free(FuriThreadList* instance) {
furi_check(instance);

FuriThreadListItemArray_it_t it;
FuriThreadListItemArray_it(it, instance->items);
while(!FuriThreadListItemArray_end_p(it)) {
FuriThreadListItem* item = *FuriThreadListItemArray_cref(it);
free(item);
FuriThreadListItemArray_next(it);
}

FuriThreadListItemDict_clear(instance->search);
FuriThreadListItemArray_clear(instance->items);

free(instance);
}

size_t furi_thread_list_size(FuriThreadList* instance) {
furi_check(instance);
return FuriThreadListItemArray_size(instance->items);
}

FuriThreadListItem* furi_thread_list_get_at(FuriThreadList* instance, size_t position) {
furi_check(instance);
furi_check(position < furi_thread_list_size(instance));

return *FuriThreadListItemArray_get(instance->items, position);
}

FuriThreadListItem* furi_thread_list_get_or_insert(FuriThreadList* instance, FuriThread* thread) {
furi_check(instance);

FuriThreadListItem** item_ptr = FuriThreadListItemDict_get(instance->search, (uint32_t)thread);
if(item_ptr) {
return *item_ptr;
}

FuriThreadListItem* item = malloc(sizeof(FuriThreadListItem));

FuriThreadListItemArray_push_back(instance->items, item);
FuriThreadListItemDict_set_at(instance->search, (uint32_t)thread, item);

return item;
}

void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32_t tick) {
furi_check(instance);

instance->runtime_previous = instance->runtime_current;
instance->runtime_current = runtime;

uint32_t runtime_counter = instance->runtime_current - instance->runtime_previous;

FuriThreadListItemArray_it_t it;
FuriThreadListItemArray_it(it, instance->items);
while(!FuriThreadListItemArray_end_p(it)) {
FuriThreadListItem* item = *FuriThreadListItemArray_cref(it);
if(item->tick != tick) {
FuriThreadListItemArray_remove(instance->items, it);
(void)FuriThreadListItemDict_erase(instance->search, (uint32_t)item->thread);
free(item);
} else {
uint32_t item_counter = item->counter_current - item->counter_previous;
if(item_counter && item->counter_previous && item->counter_current) {
item->cpu = (float)item_counter / (float)runtime_counter * 100.0f;
if(item->cpu > 200.0f) item->cpu = 0.0f;
} else {
item->cpu = 0.0f;
skotopes marked this conversation as resolved.
Show resolved Hide resolved
}

FuriThreadListItemArray_next(it);
}
}
}
Loading
Loading