arkhipenko / TaskScheduler

Cooperative multitasking for Arduino, ESPx, STM32, nRF and other microcontrollers
http://playground.arduino.cc/Code/TaskScheduler
BSD 3-Clause "New" or "Revised" License
1.22k stars 224 forks source link

Measuring CPU load #81

Closed moritz89 closed 4 years ago

moritz89 commented 4 years ago

Hi, I wanted to know if there is a method to measure the CPU load? i.e., time spent in the IDLE mode

arkhipenko commented 4 years ago

Hi. Currently, there is no such method as I have never needed one, nor been asked to implement one. Also, since this needs to happen inside the scheduling loop I am trying to avoid putting any non-essential processing inside there. Could you please elaborate on the use case? Why is it necessary? Cheers, Anatoli

moritz89 commented 4 years ago

Hi, as the scope of my program on the ESP32 grows I would like to know how much headroom I still have. A system monitor task is currently able to periodically report free RAM and heap fragmentation, but I currently have no idea how fast I can clock other tasks and what difference a more frequent execution of those tasks will have on the CPU load.

Since the loop() function is always being called, using the system CPU load reporting will be of little use as TaskScheduler is not using the system idle thread, as far as I know (for the ESP32).

TD-er commented 4 years ago

Just as a disclaimer. I do not use this scheduler, but am using something I wrote myself. (as I didn't know about this one...)

What I do in my implementation is checking if there is something to be done and if not, I do log the current time stamp. (also keep track if that timestamp is 0 before setting it) As soon as there is something to be done, I compute the current time elapsed and add that to a counter. Periodically I reset this counter and the timestamp since the last reset. Before resetting it, I store the last load value (idle time counter / time since last reset).

But if you have some priority tasks possible, then you could also schedule a lowest priority task with its only purpose to keep track of how long it was running. (and maybe calling delay() to keep the CPU power consumption low)

chrisalbertson commented 4 years ago

I simple method will cost you one output pin. The idle loop sets the pin low as the first thing it does then high on exit. Then you look at the pulse width (or average voltage) of the pin with a logic analyzer or even an AC voltmeter and you can see CPU usage in real-time.

This is very helpful when you are deciding on changes to the software and want to know if you have enough "spare" CPU time to support your proposed new feature. But it does cost a pin and adds a few instructions to the idle loop.

It is a good way to answer a question like "can I double the frequency of this control loop?" Just look and see if you are only using 10% of the CPU then "Yes" but if you are already at 60% then certainly not.

On Sun, Jan 5, 2020 at 12:00 PM Anatoli Arkhipenko notifications@github.com wrote:

Hi. Currently, there is no such method as I have never needed one, nor been asked to implement one. Also, since this needs to happen inside the scheduling loop I am trying to avoid putting any non-essential processing inside there. Could you please elaborate on the use case? Why is it necessary? Cheers, Anatoli

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/arkhipenko/TaskScheduler/issues/81?email_source=notifications&email_token=ABQKNRQGTR3ZIWUQEFLHDRDQ4I34LA5CNFSM4KC3SEWKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEID6HGA#issuecomment-570942360, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABQKNRQFTZUOZLYDFOGGQSDQ4I34LANCNFSM4KC3SEWA .

--

Chris Albertson Redondo Beach, California

arkhipenko commented 4 years ago

All good comments, guys, thank you.

However, since we are talking about ESP32, the situation is a little complicated: There is no real IDLE sleep on ESP chips. Or at least I could not find the one that puts CPU in a similar state to that of the Arduino Idle Sleep. ESP sleep function requires a reset of the chip, which is not usable for task scheduling. So IDLE sleep for ESP8266 and ESP32 is simulated by just calling esp.delay(1) if one pass through the task chain takes less then a threshold value (currently 200 microseconds). This is very unscientific and I don't even use _TASK_SLEEP_ON_IDLE_RUN compile option with ESPs.

Additionally on ESP32 loop() and therefore TS tasks run on CORE 1 (Application) inside the FreeRTOS idle task. Since tasks from CORE1 could potentially call system routines from CORE0 and then receive callbacks (especially in the WiFi methods), there is a way to create race condition on the callbacks leading to unpredictable results and crashes.

I have (successfully) experimented combining FreeRTOS and TS in one program (multiple TSs actially, since you can have a separate scheduler per RTOS task), but this proved to be not trivial and caused a lot of tripping of RTOS watchdog timers.

I have some ideas how we can measure what you are trying to measure - let me noodle on those.

Meanwhile, there is something you can already do: Use _TASK_TIMECRITICAL compile option: Compile parameters

You can use these two methods to assess whether you still have CPU capacity: getStartDelay() getOverrun()

If you start getting consistent and growing negative values on getOverrun, your CPU is not handling the load.

arkhipenko commented 4 years ago

OK. I pushed version 3.1.0 into the testing branch. Please refer to Example #24 for the CPU load measuring methods.

Below is example 24 output for ESP32:

WITH IDLE SLEEP
==================
CPU Time Measurement
Start
Loop counts c1=11001
Task counts c2=1001
Total CPU time=9999976 micros
Scheduling overhead CPU time=45013 micros
Idle Sleep CPU time=9789919 micros
CPU Callback time=165044 micros

CPU Idle Sleep 97.90 % of time.
CPU Callback processing 1.65 % of time.

WITHOUT IDLE SLEEP
==================
CPU Time Measurement
Start
Loop counts c1=548161
Task counts c2=1001
Total CPU time=9999927 micros
Scheduling overhead CPU time=2306636 micros
Idle Sleep CPU time=0 micros
CPU Callback time=7693291 micros

CPU Idle Sleep 0.00 % of time.
CPU Callback processing 76.93 % of time.

I think 'CPU Callback processing' is a bit of a misnomer. It should be "CPU callback processing and other stuff the ESP OS does outlside of loop() like running WiFi stack, etc.)

arkhipenko commented 4 years ago

@moritz89 : would be helpful if you could test and comment.

moritz89 commented 4 years ago

First off, the added functionality is exactly what I am looking for! I agree that the "CPU Callback processing" would rather be "Time not spent in TaskScheduler" which effectively translates to "Doing effictive work".

While testing the changes I noticed two issues:

  1. Due to changes in commit 44240bfd, including will cause the build to fail due to redeclaration of the millis() and micros() function.
  2. The warning that _TASK_SLEEP_ON_IDLE_RUN is not supported on the ESP32 might be removed. It actually yields the current task as is implemented by FreeRTOS.

My results while running the example code showed:

# Without _TASK_SLEEP_ON_IDLE_RUN
Start
Loop counts c1=547483
Task counts c2=1001
Total CPU time=9999778 micros
Scheduling overhead CPU time=2265702 micros
Idle Sleep CPU time=0 micros
CPU Callback time=7734076 micros

CPU Idle Sleep 0.00 % of time.
CPU Callback processing 77.34 % of time.

# With _TASK_SLEEP_ON_IDLE_RUN
CPU Time Measurement
Start
Loop counts c1=11001
Task counts c2=1001
Total CPU time=9999949 micros
Scheduling overhead CPU time=45025 micros
Idle Sleep CPU time=9789831 micros
CPU Callback time=165093 micros

CPU Idle Sleep 97.90 % of time.
CPU Callback processing 1.65 % of time.
arkhipenko commented 4 years ago

Due to changes in commit 44240bf, including will cause the build to fail due to redeclaration of the millis() and micros() function.

Could you please send me the files? I did test example 24 on ESP32 and it did not have any issues Just making sure: you include in one file and in all other files. Not both!

The warning that _TASK_SLEEP_ON_IDLE_RUN is not supported on the ESP32 might be removed. It actually yields the current task as is implemented by FreeRTOS.

Well... Idle sleep supposed to put the chip to sleep, which ESPs just does not do... just delays, so no power savings actually take place.

Thanks for testing!

arkhipenko commented 4 years ago

Also: micros()rolls over every ~72 minutes. I have not tested the stats running continuously for such a long time, (pretty sure the results would be completely incorrect since total scheduling time will suddenly go down dramatically while accumulated idle time will remain high) but if you plan to do so I would recommend calling ts.cpuLoadReset(); every hour or so if you plan to use it beyond testing.

moritz89 commented 4 years ago

@arkhipenko I reordered my includes and how the defines are set and it works now. Thanks again for the quick turn-around for the CPU load functionality!

arkhipenko commented 4 years ago

Enjoy! And thank you for the suggestion. I think this is useful and I could have used it in my projects before for sure...