ESP32 CPU load % display without special configuration.
ESP32 CPU load % display without special configuration.
I wanted a non intrusive CPU load test that I can casually print the CPU load on the terminal. And I did not want to enable the
FreeRTOS task stat subsystem.
The result is extracted to this project. (link below)
https://github.com/pglen/esp32_cpu_load
Theory:
We create a low priority (idle) task, and a higher priority (monitor) task.
The low priority task delays 0 ms. We know, the task surrenders its control
of the CPU, will not get it back until the next time slice. (10 msec)
But ...
If the delay time value is zero, the scheduler tries to give the processor back as
soon as a time slice is available. Not getting the control back is how we know the processor
is doing other 'stuff'.
The core of the measuring technique is in the following three lines:
int64_t now = esp_timer_get_time(); // Time anchor
vTaskDelay(0 / portTICK_RATE_MS);
int64_t now2 = esp_timer_get_time(); // The diff is the time away.
Testing:
Every 10 seconds we started a heavy loop. Here is what the console shows:
ESP32, 2 CPU cores, WiFi/BT/BLE, silicon revision 1, 4MB external flash
CPU Load Demo.
100% 2% -0% -0% -0% -0% -0% -0% -0% work [[ -0% 99% ]] rest 70% -0% -0%
-0% -0% -0% -0% -0% -0% -0% work [[ 31% 100% ]] rest 38% -0% -0% -0% -0%
-0% -0% -0% -0% -0% work [[ 63% 100% ]] rest 6% -0% -0% -0% -0% -0% -0%
-0% -0% -0% work [[ 95% ]] rest 74% -0% -0% -0% -0% -0% -0% -0% -0%
-0% work [[ 27% 100% ]] rest 42% -0%
Note how nicely the sliding time window overlap shows.
Peter Glen
Free to copy.
FreeRTOS task stat subsystem.
The result is extracted to this project. (link below)
https://github.com/pglen/esp32_cpu_load
Theory:
We create a low priority (idle) task, and a higher priority (monitor) task.
The low priority task delays 0 ms. We know, the task surrenders its control
of the CPU, will not get it back until the next time slice. (10 msec)
But ...
If the delay time value is zero, the scheduler tries to give the processor back as
soon as a time slice is available. Not getting the control back is how we know the processor
is doing other 'stuff'.
The core of the measuring technique is in the following three lines:
int64_t now = esp_timer_get_time(); // Time anchor
vTaskDelay(0 / portTICK_RATE_MS);
int64_t now2 = esp_timer_get_time(); // The diff is the time away.
Testing:
Every 10 seconds we started a heavy loop. Here is what the console shows:
ESP32, 2 CPU cores, WiFi/BT/BLE, silicon revision 1, 4MB external flash
CPU Load Demo.
100% 2% -0% -0% -0% -0% -0% -0% -0% work [[ -0% 99% ]] rest 70% -0% -0%
-0% -0% -0% -0% -0% -0% -0% work [[ 31% 100% ]] rest 38% -0% -0% -0% -0%
-0% -0% -0% -0% -0% work [[ 63% 100% ]] rest 6% -0% -0% -0% -0% -0% -0%
-0% -0% -0% work [[ 95% ]] rest 74% -0% -0% -0% -0% -0% -0% -0% -0%
-0% work [[ 27% 100% ]] rest 42% -0%
Note how nicely the sliding time window overlap shows.
Peter Glen
Free to copy.
Re: ESP32 CPU load % display without special configuration.
The issue I see is that you have a thread (idle_task) just burning away clock cycles all the time.
When you want to know the CPU load you should have your mon_task fire signal the idle_task to start.
Kinda like this, (taken from your example) :
P.S. dont forget to put this in main: cpu_load_event_group= xEventGroupCreate();
Also, when you use a global variable like "static int idle_cnt = 0;" and its shared amongst threads you want to make it a volatile variable.
When you want to know the CPU load you should have your mon_task fire signal the idle_task to start.
Kinda like this, (taken from your example) :
Code: Select all
const int CPU_LOAD_START_BIT = BIT0;
const int CPU_LOAD_COMPLETE_BIT = BIT1;
EventGroupHandle_t cpu_load_event_group;
static void idle_task(void *parm)
{
while(1==1)
{
//Wait here until we are called to time things
xEventGroupWaitBits(cpu_load_event_group, CPU_LOAD_START_BIT, true, true, portMAX_DELAY);
int64_t now = esp_timer_get_time(); // time anchor
vTaskDelay(0 / portTICK_RATE_MS);
int64_t now2 = esp_timer_get_time();
idle_cnt += (now2 - now) / 1000; // diff
// Signal we have finished timing things
xEventGroupSetBits(cpu_load_event_group, CPU_LOAD_COMPLETE_BIT);
}
}
static void mon_task(void *parm)
{
while(1==1)
{
// Note the trick of saving it on entry, so print time
// is not added to our timing.
// Signal idle_task to start the timing
xEventGroupSetBits(cpu_load_event_group, CPU_LOAD_START_BIT);
//Wait here until idle-task is finished with the timeing
xEventGroupWaitBits(cpu_load_event_group, CPU_LOAD_COMPLETE_BIT, true, true, portMAX_DELAY);
float new_cnt = (float)idle_cnt; // Save the count for printing it ...
// Compensate for the 100 ms delay artifact: 900 ms = 100%
float cpu_percent = ((99.9 / 90.) * new_cnt) / 10;
printf("%.0f%% ", 100 - cpu_percent); fflush(stdout);
idle_cnt = 0; // Reset variable
vTaskDelay(1000 / portTICK_RATE_MS);
}
}
Also, when you use a global variable like "static int idle_cnt = 0;" and its shared amongst threads you want to make it a volatile variable.
- gunar.kroeger
- Posts: 143
- Joined: Fri Jul 27, 2018 6:48 pm
Re: ESP32 CPU load % display without special configuration.
is there a way to modify the freertos idle task to do this?
can we use configUSE_IDLE_HOOK or does esp-idf make it so that we can't change it?
can we use configUSE_IDLE_HOOK or does esp-idf make it so that we can't change it?
"Running was invented in 1612 by Thomas Running when he tried to walk twice at the same time."
Re: ESP32 CPU load % display without special configuration.
There is (https://docs.espressif.com/projects/esp ... html#hooks), but keep in mind that vTaskDelay and other blocking functions are not allowed in the Idle task.
- gunar.kroeger
- Posts: 143
- Joined: Fri Jul 27, 2018 6:48 pm
Re: ESP32 CPU load % display without special configuration.
So if I just keep incrementing a volatile variable on this hook for each core, It should increase at a rate proportional to how much the core is being idle? Is there a formula to how I can convert that to cpu load?
Else, I would strip the code to run the least amount of code possible, and than the maximum and empirically make a formula that gives an approximation for this.
Else, I would strip the code to run the least amount of code possible, and than the maximum and empirically make a formula that gives an approximation for this.
"Running was invented in 1612 by Thomas Running when he tried to walk twice at the same time."
Re: ESP32 CPU load % display without special configuration.
The number of times the idle hook runs is not directly proportional to the time spent in idle state. After invoking the idle hooks, the idle task puts the CPU into "wait for interrupt state" ("waiti", similar to "wfi" in ARM CPUs). The CPU stays in "waiti" state until an interrupt happens. When the execution returns back to the idle task, the idle task runs the idle hooks again. So depending on how often the interrupts happen, the idle task may occupy different amount of CPU time, per idle hook invocation.
- gunar.kroeger
- Posts: 143
- Joined: Fri Jul 27, 2018 6:48 pm
Re: ESP32 CPU load % display without special configuration.
Even if I set the callback to return false?
"Register a callback to the idle hook of the core that calls this function. The callback should return true if it should be called by the idle hook once per interrupt (or FreeRTOS tick), and return false if it should be called repeatedly as fast as possible by the idle hook. "
also, would there be any downside to returning false, like slowing something down or consuming more power?
"Register a callback to the idle hook of the core that calls this function. The callback should return true if it should be called by the idle hook once per interrupt (or FreeRTOS tick), and return false if it should be called repeatedly as fast as possible by the idle hook. "
also, would there be any downside to returning false, like slowing something down or consuming more power?
"Running was invented in 1612 by Thomas Running when he tried to walk twice at the same time."
Re: ESP32 CPU load % display without special configuration.
Yes, you could return false from the idle hook to get it called repeatedly. This will probably have a small effect on current consumption. It would also not allow the automatic light sleep and dynamic frequency scaling to work correctly, if you plan to use these features.
- gunar.kroeger
- Posts: 143
- Joined: Fri Jul 27, 2018 6:48 pm
Re: ESP32 CPU load % display without special configuration.
thanks
"Running was invented in 1612 by Thomas Running when he tried to walk twice at the same time."
Re: ESP32 CPU load % display without special configuration.
#include <Arduino.h>
extern "C"
{
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"
#include "freertos/task.h"
}
const int CPU0_LOAD_START_BIT = BIT0;
const int CPU1_LOAD_START_BIT = BIT1;
EventGroupHandle_t cpu_load_event_group;
ulong idleCnt=0;
void idleCPU0Task(void *parm)
{
ulong now, now2;
while (true)
{
//Wait here if not enabled
xEventGroupWaitBits(cpu_load_event_group, CPU0_LOAD_START_BIT, false, false, portMAX_DELAY);
now = millis(); //esp_timer_get_time();
vTaskDelay(0 / portTICK_RATE_MS);
now2 = millis(); //esp_timer_get_time();
idleCnt += (now2 - now); //accumulate the usec's
}
}
void idleCPU1Task(void *parm)
{
ulong now, now2;
while (true)
{
//Wait here if not enabled
xEventGroupWaitBits(cpu_load_event_group, CPU1_LOAD_START_BIT, false, false, portMAX_DELAY);
now = millis();
vTaskDelay(0 / portTICK_RATE_MS);
now2 = millis();
idleCnt += (now2 - now); //accumulate the msec's while enabled
}
}
void cpuMonTask(void *parm)
{
float cpuPercent;
//float adjust = 1.11; //900msec = 100% - this doesnt seem to work
float adjust = 1.00;
cpu_load_event_group = xEventGroupCreate();
xEventGroupClearBits(cpu_load_event_group, CPU0_LOAD_START_BIT);
xEventGroupClearBits(cpu_load_event_group, CPU1_LOAD_START_BIT);
xTaskCreatePinnedToCore(&idleCPU0Task, "idleCPU0Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL,0); //lowest priority CPU 0 task
xTaskCreatePinnedToCore(&idleCPU1Task, "idleCPU1Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL,1); //lowest priority CPU 1 task
while (true)
{
//measure CPU0
idleCnt = 0; // Reset usec timer
xEventGroupSetBits(cpu_load_event_group, CPU0_LOAD_START_BIT); // Signal idleCPU0Task to start timing
vTaskDelay(1000 / portTICK_RATE_MS); //measure for 1 second
xEventGroupClearBits(cpu_load_event_group, CPU0_LOAD_START_BIT); // Signal to stop the timing
vTaskDelay(1 / portTICK_RATE_MS); //make sure idleCnt isnt being used
// Compensate for the 100 ms delay artifact: 900 msec = 100%
//cpuPercent = ((99.9 / 90.0) * idleCnt/1000.0) / 10.0;
cpuPercent = adjust * (float)idleCnt / 10.0;
printf("CPU0: %ul %.0f%% ",idleCnt, 100 - cpuPercent);
fflush(stdout);
//measure CPU1
idleCnt = 0; // Reset usec timer
xEventGroupSetBits(cpu_load_event_group, CPU1_LOAD_START_BIT); // Signal idleCPU1Task to start timing
vTaskDelay(1000 / portTICK_RATE_MS); //measure for 1 second
xEventGroupClearBits(cpu_load_event_group, CPU1_LOAD_START_BIT); // Signal idle_task to stop the timing
vTaskDelay(1 / portTICK_RATE_MS); //make sure idleCnt isnt being used
// Compensate for the 100 ms delay artifact: 900 msec = 100%
//cpuPercent = ((99.9 / 90.0) * idleCnt/1000.0) / 10.0;
cpuPercent = adjust * (float)idleCnt / 10.0;
printf("CPU1: %ul %.0f%% \n\r",idleCnt, 100 - cpuPercent);
fflush(stdout);
vTaskDelay(10000 / portTICK_RATE_MS); //measure every 10 seconds
}
}
extern "C"
{
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"
#include "freertos/task.h"
}
const int CPU0_LOAD_START_BIT = BIT0;
const int CPU1_LOAD_START_BIT = BIT1;
EventGroupHandle_t cpu_load_event_group;
ulong idleCnt=0;
void idleCPU0Task(void *parm)
{
ulong now, now2;
while (true)
{
//Wait here if not enabled
xEventGroupWaitBits(cpu_load_event_group, CPU0_LOAD_START_BIT, false, false, portMAX_DELAY);
now = millis(); //esp_timer_get_time();
vTaskDelay(0 / portTICK_RATE_MS);
now2 = millis(); //esp_timer_get_time();
idleCnt += (now2 - now); //accumulate the usec's
}
}
void idleCPU1Task(void *parm)
{
ulong now, now2;
while (true)
{
//Wait here if not enabled
xEventGroupWaitBits(cpu_load_event_group, CPU1_LOAD_START_BIT, false, false, portMAX_DELAY);
now = millis();
vTaskDelay(0 / portTICK_RATE_MS);
now2 = millis();
idleCnt += (now2 - now); //accumulate the msec's while enabled
}
}
void cpuMonTask(void *parm)
{
float cpuPercent;
//float adjust = 1.11; //900msec = 100% - this doesnt seem to work
float adjust = 1.00;
cpu_load_event_group = xEventGroupCreate();
xEventGroupClearBits(cpu_load_event_group, CPU0_LOAD_START_BIT);
xEventGroupClearBits(cpu_load_event_group, CPU1_LOAD_START_BIT);
xTaskCreatePinnedToCore(&idleCPU0Task, "idleCPU0Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL,0); //lowest priority CPU 0 task
xTaskCreatePinnedToCore(&idleCPU1Task, "idleCPU1Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL,1); //lowest priority CPU 1 task
while (true)
{
//measure CPU0
idleCnt = 0; // Reset usec timer
xEventGroupSetBits(cpu_load_event_group, CPU0_LOAD_START_BIT); // Signal idleCPU0Task to start timing
vTaskDelay(1000 / portTICK_RATE_MS); //measure for 1 second
xEventGroupClearBits(cpu_load_event_group, CPU0_LOAD_START_BIT); // Signal to stop the timing
vTaskDelay(1 / portTICK_RATE_MS); //make sure idleCnt isnt being used
// Compensate for the 100 ms delay artifact: 900 msec = 100%
//cpuPercent = ((99.9 / 90.0) * idleCnt/1000.0) / 10.0;
cpuPercent = adjust * (float)idleCnt / 10.0;
printf("CPU0: %ul %.0f%% ",idleCnt, 100 - cpuPercent);
fflush(stdout);
//measure CPU1
idleCnt = 0; // Reset usec timer
xEventGroupSetBits(cpu_load_event_group, CPU1_LOAD_START_BIT); // Signal idleCPU1Task to start timing
vTaskDelay(1000 / portTICK_RATE_MS); //measure for 1 second
xEventGroupClearBits(cpu_load_event_group, CPU1_LOAD_START_BIT); // Signal idle_task to stop the timing
vTaskDelay(1 / portTICK_RATE_MS); //make sure idleCnt isnt being used
// Compensate for the 100 ms delay artifact: 900 msec = 100%
//cpuPercent = ((99.9 / 90.0) * idleCnt/1000.0) / 10.0;
cpuPercent = adjust * (float)idleCnt / 10.0;
printf("CPU1: %ul %.0f%% \n\r",idleCnt, 100 - cpuPercent);
fflush(stdout);
vTaskDelay(10000 / portTICK_RATE_MS); //measure every 10 seconds
}
}
Who is online
Users browsing this forum: No registered users and 142 guests