Page 1 of 1

ESP32 CPU load % display without special configuration.

Posted: Sat Mar 23, 2019 1:52 am
by peterglen
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.

Re: ESP32 CPU load % display without special configuration.

Posted: Sat Mar 23, 2019 2:30 am
by username
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) :

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);
        }
}
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.

Re: ESP32 CPU load % display without special configuration.

Posted: Mon Apr 13, 2020 7:43 pm
by gunar.kroeger
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?

Re: ESP32 CPU load % display without special configuration.

Posted: Tue Apr 14, 2020 6:31 am
by ESP_igrr
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.

Re: ESP32 CPU load % display without special configuration.

Posted: Tue Apr 14, 2020 1:06 pm
by gunar.kroeger
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.

Re: ESP32 CPU load % display without special configuration.

Posted: Tue Apr 14, 2020 1:44 pm
by ESP_igrr
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.

Re: ESP32 CPU load % display without special configuration.

Posted: Tue Apr 14, 2020 2:20 pm
by gunar.kroeger
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?

Re: ESP32 CPU load % display without special configuration.

Posted: Tue Apr 14, 2020 3:44 pm
by ESP_igrr
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.

Re: ESP32 CPU load % display without special configuration.

Posted: Tue Apr 14, 2020 4:48 pm
by gunar.kroeger
thanks

Re: ESP32 CPU load % display without special configuration.

Posted: Sun Oct 25, 2020 2:08 pm
by andrewb
#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
}
}