Helix/Profiler
Runtime engine and scripting timings, task counts, and queue pressure for Koya.
The profiler collects process-wide wall-clock timings for Koya's engine phases and scripting work, plus task counts and queue sizes. It is disabled by default. All functions are synchronous and do not require await.
Import it from your bootstrap or any script running in Koya's scripting context. The same API is available in Syncromesh's primary orchestrator context.
Import #
import * as Profiler from 'Helix/Profiler';Functions #
setEnabled(enabled: boolean): booleanisEnabled(): booleanreset(): void- Each counter contains
thread,name,calls,totalMs,maxMs, andlastMs. - Each gauge contains
name,current, andhighWater. - Counters are sorted by thread and descending total time, making the result suitable for a simple monitoring UI.
snapshot(): { enabled, elapsedMs, counters, gauges }import * as Log from 'Helix/Log';
import * as Profiler from 'Helix/Profiler';
Profiler.reset();
Profiler.setEnabled(true);
// Allow the engine and scripts to run before sampling.
setInterval(() => {
const snapshot = Profiler.snapshot();
for (const counter of snapshot.counters) {
const averageMs = counter.calls ? counter.totalMs / counter.calls : 0;
Log.info(counter.thread, counter.name, 'calls:', counter.calls,
'average ms:', averageMs, 'max ms:', counter.maxMs);
}
for (const gauge of snapshot.gauges) {
Log.info(gauge.name, 'current:', gauge.current, 'high water:', gauge.highWater);
}
}, 1000);
// Stop collection with Profiler.setEnabled(false).
// Existing data remains readable until Profiler.reset().Interpreting results #
koyaengine counters includetick,update,solve, andrender.koya:scriptcounters include queued jobs, promise completions, QuickJS jobs, timers, and pre-render callbacks.koya:maincovers native tasks and async bridge drains.koya:mainseparatesinputEvents,uiPrepareAndHash,uiDraw,nativeRender,presentWindows, andflushLogs. Use differences incallsbetween snapshots to find repeated work on a static screen. The bundled example reports calls per second and time spent since the previous sample.koya.mainQueue,koya.postSyncQueue, andkoya.scriptQueuegauges show queued tasks.highWateris the largest observed size since reset; completed queues can have a current size of zero.- Timings include blocking and waiting inside instrumented scopes. They are not GPU execution timings or measurements of individual JavaScript functions. Nested scopes overlap, so their totals should not be added together as total CPU usage.
- Counters and high-water marks accumulate until reset. Disabling collection preserves existing data, and reset preserves the enabled state. Snapshots and logging introduce work of their own; sample periodically rather than every frame.
- The
idleWaitcounter in threadkoyameasures blocked waiting. Its total is time blocked waiting, not active CPU use. It should dominate elapsed time for a static UI; a one-second profiler logging timer intentionally wakes the application.
Runnable example #
From koya-base, run the bundled window demo with a profiler snapshot logged once per second:
koya -m assets -i examples/profiler.jsIdle scheduling #
Koya blocks on the Wayland connection and native wake notifications when there is no scheduled work. Main-thread operations, log output, console input, and new timer deadlines wake the loop. Held keys keep their repeat deadline. Static UI is prepared and hashed after input or native operations, rather than continually at the display refresh rate. Active animations and particles schedule frames; completed or held animations return to blocking. Custom shaders retain rendering on change. Native render callbacks keep frame scheduling active.
Native plugins with scriptUpdate or engineSolve callbacks retain 30 Hz periodic updates, since their current interface requires the host to poll them. Plugins with render callbacks retain frame scheduling. Importing a plugin with these callbacks can therefore prevent indefinite idle waits. Syncromesh retains its existing simulation scheduler.