diff --git a/Profiler/ProfilerTask.cpp b/Profiler/ProfilerTask.cpp new file mode 100644 index 0000000..677792f --- /dev/null +++ b/Profiler/ProfilerTask.cpp @@ -0,0 +1,174 @@ +/** + ******************************************************************************** + * @file Profiler.cpp + * @author Christy + * @date Jan 28, 2026 + * @brief + ******************************************************************************** + */ + +/************************************ + * INCLUDES + ************************************/ +#include "ProfilerTask.hpp" +#include "SystemDefines.hpp" +#include +#include +#include + +/************************************ + * PRIVATE MACROS AND DEFINES + ************************************/ + +/************************************ + * VARIABLES + ************************************/ +bool profileSystem = false; + +/************************************ + * FUNCTION DECLARATIONS + ************************************/ + +/************************************ + * FUNCTION DEFINITIONS + ************************************/ +/** + * @brief Constructor, sets all member variables + */ +ProfilerTask::ProfilerTask() + : Task(TASK_PROFILER_QUEUE_DEPTH_OBJS) {} + +/** + * @brief Init task for RTOS + */ +void ProfilerTask::InitTask() { + // Make sure the task is not already initialized + SOAR_ASSERT(rtTaskHandle == nullptr, "Cannot initialize Profiler task twice"); + + // Start the task + BaseType_t rtValue = xTaskCreate( + (TaskFunction_t)ProfilerTask::RunTask, (const char*)"ProfilerTask", + (uint16_t)TASK_PROFILER_STACK_DEPTH_WORDS, (void*)this, + (UBaseType_t)TASK_PROFILER_PRIORITY, (TaskHandle_t*)&rtTaskHandle); + + // Ensure creation succeded + SOAR_ASSERT(rtValue == pdPASS, "ProfilerTask::InitTask - xTaskCreate() failed"); +} + + +/** + * @brief Runcode for the ProfilerTask + */ +void ProfilerTask::Run(void* pvParams) { + while (1) { + // profile system while profileSystem is true + if (profileSystem) { + ProfileSystem(); + } + vTaskDelay(pdMS_TO_TICKS(2000)); // delay interval + } +} + + +void ProfilerTask::CollectTaskList(std::vector& Profiles) { + // place vTaskList output into buffer + char buffer[512]; + vTaskList(buffer); + + // parse tasks from line + char* line = strtok(buffer, "\n"); + while (line != NULL) { + char name[configMAX_TASK_NAME_LEN]; + char state; + int priority; + int stack; + int num; + + // read 5 attributes succesfully + if (sscanf(line, "%15[^ ] %c %d %d %d", name, &state, &priority, &stack, &num) == 5) { + TaskProfile profile; + + // if name, state, priority, stack, and num successfully read populate profile object + strncpy(profile.name, name, sizeof(profile.name)); + + // convert state to human readable format + std::string readableState; + switch (state) { + case 'R': readableState = "Ready"; + break; + case 'B': readableState = "Blocked"; + break; + case 'S': readableState = "Suspended"; + break; + case 'X': readableState = "Executing"; + break; + default: readableState = "Unknown"; + break; + } + strncpy(profile.state, readableState.c_str(), sizeof(profile.state)); + profile.priority = priority; + profile.stackRemaining = stack; + strcpy(profile.cpuPercent, "?"); + + // append task profile to global vector + Profiles.push_back(profile); + } + + line = strtok(NULL, "\n"); + } +} + + +void ProfilerTask::CollectCPUStats(std::vector& Profiles) { + // place vTaskGetRunTimeStats output into buffer + char buffer[512]; + vTaskGetRunTimeStats(buffer); + + // parse runtime stats + char* line = strtok(buffer, "\n"); + while (line != NULL) { + char name[32]; + char percent[8]; + + // read 2 attributes succesfully + if (sscanf(line, "%15s %*s %3s", name, percent) == 2) { // ignore abs time + // if name and time percentage sucessfully read, find task in global vector + for (auto& p : Profiles) { + if (strcmp(p.name, name) == 0) { + // initialize task percentage + strncpy(p.cpuPercent, percent, sizeof(p.cpuPercent)); + } + } + } + + line = strtok(NULL, "\n"); + } +} + + +void ProfilerTask::DisplayTable(std::vector& Profiles) { + // print profile message for tasks + SOAR_PRINT("Task Name, Task State, Task Priority, Stack High Water Mark (words), Task CPU Usage\r\n\n"); + + for (auto& p : Profiles) { + SOAR_PRINT("%s, %s, %d, %d, %s\r\n", p.name, p.state, p.priority, p.stackRemaining, p.cpuPercent); + } +} + + +void ProfilerTask::ProfileSystem() { + // clear terminal, previous task profiles + SOAR_PRINT("\033[2J\033[H"); + + // store task structs in vector + std::vector Profiles; + + // collect profile stats, display profile stat table + CollectTaskList(Profiles); + CollectCPUStats(Profiles); + DisplayTable(Profiles); + + // display heap stats + SOAR_PRINT("\r\nFree Heap (bytes), Min Ever Free Heap (bytes)"); + SOAR_PRINT("\r\n%lu, %lu", xPortGetFreeHeapSize(), xPortGetMinimumEverFreeHeapSize()); +} diff --git a/Profiler/ProfilerTask.hpp b/Profiler/ProfilerTask.hpp new file mode 100644 index 0000000..2b8a71a --- /dev/null +++ b/Profiler/ProfilerTask.hpp @@ -0,0 +1,74 @@ +/** + ******************************************************************************** + * @file Profiler.hpp + * @author Christy + * @date Jan 28, 2026 + * @brief + ******************************************************************************** + */ + +#ifndef PROFILER_TASK_HPP_ +#define PROFILER_TASK_HPP_ +extern bool profileSystem; +/************************************ + * INCLUDES + ************************************/ +#include "Task.hpp" +#include "SystemDefines.hpp" +#include + +/************************************ + * MACROS AND DEFINES + ************************************/ +extern bool profileSystem; // profiling status bool + +// struct to store task data +struct TaskProfile { + char name[configMAX_TASK_NAME_LEN]; + char state[10]; + int priority; + int stackRemaining; + char cpuPercent[8]; +}; +/************************************ + * TYPEDEFS + ************************************/ + +/************************************ + * CLASS DEFINITIONS + ************************************/ +class ProfilerTask : public Task { +public: + static ProfilerTask& Inst() { + static ProfilerTask inst; + return inst; + } + + void InitTask(); + void ProfileSystem(); // public handle to run profiling + +protected: + // Main run code + void Run(void* pvParams); + + // Static Task Interface, passes control to the instance Run(); + static void RunTask(void* pvParams) { + ProfilerTask::Inst().Run(pvParams); + } + + // profiling methods + void CollectTaskList(std::vector& Profiles); + void CollectCPUStats(std::vector& Profiles); + void DisplayTable(std::vector& Profiles); + +private: + ProfilerTask(); // Private constructor + ProfilerTask(const ProfilerTask&); // Prevent copy-construction + ProfilerTask& operator=(const ProfilerTask&); // Prevent assignment + +}; +/************************************ + * FUNCTION DECLARATIONS + ************************************/ + +#endif /* PROFILER_TASK_HPP_ */ \ No newline at end of file diff --git a/README.md b/README.md index 39460e4..1b8cc25 100644 --- a/README.md +++ b/README.md @@ -135,3 +135,86 @@ An example project utilizing Cube++ with basic SOAR_PRINT support, in addition t +# System Profiling +This system includes a task to profile FreeRTOS tasks using various APIs. Profiling requires an external timer. You can enable or disable profiling via a macro. For an example implementation, see the [Profiler branch](https://github.com/UCSOAR/H743VIT6TemplateRepository/tree/Christy/Profiler) in the H743VIT6TemplateRepository. + + +## 1. Enable Profiling in FreeRTOSConfig.h + +Open `FreeRTOSConfig.h` and add the following in the "/* USER CODE BEGIN Defines */" section: +``` +// enable definitions for profiler +#define configGENERATE_RUN_TIME_STATS 1 // 1 = enable profiling, 0 = disable profiling + +#if (configGENERATE_RUN_TIME_STATS == 1) +#define configUSE_TRACE_FACILITY 1 +#define configUSE_STATS_FORMATTING_FUNCTIONS 1 + +#define configUSE_STATS_FORMATTING_FUNCTIONS 1 +#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() StartStatsTimer() +#define portGET_RUN_TIME_COUNTER_VALUE() GetStatsTimerCount() + +#define INCLUDE_uxTaskGetStackHighWaterMark 1 + +#define INCLUDE_xTaskGetHandle 1 +#endif +``` +Flip ```configGENERATE_RUN_TIME_STATS``` to 1 to enable profiling, and 0 to disable it. + +## 2. Update ```DebugTask.cpp``` +### 1. Import the profiler header: ```#include "ProfilerTask.hpp"``` +### 2. Add profiling commands to ```HandleDebugMessage```: +``` +#if (configGENERATE_RUN_TIME_STATS == 1) // enable profiling commands if profiling enabled + else if (strcmp(msg, "top") == 0) { + profileSystem = true; + } else if (strcmp(msg, "stoptop") == 0) { + profileSystem = false; + } +#endif +``` + +## 3. Initialize the Profiler Task in main_system.cpp +### 1. Import the profiler header: ```#include "ProfilerTask.hpp"``` + +### 2. In the ```run_main``` method, add the following: +``` +#if (configGENERATE_RUN_TIME_STATS == 1) +ProfilerTask::Inst().InitTask(); +#endif +``` + +## 4. Define Profiler Task Parameters +### 1. in ```SystemDefines.hpp```, include a section for the profiler task: +``` +// Profiler task +constexpr uint8_t TASK_PROFILER_PRIORITY = 3; // Priority of the profiler task +constexpr uint8_t TASK_PROFILER_QUEUE_DEPTH_OBJS =10; // Size of the profiler task queue +constexpr uint16_t TASK_PROFILER_STACK_DEPTH_WORDS =512; // Size of the profiler task stack +``` +## 5. Configure the Timer +1. In the ioc file, pick an unused timer and change the clock source to "Internal Clock". +2. Select a prescalar value to configure the timer to be 10-20x faster than configTICK_RATE_HZ (normally set to 1000, can be found in FreeRTOSConfig.h). The prescalar you select depends on your system clock. + +## 6. Conditionally Initialize Timer in ```main.c``` +### 1. Navigate to "USER CODE BEGIN 2", and move the timer initialization call inside a conditional: +``` +#if (configGENERATE_RUN_TIME_STATS == 1) +MX_TIM2_Init(); // Replace TIM2 with your selected timer +#endif +``` + +### 2. Then navigate to "USER CODE BEGIN 4" and link the timer to the FreeRTOS API by copying the following: +``` +#if (configGENERATE_RUN_TIME_STATS == 1) +void StartStatsTimer(void) { + HAL_TIM_Base_Start(&htim2); // replace htim2 with your selected timer +} + +uint32_t GetStatsTimerCount(void) { + return __HAL_TIM_GET_COUNTER(&htim2); // replace htim2 with your selected timer +} +#endif +``` + +