# Grabar, marcar y dibujar

Trae a tu máquina una ventana de muestras a cadencia completa, marca sus momentos, añade el propio log del router y dibújala como un SVG determinista.

Source: https://docs.jmrp.io/mikroscope/es/record/

Usa `record` cuando vayas a cambiar algo en el router y quieras ver qué hace el
cambio. Trae las muestras del agente a tu máquina a cadencia completa; `mark`
añade notas y el propio log del router; `plot` dibuja el resultado.

## Órdenes y opciones

```sh
mikroscope record --for 5m --out cap                  # cap.jsonl, cap.csv, cap.markers.csv, cap.meta.json; escribe líneas para marcar
mikroscope mark --out cap "queue tree applied"        # una nota sellada con la hora actual (ver abajo)
mikroscope mark --out cap --log-markers               # las líneas del propio log del router, por la API
mikroscope plot --in cap                              # cap.svg, determinista
```

Las tres órdenes comparten un mismo juego de opciones: cada una acepta todas las
de abajo, y la tabla dice sobre qué orden actúa cada una.

| Opción          | Por defecto                                 | Qué hace                                                                                                                                                                   |
| --------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--out`         | `capture-<UTC time>`                        | Prefijo de salida: `<out>.jsonl`, `.csv`, `.markers.csv`, `.meta.json`. `mark` necesita el prefijo de una grabación existente.                                             |
| `--for`         | `0`                                         | `record`: para tras este tiempo. `0` graba hasta Ctrl-C.                                                                                                                   |
| `--from-start`  | desactivada                                 | `record`: rellena todo lo que guarda el anillo del agente antes de pasar a directo.                                                                                        |
| `--poll`        | `500ms`                                     | Cada cuánto se tira del anillo del agente.                                                                                                                                 |
| `--batch`       | `0`                                         | Muestras por petición. `0` es el doble de lo que produce un intervalo de `--poll` a la cadencia del agente, con un mínimo de 20; el relay limita una petición a 13. |
| `--transport`   | `auto`                                      | `auto`, `direct` (HTTP a la veth) o `relay` (`/tool fetch` por la API de RouterOS).                                                                                        |
| `--log-markers` | desactivada                                 | `record`: añade las líneas del log del router de la ventana cuando termina la grabación. `mark`: añade las líneas del log de la ventana de la grabación.                  |
| `--topics`      | `system,interface,container`                | Temas del log que se conservan como marcadores; añade `firewall` o `script` cuando sus líneas son la historia.                                                             |
| `--router-tz`   | `Local`                                     | Zona IANA que muestra el reloj del router. Las horas del log de RouterOS no llevan zona.                                                                                   |
| `--api`         | `MIKROSCOPE_API_ADDR`                       | `host:port` de la API de RouterOS, para el relay y para `--log-markers`.                                                                                                   |
| `--api-user`    | `MIKROSCOPE_API_USER`                       | El usuario de la API. La contraseña solo se lee de `MIKROSCOPE_API_PASSWORD`; no hay opción para ella.                                                                     |
| `--token`       | `MIKROSCOPE_TOKEN`                          | El token bearer del agente.                                                                                                                                                |
| `--port`        | `9123`                                      | El puerto HTTP del agente.                                                                                                                                                 |
| `--subnet`      | `MIKROSCOPE_SUBNET`, si no `172.30.10.0/30` | La /30 del agente; su dirección es la `.2`.                                                                                                                                |
| `--in`          | ninguno                                     | `plot`: un prefijo de grabación, o la ruta de un fichero `.jsonl`.                                                                                                         |
| `--svg`         | `<in>.svg`                                  | `plot`: el fichero de salida.                                                                                                                                              |
| `--title`       | el prefijo                                  | `plot`: el título del gráfico.                                                                                                                                             |

[Primera grabación](https://docs.jmrp.io/mikroscope/es/start/walkthrough/) recorre las tres órdenes
de principio a fin. Una grabación medida, con su número de muestras, sus huecos y
su desfase de reloj, está en la [página de pruebas](https://docs.jmrp.io/mikroscope/es/about/status/#campaign-record-2026-09-12).
Las grabaciones por encima de 10 Hz y a través del relay no se
han [medido](https://docs.jmrp.io/mikroscope/es/about/status/#not-tested).

## Transportes

| `--transport`         | Camino                                                                                                                 | Si falla                |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| `auto` (por defecto)  | `direct` si responde un `GET /healthz` a `http://<.2 de --subnet>:<--port>`, si no `relay`                              | un error, abajo         |
| `direct`              | HTTP plano de tu máquina a la veth, con `--token` como token bearer                                                     | falla, sin alternativa  |
| `relay`               | `/tool fetch output=user` en el router, por la API binaria; el usuario de la API necesita las políticas `read,api,test` | falla, sin alternativa  |

- Cuando `auto` tiene que recurrir al relay y falta `--api` o `--api-user`,
  `record` se detiene con un error que nombra `--api`, `--api-user` y
  `MIKROSCOPE_API_PASSWORD` y sugiere `install --expose`. Si falta la
  contraseña, aparece en su lugar un error de inicio de sesión,
  `api <host:port>: …`.
- La sugerencia de `--expose` es para otros clientes HTTP. `record` siempre
  llama a la `.2` de `--subnet` y no tiene opción para la dirección LAN del
  router.
- La petición desde el router no lleva cabecera, así que un agente instalado con
  token solo se puede grabar por `direct`.
- Cada llamada por relay tarda o unos 3 ms o alrededor de 1 s. Una respuesta
  está limitada a 64 512 B, así que una petición
  por relay pide como mucho 13 muestras, y una
  respuesta que llega al límite se rechaza en vez de analizarse truncada.

> **El relay llega a unas 26 muestras por segundo**
>
> Por aritmética, a partir del código. El límite de 13 muestras sale
> del tope de 64 512 B de la petición, de una línea que se cobra a
> la clase de tamaño del asignador de 3 456 B (la línea medida
> es de 3 230 B) y de un 134 % de margen, así que una petición llena de
> líneas de ese tamaño son unos 45 kB y aún cabe con líneas de hasta el 134 % de él. Una petición
> llena cuyas líneas promedien más que el tope de la petición dividido por el límite llega al tope
> y se rechaza; el tamaño de las líneas con los suelos por fuente de serie no
> se ha [medido](https://docs.jmrp.io/mikroscope/es/about/status/#not-tested).
> Una petición llena se repite en el acto, así que el rendimiento lo fija la ida y vuelta de
> `/tool fetch`, no el `--poll`: con cerca de 1 s en la mitad de las llamadas y unos 3 ms en el
> resto, eso da del orden de 26 muestras por segundo, menos cuando las llamadas lentas se agrupan.
> Por encima de eso, usa `direct`.

[Acceso por red](https://docs.jmrp.io/mikroscope/es/install/reaching-the-agent/) explica qué camino
permite una red, y [Usuario de la API](https://docs.jmrp.io/mikroscope/es/security/api-user/) el
usuario de RouterOS que necesita el relay.

### Bucle de peticiones

1. `record` lee `/healthz`. El reloj de pared del agente menos el de tu máquina
   es el **desfase de reloj**. Lo imprime por stderr junto con la versión del
   agente, la cadencia, el número de secuencia más reciente, el más antiguo que
   aún guarda el anillo y el transporte.
2. La grabación empieza en directo desde la muestra más reciente, o desde la más
   antigua que guarda el anillo con `--from-start`.
3. Cada `--poll`, pide `/snapshot?since=<last seq>&max=<batch>`. Mientras una
   petición vuelve llena, vuelve a pedir, hasta 100 veces por intervalo, así que
   un anillo que se ha adelantado se vacía en vez de seguirse a ritmo fijo.
4. Una petición que falla se registra como `pull: …` y la grabación continúa; la
   siguiente pide desde el mismo número de secuencia.
5. Cuando vence `--for` o pulsas Ctrl-C, una última petición recoge lo que llegó
   entretanto. `record` imprime cuántas muestras conservó, su rango de
   secuencia, los huecos, los marcadores, el transporte y los ficheros.

## Ficheros de salida

Cada fichero se crea con modo `0600`. Un fichero existente con el mismo prefijo
se sobrescribe.

| Fichero             | Contenido                                                                                                                                                                                                                                   |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `<out>.jsonl`       | Cada línea de muestra tal como la envió el agente: deltas crudos de ticks y contadores, con todas las fuentes que tiene el agente. Esta es la grabación; los demás ficheros son vistas de ella.                                              |
| `<out>.csv`         | Una fila ancha por muestra, para una hoja de cálculo. El conjunto de columnas se dimensiona a partir de la primera muestra: un grupo por núcleo y uno por cola softnet.                                                                     |
| `<out>.markers.csv` | `wall_ns,wall_utc,seq,kind,label`, una fila por marcador.                                                                                                                                                                                   |
| `<out>.meta.json`   | Se escribe al principio, para que `mark` y `plot` puedan ejecutarse después: `started_utc`, `skew_ns` (reloj de pared del agente menos el del anfitrión), `agent` (su versión), `rate_hz`, `transport` y `capabilities` cuando el transporte pudo obtenerlas. |

El CSV guarda un subconjunto fijo de cada muestra. Todo lo demás que lleva una
muestra — eventos del log del kernel, contadores PMU, temperaturas,
interrupciones por línea y el resto — está solo en el `.jsonl`.

| Columnas                                                                                                     | Contenido                                                                                                             |
| ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| `seq`, `wall_ns`, `wall_utc`, `dt_ns`                                                                        | El número de secuencia de la muestra, el reloj de pared del agente y el intervalo real.                               |
| `busy_total`                                                                                                 | La media de las proporciones de ocupación por núcleo, con tres decimales, calculada por la CLI a partir de los 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` | Por núcleo: la proporción de ocupación y después los deltas crudos de ticks.                                          |
| `ctxt`, `intr`, `irq_total`                                                                                  | Cambios de contexto e interrupciones de `/proc/stat`, y el delta sumado sobre todas las filas de `/proc/interrupts`.  |
| `softnet<N>_processed`, `softnet<N>_dropped`, `softnet<N>_time_squeeze`                                      | Por cola softnet, los deltas del camino de recepción.                                                                 |
| `mem_free_kb`, `mem_available_kb`, `mem_cached_kb`, `mem_slab_kb`                                            | Niveles de memoria, en kB.                                                                                            |
| `load1`, `threads_running`, `threads_total`                                                                  | Carga media y recuento de hilos.                                                                                      |
| `pgfault`, `pgmajfault`                                                                                      | Deltas de fallos de página.                                                                                           |
| `self_cpu_us`, `self_rss_bytes`                                                                              | El tiempo de CPU propio del agente durante la muestra, en µs, y su memoria residente en ese momento, en bytes.        |

La proporción de ocupación es el único número que calcula la CLI, y hereda el
suelo del kernel: un tick dura 10 ms, así que en una muestra
de 100 ms un núcleo se resuelve en escalones del 10 %.
[Límites de resolución](https://docs.jmrp.io/mikroscope/es/limits/) explica por qué, y por qué la
proporción está limitada a 1.

## Marcadores

Un marcador es una fila de `<out>.markers.csv`, de uno de tres tipos:

| `kind` | Se escribe cuando                                                                                                                                                                                                                                     |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `note` | Escribes una línea no vacía mientras `record` corre en un terminal (imprime `type a line and press Enter to add a marker; Ctrl-C stops`), o ejecutas `mark --out <prefix> "text"`. No se lee nada cuando la entrada estándar no es un terminal.    |
| `gap`  | El agente informa de que unas muestras ya no estaban en su anillo. La etiqueta es `samples <from>..<to> lost`, y el resumen que imprime `record` lista los mismos huecos.                                                                            |
| `log`  | `--log-markers` añade una línea del propio log del router (abajo).                                                                                                                                                                                   |

Las notas y los huecos llevan la hora de tu máquina más el desfase medido al
principio, así que quedan sobre el eje de tiempo del agente. `mark --out cap
"text"` hace lo mismo con el desfase guardado en `cap.meta.json`: sella la hora
actual en el reloj del agente, y ninguna opción fija otra. `mark` necesita ese
fichero y un `cap.markers.csv` existente, y su fila lleva `seq` 0.

> **Marca mientras corre la grabación**
>
> `record` no mantiene abierto `<out>.markers.csv`: lo abre, añade y lo cierra con cada marcador,
> como hace `mark --out <prefix> "text"` desde un segundo terminal. Los marcadores tecleados en un
> terminal y los añadidos desde otro caen en el fichero en el orden en que se hicieron, igual en
> Linux, macOS y Windows. Un `mark` de texto sobre una grabación ya detenida sella la hora actual,
> que cae después de la última muestra, así que `plot` no dibuja nada ahí.

## Marcadores del log del router

`--log-markers` convierte las líneas del log del router de la ventana de la
grabación en marcadores `log`, con la etiqueta `<topics>: <message>`. Úsalo
cuando haya que explicar un transitorio que no provocaste tú: ejecuciones del
planificador, errores, eventos de interfaz.

```sh
export MIKROSCOPE_API_ADDR=192.168.88.1:8728 MIKROSCOPE_API_USER=mikroscope MIKROSCOPE_API_PASSWORD=…
mikroscope record --for 60s --out burst
mikroscope mark --out burst --log-markers --router-tz Europe/Madrid
```

- `mark --log-markers` usa la ventana desde `started_utc` del fichero meta,
  desplazado por su `skew_ns` al reloj del agente, hasta la última muestra del
  `.jsonl`, e imprime cuántas líneas añadió, la ventana y los temas.
  `started_utc` se guarda al segundo. Añade sin comprobar lo que ya hay:
  ejecutarlo dos veces vuelve a añadir las mismas líneas.
- `record --log-markers` pide el log cuando la grabación se ha detenido, para la
  ventana desde su inicio hasta su final en el reloj del agente, y añade las
  líneas por el mismo camino que usa `mark`, así que el total de marcadores del
  resumen cuenta lo que contiene el fichero. Si la petición del log falla,
  `record` imprime `log markers: …` y conserva la grabación.

La CLI pide al router solo la ventana, con una consulta `?>time=` que empieza un
segundo antes, y solo los campos `time`, `topics` y `message`, porque un router
que registra un tema con mucho tráfico en disco puede tener decenas de miles de
filas. Después conserva las líneas dentro de la ventana cuyos temas incluyen
alguno de `--topics`. Un evento registrado por varias acciones de registro sobre
un mismo tema llega una vez por acción, precedido del nombre de la acción
(`[INFO]: …`, `[SYSTEM]: …`); el prefijo se elimina y los duplicados se conservan
una sola vez.

Las horas del log no llevan zona:

- Por la API, RouterOS imprime la fecha y la hora completas
  ([verificado](https://docs.jmrp.io/mikroscope/es/about/status/#verified-log-time-full-date)). El analizador acepta también
  las formas más cortas, sin año o sin fecha, y las completa a partir del final
  de la ventana.
- Las horas se leen en `--router-tz` y se sellan como reloj de pared del agente,
  que es el propio reloj del router, así que no se les aplica desfase. El valor
  por defecto `Local` es la zona de tu máquina: si la del router es distinta,
  pásala, o cada marcador de log caerá desplazado en esa diferencia.
- Las horas del log tienen resolución de un segundo, así que un marcador de log
  sitúa su evento al segundo, no a la muestra.

## Gráfico

`plot --in cap` lee `cap.jsonl`, y `cap.markers.csv` cuando existe, y escribe
`cap.svg` (o `--svg`). Imprime el nombre del fichero con sus recuentos de
muestras y marcadores. La misma entrada produce siempre los mismos bytes.

El gráfico mide 1 200 unidades de ancho, con un título (`--title`, o el prefijo)
y una línea con el número de muestras, la duración y el número de núcleos. Tres
paneles comparten un eje de tiempo, en segundos desde que empezó la grabación:

1. **CPU busy per core, %** — de 0 a 100, una línea por núcleo, cada una
   etiquetada en su extremo. La paleta tiene ocho colores, así que se dibujan los
   ocho primeros núcleos.
2. **softnet per second, all CPUs** — `dropped` y `time_squeeze`, sumados sobre
   todas las colas y agrupados en segundos enteros. Los descartes se dibujan en
   rojo.
3. **memory available, MiB** — `MemAvailable` por muestra, con el eje y ajustado
   a los datos.

Marcadores en el gráfico:

- Cada marcador es una línea vertical discontinua que cruza los tres paneles,
  gris para notas y líneas de log y roja para huecos, con una etiqueta encima del
  primer panel.
- Las etiquetas se reparten en hasta seis filas. Una etiqueta de más de 40
  caracteres se acorta; cuando no cabe en ninguna fila se sigue acortando hasta
  que cabe, y una que quedaría por debajo de 12 caracteres se descarta. Su línea
  discontinua sigue marcando el instante.
- Los marcadores de log consecutivos, en orden de tiempo, que caen en el mismo
  segundo se pliegan en una sola etiqueta, `<count>× <topics>: <first message>`.
  Una nota o un hueco entre ellos corta el pliegue, y las notas y los huecos
  nunca se pliegan.
- Los marcadores fuera del intervalo de tiempo de la grabación no se dibujan.

Con una sola muestra, el SVG dice `not enough samples to draw`; sin ninguna,
`plot` se detiene con `record: no samples` y no escribe SVG. La paleta es propia
del gráfico, validada para su fondo claro: ΔE entre pares adyacentes con
deficiencia de visión del color 9,1, con visión normal 22,9. No hay variante
oscura.

`plot` también lee una captura que el agente guardó por un disparo: guarda
`GET /captures/<id>` en un fichero `.jsonl` y pásalo a `--in`. La línea de
cabecera de la captura no lleva número de secuencia y se salta.

## Líneas de disparo

Cuando salta un disparador en el agente, una línea `{"trigger":{…}}` viaja en la
misma petición que las muestras, justo antes de la muestra en la que saltó. El
colector la reconoce; `record` no.

> **Una línea de disparo en una grabación**
>
> `record` lee una línea de disparo como si fuera una muestra con número de secuencia 0. La línea se
> escribe tal cual en el `.jsonl`, donde `plot` la salta, y como una fila de ceros con `seq` 0 en el
> `.csv`; cuenta una vez en el número de muestras que informa `record`. Si es la primera línea de la
> grabación, la cabecera del CSV se dimensiona para cero núcleos. `record` no la añade como
> marcador. Para ver qué saltó durante una ventana, lee `/captures` en el agente.

[Captura por disparo](https://docs.jmrp.io/mikroscope/es/record/triggers/) explica qué salta y cómo
traer la ventana a cadencia completa que el agente guardó a su alrededor.

## Véase también

- [Primera grabación](https://docs.jmrp.io/mikroscope/es/start/walkthrough/): grabar, marcar y
  dibujar, paso a paso.
- [Captura por disparo](https://docs.jmrp.io/mikroscope/es/record/triggers/): las muestras alrededor
  de una condición, guardadas a cadencia completa por el agente sin ninguna
  grabación en marcha.
- [Acceso por red](https://docs.jmrp.io/mikroscope/es/install/reaching-the-agent/): cuál de los
  caminos directo y relay permite tu red.
- [Usuario de la API](https://docs.jmrp.io/mikroscope/es/security/api-user/): el usuario de RouterOS
  que necesitan el relay y `--log-markers`.
