Estimating CPU load of a task scheduler
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.