How it works
One daemon, shared fairly
Every view on this page is rendered from a data file. Each says whether it is measured (from a real run or the engine's README) or a sample trace (a deterministic scenario that follows the engine's documented rules). Samples are marked as such everywhere they appear.
Many agents, one daemon
Quick questions never wait behind long work
Requests take a slot in one of two lanes before any lock: quick (decompile, functions, list, open, rename) and long (analyze, import, script, save). Long work can never take the slots quick queries need. When a lane is full, waiting requests are admitted round-robin by client, so one agent sending a hundred requests cannot starve one that sends one.
Seven agents, six programs, one daemon (maxQuick 4, maxLong 2)
A deterministic simulation of the scheduler's documented rules (two lanes, fixed slots, round-robin by client, per-client cancel, release on death). Durations are illustrative: cold opens and first decompiles use the README's order of magnitude, warm decompiles about 7 ms. Lane sizes are small so the queues are visible; the daemon's defaults are one quick slot per CPU and a quarter of the CPUs for long work.
data/agents.json · scripts/gen-traces.ts
0 quick and 0 long running · 0 waiting for a slot
What happened
- 4.20 sexporter sends cancel {target}: the export stops, its long slot goes to the next waiting request
- 6.00 sagent-e sends 96 decompiles at once; the quick lane admits other clients' requests between them (round-robin)
- 8.30 sagent-f's process dies: its analyze is cancelled, its unsaved renames are discarded, firmware.bin is read-only again
- ·7 clients connected; each bar's tooltip has its command, wait and run time.
Round-robin, up close
Quick-lane admissions in order, from 6.00 s: 96 of one client's burst (agent-e) and 3 requests from the others, which do not queue behind the burst.
- agent-a decompile: waited 2.4 ms for a slot
- agent-g open_program: waited 5.2 ms for a slot
- agent-g decompile: waited 3.4 ms for a slot
The rules: The shared daemon.
Who holds what
Readers, owners, writers, forks and overlays
Each program has a fair read/write lock: queries share it, edits and analysis hold it alone. One client owns a writable program unless it is opened shared; everyone else reads it, annotates a private overlay, or forks the project. Step through a scenario and watch the board change.
A scripted scenario: six agents, four programs, three projects
Each step follows the module README's rules ("Several clients", "Who can do what at the same time"). Request and answer lines are shortened; fields not shown are elided with ... . Steps marked next use features on main that are not in a release yet.
data/locks.json · scripts/gen-traces.ts
agent-a opens an analysed project the way File > View Project does: no lock file, nothing written.
p1:/server.exe
newfirmware.gpr · read-only view, no lock
- readers
- none
- owner
- none
- writers
- none
- overlays
- none
- waiting
- none
- unsaved
- none
p1:/libcrypto.dll
newfirmware.gpr · read-only view, no lock
- readers
- none
- owner
- none
- writers
- none
- overlays
- none
- waiting
- none
- unsaved
- none
A question's life
Where an agent's question spends its time
Cold, a question pays for the daemon's start, the program's open and a decompiler's start. Warm, the daemon, the program and its pooled decompilers are already there, for every client. Rows marked measured quote the engine's README; the others are illustrative samples.
Where an agent's question spends its time, cold and warm
Rows marked measured quote the engine's README; their phases are the README's own split where it gives one, else one total. Rows marked sample are illustrative.
data/questions.json · scripts/gen-traces.ts
- start / connect
- queue wait
- open program
- decode / work
- answer
- total (not split)
- bar length: log scale, 0.5 ms to 30 s
“Decompile this function”
decompile
quick lanecold1.52 smeasuredWhat this run was ›Hide ‹
No daemon running: launch until ready 348 ms, open_program 847 ms, first decompile 256 ms; launch until the first answer 1,521 ms (66.7 MB x64 executable, 243,855 functions, read-only; the rest of the 1,521 ms is open_project and the round trips) Split: start / connect 348 ms, open program 847 ms, decode / work 256 ms, answer 70 ms. Source
warm6.7-7.1 msmeasuredWhat this run was ›Hide ‹
Daemon up, program open, decompiler pooled (66.7 MB x64 executable, 243,855 functions, read-only). The README gives the total only. Source
“Decompile, from a second harness that just started”
connect + hello, open_project, open_program, decompile
quick lanewarm23.2 msmeasuredWhat this run was ›Hide ‹
Another harness process while the first is connected: connect + hello 14 ms, join the project 0.9 ms, open_program (already open) 0.9 ms, decompile 7.4 ms. Measured on Ghidra 12.1.4. Split: start / connect 14 ms, open program 1.8 ms, decode / work 7.4 ms. Source
“Open an analysed program”
open_program
quick lane“What is this binary? (format, language, function count)”
info
no slot neededwarm2 msmeasuredWhat this run was ›Hide ‹
Recorded session, 140 KB test program (574 functions) Source
“List the functions whose name contains crypt”
functions contains=crypt
quick lanecold61 mssample traceWhat this run was ›Hide ‹
Sample: first page of 200 after the program opened Split: queue wait 0.4 ms, decode / work 54 ms, answer 6.6 ms.
warm38 mssample traceWhat this run was ›Hide ‹
Sample: the same filter again Split: queue wait 0.3 ms, decode / work 33 ms, answer 4.7 ms.
“Who references this address?”
script (a GhidraScript; there is no xrefs command)
long lanecold1.18 ssample traceWhat this run was ›Hide ‹
Sample: the first script of the session waits for the deferred script compiler (OSGi), then compiles the script Split: queue wait 2 ms, open program 960 ms, decode / work 210 ms, answer 8 ms.
warm64 mssample traceWhat this run was ›Hide ‹
Sample: script already compiled, program warm Split: queue wait 1 ms, decode / work 58 ms, answer 5 ms.
“Find strings that mention a URL”
script (a GhidraScript; there is no strings command)
long lanecold2.35 ssample traceWhat this run was ›Hide ‹
Sample: first script of the session, defined data scanned once Split: queue wait 3 ms, open program 940 ms, decode / work 1.39 s, answer 17 ms.
warm1.24 ssample traceWhat this run was ›Hide ‹
Sample: the scan itself dominates; warmth only saves the start Split: queue wait 2 ms, decode / work 1.23 s, answer 13 ms.
“Rename this function and leave a comment”
rename + comment (or edit, or apply_overlay)
quick lanecold9.5 mssample traceWhat this run was ›Hide ‹
Sample: owner mode, first change opens a transaction log Split: queue wait 0.5 ms, decode / work 7.6 ms, answer 1.4 ms.
warm1.2 msmeasuredWhat this run was ›Hide ‹
In an overlay of a shared program: 1.2 ms per rename (66.7 MB x64 executable, 243,855 functions) Source
“Import and analyse a new binary”
import analyze=true
long lane“Re-run auto-analysis after my renames”
analyze
long lanecold5.24 ssample traceWhat this run was ›Hide ‹
Sample: a range of a large program; waits for a long slot Split: queue wait 40 ms, decode / work 5.19 s, answer 10 ms.
warm5.12 ssample traceWhat this run was ›Hide ‹
Sample: analysis is work, not start-up; warmth barely helps Split: queue wait 2 ms, decode / work 5.11 s, answer 10 ms.