# ESP32 Performance Tests - Complete Technical Documentation

## Table of Contents
1. [Overview](#overview)
2. [System Architecture](#system-architecture)
3. [Configuration System](#configuration-system)
4. [Test Functions Detailed Analysis](#test-functions-detailed-analysis)
5. [Unity Test Framework Integration](#unity-test-framework-integration)
6. [Memory Management Analysis](#memory-management-analysis)
7. [Stack Usage Monitoring](#stack-usage-monitoring)
8. [CPU Load Analysis](#cpu-load-analysis)
9. [Build System Integration](#build-system-integration)
10. [Troubleshooting Guide](#troubleshooting-guide)

---

## Overview

The ESP32 Performance Tests system is a comprehensive monitoring solution that evaluates three critical aspects of embedded system health:

- **Memory Consumption**: Monitors heap usage and availability
- **Stack Usage**: Tracks stack utilization for tasks
- **CPU Load**: Analyzes processor utilization across cores

### Key Features
- **Real-time Monitoring**: Live system performance analysis
- **Multi-core Support**: Handles ESP32's dual-core architecture
- **Configurable Thresholds**: Adjustable limits for different environments
- **Unity Integration**: Professional test framework with detailed reporting
- **Conditional Compilation**: Tests only compile when enabled

---

## System Architecture

### File Structure
```
components/perf_tests/
├── CMakeLists.txt              # Build configuration
├── test/
│   └── perf_tests.cpp         # Main test implementation
└── Kconfig                    # Configuration options

main/
├── main.cpp                   # Application entry point
└── CMakeLists.txt            # Main component build config
```

### Compilation Flow
```mermaid
graph TD
    A[sdkconfig] --> B[CONFIG_ENABLE_PERF_TESTS]
    B --> C{Tests Enabled?}
    C -->|Yes| D[Include Unity Framework]
    C -->|No| E[Normal Application Mode]
    D --> F[Compile Test Functions]
    F --> G[Register Tests with Unity]
    G --> H[Run Performance Tests]
```

---

## Configuration System

### Primary Configuration Flags

#### `CONFIG_ENABLE_PERF_TESTS`
- **Purpose**: Master switch for performance testing
- **Location**: `sdkconfig`
- **Effect**: When enabled, replaces normal application with test suite
- **Default**: `y` (enabled)

#### `CONFIG_ENABLE_MEMORY_TEST`
- **Purpose**: Controls memory consumption testing
- **Tests**: Heap usage, DMA memory, SPIRAM (if available)
- **Default**: `y`

#### `CONFIG_ENABLE_STACK_USAGE_TEST`
- **Purpose**: Controls stack monitoring tests
- **Tests**: Current task stack, idle task stacks
- **Default**: `y`

#### `CONFIG_ENABLE_CPU_LOAD_TEST`
- **Purpose**: Controls CPU utilization analysis
- **Requirements**: `configGENERATE_RUN_TIME_STATS=1`
- **Default**: `y`

### Threshold Configuration

```cpp
// Memory threshold - minimum free heap considered healthy
#define PERF_MIN_HEAP_BYTES (1024U)

// Stack thresholds - minimum unused stack (in words, not bytes)
#define PERF_MIN_STACK_WORDS_CURRENT (100U)  // Current task
#define PERF_MIN_STACK_WORDS_IDLE (50U)      // Idle tasks

// CPU load tolerance - acceptable deviation from 100%
#define PERF_RUNTIME_PERCENT_TOLERANCE (20.0f)
```

---

## Test Functions Detailed Analysis

### 1. Memory Consumption Test (`test_memory_consumption_basic`)

#### Purpose
Validates that the system has sufficient free memory for stable operation.

#### Step-by-Step Execution

**Step 1: Check 8-bit Heap**
```cpp
size_t free_now_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t free_min_8bit = heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT);
```
- `heap_caps_get_free_size()`: Returns current free memory in bytes
- `MALLOC_CAP_8BIT`: Specifies 8-bit accessible memory (most common)
- `heap_caps_get_minimum_free_size()`: Returns lowest free memory since boot
- **Why Important**: Shows both current state and worst-case scenario

**Step 2: Validate 8-bit Heap**
```cpp
TEST_ASSERT_MESSAGE(free_now_8bit >= PERF_MIN_HEAP_BYTES,
    "Not enough free 8-bit heap; increase RAM or lower threshold");
```
- Ensures minimum threshold is met
- Fails test if memory is critically low
- Provides actionable error message

**Step 3: Check DMA-Capable Heap**
```cpp
size_t free_now_dma = heap_caps_get_free_size(MALLOC_CAP_DMA);
size_t free_min_dma = heap_caps_get_minimum_free_size(MALLOC_CAP_DMA);
```
- `MALLOC_CAP_DMA`: Memory accessible by DMA controllers
- Critical for peripherals like SPI, I2C, WiFi
- May overlap with 8-bit heap on some configurations

**Step 4: SPIRAM Check (Optional)**
```cpp
#if CONFIG_SPIRAM_SUPPORT
if (esp_spiram_is_initialized()) {
    size_t free_spiram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
    // ... validation
}
#endif
```
- Only runs if SPIRAM (external RAM) is configured
- `esp_spiram_is_initialized()`: Checks if SPIRAM is available
- `MALLOC_CAP_SPIRAM`: Targets external SPIRAM memory

#### Expected Results
- **Healthy System**: >200KB free heap
- **Warning Level**: 50-200KB free heap
- **Critical Level**: <50KB free heap

---

### 2. Stack Usage Test (`test_stack_usage_current_task`)

#### Purpose
Monitors stack consumption to prevent stack overflow crashes.

#### Understanding Stack Monitoring

**What is Stack High Water Mark?**
- The "high water mark" is the maximum stack usage since task creation
- Measured in "words" (typically 4 bytes each on ESP32)
- Lower values indicate higher stack usage (less remaining)

**Step-by-Step Execution**

**Step 1: Get Current Task Stack Info**
```cpp
UBaseType_t high_water_words = uxTaskGetStackHighWaterMark(NULL);
```
- `NULL` parameter means "current task"
- Returns unused stack space in words
- **Critical**: This is REMAINING stack, not USED stack

**Step 2: Convert to Human-Readable Format**
```cpp
size_t high_water_bytes = stack_words_to_bytes(high_water_words);
```
- Converts words to bytes for easier understanding
- `sizeof(StackType_t)` accounts for platform differences

**Step 3: Validate Stack Health**
```cpp
TEST_ASSERT_MESSAGE(high_water_words >= PERF_MIN_STACK_WORDS_CURRENT,
    "Current task stack high-water too low; consider increasing stack size");
```
- Ensures sufficient stack remains
- Prevents stack overflow crashes
- Suggests solution in error message

#### Stack Usage Interpretation
- **High Water Mark = 1000 words**: 1000 words (4KB) unused - GOOD
- **High Water Mark = 100 words**: 100 words (400 bytes) unused - WARNING
- **High Water Mark = 10 words**: 10 words (40 bytes) unused - CRITICAL

---

### 3. Idle Task Stack Test (`test_stack_usage_idle_tasks`)

#### Purpose
Monitors system-critical idle task stacks across all CPU cores.

#### Multi-Core Considerations

**Why Monitor Idle Tasks?**
- Idle tasks run when no other tasks are active
- Stack overflow in idle tasks crashes the entire system
- Each CPU core has its own idle task

**Step-by-Step Execution**

**Step 1: Iterate Through CPU Cores**
```cpp
for (int cpu = 0; cpu < configNUM_CORES; ++cpu) {
```
- `configNUM_CORES`: Compile-time constant (2 for ESP32)
- Ensures all cores are checked

**Step 2: Get Idle Task Handle**
```cpp
TaskHandle_t idle = xTaskGetIdleTaskHandleForCore(cpu);
```
- `xTaskGetIdleTaskHandleForCore()`: Gets idle task for specific core
- Returns handle to system-managed idle task
- **Note**: Uses newer API (old `xTaskGetIdleTaskHandleForCPU` deprecated)

**Step 3: Check Stack Usage**
```cpp
UBaseType_t hw = uxTaskGetStackHighWaterMark(idle);
```
- Same principle as current task check
- Applied to system idle task

**Step 4: Validate Each Idle Task**
```cpp
TEST_ASSERT_MESSAGE(hw >= PERF_MIN_STACK_WORDS_IDLE,
    "Idle task stack high-water too low; increase idle task stack for CPU");
```
- Lower threshold than user tasks (idle tasks do less work)
- Critical for system stability

#### Single-Core Fallback
```cpp
#else
void test_stack_usage_idle_task(void) {
    TaskHandle_t idle = xTaskGetIdleTaskHandle();
    // ... same validation logic
}
#endif
```
- Handles single-core ESP32 variants
- Uses simpler API without core specification

---

### 4. CPU Load Test (`test_cpu_load_estimation`)

#### Purpose
Analyzes CPU utilization across all cores and tasks to identify performance bottlenecks.

#### Prerequisites
```cpp
#if ( configGENERATE_RUN_TIME_STATS == 1 )
```
- Requires FreeRTOS runtime statistics enabled
- Must be configured in `sdkconfig`: `CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y`
- Uses high-resolution timer for accurate measurements

#### Step-by-Step Execution

**Step 1: Stabilization Period**
```cpp
wait_ms(1000);  // Increased wait time for more stable measurements
```
- Allows system to reach steady state
- Accumulates meaningful runtime statistics
- Longer period = more accurate measurements

**Step 2: Get Task Count**
```cpp
UBaseType_t num_tasks = uxTaskGetNumberOfTasks();
TEST_ASSERT_GREATER_THAN_UINT32_MESSAGE(0, num_tasks, "No tasks reported");
```
- Counts all active tasks in system
- Validates FreeRTOS is functioning
- Used to allocate appropriate array size

**Step 3: Allocate Task Array**
```cpp
const UBaseType_t margin = 4;
TaskStatus_t *task_array = (TaskStatus_t *)malloc(sizeof(TaskStatus_t) * (num_tasks + margin));
```
- `TaskStatus_t`: FreeRTOS structure containing task information
- `margin`: Extra space in case tasks are created during measurement
- Heap allocation safer than stack for large arrays

**Step 4: Capture System State**
```cpp
unsigned long total_run_time = 0;
UBaseType_t fetched = uxTaskGetSystemState(task_array, num_tasks + margin, &total_run_time);
```
- `uxTaskGetSystemState()`: Atomic snapshot of all task states
- `total_run_time`: Total CPU time across all cores since boot
- `fetched`: Actual number of tasks captured

**Step 5: Validate Data Quality**
```cpp
TEST_ASSERT_GREATER_THAN_UINT32_MESSAGE(0, fetched, "uxTaskGetSystemState returned no tasks");
TEST_ASSERT_GREATER_THAN_MESSAGE(0UL, total_run_time, "Total run time is zero");
```
- Ensures data capture succeeded
- Validates runtime statistics are working
- Prevents division by zero errors

**Step 6: Multi-Core Awareness**
```cpp
#if (configNUM_CORES > 1)
ESP_LOGI(TAG, "Multi-core system detected, using adjusted calculation");
#endif
```
- Acknowledges dual-core complexity
- Runtime statistics behave differently on multi-core systems
- Each core contributes to total runtime independently

**Step 7: Calculate Task Percentages**
```cpp
for (UBaseType_t i = 0; i < fetched; ++i) {
    unsigned long run = task_array[i].ulRunTimeCounter;
    double pct = ((double)run * 100.0) / (double)total_run_time;
```
- `ulRunTimeCounter`: CPU time consumed by this task
- Percentage calculation: (task_time / total_time) × 100
- Double precision for accuracy

**Step 8: Identify Idle Tasks**
```cpp
if (task_array[i].pcTaskName != NULL &&
    (strcasecmp(task_array[i].pcTaskName, "IDLE0") == 0 ||
     strcasecmp(task_array[i].pcTaskName, "IDLE1") == 0 ||
     strstr(task_array[i].pcTaskName, "IDLE") != NULL))
```
- `pcTaskName`: Task name string
- `strcasecmp()`: Case-insensitive string comparison
- `strstr()`: Substring search for "IDLE"
- Handles both "IDLE0", "IDLE1" and generic "IDLE" names

**Step 9: Separate Idle vs Active Tasks**
```cpp
// Only count non-idle tasks for the sum check
if (task_array[i].pcTaskName == NULL || 
    (strstr(task_array[i].pcTaskName, "IDLE") == NULL)) {
    percent_sum += pct;
}
```
- Idle tasks run when nothing else needs CPU
- Active task percentage shows actual system load
- Idle percentage shows available capacity

**Step 10: System Health Validation**
```cpp
TEST_ASSERT_MESSAGE(idle_task_count > 0, "No idle tasks found");
TEST_ASSERT_MESSAGE(percent_sum < 150.0, "System appears overloaded");
```
- Ensures idle tasks exist (system sanity check)
- Validates reasonable CPU usage levels
- Relaxed thresholds for multi-core systems

#### CPU Load Interpretation

**Typical Results:**
- **Idle Tasks**: 85-95% (system mostly idle)
- **Active Tasks**: 5-15% (normal background activity)
- **Unity Test**: 3-8% (test execution overhead)
- **IPC Tasks**: 1-3% (inter-processor communication)

**Warning Signs:**
- **No Idle Time**: System overloaded
- **>50% Active Load**: High CPU usage
- **Unbalanced Cores**: One core much busier than other

---

## Unity Test Framework Integration

### Test Registration System

#### Unity Task Function
```cpp
extern "C" void unity_task(void *pvParameters)
{
    vTaskDelay(2); // Allow system to settle
    
    UNITY_BEGIN();
    
    // Run individual tests
    RUN_TEST(test_memory_consumption_basic);
    RUN_TEST(test_stack_usage_current_task);
    // ... more tests
    
    UNITY_END();
    
    // Keep task alive
    while(1) {
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}
```

**Step-by-Step Execution:**

1. **Task Delay**: `vTaskDelay(2)` allows system initialization to complete
2. **Unity Begin**: `UNITY_BEGIN()` initializes test framework
3. **Test Execution**: `RUN_TEST()` macro executes each test function
4. **Unity End**: `UNITY_END()` prints final results and statistics
5. **Task Persistence**: Infinite loop keeps task alive for system stability

#### Main Application Integration
```cpp
extern "C" void app_main(void)
{
    ESP_LOGI(TAG, "Performance tests enabled - starting Unity test suite");
    
    // Create Unity test task
    xTaskCreate(unity_task, "unity", 8192, NULL, 5, NULL);
    
    // Keep main task alive
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}
```

**Parameters Explained:**
- `unity_task`: Function to execute
- `"unity"`: Task name (for debugging)
- `8192`: Stack size in bytes (8KB)
- `NULL`: No parameters passed to task
- `5`: Task priority (medium priority)
- `NULL`: No task handle returned

---

## Memory Management Analysis

### ESP32 Memory Architecture

#### Internal RAM Types
1. **DRAM (Data RAM)**: General purpose data storage
2. **IRAM (Instruction RAM)**: Code execution, interrupt handlers
3. **RTC RAM**: Retains data during deep sleep

#### Memory Capabilities
- `MALLOC_CAP_8BIT`: 8-bit accessible memory (most common)
- `MALLOC_CAP_32BIT`: 32-bit aligned memory (faster access)
- `MALLOC_CAP_DMA`: DMA controller accessible memory
- `MALLOC_CAP_SPIRAM`: External SPIRAM memory

### Memory Test Deep Dive

#### Heap Fragmentation Considerations
```cpp
size_t free_now = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t free_min = heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT);
```

**Key Insights:**
- `free_now > free_min`: Memory was more fragmented earlier
- `free_now == free_min`: Current state is worst case
- Large difference suggests memory fragmentation issues

#### SPIRAM Integration
```cpp
if (esp_spiram_is_initialized()) {
    size_t free_spiram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
}
```

**SPIRAM Benefits:**
- Expands available memory from ~300KB to several MB
- Slower access than internal RAM
- Ideal for large buffers, images, audio data

---

## Stack Usage Monitoring

### FreeRTOS Stack Management

#### Stack Growth Direction
- ESP32 stacks grow downward (high to low addresses)
- Stack pointer starts at high address, decreases with usage
- Stack overflow occurs when pointer goes below allocated region

#### High Water Mark Calculation
```cpp
UBaseType_t high_water_words = uxTaskGetStackHighWaterMark(NULL);
```

**Internal Process:**
1. FreeRTOS fills unused stack with pattern (0xa5a5a5a5)
2. Periodically scans from stack bottom upward
3. Finds first non-pattern word (maximum usage point)
4. Returns remaining unused words

#### Stack Size Recommendations
- **Minimal Tasks**: 2KB (512 words)
- **Standard Tasks**: 4KB (1024 words)
- **Complex Tasks**: 8KB+ (2048+ words)
- **Unity Test Task**: 8KB (handles test framework overhead)

---

## CPU Load Analysis

### FreeRTOS Runtime Statistics

#### Timer Configuration
```cpp
CONFIG_FREERTOS_RUN_TIME_STATS_USING_ESP_TIMER=y
```

**Timer Options:**
- **ESP Timer**: High-resolution microsecond timer
- **CPU Clock**: Cycle-accurate but overflow-prone
- **Custom Timer**: User-defined timer source

#### Multi-Core Considerations

**Dual-Core Behavior:**
- Each core runs independently
- Idle tasks per core (IDLE0, IDLE1)
- Runtime statistics accumulate per core
- Total runtime = sum of both cores

**Percentage Calculation Challenges:**
- Single-core: percentages sum to ~100%
- Dual-core: percentages can sum to ~200%
- Our solution: separate idle vs active task analysis

#### Task Types Analysis

**System Tasks:**
- `IDLE0/IDLE1`: Core idle tasks (run when nothing else active)
- `main`: Main application task
- `unity`: Test execution task
- `ipc0/ipc1`: Inter-processor communication

**Typical Load Distribution:**
```
IDLE0: 91.94% (CPU0 mostly idle)
IDLE1: 97.26% (CPU1 mostly idle)
unity: 5.14%  (test execution)
main:  1.09%  (minimal main task work)
ipc0:  1.77%  (core communication)
ipc1:  1.83%  (core communication)
```

---

## Build System Integration

### CMakeLists.txt Configuration

#### Component Registration
```cmake
# Set up dependencies conditionally
set(PERF_SRCS "")
set(PERF_PUBLIC_REQUIRES freertos esp_timer esp_system heap log)
set(PERF_PRIVATE_REQUIRES unity)  # Always include unity

if(CONFIG_ENABLE_PERF_TESTS)
    list(APPEND PERF_SRCS "test/perf_tests.cpp")
endif()

idf_component_register(
    SRCS ${PERF_SRCS}
    INCLUDE_DIRS "."
    REQUIRES ${PERF_PUBLIC_REQUIRES}
    PRIV_REQUIRES ${PERF_PRIVATE_REQUIRES}
)
```

**Key Points:**
- `PERF_SRCS`: Source files (conditional)
- `REQUIRES`: Public dependencies (always available)
- `PRIV_REQUIRES`: Private dependencies (internal use only)
- Unity always included to avoid compilation issues

#### Main Component Integration
```cmake
set(MAIN_REQUIRES sensor_manager actuator_manager event_system)
set(MAIN_PRIV_REQUIRES esp_common unity)

if(CONFIG_ENABLE_PERF_TESTS)
    # Unity dependency already included above
endif()
```

### Conditional Compilation Strategy

#### Preprocessor Guards
```cpp
#if CONFIG_ENABLE_PERF_TESTS
// Test code only compiled when enabled
#include "unity.h"
// ... test functions
#endif
```

**Benefits:**
- Zero overhead when tests disabled
- Clean separation of test vs production code
- Compile-time optimization

---

## Troubleshooting Guide

### Common Issues and Solutions

#### 1. "unity.h: No such file or directory"
**Cause**: Unity component not properly linked
**Solution**: 
```cmake
# In CMakeLists.txt
PRIV_REQUIRES unity
```

#### 2. "0 Tests 0 Failures 0 Ignored"
**Cause**: Tests not registered with Unity framework
**Solution**: Ensure `unity_task()` function calls `RUN_TEST()` macros

#### 3. CPU Load Test Fails with >100% Usage
**Cause**: Multi-core runtime statistics accumulation
**Solution**: Use separate idle vs active task analysis (already implemented)

#### 4. Stack Overflow in Tests
**Cause**: Insufficient stack size for Unity test task
**Solution**: 
```cpp
xTaskCreate(unity_task, "unity", 8192, NULL, 5, NULL);
//                              ^^^^ Increase this value
```

#### 5. Memory Test Fails Immediately
**Cause**: System already low on memory
**Solutions**:
- Reduce `PERF_MIN_HEAP_BYTES` threshold
- Optimize application memory usage
- Enable SPIRAM if available

#### 6. Runtime Statistics Not Working
**Cause**: FreeRTOS runtime stats disabled
**Solution**:
```
CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
CONFIG_FREERTOS_RUN_TIME_STATS_USING_ESP_TIMER=y
```

### Performance Optimization Tips

#### Memory Optimization
1. **Use SPIRAM**: For large data structures
2. **Optimize Heap Usage**: Avoid frequent malloc/free
3. **Static Allocation**: Use static variables when possible
4. **Memory Pools**: Pre-allocate fixed-size blocks

#### Stack Optimization
1. **Right-Size Stacks**: Don't over-allocate
2. **Monitor Usage**: Regular high water mark checks
3. **Avoid Deep Recursion**: Use iteration instead
4. **Local Variable Management**: Minimize large local arrays

#### CPU Optimization
1. **Task Priorities**: Balance workload across cores
2. **Yield Points**: Add `vTaskDelay()` in long loops
3. **Interrupt Efficiency**: Keep ISRs short
4. **Core Affinity**: Pin tasks to specific cores when beneficial

---

## Conclusion

This performance testing system provides comprehensive monitoring of ESP32 system health. The tests are designed to catch issues early in development and provide actionable feedback for optimization.

### Key Benefits
- **Proactive Monitoring**: Catch issues before they cause crashes
- **Quantitative Analysis**: Precise measurements vs. guesswork
- **Multi-Core Awareness**: Proper handling of ESP32's dual-core architecture
- **Professional Framework**: Unity provides industry-standard test reporting
- **Configurable Thresholds**: Adaptable to different application requirements

### Best Practices
1. **Run Tests Regularly**: Include in CI/CD pipeline
2. **Monitor Trends**: Track performance over time
3. **Adjust Thresholds**: Tune limits based on application needs
4. **Document Results**: Keep performance baselines
5. **Investigate Failures**: Don't ignore test failures

The system is designed to grow with your project, providing valuable insights throughout the development lifecycle.