Skip to content

Commit

Permalink
Cli: top (#3707)
Browse files Browse the repository at this point in the history
* Cli: top command to replace ps. Furi: ThreadList and thread enumeration routine.
* Sync API Symbols
* Cli: cleanup top output, add memory section. Furi: thread enumeration code cleanup. Fix doxygen and make pvs happy.
* Furi: iterator in thread_list instead of M_EACH, fix memory leak
* Update documentation
* Cli: customizable refres interval for top command
* Furi: add consistentency into float declaration in thread list
* FreeRTOSConfig: remove invalid comment

Co-authored-by: Sergei Gavrilov <who.just.the.doctor@gmail.com>
  • Loading branch information
skotopes and DrZlo13 committed Jun 13, 2024
1 parent 5a8a136 commit ca8517a
Show file tree
Hide file tree
Showing 10 changed files with 337 additions and 61 deletions.
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;
}

FuriThreadListItemArray_next(it);
}
}
}
Loading

0 comments on commit ca8517a

Please sign in to comment.