Wheelhouse docs

What the agent asks the router#

The agent has one way to reach the router: the router's own HTTP API, over HTTPS. It runs no commands, links against no router code and opens no shell. Everything the web UI shows came back from that API. Two properties of the API shape the design: a call forks a process on the router, and the API serialises, so parallel calls are no faster than sequential ones.

How current a page is#

A page shows recent state, not live state. How recent depends on what the number is:

What you are looking atHow far behind it can be
Configuration — what is in the tree, the set lines behind a field, the raw formNever behind a commit
Operational counters and show output3 seconds
Per-interface detail — link state, MTU, addresses, qdisc, RX/TX20 seconds
The live stream, whose connection the top bar's router chip showsNot cached; read fresh every 2 seconds

A commit invalidates every configuration-derived answer at once, and the agent re-warms them, so the next request for any of them reads the configuration you just committed.

Those windows are ceilings, not schedules. Past one, the cached value is not thrown away: the read is answered from it immediately and a fresh read starts behind it, so the request after that has the newer value.

The cost of a call#

Each call into the router's HTTP API forks cli-shell-api on the router, and a read takes between 150 and 800 milliseconds. Concurrency does not help: seven parallel reads take as long as seven sequential ones. The agent therefore budgets calls, not intervals.

The read-through cache#

Three classes, with the windows above and the reasons for them:

ClassWindowWhy
Configuration reads (showConfig, the command rendering, the raw form)Until a commit invalidates them; configTTL is one hour and is only a backstop against a missed invalidationThe configuration changes only when somebody commits, and the agent is the thing that commits
Operational reads (op-mode show)opModeTTL, 3 secondsThey drift constantly. Long enough to serve a page's fan-out and a burst of navigation from one fetch, short enough that counters stay live
Per-interface detail (show interfaces <kind> <name>)interfaceDetailTTL, 20 secondsOne router call each, for facts that change on the scale of minutes

All three are served stale-while-revalidate: a stale hit returns immediately and starts a single background refresh, and a key already being refreshed is not refreshed again. Only the very first read of a key ever blocks.

Two cases behave differently from the windows:

  • A transport failure is never cached. A router that could not be reached is retried on the next request rather than remembered as broken.
  • A "path is empty" answer is cached. It is a stable negative and it is invalidated by a commit like any other configuration read.

The primer#

A background task warms the cache so the browser rarely waits. At start-up it fetches the configuration subtrees every page reads and the op-mode reads behind the Overview and the busiest pages. After that:

  • Configuration entries stay warm on their own, because they are invalidated by commits rather than by time.
  • Op-mode reads are not refreshed on a timer. Each one forks a process on the router, and a page that hits a stale entry is served the stale value at once and refreshed in the background, so a periodic refresh would only add steady load to an idle router.

What an idle router is actually asked#

SourceRate
Throughput samplerOne show interfaces counters every 5 seconds — one call for every interface, not one per interface
Each open tab of the web UI, whatever the pageOne show interfaces every 2 seconds, uncached, for the live stream
Per-interface detailAt most one call per interface per 20 seconds, and only while something is looking
The primerConfig subtrees at start and after a commit; op-mode reads at start

Idle, that is about a dozen calls a minute. Sampling each interface separately instead of once for all of them can hold the load average of a two-vCPU router at 15.

Steady-state, every page endpoint answers from the cache in well under a millisecond.

Timeouts, and who gets cancelled#

The agent keeps three HTTP clients for the router, because one timeout cannot be right for all of it:

ClientTimeoutUsed for
reads30 secondsshow, retrieve
writes3 minutes/configure, /config-file — a container commit or a rollback on slow hardware runs well past 15 seconds, and timing out mid-commit leaves the caller unable to tell whether it landed
slow15 minutescontainer image pulls, system images, traceroute, reboot, power off — bounded by the uplink, not the CPU

On top of that, one browser request may spend at most 20 seconds talking to the router in total, and the reads it makes are cancelled when the browser goes away. Without that budget, a page fanning out to six sequential reads could hold a connection for six times the read timeout.

A commit is exempt. It uses the daemon's own client and is not cancelled by a closed tab: a commit abandoned halfway is worse than a commit nobody is watching.

Knowing whether the router is there#

Every round trip records its outcome, so "is the router reachable right now" has an answer outside the page that happened to fail. Reachable means the most recent answer was a good one, not that a call was tried recently, which is why the timestamps and the last error come with it. A router that answered with an error of its own is reachable; transport failures mean gone.

The same counters feed /metrics: wheelhouse_vyos_calls_total and wheelhouse_vyos_failures_total, plus the cache's hits, stale hits and misses. A cache that has stopped hitting is visible there.

Two consequences#

Do not write a script that polls a per-object endpoint on a timer. Each call is work on the router. Read the aggregate endpoints, or scrape /metrics, which the agent serves from what it already knows.

A page can be a few seconds behind. For a number that is certainly current, the telemetry stream bypasses the cache, and a commit invalidates the configuration reads immediately. Everything else is within its window — see State, intent and drift for the freshness table.

See also#

Updated 2026-09-18 concepts performance agent