Skip to content

Five minutes with a router

The question this tool exists for: I am about to change something on the router — what does it actually do, at a resolution where I can see it? This page is the shortest path from a router that has never seen mikroscope to a chart that answers it: six steps with the commands as they are, and one real recording from the RB5009 read line by line.

The router needs what the tool cannot give it: RouterOS 7.24 or later with the container package and device-mode container=yes, which MikroTik gates behind a reset-button press or a power cycle. 7.24 is the floor because the container step writes privileged=, an attribute MikroTik added in that release; --privileged=false changes its value, not whether it is written, so an older 7.x rejects the command either way. Everything on this page was measured on 7.24.2. doctor reports the router’s version in its first line but does not gate on it. What the router needs has the whole list, and mikroscope doctor checks the rest read-only and names the fix for anything missing.

The CLI does not read .env itself, and only some flags take their default from a MIKROSCOPE_* variable: the connection and naming ones (ROUTER, SSH_PORT, SSH_KEY, NAME, VETH, SUBNET, IFACE_LIST, ADDR_LIST, DISK, ARCH, TOKEN, LAN_ADDRESS), the API ones (API_ADDR, API_USER, API_PASSWORD), the sink URLs such as INFLUX_URL, INTERFACES and HOST_TAG. The rest, among them --ephemeral, --rate, --for, --topics and --prom, have plain defaults, whatever the CLI’s usage text says. Export the variables you need, or copy .env.example to .env and source it with set -a; . ./.env; set +a; environment variables lists every one.

  1. Get mikroscope.

    Terminal window
    tar xzf mikroscope_1.0.0_linux_x86_64.tar.gz # a .zip on Windows
    ./mikroscope version

    The release carries the CLI as an archive per platform — linux, macOS, Windows and FreeBSD on amd64, arm64 and arm — with checksums.txt, cosign signatures and SBOMs beside it. The agent travels separately, as one image tar per architecture (mikroscope-agent-arm64.tar, mikroscope-agent-arm.tar, mikroscope-agent-amd64.tar) and as a registry image, published both as jmrplens/mikroscope-agent:1.0.0 on Docker Hub and as ghcr.io/jmrplens/mikroscope-agent:1.0.0 on GHCR; step 2 takes one of the two.

    From a checkout instead:

    Terminal window
    git clone https://github.com/jmrplens/mikroscope && cd mikroscope && make build

    That needs Go 1.27 and leaves the CLI in bin/mikroscope, which is how the commands below are written. It is also the only path that installs an agent built from your own tree, because install cross-compiles the agent from the module root.

  2. Install, once.

    Terminal window
    export MIKROSCOPE_ROUTER=admin@192.168.88.1
    bin/mikroscope plan --ephemeral
    bin/mikroscope doctor --ephemeral && bin/mikroscope install --ephemeral

    plan prints every RouterOS command and writes nothing. install puts the agent image on the router, lists the objects, runs the same preflight again (--no-doctor skips it), asks write the objects above to the router? [y/N] (--yes skips the question), writes, and then probes the agent from your host.

    Where the image comes from is your choice, and it is the only difference between the three installs:

    • your Go toolchain, as above: the CLI runs go build ./cmd/mikroscope-agent with CGO_ENABLED=0, GOOS=linux and GOARCH from --arch, which is why it has to run from the module root;
    • the published tar, --agent-tar mikroscope-agent-arm64.tar: no Go toolchain and no checkout. The CLI reads the tar before it uploads it — it has to be a mikroscope agent image and its architecture has to match --arch, or the verb stops and names the asset to download instead;
    • the registry, --remote-image jmrplens/mikroscope-agent:1.0.0: the router pulls the image itself, nothing is uploaded, and uninstall has no file to account for. RouterOS takes the registry host from the global /container/config registry-url, which mikroscope never writes because every container on the device shares it, and which ships as https://registry-1.docker.io — so the Docker Hub reference above runs on a stock router as it stands. The GHCR reference, ghcr.io/jmrplens/mikroscope-agent:1.0.0, names a host of its own: doctor checks the setting against it and prints /container/config/set registry-url=https://ghcr.io when it does not match, or points at --agent-tar. The pull needs the router to reach the registry and the free RAM for the layers.

    --arch defaults to arm64, the RB5009’s; it is not detected from the router, but doctor compares it with the architecture the router reports and names the --arch value to re-run with if they differ.

    On a router you only reach through WinBox or WebFig, there is a fourth way with no CLI on your side at all: mikroscope plan --rsc --remote-image jmrplens/mikroscope-agent:1.0.0 --out install.rsc writes the same commands, in the same order and with the same tags, as a RouterOS script to paste into the terminal or /import. Installing the agent has that path and its two caveats in full.

    --ephemeral puts the image tar and the container’s root on the router’s tmpfs RAM disk and creates the container with start-on-boot=no, so the agent does not come back after a reboot. install deletes the tar once the container has extracted it. Nothing was written to flash: write-sect-since-reboot stayed at 58 279 across install, run and removal (verified on RB5009UG+S+, RouterOS 7.24.2, ). Drop it for a persistent install. The tmpfs disk has to exist; the RB5009 has one, and doctor --ephemeral checks for it and prints the /disk/add that creates it if it does not. Pass --ephemeral to doctor too: without it, doctor checks free flash instead.

    install ends by probing the agent from your host, and prints one line:

    direct transport ok: agent 1.0.0 (9ddd760) built 2026-09-16T08:38:27Z, 10 Hz, seq 29, 0 slipped, 7ms round trip

    The first field is the agent’s build identity: the version, which a release stamps from the VERSION file, then the commit and the build date. An unstamped go build inside a checkout reports that same version with the commit and time the Go toolchain records, so the field is never dev and never a bare hash. An agent from the tar or the registry reports what the release build stamped into it; an agent install built from your tree carries the CLI’s own stamp, so the two report the same string. The rest of the line is the probe itself: the sampler rate, the sequence the agent had reached, the ticks it slipped and the round trip. On the RB5009 the probe answered after about three seconds with a 7 ms round trip, and 5 ms after an upgrade the same day (2026-09-12).

    The probe waits up to 30 s. If your host cannot reach the agent’s /30, it first asks the router whether the container is running and only then suggests alternatives; see reaching the agent.

  3. Record while you do the thing.

    Terminal window
    bin/mikroscope record --for 70s --out burst

    Type a line and press Enter whenever you do something worth remembering; it becomes a marker with the agent’s timestamp. That works when standard input is a terminal, and the CLI says so: type a line and press Enter to add a marker; Ctrl-C stops. From another shell,

    Terminal window
    bin/mikroscope mark --out burst "queue tree applied"

    does the same: mark appends to <prefix>.markers.csv while record holds the file open, so a note from a second terminal and a note typed into the recorder’s own end up in the same file. --for 0, the default, records until Ctrl-C. At the end record prints the sample count, the sequence range, the gaps, the markers and the transport it used, and names any stretch of samples that was no longer in the agent’s ring.

  4. Add the router’s own log.

    This step talks to the RouterOS API, not to the agent, so it needs an account on the router to talk to. A read-only one is enough, and the API user has the two commands that create the group and the user; the CLI never needs admin for this.

    Terminal window
    export MIKROSCOPE_API_ADDR=192.168.88.1:8728 MIKROSCOPE_API_USER=mikroscope MIKROSCOPE_API_PASSWORD=
    bin/mikroscope mark --out burst --log-markers --router-tz Europe/Madrid

    (Or put those three in .envcp .env.example .env — and source it, as above.)

    Every log line of the recording’s window — from its start to its last sample, in the agent’s clock — whose topic is system, interface or container becomes a marker; add firewall or script with --topics when their lines are the story. The router reports errors and scheduler runs there, and the log often explains a transient you did not cause.

    MIKROSCOPE_API_ADDR has the flag --api and MIKROSCOPE_API_USER the flag --api-user; the password has no flag. --router-tz is the IANA zone the router’s clock shows, because RouterOS log times carry no zone; it defaults to your machine’s. record --log-markers does the same at the end of a recording, and mark --log-markers does it afterwards, as here (the router’s own log as markers).

  5. Look.

    Terminal window
    bin/mikroscope plot --in burst

    This writes burst.svg: three panels on one time axis — busy ratio per core, softnet drops and time squeezes per second, memory available — with every marker as a dashed vertical drawn per panel, so no panel title is struck through. Log markers that fall in the same second fold into one line whose label reads and the first message; a gap in the samples is drawn as a red line; a label longer than 40 characters is cut to 37 and an ellipsis, and a chip with no room left shortens further until it fits rather than being laid over its neighbour, keeping only its rule when nothing readable is left; a marker outside the recording’s span is not drawn. The same recording always yields the same bytes. --title sets the heading and --svg another output path.

  6. Keep it running (optional).

    Terminal window
    bin/mikroscope forward --prom :9124 --influx "$MIKROSCOPE_INFLUX_URL" --interfaces bridge,ether1

    This runs the collector: the kernel tier from the agent and, with the MIKROSCOPE_API_* variables from step 4 in the environment, the RouterOS API tier beside it. With the default --api-mode full that tier reads /system/resource, /system/resource/cpu, /system/health, monitor-traffic every second for the interfaces named in --interfaces, every port’s cumulative counters every 10 s, and what each interface is — its comment, type, interface lists, bridge and MTU — at start and every 5 min; the RouterOS API tier says which of those the agent can read itself. Without those variables the API tier is disabled with a warning and the kernel tier still runs. Both go to a Prometheus exposition on :9124 and to InfluxDB 3. From there, import and check sets up the two Grafana dashboards, the datasource field an InfluxDB 3 import needs and the two Prometheus scrape jobs.

RB5009UG+S+, 70 s at 10 Hz: 700 samples over 69.9 s on four cores, with three dashed markers — “baseline, router idle” at 12 s, “dashboards check started” at 30 s and “check finished” at 50 s. Per-core busy stays low with single-sample excursions to 100 %; the softnet panel shows time squeezes and a flat zero for dropped; memory available stays between 662 and 671 MiB.

Open the chart at full size (SVG, 1200 × 754) to read its labels on a phone.

Measured on RB5009UG+S+ · 4 × 1.4 GHz Cortex-A72 · RouterOS 7.24.2 · · a 70 s record at 10 Hz, 700 samples over 69.9 s, the router otherwise at rest, three notes typed into record's terminal

This is a real recording of a router at rest, the shape everything else is read against. The three dashed lines are the notes typed into record’s terminal during it; the chips carry them in full because they fit, and a longer one is shortened rather than laid over the next.

  • t = 12 s, baseline, router idle. Nothing is happening, and the panels say so: every core averages under 10 % over the whole recording, and over the quiet stretch that follows this note the four together average 3.9 %.
  • t = 30 to 50 s, between dashboards check started and check finished. A browser loading the two Grafana dashboards against this router’s own collector. The four cores together average 5.0 % over that stretch, and the work arrives in two short bursts right after the note: core 0 at or above 50 % for 0.6 s from 32.3 s, and again for 0.3 s at 33.2 s.
  • The work is in short excursions. 76 of the 700 samples have a core at or above 50 %, in 46 separate stretches; 38 of those are one sample long, and the longest is 1.4 s, at the start of the recording and before the first note. The kernel’s scheduler puts each excursion on whichever core is free. One isolated sample — one core at 100 % for 100 ms — moves a four-core, one-second average by 2.5 %.
  • softnet: dropped flat at zero, time_squeeze between 0 and about 20 per second. Nothing was lost in 70 s. The squeezes are this device’s background rather than an event; what the collector calls a microburst is a cluster of them — three flagged samples on one core inside 60 s — and never a single one.
  • memory available, 662 to 671 MiB. About 9 MiB of ordinary churn across the recording, with no step at either end of the dashboards check.

RouterOS’s own cpu-load, at 1 s, reports this minute as a flat few per cent. The recording shows what the few per cent are made of: which core took each excursion, how long it lasted, and where the notes fall against it.

What install writes to your router

  • a veth
  • one address
  • one interface-list membership
  • one address-list entry
  • an envlist
  • the image tar, unless --remote-image has the router pull the image
  • the container

Every object carries the comment mikroscope:<name> (managed by mikroscope)

mikroscope plan prints every command before anything is written.

uninstall removes by exact tag plus identity, never by pattern, and fails naming the step if anything remains.

Terminal window
bin/mikroscope uninstall --ephemeral

Pass the same --ephemeral, --disk and --name you installed with: uninstall and status rebuild the plan from their own flags, and without --ephemeral they look for the image tar on flash instead of on tmpfs. uninstall removes every step of the plan in reverse order, by exact tag, then counts what is left owned by mikroscope on the router and fails, naming the objects, if anything remains. bin/mikroscope status --ephemeral prints the same ownership counts at any time, plus the agent’s health when it is reachable.