From e66c4a7c48901d48f6857d9cf55f88ed745fa925 Mon Sep 17 00:00:00 2001 From: Aleksandr Kutuzov Date: Wed, 12 Jun 2024 21:55:09 +0100 Subject: [PATCH 1/8] Cli: top command to replace ps. Furi: ThreadList and thread enumeration routine. --- applications/services/cli/cli_commands.c | 63 ++++++++------ furi/core/thread.c | 62 ++++++++++--- furi/core/thread.h | 5 +- furi/core/thread_list.c | 105 +++++++++++++++++++++++ furi/core/thread_list.h | 49 +++++++++++ furi/furi.h | 1 + targets/f7/api_symbols.csv | 10 ++- targets/f7/inc/FreeRTOSConfig.h | 3 - 8 files changed, 255 insertions(+), 43 deletions(-) create mode 100644 furi/core/thread_list.c create mode 100644 furi/core/thread_list.h diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 43f1c01c4da..6752d7623de 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -382,37 +382,48 @@ 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]); + FuriThreadList* thread_list = furi_thread_list_alloc(); + while(!cli_cmd_interrupt_received(cli)) { + uint32_t tick = furi_thread_enumerate(thread_list); + + printf("\e[2J" + "\e[0;0f"); + uint32_t uptime = tick / furi_kernel_get_tick_frequency(); + printf("Uptime: %luh%lum%lus\r\n", 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])); + "%-17s %-20s %-10s %-5s %-13s %-8s %-10s %-10s %-4s\r\n\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%-11lx %-8lu %-10lu %-10zu %2.2f\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); + } + printf("\r\nThreads: %zu\r\n", furi_thread_list_size(thread_list)); + furi_delay_ms(1000); } - printf("\r\nTotal: %lu", thread_num); + furi_thread_list_free(thread_list); } void cli_command_free(Cli* cli, FuriString* args, void* context) { @@ -472,7 +483,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); diff --git a/furi/core/thread.c b/furi/core/thread.c index 9d330b71b4a..b2abc6c1fcc 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -1,4 +1,5 @@ #include "thread.h" +#include "thread_list.h" #include "kernel.h" #include "memmgr.h" #include "memmgr_heap.h" @@ -13,6 +14,8 @@ #include #include +#include + #define TAG "FuriThread" #define THREAD_NOTIFY_INDEX (1) // Index 0 is used for stream buffers @@ -547,14 +550,34 @@ 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; +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 "?"; + } +} + +uint32_t furi_thread_enumerate(FuriThreadList* thread_list) { + uint32_t i, count, tick; TaskStatus_t* task; - if(FURI_IS_IRQ_MODE() || (thread_array == NULL) || (array_item_count == 0U)) { - count = 0U; + if(FURI_IS_IRQ_MODE() || (thread_list == NULL)) { + tick = 0U; } else { vTaskSuspendAll(); + tick = furi_get_tick(); count = uxTaskGetNumberOfTasks(); task = pvPortMalloc(count * sizeof(TaskStatus_t)); @@ -562,18 +585,35 @@ uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_item_c if(task != NULL) { count = uxTaskGetSystemState(task, count, &total_run_time); - - for(i = 0U; (i < count) && (i < array_item_count); i++) { - thread_array[i] = (FuriThreadId)task[i].xHandle; + for(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; } - count = i; } (void)xTaskResumeAll(); - - vPortFree(task); + if(task) { + vPortFree(task); + furi_thread_list_cleanup(thread_list, total_run_time, tick); + } } - return count; + return tick; } const char* furi_thread_get_name(FuriThreadId thread_id) { diff --git a/furi/core/thread.h b/furi/core/thread.h index d78272a4d91..f57c4556595 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -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). */ @@ -385,7 +388,7 @@ uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeo * @param[in] array_item_count output array capacity in elements (NOT bytes) * @return total thread count (array_item_count or less) */ -uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_item_count); +uint32_t furi_thread_enumerate(FuriThreadList* thread_list); /** * @brief Get the name of a thread based on its unique identifier. diff --git a/furi/core/thread_list.c b/furi/core/thread_list.c new file mode 100644 index 00000000000..3abd0785786 --- /dev/null +++ b/furi/core/thread_list.c @@ -0,0 +1,105 @@ +#include "thread_list.h" +#include "check.h" + +#include +#include + +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); + + for + M_EACH(item, instance->items, FuriThreadListItemArray_t) { + free(item); + } + + 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_cleanup(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; + for(FuriThreadListItemArray_it(it, instance->items); !FuriThreadListItemArray_end_p(it);) { + FuriThreadListItem* item = *FuriThreadListItemArray_cref(it); + if(item->tick != tick) { + FuriThreadListItemArray_remove(instance->items, it); + 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; + } else { + item->cpu = 0.0f; + } + + FuriThreadListItemArray_next(it); + } + } +} diff --git a/furi/core/thread_list.h b/furi/core/thread_list.h new file mode 100644 index 00000000000..2b4b485c933 --- /dev/null +++ b/furi/core/thread_list.h @@ -0,0 +1,49 @@ +#pragma once + +#include "base.h" +#include "common_defines.h" +#include "thread.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + FuriThread* thread; + + const char* app_id; + const char* name; + + FuriThreadPriority priority; + uint32_t stack_address; + size_t heap; + uint32_t stack_size; + uint32_t stack_min_free; + + const char* state; + + float cpu; + + uint32_t counter_previous; + uint32_t counter_current; + + uint32_t tick; +} FuriThreadListItem; + +typedef struct FuriThreadList FuriThreadList; + +FuriThreadList* furi_thread_list_alloc(void); + +void furi_thread_list_free(FuriThreadList* instance); + +size_t furi_thread_list_size(FuriThreadList* instance); + +FuriThreadListItem* furi_thread_list_get_at(FuriThreadList* instance, size_t position); + +FuriThreadListItem* furi_thread_list_get_or_insert(FuriThreadList* instance, FuriThread* thread); + +void furi_thread_list_cleanup(FuriThreadList* instance, uint32_t runtime, uint32_t tick); + +#ifdef __cplusplus +} +#endif diff --git a/furi/furi.h b/furi/furi.h index 24e597acfe6..400cf1d6439 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -16,6 +16,7 @@ #include "core/record.h" #include "core/semaphore.h" #include "core/thread.h" +#include "core/thread_list.h" #include "core/timer.h" #include "core/string.h" #include "core/stream_buffer.h" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c2e7f59e354..ddcbe2e6db0 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,64.3,, +Version,+,67.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -1808,7 +1808,7 @@ Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCa Function,-,furi_thread_alloc_service,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" Function,-,furi_thread_disable_heap_trace,void,FuriThread* Function,+,furi_thread_enable_heap_trace,void,FuriThread* -Function,+,furi_thread_enumerate,uint32_t,"FuriThreadId*, uint32_t" +Function,+,furi_thread_enumerate,uint32_t,FuriThreadList* Function,+,furi_thread_flags_clear,uint32_t,uint32_t Function,+,furi_thread_flags_get,uint32_t, Function,+,furi_thread_flags_set,uint32_t,"FuriThreadId, uint32_t" @@ -1828,6 +1828,12 @@ Function,+,furi_thread_get_state,FuriThreadState,FuriThread* Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* +Function,+,furi_thread_list_alloc,FuriThreadList*, +Function,+,furi_thread_list_cleanup,void,"FuriThreadList*, uint32_t, uint32_t" +Function,+,furi_thread_list_free,void,FuriThreadList* +Function,+,furi_thread_list_get_at,FuriThreadListItem*,"FuriThreadList*, size_t" +Function,+,furi_thread_list_get_or_insert,FuriThreadListItem*,"FuriThreadList*, FuriThread*" +Function,+,furi_thread_list_size,size_t,FuriThreadList* Function,+,furi_thread_resume,void,FuriThreadId Function,+,furi_thread_set_appid,void,"FuriThread*, const char*" Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index e6233f624ad..14ef34c5b03 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -30,12 +30,9 @@ #define configMAX_TASK_NAME_LEN (32) /* Run-time stats - broken ATM, to be fixed */ -/* #define configGENERATE_RUN_TIME_STATS 1 -#define configRUN_TIME_COUNTER_TYPE uint64_t #define portGET_RUN_TIME_COUNTER_VALUE() (DWT->CYCCNT) #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() -*/ #define configUSE_TRACE_FACILITY 1 #define configUSE_16_BIT_TICKS 0 From c1f5d71d994027583f4418700a79c45b99f5236c Mon Sep 17 00:00:00 2001 From: Aleksandr Kutuzov Date: Wed, 12 Jun 2024 21:58:59 +0100 Subject: [PATCH 2/8] Sync API Symbols --- targets/f18/api_symbols.csv | 10 ++++++++-- targets/f7/api_symbols.csv | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index fc5d86599c7..6d6d42578f0 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,64.3,, +Version,+,65.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1594,7 +1594,7 @@ Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCa Function,-,furi_thread_alloc_service,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" Function,-,furi_thread_disable_heap_trace,void,FuriThread* Function,+,furi_thread_enable_heap_trace,void,FuriThread* -Function,+,furi_thread_enumerate,uint32_t,"FuriThreadId*, uint32_t" +Function,+,furi_thread_enumerate,uint32_t,FuriThreadList* Function,+,furi_thread_flags_clear,uint32_t,uint32_t Function,+,furi_thread_flags_get,uint32_t, Function,+,furi_thread_flags_set,uint32_t,"FuriThreadId, uint32_t" @@ -1614,6 +1614,12 @@ Function,+,furi_thread_get_state,FuriThreadState,FuriThread* Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* +Function,+,furi_thread_list_alloc,FuriThreadList*, +Function,+,furi_thread_list_cleanup,void,"FuriThreadList*, uint32_t, uint32_t" +Function,+,furi_thread_list_free,void,FuriThreadList* +Function,+,furi_thread_list_get_at,FuriThreadListItem*,"FuriThreadList*, size_t" +Function,+,furi_thread_list_get_or_insert,FuriThreadListItem*,"FuriThreadList*, FuriThread*" +Function,+,furi_thread_list_size,size_t,FuriThreadList* Function,+,furi_thread_resume,void,FuriThreadId Function,+,furi_thread_set_appid,void,"FuriThread*, const char*" Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index ddcbe2e6db0..f44b6017d21 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,67.0,, +Version,+,65.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, From 88f524841fb1ebd3a5338b4dcbceb08d7d7a3483 Mon Sep 17 00:00:00 2001 From: Aleksandr Kutuzov Date: Wed, 12 Jun 2024 23:09:27 +0100 Subject: [PATCH 3/8] Cli: cleanup top output, add memory section. Furi: thread enumeration code cleanup. Fix doxygen and make pvs happy. --- applications/services/cli/cli_commands.c | 29 +++++++-- furi/core/thread.c | 81 ++++++++++++------------ furi/core/thread.h | 12 ++-- furi/core/thread_list.c | 5 +- furi/core/thread_list.h | 72 +++++++++++++++------ targets/f18/api_symbols.csv | 4 +- targets/f7/api_symbols.csv | 4 +- 7 files changed, 128 insertions(+), 79 deletions(-) diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 6752d7623de..3c1a1a513e6 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -389,14 +389,28 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { FuriThreadList* thread_list = furi_thread_list_alloc(); while(!cli_cmd_interrupt_received(cli)) { - uint32_t tick = furi_thread_enumerate(thread_list); + uint32_t tick = furi_get_tick(); + furi_thread_enumerate(thread_list); + + printf("\e[2J\e[0;0f"); // Clear display and return to 0 - printf("\e[2J" - "\e[0;0f"); uint32_t uptime = tick / furi_kernel_get_tick_frequency(); - printf("Uptime: %luh%lum%lus\r\n", uptime / 60 / 60, uptime / 60 % 60, uptime % 60); printf( - "%-17s %-20s %-10s %-5s %-13s %-8s %-10s %-10s %-4s\r\n\r\n", + "Threads: %zu, Uptime: %luh%lum%lus\r\n", + furi_thread_list_size(thread_list), + uptime / 60 / 60, + uptime / 60 % 60, + uptime % 60); + + printf( + "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", @@ -406,10 +420,11 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { "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%-11lx %-8lu %-10lu %-10zu %2.2f\r\n", + "%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\r\n", item->app_id, item->name, item->state, @@ -420,7 +435,7 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { item->heap, (double)item->cpu); } - printf("\r\nThreads: %zu\r\n", furi_thread_list_size(thread_list)); + furi_delay_ms(1000); } furi_thread_list_free(thread_list); diff --git a/furi/core/thread.c b/furi/core/thread.c index b2abc6c1fcc..4e9477712a0 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -569,51 +569,52 @@ static const char* furi_thread_state_name(eTaskState state) { } } -uint32_t furi_thread_enumerate(FuriThreadList* thread_list) { - uint32_t i, count, tick; - TaskStatus_t* task; +bool furi_thread_enumerate(FuriThreadList* thread_list) { + furi_check(thread_list); + furi_check(!FURI_IS_IRQ_MODE()); - if(FURI_IS_IRQ_MODE() || (thread_list == NULL)) { - tick = 0U; - } else { - vTaskSuspendAll(); - tick = furi_get_tick(); + bool result = false; - count = uxTaskGetNumberOfTasks(); - task = pvPortMalloc(count * sizeof(TaskStatus_t)); - configRUN_TIME_COUNTER_TYPE total_run_time; + vTaskSuspendAll(); + do { + uint32_t tick = furi_get_tick(); + uint32_t count = uxTaskGetNumberOfTasks(); - if(task != NULL) { - count = uxTaskGetSystemState(task, count, &total_run_time); - for(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(); - if(task) { - vPortFree(task); - furi_thread_list_cleanup(thread_list, total_run_time, tick); + 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; } - } - return tick; + vPortFree(task); + furi_thread_list_process(thread_list, total_run_time, tick); + + result = true; + } while(false); + (void)xTaskResumeAll(); + + return result; } const char* furi_thread_get_name(FuriThreadId thread_id) { diff --git a/furi/core/thread.h b/furi/core/thread.h index f57c4556595..9c113bd4943 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -382,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(FuriThreadList* thread_list); +bool furi_thread_enumerate(FuriThreadList* thread_list); /** * @brief Get the name of a thread based on its unique identifier. diff --git a/furi/core/thread_list.c b/furi/core/thread_list.c index 3abd0785786..31cbe792ddc 100644 --- a/furi/core/thread_list.c +++ b/furi/core/thread_list.c @@ -76,7 +76,7 @@ FuriThreadListItem* furi_thread_list_get_or_insert(FuriThreadList* instance, Fur return item; } -void furi_thread_list_cleanup(FuriThreadList* instance, uint32_t runtime, uint32_t tick) { +void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32_t tick) { furi_check(instance); instance->runtime_previous = instance->runtime_current; @@ -89,12 +89,13 @@ void furi_thread_list_cleanup(FuriThreadList* instance, uint32_t runtime, uint32 FuriThreadListItem* item = *FuriThreadListItemArray_cref(it); if(item->tick != tick) { FuriThreadListItemArray_remove(instance->items, it); - FuriThreadListItemDict_erase(instance->search, (uint32_t)item->thread); + (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 = .0f; } else { item->cpu = 0.0f; } diff --git a/furi/core/thread_list.h b/furi/core/thread_list.h index 2b4b485c933..bf15e4032be 100644 --- a/furi/core/thread_list.h +++ b/furi/core/thread_list.h @@ -9,40 +9,72 @@ extern "C" { #endif typedef struct { - FuriThread* thread; - - const char* app_id; - const char* name; - - FuriThreadPriority priority; - uint32_t stack_address; - size_t heap; - uint32_t stack_size; - uint32_t stack_min_free; - - const char* state; - - float cpu; - - uint32_t counter_previous; - uint32_t counter_current; - - uint32_t tick; + FuriThread* thread; /**< Pointer to FuriThread, valid while it is running */ + const char* app_id; /**< Thread application id, valid while it is running */ + const char* name; /**< Thread name, valid while it is running */ + FuriThreadPriority priority; /**< Thread priority */ + uint32_t stack_address; /**< Thread stack address */ + size_t heap; /**< Thread heap size if tracking enabled, 0 - otherwise */ + uint32_t stack_size; /**< Thread stack size */ + uint32_t stack_min_free; /**< Thread minimum of the stack size ever reached */ + const char* + state; /**< Thread state, can be: "Running", "Ready", "Blocked", "Suspended", "Deleted", "Invalid" */ + float cpu; /**< Thread CPU usage time in percents (including interrupts happened while running) */ + + // Service variables + uint32_t counter_previous; /**< Thread previous runtime counter */ + uint32_t counter_current; /**< Thread current runtime counter */ + uint32_t tick; /**< Thread last seen tick */ } FuriThreadListItem; +/** Anonymous FuriThreadList type */ typedef struct FuriThreadList FuriThreadList; +/** Allocate FuriThreadList instance + * + * @return FuriThreadList instance + */ FuriThreadList* furi_thread_list_alloc(void); +/** Free FuriThreadList instance + * + * @param instance The FuriThreadList instance to free + */ void furi_thread_list_free(FuriThreadList* instance); +/** Get FuriThreadList instance size + * + * @param instance The instance + * + * @return Item count + */ size_t furi_thread_list_size(FuriThreadList* instance); +/** Get item at position + * + * @param instance The FuriThreadList instance + * @param[in] position The position of the item + * + * @return The FuriThreadListItem instance + */ FuriThreadListItem* furi_thread_list_get_at(FuriThreadList* instance, size_t position); +/** Get item by thread FuriThread pointer + * + * @param instance The FuriThreadList instance + * @param thread The FuriThread pointer + * + * @return The FuriThreadListItem instance + */ FuriThreadListItem* furi_thread_list_get_or_insert(FuriThreadList* instance, FuriThread* thread); -void furi_thread_list_cleanup(FuriThreadList* instance, uint32_t runtime, uint32_t tick); +/** Process items in the FuriThreadList instance + * + * @param instance The instance + * @param[in] runtime The runtime of the system since start + * @param[in] tick The tick when processing happened + */ +void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32_t tick); #ifdef __cplusplus } diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 6d6d42578f0..4b40107ce35 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1594,7 +1594,7 @@ Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCa Function,-,furi_thread_alloc_service,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" Function,-,furi_thread_disable_heap_trace,void,FuriThread* Function,+,furi_thread_enable_heap_trace,void,FuriThread* -Function,+,furi_thread_enumerate,uint32_t,FuriThreadList* +Function,+,furi_thread_enumerate,_Bool,FuriThreadList* Function,+,furi_thread_flags_clear,uint32_t,uint32_t Function,+,furi_thread_flags_get,uint32_t, Function,+,furi_thread_flags_set,uint32_t,"FuriThreadId, uint32_t" @@ -1615,10 +1615,10 @@ Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* Function,+,furi_thread_list_alloc,FuriThreadList*, -Function,+,furi_thread_list_cleanup,void,"FuriThreadList*, uint32_t, uint32_t" Function,+,furi_thread_list_free,void,FuriThreadList* Function,+,furi_thread_list_get_at,FuriThreadListItem*,"FuriThreadList*, size_t" Function,+,furi_thread_list_get_or_insert,FuriThreadListItem*,"FuriThreadList*, FuriThread*" +Function,+,furi_thread_list_process,void,"FuriThreadList*, uint32_t, uint32_t" Function,+,furi_thread_list_size,size_t,FuriThreadList* Function,+,furi_thread_resume,void,FuriThreadId Function,+,furi_thread_set_appid,void,"FuriThread*, const char*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index f44b6017d21..d64571963e5 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1808,7 +1808,7 @@ Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCa Function,-,furi_thread_alloc_service,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" Function,-,furi_thread_disable_heap_trace,void,FuriThread* Function,+,furi_thread_enable_heap_trace,void,FuriThread* -Function,+,furi_thread_enumerate,uint32_t,FuriThreadList* +Function,+,furi_thread_enumerate,_Bool,FuriThreadList* Function,+,furi_thread_flags_clear,uint32_t,uint32_t Function,+,furi_thread_flags_get,uint32_t, Function,+,furi_thread_flags_set,uint32_t,"FuriThreadId, uint32_t" @@ -1829,10 +1829,10 @@ Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* Function,+,furi_thread_list_alloc,FuriThreadList*, -Function,+,furi_thread_list_cleanup,void,"FuriThreadList*, uint32_t, uint32_t" Function,+,furi_thread_list_free,void,FuriThreadList* Function,+,furi_thread_list_get_at,FuriThreadListItem*,"FuriThreadList*, size_t" Function,+,furi_thread_list_get_or_insert,FuriThreadListItem*,"FuriThreadList*, FuriThread*" +Function,+,furi_thread_list_process,void,"FuriThreadList*, uint32_t, uint32_t" Function,+,furi_thread_list_size,size_t,FuriThreadList* Function,+,furi_thread_resume,void,FuriThreadId Function,+,furi_thread_set_appid,void,"FuriThread*, const char*" From 97f9e44cbc8042c4defb2b57f52f79a2674e366f Mon Sep 17 00:00:00 2001 From: Aleksandr Kutuzov Date: Wed, 12 Jun 2024 23:26:27 +0100 Subject: [PATCH 4/8] Furi: iterator in thread_list instead of M_EACH, fix memory leak --- furi/core/thread_list.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/furi/core/thread_list.c b/furi/core/thread_list.c index 31cbe792ddc..978c014610a 100644 --- a/furi/core/thread_list.c +++ b/furi/core/thread_list.c @@ -37,10 +37,13 @@ FuriThreadList* furi_thread_list_alloc(void) { void furi_thread_list_free(FuriThreadList* instance) { furi_check(instance); - for - M_EACH(item, instance->items, FuriThreadListItemArray_t) { - free(item); - } + 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); @@ -85,7 +88,8 @@ void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32 uint32_t runtime_counter = instance->runtime_current - instance->runtime_previous; FuriThreadListItemArray_it_t it; - for(FuriThreadListItemArray_it(it, instance->items); !FuriThreadListItemArray_end_p(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); From f8d4c25a86ec69181e18dbd6df275c8d4bb4eb3e Mon Sep 17 00:00:00 2001 From: Aleksandr Kutuzov Date: Thu, 13 Jun 2024 10:37:29 +0100 Subject: [PATCH 5/8] Update documentation --- documentation/AppManifests.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index b612df1b79f..98a38ffd85f 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -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. From 4167022def2c99ac0530488d803b69c7d864e7a6 Mon Sep 17 00:00:00 2001 From: Aleksandr Kutuzov Date: Thu, 13 Jun 2024 10:47:03 +0100 Subject: [PATCH 6/8] Cli: customizable refres interval for top command --- applications/services/cli/cli_commands.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 3c1a1a513e6..7bf86c90cf5 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -384,15 +384,17 @@ void cli_command_led(Cli* cli, FuriString* args, void* context) { static void cli_command_top(Cli* cli, FuriString* args, void* context) { UNUSED(cli); - UNUSED(args); UNUSED(context); + 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); - printf("\e[2J\e[0;0f"); // Clear display and return to 0 + if(interval) printf("\e[2J\e[0;0f"); // Clear display and return to 0 uint32_t uptime = tick / furi_kernel_get_tick_frequency(); printf( @@ -436,7 +438,11 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { (double)item->cpu); } - furi_delay_ms(1000); + if(interval > 0) { + furi_delay_ms(interval); + } else { + break; + } } furi_thread_list_free(thread_list); } From 179483837acdadf8befb91e9107b8e5555ae5d92 Mon Sep 17 00:00:00 2001 From: Aleksandr Kutuzov Date: Thu, 13 Jun 2024 17:56:33 +0100 Subject: [PATCH 7/8] Furi: add consistentency into float declaration in thread list --- furi/core/thread_list.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/furi/core/thread_list.c b/furi/core/thread_list.c index 978c014610a..65ee11ad388 100644 --- a/furi/core/thread_list.c +++ b/furi/core/thread_list.c @@ -99,7 +99,7 @@ void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32 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 = .0f; + if(item->cpu > 200.0f) item->cpu = 0.0f; } else { item->cpu = 0.0f; } From 1f682f6b7fb5d42308b832ceff040c09cdf1048d Mon Sep 17 00:00:00 2001 From: Aleksandr Kutuzov Date: Thu, 13 Jun 2024 17:58:55 +0100 Subject: [PATCH 8/8] FreeRTOSConfig: remove invalid comment --- targets/f7/inc/FreeRTOSConfig.h | 1 - 1 file changed, 1 deletion(-) diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index 14ef34c5b03..37aac1eb092 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -29,7 +29,6 @@ // #define configTOTAL_HEAP_SIZE ((size_t)0) #define configMAX_TASK_NAME_LEN (32) -/* Run-time stats - broken ATM, to be fixed */ #define configGENERATE_RUN_TIME_STATS 1 #define portGET_RUN_TIME_COUNTER_VALUE() (DWT->CYCCNT) #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()