Skip to content

Where the project stands

This page answers what a reader deciding whether to try mikroscope needs first: which parts work end to end, on what hardware that was shown, what the observer costs today, which defects are known and still open, and whether there is anything to download. It is checked against the code as of 2026-09-16.

Everything below except the test suites and the seven sinks marked as such has run against the reference RB5009 (RouterOS 7.24.2), not only against fakes:

  • The agent reads the shared kernel’s /proc, /sys, /dev/kmsg and perf_event_open counters on a fixed ticker at 1 to 100 Hz (10 Hz by default; 10, 50 and 100 Hz measured), keeps the samples in a ring, and serves them: /healthz, /capabilities, /snapshot, /stream, /metrics, and the triggered-capture endpoints /captures and /capture.
  • The deployment CLI installs, upgrades and removes it — doctor, plan, install, status, upgrade, uninstall — with every write listed before it happens and every removal verified by ownership counts. On 2026-09-12, doctorinstallstatusupgradeuninstall left the router’s /export byte-identical, compared by hash in memory and never written to disk. The agent answered 3 s after install, at a 5–7 ms round trip. On 2026-09-17 all four install routes were run against the same device, one after another and each removed before the next: the checkout build, the published agent tar, the router’s own pull from Docker Hub, and the plan --rsc script imported on the router with no CLI in the install itself. The agent answered in every one, and the /export after the four was byte-identical to the one before them.
  • record, mark and plot work end to end. A 60 s recording at 10 Hz on 2026-09-12 yielded exactly 600 samples, 0 gaps and −7 ms of clock skew.
  • forward, the collector, merges the kernel tier with the RouterOS API tier and writes to ten sinks. File, Prometheus and InfluxDB 3 have run from the RB5009; Loki, OTLP, Graphite, Elasticsearch, SQL, Telegraf and stdout have not yet had router samples pushed through them, but every one of them now writes into the real product — InfluxDB 3, PostgreSQL, Elasticsearch, Graphite, Loki, an OpenTelemetry Collector, Telegraf and Prometheus in containers — and the suite reads each one back through its own API (2026-09-16). Its derive stage adds derived values and detection events beside the raw rows, and it emits a device-info stream once per capability hash.
  • Five Grafana dashboards, one per store — InfluxDB 3, Prometheus, PostgreSQL, Graphite and Elasticsearch — generated from one panel list, with alert rules for the three whose query language the rules are written in. The two SQL stores are asked the same question in two dialects: the PostgreSQL panels are the InfluxDB ones rewritten, and every one of its 209 queries is planned by a real PostgreSQL in the container suite. Graphite and Elasticsearch carry fewer panels on purpose (41 and 30 against 171): Graphite has no labels and Elasticsearch no nested documents, so what they cannot express is absent rather than wrong. All five are imported into a real Grafana over the stores the suite filled, and every panel is asked: on 2026-09-17, 57, 98, 136, 35 and 28 panels returned data and none failed. On 2026-09-16, in Grafana 13.2.1, against the RB5009 with the default triggers and a 30-minute forward into both stores, dashboards check passed 171 InfluxDB panels (0 failing, 10 known-empty tolerated) and 133 Prometheus panels (0 failing, 9 known-empty tolerated). The headless row-by-row render walk of both dashboards — 0 error badges, 0 “No data” — is from 2026-09-15 and covers 168 and 130 panels; it has not been repeated for the two port-event panels and the interface-inventory table it does not cover. A walk on 2026-09-14, against a 10.5 h capture from the same device, rendered 140 InfluxDB panels with the same result.
  • An end-to-end suite builds both binaries and drives them against a captured /proc tree of the reference device and against a fake agent, with one receiver per sink protocol asserting the bytes. It needs no router, no Grafana and no network, and runs in CI.
  • A second suite against the stores themselves (make test-e2e-docker) starts nine of them with docker compose, runs the same collector against the same fake agent with every sink pointed at them, and then asks each store its own question with its own API: SQL against InfluxDB 3, psql against the script the SQL sink wrote, a search against Elasticsearch, query_range against Loki, render against Graphite, the decoded OTLP the collector received, the line protocol Telegraf parsed, and a scrape of the exporter stored in Prometheus. It then imports both dashboards into Grafana and runs every panel’s query through Grafana’s own API. It needs Docker and no router: the samples are the same canned ones, so the run is reproducible anywhere. The first full run, on 2026-09-16, found a panel that named two columns the store only has when the API tier ran.

At the install default — 10 Hz, default per-source floors, a 300 s ring — the agent costs 2.85 % of one core and 31.3 MiB RSS, read from its own cgroup at steady state with the ring full:

Measured on RB5009UG+S+ · 4 × 1.4 GHz Cortex-A72 · RouterOS 7.24.2 · · 60 s windows at steady state (ring full), full source set, collector forwarding to a file, a Prometheus exposition and InfluxDB 3 at once

The measured runs
ratefloorsCPU of one coreµs/sampleRSSslipped ticksgaps / drops
10 Hz (default)default2.85 %2 85631.3 MiB00 / 0

That is above the budget of 2 % of one core and 16 MiB RSS. The budget is guidance rather than a contract: cost scales with the device, the source set and the ring size. The image budget is 8 MiB; the one image size on record is 6.1 MiB, and it carries no date.

Two rules keep the cost figure honest: wait out the ring (BUFFER_S) before quoting a steady-state figure — on the RB5009, at a 14 MiB soft memory limit, a reading taken in the first minute after install came back at 1.47 % of one core against a 9.38 % steady state — and read cost from /metrics rather than from a large /snapshot, whose ~1.5 MB response the agent must serialise.

Every row was measured on the same RB5009 at 10 Hz, and each is one window with no spread recorded. They are the settings a reader can choose, so the table says what each one costs — not what the number was at some earlier point.

Configuration CPU of one core RSS Measured Note
Every source read every tick 2.43 % not recorded 2026-09-12 above the budget
Ring full, MEM_LIMIT_MB 14 9.38 % (9 374 µs/sample) not recorded 2026-09-12 a 300 s ring of lines of about 2.4 kB holds ~7.3 MB; the Go GC runs without pause
Ring full, --mem-limit-mb 40, --memory-max 64M 1.39 % (1 388 µs/sample) 25.13 MiB 2026-09-12 0 slipped ticks
PMU counters on, a live forward writing to InfluxDB 1.72 % not recorded 2026-09-12 0 slipped ticks; 2 400 samples forwarded, 0 gaps, 0 drops
The install default 2.85 % 31.3 MiB 2026-09-15 the figure above

Every on-device figure in this documentation comes from one RB5009UG+S+ on RouterOS 7.24.2, the owner’s production router. There is no lab device.

Checked against the code as of this page:

  • forward’s end-of-run summary goes to stdout, the same stream the --stdout sink writes records to, so forward --stdout=lp | telegraf ends every run with lines the consumer cannot parse.
  • A wrong --token does not fail the run. Each pull is logged as 401 Unauthorized, and the run then ends at its --for deadline with exit status 0 and forwarded 0 kernel samples. The health check forward makes before it starts pulling reads /healthz, which needs no token, so it passes. The message is right; the exit status tells a unit file nothing.
  • Duplicate sequence numbers in the 2026-09-13 overnight run. The 50 Hz run wrote repeated seq values into InfluxDB; the dashboards name those rows “duplicate sample (seq repeated)”. The leading explanation, which is unverified, is Go’s HTTP client replaying a POST on a dead pooled connection after the server had committed it. The InfluxDB, Loki, OTLP, Elasticsearch and Telegraf sinks refuse that replay, so a dead connection is an error the sink retries and counts. No run since that night shows whether the duplicates are gone.
  • Graphite names thermal zones by index. The index is deliberate — a zone’s type string is not unique across zones — but the path listing at the top of internal/sinks/graphite.go reads thermal.<zone>.

Two readable files on the reference device, read there on 2026-09-15, are not collected: /proc/cmdline, which carries board=5009 ver=7.24.1 — a second device identity without the API — and the hardware watchdog at /sys/class/watchdog/watchdog0.

The current release is v1.0.0 — the version in VERSION, compiled into both binaries and reported by mikroscope version. A v* tag runs the GoReleaser configuration, which publishes:

  • CLI archives for linux, darwin, windows and freebsd on amd64, arm64 and arm: .tar.gz, and .zip on Windows, each carrying LICENSE and README.md.
  • Agent archives for linux on the same three architectures, for a host that wants the bare binary rather than an image.
  • Side-loadable agent image tars, one per architecture — mikroscope-agent-arm64.tar, mikroscope-agent-arm.tar, mikroscope-agent-amd64.tar — which install --agent-tar uploads to the router.
  • The agent image in two registries, jmrplens/mikroscope-agent:1.0.0 on Docker Hub and ghcr.io/jmrplens/mikroscope-agent:1.0.0 on GHCR, each one manifest over linux/amd64, linux/arm64 and linux/arm/v7, which install --remote-image makes the router pull. RouterOS takes the registry host from the global /container/config registry-url, which ships as https://registry-1.docker.io, so the Docker Hub reference needs nothing set on the device and the GHCR one needs that setting changed first.
  • checksums.txt, covering every archive and every image tar, a keyless cosign signature over it, and an SPDX SBOM per archive, signed in its own right.

Installing from a release needs no Go toolchain and no checkout. A checkout and Go 1.27 install an agent built from your own tree instead: make build for the CLI, make build-agent for the agent, which install and image also compile themselves. The four ways to get the agent onto a router, and what each one needs, are on Installing the agent.

What the release publishes for linux/arm and linux/amd64 is cross-built and checked in CI. Of the three architectures, only arm64 has run on hardware — on the one RB5009 above.