The "Holy Bible" for embedded engineers
Comprehensive guide to implementing performance monitoring systems for CPU utilization, memory usage, and timing analysis in embedded real-time systems with FreeRTOS examples
Performance monitoring is like having a dashboard in your car that shows you exactly how fast you’re going, how much fuel you’re using, and whether your engine is running smoothly. In embedded systems, it’s your window into what’s happening under the hood, helping you spot problems before they become critical.
In real-time systems, performance issues can mean missed deadlines, system crashes, or even safety failures. Without monitoring, you’re flying blind - you won’t know if your system is running efficiently or if it’s about to fail. Good monitoring gives you the data to make informed decisions and catch problems early.
// Simple performance monitoring hooks
void vApplicationIdleHook(void) {
static uint32_t idle_count = 0;
idle_count++;
// Calculate CPU utilization every 1000 ticks
if (idle_count % 1000 == 0) {
uint32_t total_ticks = xTaskGetTickCount();
uint32_t cpu_util = 100 - ((idle_count * 100) / total_ticks);
// Log or send CPU utilization data
log_performance_data("CPU_UTIL", cpu_util);
}
}
// Memory monitoring hook
void vApplicationMallocFailedHook(void) {
// Log memory allocation failure
log_performance_data("MEMORY_FAIL", 1);
// Take corrective action
perform_memory_cleanup();
}
Performance monitoring transforms reactive debugging into proactive optimization, giving you the data you need to build reliable, efficient embedded systems.
Performance monitoring is essential in real-time systems to ensure optimal operation, identify bottlenecks, and maintain system reliability. Effective monitoring systems provide real-time insights into CPU utilization, memory consumption, and timing performance, enabling proactive optimization and debugging.
1. System Health:
2. Debugging Support:
3. Optimization:
Core Components:
Monitoring Flow:
System Events → Data Collection → Analysis → Storage → Reporting → Alerts
1. Resource Metrics:
2. Timing Metrics:
3. Quality Metrics:
1. Idle Time Monitoring:
2. Task-Level Monitoring:
3. Load Average Calculation:
Idle Time Tracking:
typedef struct {
uint32_t total_ticks;
uint32_t idle_ticks;
uint32_t busy_ticks;
float cpu_utilization;
uint32_t update_counter;
} cpu_monitor_t;
cpu_monitor_t g_cpu_monitor = {0};
void vUpdateCPUMonitoring(void) {
static uint32_t last_idle_time = 0;
uint32_t current_idle_time = xTaskGetIdleRunTimeCounter();
// Calculate idle time delta
uint32_t idle_delta = current_idle_time - last_idle_time;
uint32_t total_delta = pdMS_TO_TICKS(1000); // 1 second window
g_cpu_monitor.total_ticks += total_delta;
g_cpu_monitor.idle_ticks += idle_delta;
g_cpu_monitor.busy_ticks += (total_delta - idle_delta);
g_cpu_monitor.update_counter++;
// Update CPU utilization every second
if (g_cpu_monitor.update_counter >= 1000) {
g_cpu_monitor.cpu_utilization = (float)g_cpu_monitor.busy_ticks /
(float)g_cpu_monitor.total_ticks * 100.0f;
// Reset counters
g_cpu_monitor.total_ticks = 0;
g_cpu_monitor.idle_ticks = 0;
g_cpu_monitor.busy_ticks = 0;
g_cpu_monitor.update_counter = 0;
printf("CPU Utilization: %.2f%%\n", g_cpu_monitor.cpu_utilization);
}
last_idle_time = current_idle_time;
}
Task-Level CPU Monitoring:
typedef struct {
TaskHandle_t task_handle;
char task_name[16];
uint32_t total_execution_time;
uint32_t execution_count;
uint32_t last_start_time;
uint32_t max_execution_time;
uint32_t min_execution_time;
float average_execution_time;
} task_performance_t;
task_performance_t task_performance[10];
uint8_t task_count = 0;
void vStartTaskTiming(TaskHandle_t task_handle) {
// Find or create task performance entry
int task_index = vFindTaskIndex(task_handle);
if (task_index >= 0) {
task_performance[task_index].last_start_time = xTaskGetTickCount();
}
}
void vEndTaskTiming(TaskHandle_t task_handle) {
int task_index = vFindTaskIndex(task_handle);
if (task_index >= 0) {
uint32_t end_time = xTaskGetTickCount();
uint32_t execution_time = end_time - task_performance[task_index].last_start_time;
// Update statistics
task_performance[task_index].total_execution_time += execution_time;
task_performance[task_index].execution_count++;
if (execution_time > task_performance[task_index].max_execution_time) {
task_performance[task_index].max_execution_time = execution_time;
}
if (execution_time < task_performance[task_index].min_execution_time ||
task_performance[task_index].min_execution_time == 0) {
task_performance[task_index].min_execution_time = execution_time;
}
// Calculate average
task_performance[task_index].average_execution_time =
(float)task_performance[task_index].total_execution_time /
(float)task_performance[task_index].execution_count;
}
}
Exponential Moving Average:
typedef struct {
float load_1min;
float load_5min;
float load_15min;
uint32_t update_counter;
} load_average_t;
load_average_t g_load_average = {0};
void vUpdateLoadAverage(float current_load) {
// Exponential moving average calculation
float alpha_1min = 1.0f - exp(-1.0f / 60.0f); // 1 minute
float alpha_5min = 1.0f - exp(-1.0f / 300.0f); // 5 minutes
float alpha_15min = 1.0f - exp(-1.0f / 900.0f); // 15 minutes
g_load_average.load_1min = alpha_1min * current_load + (1.0f - alpha_1min) * g_load_average.load_1min;
g_load_average.load_5min = alpha_5min * current_load + (1.0f - alpha_5min) * g_load_average.load_5min;
g_load_average.load_15min = alpha_15min * current_load + (1.0f - alpha_15min) * g_load_average.load_15min;
printf("Load Average: 1min=%.2f, 5min=%.2f, 15min=%.2f\n",
g_load_average.load_1min, g_load_average.load_5min, g_load_average.load_15min);
}
1. Stack Usage Monitoring:
2. Heap Usage Monitoring:
3. Static Memory Analysis:
Stack Usage Monitoring:
typedef struct {
TaskHandle_t task_handle;
char task_name[16];
uint32_t stack_size;
uint32_t stack_high_water_mark;
uint32_t stack_usage_percentage;
uint32_t min_stack_high_water;
uint32_t max_stack_high_water;
} stack_monitor_t;
stack_monitor_t stack_monitors[10];
uint8_t stack_monitor_count = 0;
void vUpdateStackMonitoring(void) {
for (int i = 0; i < stack_monitor_count; i++) {
if (stack_monitors[i].task_handle != NULL) {
// Get current stack high water mark
uint32_t current_high_water = uxTaskGetStackHighWaterMark(stack_monitors[i].task_handle);
// Update statistics
if (current_high_water < stack_monitors[i].min_stack_high_water ||
stack_monitors[i].min_stack_high_water == 0) {
stack_monitors[i].min_stack_high_water = current_high_water;
}
if (current_high_water > stack_monitors[i].max_stack_high_water) {
stack_monitors[i].max_stack_high_water = current_high_water;
}
stack_monitors[i].stack_high_water_mark = current_high_water;
stack_monitors[i].stack_usage_percentage =
((stack_monitors[i].stack_size - current_high_water) * 100) / stack_monitors[i].stack_size;
// Alert if stack usage is high
if (stack_monitors[i].stack_usage_percentage > 80) {
printf("WARNING: High stack usage for task %s: %.1f%%\n",
stack_monitors[i].task_name,
(float)stack_monitors[i].stack_usage_percentage);
}
}
}
}
Heap Usage Monitoring:
typedef struct {
size_t total_heap_size;
size_t free_heap_size;
size_t minimum_free_heap;
size_t largest_free_block;
uint32_t allocation_count;
uint32_t free_count;
float fragmentation_percentage;
} heap_monitor_t;
heap_monitor_t g_heap_monitor = {0};
void vUpdateHeapMonitoring(void) {
// Get current heap statistics
g_heap_monitor.total_heap_size = configTOTAL_HEAP_SIZE;
g_heap_monitor.free_heap_size = xPortGetFreeHeapSize();
g_heap_monitor.minimum_free_heap = xPortGetMinimumEverFreeHeapSize();
g_heap_monitor.largest_free_block = xPortGetLargestFreeBlock();
// Calculate fragmentation
if (g_heap_monitor.free_heap_size > 0) {
g_heap_monitor.fragmentation_percentage =
(float)(g_heap_monitor.free_heap_size - g_heap_monitor.largest_free_block) /
(float)g_heap_monitor.free_heap_size * 100.0f;
}
// Alert if heap usage is critical
if (g_heap_monitor.free_heap_size < (g_heap_monitor.total_heap_size / 10)) {
printf("CRITICAL: Low heap memory! Free: %zu, Total: %zu\n",
g_heap_monitor.free_heap_size, g_heap_monitor.total_heap_size);
}
if (g_heap_monitor.fragmentation_percentage > 50) {
printf("WARNING: High heap fragmentation: %.1f%%\n",
g_heap_monitor.fragmentation_percentage);
}
}
Memory Leak Detection:
typedef struct {
void *address;
size_t size;
uint32_t allocation_time;
char allocation_file[32];
uint32_t allocation_line;
bool is_freed;
} memory_allocation_t;
memory_allocation_t memory_allocations[100];
uint32_t allocation_count = 0;
void* vTrackedMalloc(size_t size, const char *file, uint32_t line) {
void *ptr = pvPortMalloc(size);
if (ptr != NULL && allocation_count < 100) {
memory_allocations[allocation_count].address = ptr;
memory_allocations[allocation_count].size = size;
memory_allocations[allocation_count].allocation_time = xTaskGetTickCount();
strncpy(memory_allocations[allocation_count].allocation_file, file, 31);
memory_allocations[allocation_count].allocation_line = line;
memory_allocations[allocation_count].is_freed = false;
allocation_count++;
}
return ptr;
}
void vTrackedFree(void *ptr) {
// Mark allocation as freed
for (int i = 0; i < allocation_count; i++) {
if (memory_allocations[i].address == ptr && !memory_allocations[i].is_freed) {
memory_allocations[i].is_freed = true;
break;
}
}
vPortFree(ptr);
}
void vCheckMemoryLeaks(void) {
uint32_t leak_count = 0;
size_t total_leaked_size = 0;
for (int i = 0; i < allocation_count; i++) {
if (!memory_allocations[i].is_freed) {
leak_count++;
total_leaked_size += memory_allocations[i].size;
printf("Memory leak detected: %p, size: %zu, file: %s, line: %lu\n",
memory_allocations[i].address,
memory_allocations[i].size,
memory_allocations[i].allocation_file,
memory_allocations[i].allocation_line);
}
}
if (leak_count > 0) {
printf("Total memory leaks: %lu, Total leaked size: %zu bytes\n",
leak_count, total_leaked_size);
} else {
printf("No memory leaks detected\n");
}
}
1. Task Response Time:
2. Interrupt Response Time:
3. System Response Time:
Task Response Time Monitoring:
typedef struct {
TaskHandle_t task_handle;
char task_name[16];
uint32_t activation_count;
uint32_t deadline_misses;
uint32_t total_response_time;
uint32_t min_response_time;
uint32_t max_response_time;
float average_response_time;
uint32_t deadline_ticks;
} task_timing_t;
task_timing_t task_timing[10];
uint8_t timing_task_count = 0;
void vStartTaskTiming(TaskHandle_t task_handle, uint32_t deadline_ticks) {
int task_index = vFindTimingTaskIndex(task_handle);
if (task_index >= 0) {
task_timing[task_index].last_activation_time = xTaskGetTickCount();
task_timing[task_index].deadline_ticks = deadline_ticks;
}
}
void vEndTaskTiming(TaskHandle_t task_handle) {
int task_index = vFindTimingTaskIndex(task_handle);
if (task_index >= 0) {
uint32_t end_time = xTaskGetTickCount();
uint32_t response_time = end_time - task_timing[task_index].last_activation_time;
// Update statistics
task_timing[task_index].activation_count++;
task_timing[task_index].total_response_time += response_time;
if (response_time < task_timing[task_index].min_response_time ||
task_timing[task_index].min_response_time == 0) {
task_timing[task_index].min_response_time = response_time;
}
if (response_time > task_timing[task_index].max_response_time) {
task_timing[task_index].max_response_time = response_time;
}
// Check deadline compliance
if (response_time > task_timing[task_index].deadline_ticks) {
task_timing[task_index].deadline_misses++;
printf("DEADLINE MISS: Task %s, Response: %lu, Deadline: %lu\n",
task_timing[task_index].task_name,
response_time,
task_timing[task_index].deadline_ticks);
}
// Calculate average
task_timing[task_index].average_response_time =
(float)task_timing[task_index].total_response_time /
(float)task_timing[task_index].activation_count;
}
}
Interrupt Timing Monitoring:
typedef struct {
uint32_t interrupt_number;
uint32_t interrupt_count;
uint32_t total_execution_time;
uint32_t min_execution_time;
uint32_t max_execution_time;
float average_execution_time;
uint32_t last_interrupt_time;
uint32_t min_interrupt_interval;
uint32_t max_interrupt_interval;
} interrupt_timing_t;
interrupt_timing_t interrupt_timing[32];
uint8_t interrupt_count = 0;
void vStartInterruptTiming(uint32_t interrupt_num) {
int int_index = vFindInterruptIndex(interrupt_num);
if (int_index >= 0) {
uint32_t current_time = DWT->CYCCNT;
interrupt_timing[int_index].last_interrupt_time = current_time;
}
}
void vEndInterruptTiming(uint32_t interrupt_num) {
int int_index = vFindInterruptIndex(interrupt_num);
if (int_index >= 0) {
uint32_t current_time = DWT->CYCCNT;
uint32_t execution_time = current_time - interrupt_timing[int_index].last_interrupt_time;
// Update execution time statistics
interrupt_timing[int_index].interrupt_count++;
interrupt_timing[int_index].total_execution_time += execution_time;
if (execution_time < interrupt_timing[int_index].min_execution_time ||
interrupt_timing[int_index].min_execution_time == 0) {
interrupt_timing[int_index].min_execution_time = execution_time;
}
if (execution_time > interrupt_timing[int_index].max_execution_time) {
interrupt_timing[int_index].max_execution_time = execution_time;
}
// Calculate average
interrupt_timing[int_index].average_execution_time =
(float)interrupt_timing[int_index].total_execution_time /
(float)interrupt_timing[int_index].interrupt_count;
}
}
Jitter Calculation:
typedef struct {
uint32_t sample_count;
uint32_t total_jitter;
uint32_t max_jitter;
uint32_t min_jitter;
float average_jitter;
uint32_t jitter_threshold;
uint32_t jitter_violations;
} jitter_analysis_t;
jitter_analysis_t g_jitter_analysis = {0};
void vUpdateJitterAnalysis(uint32_t expected_interval, uint32_t actual_interval) {
int32_t jitter = (int32_t)actual_interval - (int32_t)expected_interval;
uint32_t absolute_jitter = abs(jitter);
g_jitter_analysis.sample_count++;
g_jitter_analysis.total_jitter += absolute_jitter;
if (absolute_jitter > g_jitter_analysis.max_jitter) {
g_jitter_analysis.max_jitter = absolute_jitter;
}
if (absolute_jitter < g_jitter_analysis.min_jitter ||
g_jitter_analysis.min_jitter == 0) {
g_jitter_analysis.min_jitter = absolute_jitter;
}
// Check jitter threshold violations
if (absolute_jitter > g_jitter_analysis.jitter_threshold) {
g_jitter_analysis.jitter_violations++;
printf("JITTER VIOLATION: Expected: %lu, Actual: %lu, Jitter: %ld\n",
expected_interval, actual_interval, jitter);
}
// Calculate average
g_jitter_analysis.average_jitter =
(float)g_jitter_analysis.total_jitter / (float)g_jitter_analysis.sample_count;
}
Performance Data Structure:
typedef struct {
cpu_monitor_t cpu;
heap_monitor_t heap;
load_average_t load;
jitter_analysis_t jitter;
uint32_t system_uptime;
uint32_t last_update_time;
bool monitoring_enabled;
} performance_dashboard_t;
performance_dashboard_t g_performance_dashboard = {0};
void vUpdatePerformanceDashboard(void) {
if (!g_performance_dashboard.monitoring_enabled) {
return;
}
// Update all monitoring components
vUpdateCPUMonitoring();
vUpdateHeapMonitoring();
vUpdateStackMonitoring();
vUpdateLoadAverage(g_cpu_monitor.cpu_utilization / 100.0f);
// Update system uptime
g_performance_dashboard.system_uptime = xTaskGetTickCount();
g_performance_dashboard.last_update_time = xTaskGetTickCount();
// Print performance summary
vPrintPerformanceSummary();
}
Performance Summary Display:
void vPrintPerformanceSummary(void) {
printf("\n=== Performance Dashboard ===\n");
printf("System Uptime: %lu ticks\n", g_performance_dashboard.system_uptime);
printf("CPU Utilization: %.2f%%\n", g_cpu_monitor.cpu_utilization);
printf("Load Average: 1min=%.2f, 5min=%.2f, 15min=%.2f\n",
g_load_average.load_1min, g_load_average.load_5min, g_load_average.load_15min);
printf("Heap Usage: %zu/%zu bytes (%.1f%%)\n",
g_heap_monitor.total_heap_size - g_heap_monitor.free_heap_size,
g_heap_monitor.total_heap_size,
((float)(g_heap_monitor.total_heap_size - g_heap_monitor.free_heap_size) /
(float)g_heap_monitor.total_heap_size) * 100.0f);
printf("Heap Fragmentation: %.1f%%\n", g_heap_monitor.fragmentation_percentage);
printf("Average Jitter: %.2f ticks\n", g_jitter_analysis.average_jitter);
printf("Jitter Violations: %lu\n", g_jitter_analysis.jitter_violations);
printf("============================\n\n");
}
Alert Configuration:
typedef enum {
ALERT_LEVEL_INFO,
ALERT_LEVEL_WARNING,
ALERT_LEVEL_CRITICAL
} alert_level_t;
typedef struct {
alert_level_t level;
char message[128];
uint32_t timestamp;
bool acknowledged;
} performance_alert_t;
performance_alert_t performance_alerts[50];
uint8_t alert_count = 0;
void vAddPerformanceAlert(alert_level_t level, const char *message) {
if (alert_count < 50) {
performance_alerts[alert_count].level = level;
strncpy(performance_alerts[alert_count].message, message, 127);
performance_alerts[alert_count].timestamp = xTaskGetTickCount();
performance_alerts[alert_count].acknowledged = false;
alert_count++;
// Print alert based on level
switch (level) {
case ALERT_LEVEL_INFO:
printf("[INFO] %s\n", message);
break;
case ALERT_LEVEL_WARNING:
printf("[WARNING] %s\n", message);
break;
case ALERT_LEVEL_CRITICAL:
printf("[CRITICAL] %s\n", message);
break;
}
}
}
void vCheckPerformanceAlerts(void) {
// Check CPU utilization
if (g_cpu_monitor.cpu_utilization > 90) {
vAddPerformanceAlert(ALERT_LEVEL_CRITICAL, "CPU utilization critically high");
} else if (g_cpu_monitor.cpu_utilization > 80) {
vAddPerformanceAlert(ALERT_LEVEL_WARNING, "CPU utilization high");
}
// Check heap usage
if (g_heap_monitor.free_heap_size < (g_heap_monitor.total_heap_size / 20)) {
vAddPerformanceAlert(ALERT_LEVEL_CRITICAL, "Heap memory critically low");
} else if (g_heap_monitor.free_heap_size < (g_heap_monitor.total_heap_size / 10)) {
vAddPerformanceAlert(ALERT_LEVEL_WARNING, "Heap memory low");
}
// Check jitter violations
if (g_jitter_analysis.jitter_violations > 10) {
vAddPerformanceAlert(ALERT_LEVEL_WARNING, "High jitter violation count");
}
}
System Initialization:
void vInitializePerformanceMonitoring(void) {
// Initialize all monitoring components
memset(&g_cpu_monitor, 0, sizeof(cpu_monitor_t));
memset(&g_heap_monitor, 0, sizeof(heap_monitor_t));
memset(&g_load_average, 0, sizeof(load_average_t));
memset(&g_jitter_analysis, 0, sizeof(jitter_analysis_t));
memset(&g_performance_dashboard, 0, sizeof(performance_dashboard_t));
// Set initial values
g_heap_monitor.minimum_free_heap = SIZE_MAX;
g_jitter_analysis.min_jitter = UINT32_MAX;
g_performance_dashboard.monitoring_enabled = true;
g_jitter_analysis.jitter_threshold = 10; // 10 ticks threshold
// Start monitoring task
xTaskCreate(vPerformanceMonitoringTask, "PerfMon", 512, NULL, 1, NULL);
printf("Performance monitoring system initialized\n");
}
Performance Monitoring Task:
void vPerformanceMonitoringTask(void *pvParameters) {
TickType_t last_wake_time = xTaskGetTickCount();
while (1) {
// Update all monitoring components
vUpdatePerformanceDashboard();
// Check for performance alerts
vCheckPerformanceAlerts();
// Check for memory leaks periodically
static uint32_t leak_check_counter = 0;
leak_check_counter++;
if (leak_check_counter >= 60) { // Every 60 seconds
vCheckMemoryLeaks();
leak_check_counter = 0;
}
// Wait for next monitoring cycle
vTaskDelayUntil(&last_wake_time, pdMS_TO_TICKS(1000));
}
}
Objective: Implement basic CPU utilization monitoring in FreeRTOS Steps:
Expected Outcome: Real-time CPU utilization data with minimal overhead
Objective: Monitor memory allocation and usage patterns Steps:
Expected Outcome: Complete visibility into memory usage patterns
Objective: Create a simple performance dashboard Steps:
Expected Outcome: Real-time performance dashboard with alerts
This comprehensive Performance Monitoring document provides embedded engineers with the theoretical foundation, practical implementation examples, and best practices needed to implement effective performance monitoring systems in real-time environments.