Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions Profiler/ProfilerTask.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/**
********************************************************************************
* @file Profiler.cpp
* @author Christy
* @date Jan 28, 2026
* @brief
********************************************************************************
*/

/************************************
* INCLUDES
************************************/
#include "ProfilerTask.hpp"
#include "SystemDefines.hpp"
#include <string>
#include <cstring>
#include <vector>

/************************************
* 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<TaskProfile>& 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<TaskProfile>& 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<TaskProfile>& 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<TaskProfile> 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());
}
74 changes: 74 additions & 0 deletions Profiler/ProfilerTask.hpp
Original file line number Diff line number Diff line change
@@ -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 <vector>

/************************************
* 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<TaskProfile>& Profiles);
void CollectCPUStats(std::vector<TaskProfile>& Profiles);
void DisplayTable(std::vector<TaskProfile>& Profiles);

private:
ProfilerTask(); // Private constructor
ProfilerTask(const ProfilerTask&); // Prevent copy-construction
ProfilerTask& operator=(const ProfilerTask&); // Prevent assignment

};
/************************************
* FUNCTION DECLARATIONS
************************************/

#endif /* PROFILER_TASK_HPP_ */
83 changes: 83 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,86 @@ An example project utilizing Cube++ with basic SOAR_PRINT support, in addition t
<img src="https://github.com/user-attachments/assets/2a83056c-2803-40c3-85aa-41dda7d9c48e" width="450">


# 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) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is top for?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"top" is the UART command to start profiling, "stoptop" is the UART command to stop.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to ask what it stands for? Why not just profile? and stop profiling?

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
```