# How it works

The agent in a RouterOS container, the veth it answers on, the collector on your machine and the sinks it writes to, and what the design cannot do.

Source: https://jmrplens.github.io/mikroscope/how-it-works/

mikroscope is two programs. An agent runs in a container on the router and samples the kernel it
shares with RouterOS; a CLI on your machine installs it, records from it and runs the collector
that writes to your stores.

## Components

- **`mikroscope-agent`** is a static Go binary in a
  [scratch container](https://jmrplens.github.io/mikroscope/reference/glossary/#scratch-container). It samples on a fixed
  ticker at 1 to 100 Hz, 10 Hz by default, keeps the last 60 s in a
  [ring](https://jmrplens.github.io/mikroscope/reference/glossary/#ring), and serves them over HTTP on its
  [veth](https://jmrplens.github.io/mikroscope/reference/glossary/#veth) address. It opens no outbound connection and holds
  one secret at most: the token it asks of whoever reads it, which sits in the container's
  [envlist](https://jmrplens.github.io/mikroscope/reference/glossary/#envlist).
- **`mikroscope`**, the CLI, runs on your machine. It installs, upgrades and removes the agent over
  ssh; records a window with markers and plots it as an SVG; and runs `forward`, the collector.
- **The router's own objects**: a veth with a /30, the router's address on it, two list
  memberships, an envlist, the container, and an install manifest that lists them. Every one
  carries the tag `mikroscope:<name> (managed by mikroscope)`, and `uninstall` removes them all.

The release publishes the CLI as an archive per platform (Linux and FreeBSD on amd64, arm64 and
arm; macOS and Windows on amd64 and arm64), and the agent as one image tar per architecture and as
a registry image, `jmrplens/mikroscope-agent` on Docker Hub and
`ghcr.io/jmrplens/mikroscope-agent` on GHCR. The collector is also an image,
`jmrplens/mikroscope` and `ghcr.io/jmrplens/mikroscope` (linux/amd64, linux/arm64), and the
directory [`deploy/`](https://github.com/jmrplens/mikroscope/tree/main/deploy) has two compose stacks that pair it with InfluxDB 3 or Prometheus and
Grafana. That image's default command is `forward`, and `dashboards` runs in it too; the install
commands need ssh and scp, which it leaves out. Both programs are under the MIT licence,
in [`LICENSE`](https://github.com/jmrplens/mikroscope/blob/main/LICENSE).

## Data path

_Where the data comes from and where it goes_ — The router runs the agent in a container that reads the shared kernel and serves it over a veth. The collector on your machine pulls that, merges the RouterOS API tier into it, derives, and writes to every sink you named.

1. The agent reads the kernel on each tick and appends a sample to its ring.
2. `forward`, or `record`, pulls new samples from the agent over HTTP: directly to the veth address
   through the router, through the RouterOS API relay, or on the router's LAN address with
   `--expose` ([Network access](https://jmrplens.github.io/mikroscope/install/reaching-the-agent/)).
3. The collector asks the RouterOS API, once a second, for what the kernel cannot show the
   container, and stamps both tiers in the agent's clock.
4. It derives ratios, rates and detections from the merged samples and writes them to every sink
   you name ([Run the collector](https://jmrplens.github.io/mikroscope/sinks/)).

ssh is never on this path. The CLI uses it to install and check, and batches each read into one
connect, because each connect costs the router CPU for its duration
([SSH cost](https://jmrplens.github.io/mikroscope/cost/#ssh-cost)).

## What the agent reads

A RouterOS container shares the router's kernel, so `/proc` inside it is the router's own:

- `/proc/stat` per core, `/proc/interrupts`, `/proc/softirqs` and
  [`/proc/net/softnet_stat`](https://jmrplens.github.io/mikroscope/reference/glossary/#softnet);
- `/proc/meminfo`, `/proc/vmstat`, `/proc/diskstats` and `/proc/loadavg`;
- with the container privileged: `/dev/kmsg`, the kernel log; `/proc/slabinfo`, which holds the
  router's real connection count; the flash's ECC counters; and the CPU's
  [PMU](https://jmrplens.github.io/mikroscope/reference/glossary/#pmu) counters, system-wide, through `perf_event_open`.

The container runs privileged by default; [Privileged mode](https://jmrplens.github.io/mikroscope/limits/privileged/) lists
what each setting gives. The agent ships raw counter deltas, CPU time as ticks, never a
percentage, so the reader chooses the window. It reports at start which sources this kernel has,
and a source the kernel lacks is absent from the output rather than zero.
[Prometheus metrics](https://jmrplens.github.io/mikroscope/reference/metrics/) lists every series.

## What the collector adds

- **The RouterOS API tier**: per-interface traffic and error counters, `cpu-load` per core,
  `/system/health`, and what each interface is (its comment, type, lists, bridge and MTU). The
  container cannot read these: its network namespace is its own.
  [RouterOS API tier](https://jmrplens.github.io/mikroscope/sinks/api-tier/) has the modes.
- **One clock**: every record is stamped in the agent's clock, so kernel and API samples line up.
- **Derived values**: busy ratios, rates and shares, computed from the raw deltas
  ([Derived values](https://jmrplens.github.io/mikroscope/sinks/derive/)).
- **Detections**: a layer-2 loop, a link flap, a microburst and the other rules, each written as
  an event ([Detection rules](https://jmrplens.github.io/mikroscope/sinks/detections/)).
- **Sinks**: file, Prometheus, InfluxDB 3, Loki, OTLP, Graphite, Elasticsearch and OpenSearch,
  SQL, PostgreSQL, Telegraf and standard output.

## Limitations

- **RouterOS 7.24 or later, with a container.** The agent needs the `container` package and
  `device-mode container=yes`, which takes a button press or a power cycle at the router. MIPS,
  TILE and PPC routers have no `container` package.
- **The kernel's resolution, not the tool's.** `/proc/stat` counts CPU time in
  [`USER_HZ` ticks](https://docs.kernel.org/filesystems/proc.html#miscellaneous-kernel-statistics-in-proc-stat),
  100 a second, so a 100 ms window resolves one core in steps of 10 %
  ([Resolution limits](https://jmrplens.github.io/mikroscope/limits/)).
- **PSI and schedstat only where the kernel has them.** The agent reads both when they exist; a
  RouterOS kernel may be built without them.
- **The container's network is its own.** `/proc/net/dev`, `/proc/net/snmp` and
  `nf_conntrack_count` describe the container, not the router; the conntrack count from the slab,
  when privileged, is the exception. Interface counters come from the RouterOS API and are merged,
  never estimated ([Container visibility](https://jmrplens.github.io/mikroscope/limits/namespaces/)).
- **It merges with the RouterOS API and does not replace it.** Without an API user the kernel tier
  still runs, and the interface panels stay empty.
- **No percentages from the agent.** It never computes one, so it never decides what a percentage
  means; the collector and the dashboards derive them.
- **The observer has a cost.** At 10 Hz the agent takes 2.69 % of one core;
  [Agent cost](https://jmrplens.github.io/mikroscope/cost/) has the table and how to measure it on your router.

## See also

- [Quick install](https://jmrplens.github.io/mikroscope/start/): the four commands that put the agent on a router.
- [Security model](https://jmrplens.github.io/mikroscope/security/): what runs where, and which credentials stay on your
  machine.
- [Resolution limits](https://jmrplens.github.io/mikroscope/limits/): ticks, PMU counters and how far back the ring goes.
- [Compared with alternatives](https://jmrplens.github.io/mikroscope/start/compared/): what SNMP, mktxp and The Dude read
  instead.
