The tray refreshes its status dot by spawning hawser status --json every 4 seconds:
t := time.NewTicker(4 * time.Second)
...
st := cli.Poll(context.Background()) // exec.CommandContext(c.Exe, "status", "--json")
Measured on Windows 10, engine stopped:
| call |
cost |
hawser status --json |
285 ms |
hawser version --json |
203 ms |
| bare process startup (unknown subcommand, exits immediately) |
99 ms |
At a 4-second interval that is 285 ms of work every 4000 ms — roughly 7% of one core, continuously, for as long as the user is logged in. About 900 process spawns an hour; ~7,200 and ~34 minutes of CPU across an 8-hour day. For a status dot.
The 99 ms floor is just starting a ~10 MB Go binary. On the corporate machines this project targets, every one of those spawns is also inspected by endpoint security — the same class of overhead that made #166 interesting.
Why it is this way
4-second freshness only matters right after the user clicks Start/Stop/Restart in the tray itself. For everything else, a status light does not need to be four seconds fresh.
Options, cheapest first
-
Refresh on action, poll slowly otherwise. Immediately re-poll after a tray-initiated lifecycle action, and drop the idle ticker to 20-30 s. A few lines, ~85% fewer spawns, and the dot still feels instant exactly when the user did something.
-
Stop spawning a process at all. The supervisor already publishes supervisor-stats.json (with updatedAt, pid, lifecycle, bridge counters) and desired-state into the state dir. If it also wrote the collapsed dot state — the same five values tray.State already uses — the tray could read a small file: microseconds instead of 285 ms, and no subprocess.
This keeps the tray's design rule intact ("the tray itself holds no engine logic and makes no decisions the CLI would not"): the supervisor decides the state and publishes it; the tray only renders it. Deriving the state from the raw counters in the tray would break that rule, so the state has to be written, not inferred.
updatedAt gives staleness for free — a stats file older than N seconds means the supervisor is gone, which is itself a state worth showing.
-
Fall back to the CLI call when no state file exists (no supervisor, fresh install), so nothing regresses.
Suggested: 1 now, 2 as the real fix, 3 as its safety net.
Checklist
The tray refreshes its status dot by spawning
hawser status --jsonevery 4 seconds:Measured on Windows 10, engine stopped:
hawser status --jsonhawser version --jsonAt a 4-second interval that is 285 ms of work every 4000 ms — roughly 7% of one core, continuously, for as long as the user is logged in. About 900 process spawns an hour; ~7,200 and ~34 minutes of CPU across an 8-hour day. For a status dot.
The 99 ms floor is just starting a ~10 MB Go binary. On the corporate machines this project targets, every one of those spawns is also inspected by endpoint security — the same class of overhead that made #166 interesting.
Why it is this way
4-second freshness only matters right after the user clicks Start/Stop/Restart in the tray itself. For everything else, a status light does not need to be four seconds fresh.
Options, cheapest first
Refresh on action, poll slowly otherwise. Immediately re-poll after a tray-initiated lifecycle action, and drop the idle ticker to 20-30 s. A few lines, ~85% fewer spawns, and the dot still feels instant exactly when the user did something.
Stop spawning a process at all. The supervisor already publishes
supervisor-stats.json(withupdatedAt, pid, lifecycle, bridge counters) anddesired-stateinto the state dir. If it also wrote the collapsed dot state — the same five valuestray.Statealready uses — the tray could read a small file: microseconds instead of 285 ms, and no subprocess.This keeps the tray's design rule intact ("the tray itself holds no engine logic and makes no decisions the CLI would not"): the supervisor decides the state and publishes it; the tray only renders it. Deriving the state from the raw counters in the tray would break that rule, so the state has to be written, not inferred.
updatedAtgives staleness for free — a stats file older than N seconds means the supervisor is gone, which is itself a state worth showing.Fall back to the CLI call when no state file exists (no supervisor, fresh install), so nothing regresses.
Suggested: 1 now, 2 as the real fix, 3 as its safety net.
Checklist
hawser status --jsonwhen absent