ESP32 CPU load % display without special configuration.

peterglen
Posts: 27
Joined: Thu Mar 22, 2018 5:55 pm

ESP32 CPU load % display without special configuration.

Postby peterglen » Sat Mar 23, 2019 1:52 am

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.

username
Posts: 535
Joined: Thu May 03, 2018 1:18 pm

Re: ESP32 CPU load % display without special configuration.

Postby username » Sat Mar 23, 2019 2:30 am

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.

User avatar
gunar.kroeger
Posts: 143
Joined: Fri Jul 27, 2018 6:48 pm

Re: ESP32 CPU load % display without special configuration.

Postby gunar.kroeger » Mon Apr 13, 2020 7:43 pm

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?
"Running was invented in 1612 by Thomas Running when he tried to walk twice at the same time."

ESP_igrr
Posts: 2071
Joined: Tue Dec 01, 2015 8:37 am

Re: ESP32 CPU load % display without special configuration.

Postby ESP_igrr » Tue Apr 14, 2020 6:31 am

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.

User avatar
gunar.kroeger
Posts: 143
Joined: Fri Jul 27, 2018 6:48 pm

Re: ESP32 CPU load % display without special configuration.

Postby gunar.kroeger » Tue Apr 14, 2020 1:06 pm

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.
"Running was invented in 1612 by Thomas Running when he tried to walk twice at the same time."

ESP_igrr
Posts: 2071
Joined: Tue Dec 01, 2015 8:37 am

Re: ESP32 CPU load % display without special configuration.

Postby ESP_igrr » Tue Apr 14, 2020 1:44 pm

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.

User avatar
gunar.kroeger
Posts: 143
Joined: Fri Jul 27, 2018 6:48 pm

Re: ESP32 CPU load % display without special configuration.

Postby gunar.kroeger » Tue Apr 14, 2020 2:20 pm

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?
"Running was invented in 1612 by Thomas Running when he tried to walk twice at the same time."

ESP_igrr
Posts: 2071
Joined: Tue Dec 01, 2015 8:37 am

Re: ESP32 CPU load % display without special configuration.

Postby ESP_igrr » Tue Apr 14, 2020 3:44 pm

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.

User avatar
gunar.kroeger
Posts: 143
Joined: Fri Jul 27, 2018 6:48 pm

Re: ESP32 CPU load % display without special configuration.

Postby gunar.kroeger » Tue Apr 14, 2020 4:48 pm

thanks
"Running was invented in 1612 by Thomas Running when he tried to walk twice at the same time."

andrewb
Posts: 1
Joined: Sun Oct 25, 2020 1:55 pm

Re: ESP32 CPU load % display without special configuration.

Postby andrewb » Sun Oct 25, 2020 2:08 pm

#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
}
}

Who is online

Users browsing this forum: ESP_Sprite, ltsm0265 and 96 guests