From 8a2dafc1bd71034fce9cd21c92f21fea966b4f03 Mon Sep 17 00:00:00 2001 From: impressionyang Date: Sat, 22 Oct 2022 11:52:13 +0800 Subject: [PATCH] first add --- .gitignore | 7 ++ CMakeLists.txt | 6 + Makefile | 8 ++ README.md | 72 +++++++++++ example_test.py | 19 +++ main/CMakeLists.txt | 2 + main/component.mk | 4 + main/real_time_stats_example_main.c | 186 ++++++++++++++++++++++++++++ sdkconfig.ci | 2 + sdkconfig.defaults | 2 + 10 files changed, 308 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Makefile create mode 100644 README.md create mode 100644 example_test.py create mode 100644 main/CMakeLists.txt create mode 100644 main/component.mk create mode 100644 main/real_time_stats_example_main.c create mode 100644 sdkconfig.ci create mode 100644 sdkconfig.defaults diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c8dab0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +components/ +.vscode/ +.devcontainer/ +build/ +*.o +*.d +*.log \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..14aed0e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ESP3232S2_Core_Box) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a875110 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := real_time_stats + +include $(IDF_PATH)/make/project.mk diff --git a/README.md b/README.md new file mode 100644 index 0000000..bf52abc --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# FreeRTOS Real Time Stats Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +FreeRTOS provides the function `vTaskGetRunTimeStats()` to obtain CPU usage statistics of tasks. However, these statistics are with respect to the entire runtime of FreeRTOS (i.e. **run time stats**). Furthermore, statistics of `vTaskGetRunTimeStats()` are only valid whilst the timer for run time statistics has not overflowed. + +This example demonstrates how to get CPU usage statistics of tasks with respect to a specified duration (i.e. **real time stats**) rather than over the entire runtime of FreeRTOS. The `print_real_time_stats()` function of this example demonstrates how this can be achieved. + +## How to use example + +### Hardware Required + +This example should be able to run on any commonly available ESP32 development board. + +### Configure the project + +``` +idf.py menuconfig +``` + +* Select `Enable FreeRTOS to collect run time stats` under `Component Config > FreeRTOS` (this should be enabled in the example by default) + +* `Choose the clock source for run time stats` configured under `Component Config > FreeRTOS`. The `esp_timer` should be selected be default. This option will affect the time unit resolution in which the statistics are measured with respect to. + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +The example should have the following log output: + +``` +... +Getting real time stats over 100 ticks +| Task | Run Time | Percentage +| stats | 1304 | 0% +| IDLE0 | 206251 | 10% +| IDLE1 | 464785 | 23% +| spin2 | 225389 | 11% +| spin0 | 227174 | 11% +| spin4 | 225303 | 11% +| spin1 | 207264 | 10% +| spin3 | 225331 | 11% +| spin5 | 225369 | 11% +| Tmr Svc | 0 | 0% +| esp_timer | 0 | 0% +| ipc1 | 0 | 0% +| ipc0 | 0 | 0% +Real time stats obtained +... +``` + +## Example Breakdown + +### Spin tasks + +During the examples initialization process, multiple `spin` tasks are created. These tasks will simply spin a certain number of CPU cycles to consume CPU time, then block for a predetermined period. + +### Understanding the stats + +From the log output, it can be seen that the spin tasks consume nearly an equal amount of time over the specified stats collection period of `print_real_time_stats()`. The real time stats also display the CPU time consumption of other tasks created by default in ESP-IDF (e.g. `IDLE` and `ipc` tasks). \ No newline at end of file diff --git a/example_test.py b/example_test.py new file mode 100644 index 0000000..2b66a6e --- /dev/null +++ b/example_test.py @@ -0,0 +1,19 @@ +from __future__ import print_function + +import ttfw_idf + +STATS_TASK_ITERS = 3 +STATS_TASK_EXPECT = 'Real time stats obtained' + + +@ttfw_idf.idf_example_test(env_tag='Example_GENERIC', target=['esp32', 'esp32c3']) +def test_real_time_stats_example(env, extra_data): + dut = env.get_dut('real_time_stats', 'examples/system/freertos/real_time_stats') + dut.start_app() + + for iteration in range(0, STATS_TASK_ITERS): + dut.expect(STATS_TASK_EXPECT) + + +if __name__ == '__main__': + test_real_time_stats_example() diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..0488bd1 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "real_time_stats_example_main.c" + INCLUDE_DIRS ".") diff --git a/main/component.mk b/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/main/real_time_stats_example_main.c b/main/real_time_stats_example_main.c new file mode 100644 index 0000000..9b3b9ce --- /dev/null +++ b/main/real_time_stats_example_main.c @@ -0,0 +1,186 @@ +/* FreeRTOS Real Time Stats Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_err.h" + +#define NUM_OF_SPIN_TASKS 6 +#define SPIN_ITER 500000 //Actual CPU cycles used will depend on compiler optimization +#define SPIN_TASK_PRIO 2 +#define STATS_TASK_PRIO 3 +#define STATS_TICKS pdMS_TO_TICKS(1000) +#define ARRAY_SIZE_OFFSET 5 //Increase this if print_real_time_stats returns ESP_ERR_INVALID_SIZE + +static char task_names[NUM_OF_SPIN_TASKS][configMAX_TASK_NAME_LEN]; +static SemaphoreHandle_t sync_spin_task; +static SemaphoreHandle_t sync_stats_task; + +/** + * @brief Function to print the CPU usage of tasks over a given duration. + * + * This function will measure and print the CPU usage of tasks over a specified + * number of ticks (i.e. real time stats). This is implemented by simply calling + * uxTaskGetSystemState() twice separated by a delay, then calculating the + * differences of task run times before and after the delay. + * + * @note If any tasks are added or removed during the delay, the stats of + * those tasks will not be printed. + * @note This function should be called from a high priority task to minimize + * inaccuracies with delays. + * @note When running in dual core mode, each core will correspond to 50% of + * the run time. + * + * @param xTicksToWait Period of stats measurement + * + * @return + * - ESP_OK Success + * - ESP_ERR_NO_MEM Insufficient memory to allocated internal arrays + * - ESP_ERR_INVALID_SIZE Insufficient array size for uxTaskGetSystemState. Trying increasing ARRAY_SIZE_OFFSET + * - ESP_ERR_INVALID_STATE Delay duration too short + */ +static esp_err_t print_real_time_stats(TickType_t xTicksToWait) +{ + TaskStatus_t *start_array = NULL, *end_array = NULL; + UBaseType_t start_array_size, end_array_size; + uint32_t start_run_time, end_run_time; + esp_err_t ret; + + //Allocate array to store current task states + start_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET; + start_array = malloc(sizeof(TaskStatus_t) * start_array_size); + if (start_array == NULL) { + ret = ESP_ERR_NO_MEM; + goto exit; + } + //Get current task states + start_array_size = uxTaskGetSystemState(start_array, start_array_size, &start_run_time); + if (start_array_size == 0) { + ret = ESP_ERR_INVALID_SIZE; + goto exit; + } + + vTaskDelay(xTicksToWait); + + //Allocate array to store tasks states post delay + end_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET; + end_array = malloc(sizeof(TaskStatus_t) * end_array_size); + if (end_array == NULL) { + ret = ESP_ERR_NO_MEM; + goto exit; + } + //Get post delay task states + end_array_size = uxTaskGetSystemState(end_array, end_array_size, &end_run_time); + if (end_array_size == 0) { + ret = ESP_ERR_INVALID_SIZE; + goto exit; + } + + //Calculate total_elapsed_time in units of run time stats clock period. + uint32_t total_elapsed_time = (end_run_time - start_run_time); + if (total_elapsed_time == 0) { + ret = ESP_ERR_INVALID_STATE; + goto exit; + } + + printf("| Task | Run Time | Percentage\n"); + //Match each task in start_array to those in the end_array + for (int i = 0; i < start_array_size; i++) { + int k = -1; + for (int j = 0; j < end_array_size; j++) { + if (start_array[i].xHandle == end_array[j].xHandle) { + k = j; + //Mark that task have been matched by overwriting their handles + start_array[i].xHandle = NULL; + end_array[j].xHandle = NULL; + break; + } + } + //Check if matching task found + if (k >= 0) { + uint32_t task_elapsed_time = end_array[k].ulRunTimeCounter - start_array[i].ulRunTimeCounter; + uint32_t percentage_time = (task_elapsed_time * 100UL) / (total_elapsed_time * portNUM_PROCESSORS); + printf("| %s | %d | %d%%\n", start_array[i].pcTaskName, task_elapsed_time, percentage_time); + } + } + + //Print unmatched tasks + for (int i = 0; i < start_array_size; i++) { + if (start_array[i].xHandle != NULL) { + printf("| %s | Deleted\n", start_array[i].pcTaskName); + } + } + for (int i = 0; i < end_array_size; i++) { + if (end_array[i].xHandle != NULL) { + printf("| %s | Created\n", end_array[i].pcTaskName); + } + } + ret = ESP_OK; + +exit: //Common return path + free(start_array); + free(end_array); + return ret; +} + +static void spin_task(void *arg) +{ + xSemaphoreTake(sync_spin_task, portMAX_DELAY); + while (1) { + //Consume CPU cycles + for (int i = 0; i < SPIN_ITER; i++) { + __asm__ __volatile__("NOP"); + } + vTaskDelay(pdMS_TO_TICKS(100)); + } +} + +static void stats_task(void *arg) +{ + xSemaphoreTake(sync_stats_task, portMAX_DELAY); + + //Start all the spin tasks + for (int i = 0; i < NUM_OF_SPIN_TASKS; i++) { + xSemaphoreGive(sync_spin_task); + } + + //Print real time stats periodically + while (1) { + printf("\n\nGetting real time stats over %d ticks\n", STATS_TICKS); + if (print_real_time_stats(STATS_TICKS) == ESP_OK) { + printf("Real time stats obtained\n"); + } else { + printf("Error getting real time stats\n"); + } + vTaskDelay(pdMS_TO_TICKS(1000)); + } +} + +void app_main(void) +{ + //Allow other core to finish initialization + vTaskDelay(pdMS_TO_TICKS(100)); + + //Create semaphores to synchronize + sync_spin_task = xSemaphoreCreateCounting(NUM_OF_SPIN_TASKS, 0); + sync_stats_task = xSemaphoreCreateBinary(); + + //Create spin tasks + for (int i = 0; i < NUM_OF_SPIN_TASKS; i++) { + snprintf(task_names[i], configMAX_TASK_NAME_LEN, "spin%d", i); + xTaskCreatePinnedToCore(spin_task, task_names[i], 1024, NULL, SPIN_TASK_PRIO, NULL, tskNO_AFFINITY); + } + + //Create and start stats task + xTaskCreatePinnedToCore(stats_task, "stats", 4096, NULL, STATS_TASK_PRIO, NULL, tskNO_AFFINITY); + xSemaphoreGive(sync_stats_task); +} diff --git a/sdkconfig.ci b/sdkconfig.ci new file mode 100644 index 0000000..c87713d --- /dev/null +++ b/sdkconfig.ci @@ -0,0 +1,2 @@ +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000..c87713d --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y