On my RB5009 the kernel log was printing this line every two seconds:
br0: received packet on eth1 with own address as source address
The frames were 2.00–2.01 s apart, which is the STP hello interval: a layer-2 loop through a mesh access point. RouterOS's own log and the port monitor showed a healthy device the whole time. STP kept the port blocked, so nothing melted down, but for days the bridge sent that port about 0.6 packets a second. I only saw it because I was reading the kernel from inside the router for something else.
Caption: The second time it happened, 2026-09-20 to 09-24 (UTC): 1 800 own-address records an hour in the kernel log, and
ether2 receiving a median 13 packets/s (hourly means) while the bridge sent it 0.6. RouterOS's log and port monitor showed nothing.
That something else is mikroscope, and it is now public.
GitHub: GitHub - jmrplens/mikroscope: Sub-second, kernel-level telemetry for container-capable MikroTik RouterOS devices: a static Go agent that runs on the router and a CLI/collector that installs it, records, plots and forwards. · GitHub
Docs (English and Spanish): mikroscope — sub-second kernel telemetry for MikroTik RouterOS
What it is
Two small MIT-licensed programs:
- An agent that runs on the router in a RouterOS container (a static Go binary in a scratch image, about 6.4 MiB for arm64). Because the container shares the router's kernel, it reads the router's own
/proc,/sys,/dev/kmsgand perf counters at 1 to 100 Hz (10 Hz by default): per-core CPU ticks, interrupts, softirqs, softnet drops and time squeezes, memory, load, disk I/O. Withprivileged=yesit also reads the kernel log as timestamped events. It keeps a short ring in memory (60 s by default) and serves it over HTTP on a /30 veth. - A CLI on your machine that installs, checks and removes the agent over SSH. It also records a window while you change something and draws it as a chart, or runs as a collector that merges a 1 Hz RouterOS API tier (interface counters) and forwards everything to Prometheus, InfluxDB 3, PostgreSQL, a SQL script, Loki, OTLP, Graphite, Elasticsearch, Telegraf, a file or stdout. It ships ready-made Grafana dashboards and alert rules.
Caption: The Overview row of the bundled InfluxDB dashboard: my RB5009, last 6 h.
The agent ships raw tick deltas, never percentages. For comparison, cpu-load over the API is roughly a one-second trailing mean. I fitted it against /proc/stat read at 10 Hz on the RB5009 over two separate hours: a 1.0 s mean reaching the API 0.6 s late (r = 0.9825 over 3 499 samples), and a 1.1 s mean 0.1 s late. A core saturated for 100 ms moves a four-core one-second average by only 2.5 %.
What it costs the router
Measured on an RB5009UG+S+ (RouterOS 7.24.2, 2026-09-18) from the agent's own cgroup. Each row is one 300 s window at steady state:
| rate | CPU of one core | RSS | gaps / drops |
|---|---|---|---|
| 10 Hz (default) | 2.69 % | 13.2 MiB | 0 / 0 |
| 50 Hz | 9.63 % | 23.3 MiB | 0 / 0 |
| 100 Hz | 16.83 % | 45.7 MiB | 0 / 0 |
This is one board. The docs explain how to measure it on yours.
Things it found on my own network
- The layer-2 loop above, twice. The second time the port was effectively out of service for four days. Now there is an alert for a bridge port that receives traffic while the bridge sends it nothing, which RouterOS reports as running and error-free.
- A Home Assistant integration polling the router over the API pushed the timer interrupts from about 2 500 to about 35 000 per second, in bursts on one core at a time.
/tool profilebarely showed it. Disabling the integration brought it back within 30 s. - Short egress-queue drop bursts on the port to my server: 3 337 packets in six one-second bursts over 6.5 h, peaking at 436 packets/s. It's working as designed, so it's on a dashboard panel and deliberately not an alert.
Caption: The Home Assistant item above, on the bundled dashboard, 2026-09-23 08:00 to 09-24 10:00 UTC:
arch_timer goes from about 2 500 to about 35 000 interrupts a second at 12:08, when the Home Assistant integration connected, and drops back the moment I disabled it.
Installing
Requirements: RouterOS 7.24 or later, the container package, and device-mode container=yes (the physical button press). arm64, arm or x86_64; no MIPS or TILE.
export MIKROSCOPE_ROUTER=admin@192.168.88.1
mikroscope doctor # read-only preflight; names the fix for anything missing
mikroscope plan # every RouterOS command it would run, nothing written
mikroscope install --remote-image jmrplens/mikroscope-agent:1.3.0
The router pulls the image from Docker Hub by its full reference, registry-1.docker.io/…. You don't need to change /container/config registry-url or add a registry login. I checked that on 7.24.4: a host in remote-image= overrides the global setting, and Docker Hub serves the image anonymously.
Every object it creates carries a mikroscope: comment. uninstall removes exactly those objects and verifies nothing is left. There is also a .rsc route if you'd rather paste a script, and a published tar for offline installs.
Honest status
- It has run on one device, my RB5009UG+S+ (RouterOS 7.24.2, then 7.24.4). The arm and x86_64 builds are cross-built and tested in CI but have never run on hardware.
- Three of the eleven sinks have taken real router samples (file, Prometheus, InfluxDB 3). The other eight are tested against the real products in containers.
- Everything the docs claim carries the device, version and date it was measured on, and there is a "what is not claimed" box on almost every page.
Where I'd love help
- Other boards. If you run it on a hAP ax³, CCR2004, RB4011, a hEX refresh or a CHR, the output of
mikroscope statusand one measured window would tell me whether the cost holds. So would which kernelethNeach port is: the kernel-to-RouterOS port map is measured per board, and only the RB5009's exists so far. - Faults you know how to provoke that RouterOS's own counters hide.
Feedback, issues and "this is wrong on my device" are all welcome.





