Estimating CPU load of a task scheduler First off, I should note that I'm not sure what tags best apply here, and I can't seem to find any that relate to schedulers, etc. Please inform me if they are not the right ones.
I have a simple, portable task scheduler (it only relies on millis(), and runs on different Arduino-supported systems) that is called via a function in loop() and that picks and then runs a function pointer (the &quot;task&quot;) from a list of tasks with status, sleep, and priority information.
I would like to have a way to determine and to tell the user how much CPU time is being spent on running tasks vs. being idle.
The problem is that there can be other functions being called from loop(), not just my scheduler. It would thus be a bad idea to track total time vs. time spent in a task. If I did, the CPU use would always read low, since the actual task time cannot use all of the CPU time since the other tasks in loop() would be quietly using some of it.
However, if I specifically track time in the scheduler vs. time spent in task, I run into a new issue. I would use more memory (a few more bytes, but I'm using a lot already...) and more CPU*, since I'd have to keep track of the total so far and also the current runtime for that iteration of the scheduler. That's on top of the similar construct determining the runtime of the task. Mainly, though, the issue is that the loop() function and scheduler run fast enough that the millis() timer can tick slower than the changeover between my scheduler and the loop() function. This causes the timing to also be off, since it would be counted if it ticks in the scheduler even if it was mainly loop()'s fault.
Is there a general methodology for computing the CPU time? It would be difficult to time-slice the scheduler, and that would run slower anyway. I could count the proportion of scheduler calls that run/don't run tasks, but that won't work because tasks take longer than the no-op that happens if a task doesn't run, and it makes no accounting for task length.
*On an AVR system, the 8-bit CPU uses a surprising aggregate time on all those 32-bit operations, between the sleep time calculations (on sleeping tasks only, to be fair) and the elapsed time calculations that fed into my prior CPU use attempt. It wasn't the only cause, but I saw quite a bit of timing slip on one attempt of coding the scheduler because of a similar issue.
The only metric you have is time. The only thing you can do is compare that time over iterations / task calls. The typical approach is to note the time at the start of a task &quot;tick&quot;, and note it at the end. Then add the difference to the total for that task.
When you want to then look at usage percentages you can compare those totals to the total time the system has been running.
The total runtime is already done for you, in the form of millis(), so you don't need to worry about that.
Recording each task's total runtime in microseconds is simple enough, but of course will entail an overhead. That is unavoidable no matter what strategy you take. It's kind of quantum: the act of measuring changes the results.
If you treat your &quot;idle&quot; task the same as any other task (which is the normal way of doing things - the idle task is just a task that does nothing) then the sum of all the time counts for all the tasks = (within a certain granularity) the total amount of time your task scheduler has been running. The difference between that and the system runtime (millis()) is the proportion of the time your scheduler has been running compared to other things happening in loop().
Whether you compare to millis() or micros() for the system runtime is up to you. By storing micros() per task you're getting a reasonable resolution for each tasks's runtime (but with a limit to how long before it wraps - but you can handle that in software by carrying over to another lower resolution counter if you want), but you can then discard some of that resolution when doing your calculations if you don't need it at the time.
Estimating runtime in a co-operative multitasking system like this is never good, and often not bothered with because of that. It's always a guestimate with the results being skewed by the measuring, since the measuring is synchronous with the execution of the tasks.
In a proper context-switching multitasking system you would normally operate on the number of &quot;ticks&quot; of the system rather on time. That is, simply count 1 each time a task is switched in by the context switcher. Since each task has a fixed runtime controlled by the granularity of the scheduler it's then trivial to know that task X has been running for X/TOTAL_TICKS % of the time. Any calculations will be themselves done in a task - maybe the idle task, or a system accounting task.