Record, mark and plot
Use record when you are about to change something on the router and want to
see what the change does. It pulls the agent’s samples to your machine at full
rate; mark adds notes and the router’s own log; plot draws the result.
Commands and flags
Section titled “Commands and flags”mikroscope record --for 5m --out cap # cap.jsonl, cap.csv, cap.markers.csv, cap.meta.json; type lines to markmikroscope mark --out cap "queue tree applied" # a note stamped with the current time (see below)mikroscope mark --out cap --log-markers # the router's own log lines, over the APImikroscope plot --in cap # cap.svg, deterministicThe three commands share one flag set: each accepts every flag below, and the table says which command acts on it.
| Flag | Default | What it does |
|---|---|---|
--out |
capture-<UTC time> |
Output prefix: <out>.jsonl, .csv, .markers.csv, .meta.json. mark needs the prefix of an existing recording. |
--for |
0 |
record: stop after this long. 0 records until Ctrl-C. |
--from-start |
off | record: backfill everything the agent’s ring holds before going live. |
--poll |
500ms |
How often to pull the agent’s ring. |
--batch |
0 |
Samples per pull. 0 is twice what one --poll interval produces at the agent’s rate, at least 20; the relay caps a pull at 13. |
--transport |
auto |
auto, direct (HTTP to the veth) or relay (/tool fetch over the RouterOS API). |
--log-markers |
off | record: adds the router log lines of the window once the recording stops. mark: adds the log lines of the recording’s window. |
--topics |
system, |
Log topics kept as markers; add firewall or script when their lines are the story. |
--router-tz |
Local |
IANA zone the router’s clock shows. RouterOS log times carry no zone. |
--api |
MIKROSCOPE_API_ADDR |
RouterOS API host:port, for the relay and for --log-markers. |
--api-user |
MIKROSCOPE_API_USER |
The API user. The password is read only from MIKROSCOPE_API_PASSWORD; there is no flag for it. |
--token |
MIKROSCOPE_TOKEN |
The agent’s bearer token. |
--port |
9123 |
The agent’s HTTP port. |
--subnet |
MIKROSCOPE_SUBNET, else 172.30.10.0/30 |
The agent’s /30; its address is .2. |
--in |
none | plot: a recording prefix, or the path of a .jsonl file. |
--svg |
<in>.svg |
plot: the output file. |
--title |
the prefix | plot: the chart’s title. |
Scroll sideways to see every column
First recording runs the three commands end to end. A measured recording, with its sample count, gaps and clock skew, is on the evidence page. Recordings above 10 Hz and through the relay are not measured.
Transports
Section titled “Transports”--transport |
Path | On failure |
|---|---|---|
auto (default) |
direct if GET /healthz on http://<.2 of --subnet>:<--port> answers, else relay |
an error, below |
direct |
plain HTTP from your machine to the veth, with --token as a bearer token |
fails, no fallback |
relay |
/tool fetch output=user on the router, over the binary API; the API user needs the read,api,test policies |
fails, no fallback |
Scroll sideways to see every column
- When
autohas to fall back and--apior--api-useris missing,recordstops with an error that names--api,--api-userandMIKROSCOPE_API_PASSWORDand suggestsinstall --expose. A missing password shows as a login error instead,api <host:port>: …. - The
--exposehint is for other HTTP clients.recordalways dials the.2of--subnetand has no flag for the router’s LAN address. - The router-side fetch sends no header, so an agent installed with a token can
be recorded only over
direct. - Each relayed call takes either about 3 ms or about 1 s. A reply is capped at 64 512 B, so a relayed pull asks for at most 13 samples, and a reply that reaches the cap is refused rather than parsed truncated.
Network access covers which path a network allows, and API user the RouterOS user the relay needs.
Pull loop
Section titled “Pull loop”recordreads/healthz. The agent’s wall clock minus your machine’s is the clock skew. It prints the skew on stderr with the agent’s version, rate, newest sequence number, the oldest one the ring still holds, and the transport.- The recording starts live from the newest sample, or from the oldest the ring
holds with
--from-start. - Every
--poll, it pulls/snapshot?since=<last seq>&max=<batch>. While a pull comes back full it asks again, up to 100 times per poll, so a ring that got ahead is drained rather than followed at a fixed pace. - A pull that fails is logged as
pull: …and the recording carries on; the next pull asks from the same sequence number. - When
--forelapses or you press Ctrl-C, one last pull drains what arrived meanwhile.recordprints how many samples it kept, their sequence range, the gaps, the markers, the transport and the files.
Output files
Section titled “Output files”Every file is created with mode 0600. An existing file with the same prefix is
overwritten.
| File | Contents |
|---|---|
<out>.jsonl |
Each sample line exactly as the agent sent it: raw tick and counter deltas, with every source the agent has. This is the recording; the other files are views of it. |
<out>.csv |
One wide row per sample, for a spreadsheet. The column set is sized from the first sample: one group per core and one per softnet queue. |
<out>.markers.csv |
wall_, one row per marker. |
<out>.meta.json |
Written at the start, so mark and plot can run later: started_utc, skew_ns (agent wall minus host wall), agent (its version), rate_hz, transport, and capabilities when the transport could fetch them. |
Scroll sideways to see every column
The CSV holds a fixed subset of each sample. Everything else a sample carries —
kernel-log events, PMU counters, temperatures, interrupts per line and the rest —
is in the .jsonl only.
| Columns | Content |
|---|---|
seq, wall_ns, wall_utc, dt_ns |
The sample’s sequence number, agent wall clock, and real interval. |
busy_total |
The mean of the per-core busy ratios, three decimals, computed by the CLI from the ticks. |
c<N>_busy, c<N>_user, c<N>_nice, c<N>_system, c<N>_idle, c<N>_iowait, c<N>_irq, c<N>_softirq |
Per core: the busy ratio, then the raw tick deltas. |
ctxt, intr, irq_total |
Context switches and interrupts from /proc/stat, and the delta summed over every row of /proc/interrupts. |
softnet<N>_processed, softnet<N>_dropped, softnet<N>_time_squeeze |
Per softnet queue, the receive path’s deltas. |
mem_free_kb, mem_available_kb, mem_cached_kb, mem_slab_kb |
Memory levels, in kB. |
load1, threads_running, threads_total |
Load average and thread counts. |
pgfault, pgmajfault |
Page-fault deltas. |
self_cpu_us, self_rss_bytes |
The agent’s own CPU time during the sample, in µs, and its resident memory at that moment, in bytes. |
Scroll sideways to see every column
The busy ratio is the one number the CLI computes, and it inherits the kernel’s floor: a tick is 10 ms, so one core in a 100 ms sample resolves to 10 % steps. Resolution limits explains why, and why the ratio is capped at 1.
Markers
Section titled “Markers”A marker is a row of <out>.markers.csv, of one of three kinds:
kind |
Written when |
|---|---|
note |
You type a non-empty line while record runs in a terminal (it prints type a line and press Enter to add a marker; Ctrl-C stops), or run mark --out <prefix> "text". Nothing is read when standard input is not a terminal. |
gap |
The agent reports that samples were no longer in its ring. The label is samples <from>..<to> lost, and the summary record prints lists the same gaps. |
log |
--log-markers adds a line of the router’s own log (below). |
Scroll sideways to see every column
Notes and gaps are stamped with your machine’s clock plus the skew measured at
the start, so they sit on the agent’s time axis. mark --out cap "text" does the
same with the skew stored in cap.meta.json: it stamps the current time in the
agent’s clock, and no flag sets another time. mark needs that file and an
existing cap.markers.csv, and its row carries seq 0.
Router log markers
Section titled “Router log markers”--log-markers turns the router’s log lines of the recording’s window into log
markers, labelled <topics>: <message>. Use it when a transient you did not
cause needs explaining: scheduler runs, errors, interface events.
export MIKROSCOPE_API_ADDR=192.168.88.1:8728 MIKROSCOPE_API_USER=mikroscope MIKROSCOPE_API_PASSWORD=…mikroscope record --for 60s --out burstmikroscope mark --out burst --log-markers --router-tz Europe/Madridmark --log-markersuses the window fromstarted_utcin the meta file, shifted by itsskew_nsinto the agent’s clock, to the last sample in the.jsonl, and prints how many lines it added, the window and the topics.started_utcis stored to the second. It appends without checking what is already there: running it twice adds the same lines again.record --log-markersasks for the log once the recording has stopped, for the window from its start to its end in the agent’s clock, and appends the lines through the same pathmarkuses, so the summary’s marker total counts what the file holds. If the log request fails,recordprintslog markers: …and keeps the recording.
The CLI asks the router only for the window, with a ?>time= query starting one
second before it, and only for the time, topics and message fields, because
a router that logs a busy topic to disk can hold tens of thousands of rows. It
then keeps the lines inside the window whose topics include one of --topics.
An event logged by several logging actions on one topic arrives once per action,
prefixed with the action’s name ([INFO]: …, [SYSTEM]: …); the prefix is
stripped and the duplicates are kept once.
Log times carry no zone:
- Over the API, RouterOS prints a full date and time (verified). The parser also accepts the shorter forms without a year or without a date, and fills them in from the end of the window.
- Times are read in
--router-tzand stamped as the agent’s wall clock, which is the router’s own clock, so no skew is applied. The defaultLocalis your machine’s zone: if the router’s differs, pass it, or every log marker is off by the difference. - Log times have one-second resolution, so a log marker places its event to the second, not to the sample.
plot --in cap reads cap.jsonl, and cap.markers.csv when it exists, and
writes cap.svg (or --svg). It prints the file name with its sample and marker
counts. The same input always produces the same bytes.
The chart is 1 200 units wide, with a title (--title, or the prefix) and a line
giving the sample count, the duration and the core count. Three panels share one
time axis, in seconds since the recording started:
- CPU busy per core, % — 0 to 100, one line per core, each labelled at its end. The palette has eight colours, so the first eight cores are drawn.
- softnet per second, all CPUs —
droppedandtime_squeeze, summed over every queue and bucketed into whole seconds. Drops are drawn in red. - memory available, MiB —
MemAvailableper sample, with the y-axis bounded to the data.
Markers on the chart:
- Every marker is a dashed vertical line across all three panels, grey for notes and log lines and red for gaps, with a label chip above the first panel.
- Chips are laid out in up to six rows. A label longer than 40 characters is shortened; when no row has room it is shortened further until one does, and a chip that would fall under 12 characters is dropped. Its dashed line still marks the instant.
- Consecutive log markers, in time order, that fall in the same second are
folded into one chip,
<count>× <topics>: <first message>. A note or gap between them breaks the fold, and notes and gaps are never folded. - Markers outside the recording’s time span are not drawn.
With a single sample the SVG says not enough samples to draw; with none,
plot stops with record: no samples and writes no SVG. The palette is the
chart’s own, validated for its light background: adjacent-pair
colour-vision-deficiency ΔE 9.1, normal-vision ΔE 22.9. There is no dark variant.
plot also reads a capture the agent kept on a trigger: save
GET /captures/<id> to a .jsonl file and pass it to --in. The capture’s
header line carries no sequence number and is skipped.
Trigger lines
Section titled “Trigger lines”When a trigger fires on the agent, a {"trigger":{…}} line rides in the same
pull as the samples, just before the sample it fired on. The collector
recognises it; record does not.
Triggered capture covers what fires, and how to fetch the full-rate window the agent kept around it.