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 at | How far behind it can be |
|---|---|
Configuration — what is in the tree, the set lines behind a field, the raw form | Never behind a commit |
Operational counters and show output | 3 seconds |
| Per-interface detail — link state, MTU, addresses, qdisc, RX/TX | 20 seconds |
| The live stream, whose connection the top bar's router chip shows | Not 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:
| Class | Window | Why |
|---|---|---|
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 invalidation | The configuration changes only when somebody commits, and the agent is the thing that commits |
Operational reads (op-mode show) | opModeTTL, 3 seconds | They 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 seconds | One 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#
| Source | Rate |
|---|---|
| Throughput sampler | One show interfaces counters every 5 seconds — one call for every interface, not one per interface |
| Each open tab of the web UI, whatever the page | One show interfaces every 2 seconds, uncached, for the live stream |
| Per-interface detail | At most one call per interface per 20 seconds, and only while something is looking |
| The primer | Config 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:
| Client | Timeout | Used for |
|---|---|---|
| reads | 30 seconds | show, retrieve |
| writes | 3 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 |
| slow | 15 minutes | container 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#
- State, intent and drift — how fresh each number on a page is.
- The configuration tree — what those reads return.
- On-router, off-router, fleet — where the agent runs.
- Metrics reference — the cache and router-call series.
- API status codes — what a 502 from the agent means.
- Architecture — the cache and primer as code.