v0.7.1 (build 925)

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
Enables or disables collection and returns the resulting state.
setEnabled(enabled: boolean): boolean
isEnabled
Returns whether profiling collection is enabled.
isEnabled(): boolean
reset
Clears counters, gauges, high-water marks, and the elapsed interval.
reset(): void
snapshot
Returns a synchronous snapshot of all accumulated profiler data.
  • Each counter contains thread, name, calls, totalMs, maxMs, and lastMs.
  • Each gauge contains name, current, and highWater.
  • 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 #

  • koya engine counters include tick, update, solve, and render.
  • koya:script counters include queued jobs, promise completions, QuickJS jobs, timers, and pre-render callbacks. koya:main covers native tasks and async bridge drains.
  • koya:main separates inputEvents, uiPrepareAndHash, uiDraw, nativeRender, presentWindows, and flushLogs. Use differences in calls between 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, and koya.scriptQueue gauges show queued tasks. highWater is 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 idleWait counter in thread koya measures 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.js

Idle 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.