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.
Before you start
Section titled “Before you start”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.
The path
Section titled “The path”-
Get mikroscope.
Terminal window tar xzf mikroscope_1.0.0_linux_x86_64.tar.gz # a .zip on Windows./mikroscope versionThe 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.) and as a registry image, published both astar jmrplens/on Docker Hub and asmikroscope-agent:1. 0. 0 ghcr.on GHCR; step 2 takes one of the two.io/ jmrplens/ mikroscope-agent:1. 0. 0 From a checkout instead:
Terminal window git clone https://github.com/jmrplens/mikroscope && cd mikroscope && make buildThat 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, becauseinstallcross-compiles the agent from the module root. -
Install, once.
Terminal window export MIKROSCOPE_ROUTER=admin@192.168.88.1bin/mikroscope plan --ephemeralbin/mikroscope doctor --ephemeral && bin/mikroscope install --ephemeralplanprints every RouterOS command and writes nothing.installputs the agent image on the router, lists the objects, runs the same preflight again (--no-doctorskips it), askswrite the objects above to the router? [y/N](--yesskips 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 .with/ cmd/ mikroscope-agent CGO_ENABLED=0,GOOS=linuxandGOARCHfrom--arch, which is why it has to run from the module root; - the published tar,
--agent-tar mikroscope-agent-arm64.: 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 matchtar --arch, or the verb stops and names the asset to download instead; - the registry,
--remote-image jmrplens/: the router pulls the image itself, nothing is uploaded, andmikroscope-agent:1. 0. 0 uninstallhas no file to account for. RouterOS takes the registry host from the global/, which mikroscope never writes because every container on the device shares it, and which ships ascontainer/ config registry-url https:/— so the Docker Hub reference above runs on a stock router as it stands. The GHCR reference,/ registry-1. docker. io ghcr., names a host of its own:io/ jmrplens/ mikroscope-agent:1. 0. 0 doctorchecks the setting against it and prints/when it does not match, or points atcontainer/ config/ set registry-url=https:/ / ghcr. io --agent-tar. The pull needs the router to reach the registry and the free RAM for the layers.
--archdefaults toarm64, the RB5009’s; it is not detected from the router, butdoctorcompares it with the architecture the router reports and names the--archvalue 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/writes the same commands, in the same order and with the same tags, as a RouterOS script to paste into the terminal ormikroscope-agent:1. 0. 0 --out install.rsc /import. Installing the agent has that path and its two caveats in full.--ephemeralputs the image tar and the container’s root on the router’s tmpfs RAM disk and creates the container withstart-on-boot=no, so the agent does not come back after a reboot.installdeletes the tar once the container has extracted it. Nothing was written to flash:write-sect-since-rebootstayed 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, anddoctor --ephemeralchecks for it and prints the/disk/addthat creates it if it does not. Pass--ephemeraltodoctortoo: without it,doctorchecks free flash instead.installends 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 tripThe first field is the agent’s build identity: the version, which a release stamps from the
VERSIONfile, then the commit and the build date. An unstampedgo buildinside a checkout reports that same version with the commit and time the Go toolchain records, so the field is neverdevand never a bare hash. An agent from the tar or the registry reports what the release build stamped into it; an agentinstallbuilt 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 anupgradethe 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.
- your Go toolchain, as above: the CLI runs
-
Record while you do the thing.
Terminal window bin/mikroscope record --for 70s --out burstType 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:
markappends to<prefix>.markers.csvwhilerecordholds 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 endrecordprints 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. -
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
adminfor 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
.env—cp .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,interfaceorcontainerbecomes a marker; addfirewallorscriptwith--topicswhen 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_ADDRhas the flag--apiandMIKROSCOPE_API_USERthe flag--api-user; the password has no flag.--router-tzis the IANA zone the router’s clock shows, because RouterOS log times carry no zone; it defaults to your machine’s.record --log-markersdoes the same at the end of a recording, andmark --log-markersdoes it afterwards, as here (the router’s own log as markers). -
Look.
Terminal window bin/mikroscope plot --in burstThis 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 readsN×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.--titlesets the heading and--svganother output path. -
Keep it running (optional).
Terminal window bin/mikroscope forward --prom :9124 --influx "$MIKROSCOPE_INFLUX_URL" --interfaces bridge,ether1This 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 fullthat tier reads/system/resource,/system/resource/cpu,/system/health,monitor-trafficevery 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:9124and 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.
What the chart shows
Section titled “What the chart shows”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 startedandcheck 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:
droppedflat at zero,time_squeezebetween 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 step 2 wrote, and taking it off
Section titled “What step 2 wrote, and taking it off”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-imagehas 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.
bin/mikroscope uninstall --ephemeralPass 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.