Compare representative snapshots.
Take snapshots during idle operation, representative load, and recovery. Group snapshots are coherent, but they briefly hold scheduler locks and cost O(group members). Sample periodically instead of sampling each request.
Because lifetime counters wrap and do not reset, compare two samples instead of using one cumulative value as a rate.
- Healthy waiting
- A high
Waitingcount with timer, descriptor, interrupt, or file waits usually means tasks are using the intended kernel-backed wait path. - Runnable pressure
- A persistently high
Readycount means runnable work is sharing the group. Compare dispatch progress, CPU use, checkpoint placement, and the result of adding a group. - Loop utilization
Idle_Nanosecondsis the part ofUptime_Nanosecondsin which the group had no runnable task. The difference is the time the loop worked. Read it together withReadybefore adding a group.- Possible stall
- Ready or running work without dispatch or polling progress indicates possible loop lag.
Stall_Watchdogscan sample this condition from a native monitor task. The watchdog diagnoses the condition but does not preempt a task. - File pressure
Pending_File_Submissionsshows file operations waiting behind the bounded kernel completion queue. It is backpressure, not a hidden worker-thread queue.- Stack pressure
- Track live stacks, effective usable bytes, guarded virtual reservation, process RSS, and task-creation failures together. None of those values alone is a complete memory limit.
- Dormant stack effect
- Compare candidate bytes, currently advised bytes, cumulative accepted and failed advice, process RSS, and timer wake lateness. Advice counters do not prove that pages were evicted.
- Region growth
- Read consumed and reserved region storage before release. Reserved minus consumed shows current chunk slack, not leaked memory.
Measure how busy an event loop is
A group's event loop blocks in its poller only when no task in that group can run. The runtime times that wait, so a snapshot reports the loop's elapsed time and the part of it the loop spent idle. Subtract one from the other to get the time the loop spent running task code and its own scheduler work.
A snapshot never reports more idle time than elapsed time, and it includes a poller wait that is still in progress. The subtraction is therefore always valid, even while the loop is blocked.
Snapshot into a Group_Snapshotdeclare
use type Flyology.Observability.Counter;
Sample : Flyology.Observability.Group_Snapshot;
begin
if Flyology.Observability.Snapshot (0, Sample) then
Ada.Text_IO.Put_Line
("uptime ns=" & Sample.Uptime_Nanoseconds'Image
& " idle ns=" & Sample.Idle_Nanoseconds'Image
& " busy ns="
& Flyology.Observability.Counter'Image
(Sample.Uptime_Nanoseconds - Sample.Idle_Nanoseconds)
& " poller waits=" & Sample.Idle_Waits'Image);
end if;
end;
Counter is declared in another package, so its operators need a use type clause before the subtraction.
Both values cover the loop's whole lifetime, so one sample reports an average and not current load. Take two samples and subtract each field to measure one interval.
The measure describes the event loop, not the tasks on it. Two situations look alike:
- A group whose tasks all wait on readiness or a deadline reports mostly idle time, and so does a group with nothing to do. Read
WaitingandMembersto separate them. - A task that runs CPU code without reaching a suspension point keeps its loop out of the poller. That group reports as fully busy with one task running, which cooperative checkpoints address.
Idle_Waits counts the blocking poller waits behind those totals. Divide it by the elapsed time to get the rate at which the loop ran out of runnable work.
The runtime reads the monotonic clock twice for each blocking poller wait and never during a dispatch. This cost follows how often a loop runs out of work, not how much work it does. On the checked macOS/AArch64 host, one reading costs about 20 ns. A loop that only exchanges socket messages reaches about 32,000 to 38,000 waits per second. Run scripts/bench-runtime.sh to measure both values on the target host.
./scripts/showcases.sh
# After the showcase build, choose smaller local counts if needed:
./showcases/run_connection_density.sh 1000 10000
./showcases/run_event_loop_pool.sh 1024 20 4
./showcases/run_dormant_stack_pressure.sh 128 64
The density run compares lightweight and native tasks with the same requested stack size. It reports RSS, virtual address space, thread count, and lightweight stack-pool counters. The pool run compares one event loop with a configured pool under repeated readiness activity.
The dormant-stack run compares Prompt, Reclaimable, and Page_Out in separate processes. It verifies every modified stack payload after wake. Treat each result as a measurement of the tested host and workload, not as a universal sizing constant.