Skip to content

tray: polling hawser status every 4s costs ~7% of a core, for a status dot #192

Description

@zcsizmadia

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

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

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

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

  • Immediate refresh after tray-initiated start/stop/restart
  • Idle interval raised from 4 s
  • Supervisor publishes the collapsed dot state alongside the stats it already writes
  • Tray reads it, with staleness handling, and falls back to hawser status --json when absent
  • Measure again and record the numbers in the PR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions