# mikroscope Telemetría de kernel por debajo del segundo desde dentro de un router MikroTik, con el coste del observador medido y no prometido. Source: https://jmrplens.github.io/mikroscope/es/ ## Medido, no presupuestado - [**2,85 %** — de un núcleo a 10 Hz, la cadencia por defecto](/mikroscope/es/cost/) - [**31,3 MiB** — de memoria residente a 10 Hz](/mikroscope/es/cost/) - [**17,81 %** — de un núcleo a 100 Hz, el máximo de la CLI](/mikroscope/es/cost/rate-ceiling/) - [**0 / 0** — huecos y descartes, en todos los destinos, en cinco ejecuciones](/mikroscope/es/cost/rate-ceiling/) Las tres primeras, desde el propio cgroup del agente y `/metrics`; la cuarta, desde los tres destinos a los que reenviaba el colector; en un RB5009 (4 × 1,4 GHz Cortex-A72, RouterOS 7.24.2), ventanas de 60 s en régimen estacionario, 2026-09-15. Las cifras a 10 Hz superan el presupuesto de ≤ 2 % y ≤ 16 MiB. ## Una media de un segundo es un informe sobre un segundo La API de RouterOS publica `cpu-load` una vez por segundo. Un núcleo saturado 100 ms y ocioso los otros 900 mueve la media de un segundo de cuatro núcleos en 2,5 %. Es aritmética, no una medida, y la cifra es cierta: solo que no puede decir cuándo. El agente lee `/proc/stat`, `/proc/interrupts`, `/proc/softirqs` y `/proc/net/softnet_stat` desde dentro del router, a 10 Hz por defecto, y envía los deltas crudos de ticks con el intervalo que cubre cada uno. Nunca calcula un porcentaje; la ventana la eliges tú. El suelo es del kernel, no de la herramienta. `/proc/stat` cuenta en ticks de 10 ms, así que una muestra de 100 ms resuelve un núcleo en escalones de 10 %. En el RB5009 (RouterOS 7.24.2, Linux 5.6.3) no hay PSI ni schedstat con los que afinar: ambos ficheros faltan, comprobado el 2026-09-11. ## Lo que cuesta, a tres cadencias Cada fila es una ventana de 60 s con el anillo ya lleno. La memoria cambia por fila porque cambian el anillo y el límite de memoria. Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · 2026-09-15 · ventanas de 60 s en régimen estacionario (con el anillo ya lleno), conjunto completo de fuentes, colector reenviando a la vez a fichero, a una exposición Prometheus y a InfluxDB 3 Las ejecuciones medidas: | cadencia | suelos | CPU de un núcleo | µs/muestra | RSS | ticks retrasados | huecos / descartes | | --- | --- | --- | --- | --- | --- | --- | | 10 Hz (por defecto) | por defecto | **2,85 %** | 2 856 | 31,3 MiB | **0** | 0 / 0 | | 50 Hz | por defecto | **10,13 %** | 2 026 | 51,9 MiB | **0** | 0 / 0 | | 100 Hz | por defecto | **17,81 %** | 1 781 | 76,5 MiB | 5 (0,08 %) | 0 / 0 | No se perdió nada a ninguna de estas cadencias: todos los destinos informaron de 0 huecos y 0 descartes, y la cadencia entregada coincidió con la configurada a tres cifras. Con los suelos por defecto y a 100 Hz, todas las fuentes de un tick se leyeron en menos de 2 ms en el 97,5 % de las muestras, dentro de un periodo de 10 ms. [Las cinco ejecuciones, incluida la de todas las fuentes en cada tick →](/mikroscope/es/cost/rate-ceiling/) ## La CPU del router desde el kernel, sus interfaces desde la API ### Capa del kernel · el agente · de 10 a 100 Hz Globales dentro del contenedor, así que son los del propio router: ticks de CPU por núcleo, interrupciones, softirqs, descartes y time squeezes de softnet, `/proc/meminfo`, `/proc/vmstat`, carga y E/S de disco. Un contenedor privilegiado añade el log del kernel como eventos con marca de tiempo y las cachés slab globales. ### Capa de la API · el colector · 1 Hz El contenedor tiene su propio espacio de nombres de red, así que `/proc/net/dev` describe al contenedor, no al router. Los bytes y paquetes por interfaz vienen de la API de RouterOS y los fusiona el colector, sin interpolar. `privileged=yes` no cambia eso (comprobado el 2026-09-12). ## Cada escritura, listada antes de hacerla Descarga el archivo para tu plataforma de la versión publicada, o compila la CLI desde una copia del repositorio con `make build`. El router necesita RouterOS 7.24 o posterior —el paso del contenedor escribe `privileged=`, un atributo que las versiones 7.x anteriores rechazan— con el paquete `container` y `device-mode container=yes`, que MikroTik condiciona a pulsar el botón de reset o a un corte de alimentación. arm64, arm y x86_64; ni MIPS ni TILE. 1. `mikroscope doctor` Comprobación de solo lectura; nombra el arreglo de lo que falte. 2. `mikroscope plan` Cada orden de RouterOS, sin escribir nada. 3. `mikroscope install` Doctor, confirmación, las escrituras y luego una sonda al agente. La imagen sale de tu propia toolchain de Go, del tar del agente publicado o del registro del que el router se la descarga. 4. `mikroscope status` Recuento de propiedad y salud del agente. 5. `mikroscope uninstall` Elimina y verifica. **Lo que `install` escribe en tu router** - una veth - una dirección - una pertenencia a lista de interfaces - una entrada de address-list - una envlist - el tar de la imagen, salvo que `--remote-image` haga que el router se la baje - el contenedor Cada objeto lleva el comentario `mikroscope: (managed by mikroscope)` `mikroscope plan` imprime cada orden antes de escribir nada. `uninstall` elimina por etiqueta exacta más identidad, nunca por patrón, y falla nombrando el paso si queda algo. ## Lo que no se afirma > **No medido, luego no afirmado** > > Cualquier cadencia en una placa que no sea este RB5009, y cualquier carga de tráfico mayor que la de una tarde normal de este router, unos 30 Mbit/s. El coste depende de la velocidad del núcleo, del conjunto de fuentes y del tamaño del anillo: mídelo en tu propio equipo antes de presupuestarlo. El proyecto ha corrido en un equipo, un RB5009UG+S+ con RouterOS 7.24.2, arm64; las compilaciones para arm y x86_64 son cruzadas y pasan por CI, pero no han corrido nunca en hardware, y siete de los diez destinos solo se ejercitan contra dobles. ## Por dónde seguir - [Qué es](/mikroscope/es/start/): Dos programas, un contenedor, cuatro límites dichos primero - [Cinco minutos con un router](/mikroscope/es/start/walkthrough/): Instalar, grabar mientras cambias algo y dibujar el gráfico, con una grabación real de un RB5009 - [Instalar el agente](/mikroscope/es/install/): Cuatro maneras de llevar el agente al router, qué escribe install y en qué orden, y cómo uninstall quita solo lo que creó - [El colector](/mikroscope/es/sinks/): Extraer, fusionar, derivar, repartir a diez destinos, y por qué uno lento nunca para el bucle - [Cómo leer lo que muestra](/mikroscope/es/playbooks/): Un fallo de producción que la API no veía, fallos provocados y la forma en reposo contra la que se leen - [Lo que los números no dicen](/mikroscope/es/cost/limits/): Cada límite de las cifras de arriba --- # Qué es Un agente dentro del router leyendo el kernel compartido, una CLI fuera, y los cuatro límites que no se pueden diseñar para que desaparezcan. Source: https://jmrplens.github.io/mikroscope/es/start/ mikroscope son dos programas y un contenedor. El **agente** es un binario Go estático en un contenedor scratch dentro del router. Un contenedor de RouterOS comparte el kernel del anfitrión, así que el `/proc` de dentro es el `/proc` del propio router: `/proc/stat` por núcleo, `/proc/interrupts`, `/proc/softirqs`, `/proc/net/softnet_stat`, `/proc/meminfo`, `/proc/vmstat`, `/proc/diskstats`, `/dev/kmsg`. Los muestrea con un temporizador de 1 a 100 Hz (10 Hz por defecto; medidos a 10, 50 y 100 Hz), guarda los últimos 300 segundos en un anillo y los sirve. No tiene conexión saliente ni presenta ninguna credencial; el único secreto que guarda es el token opcional que exige a quien lo lea. La **CLI** corre en tu máquina. Instala y desinstala el agente, graba una ventana con marcadores, dibuja un SVG determinista de ella, y funciona como colector que tira del agente, fusiona una capa de la API de RouterOS a 1 Hz y reparte a fichero, Prometheus e InfluxDB 3. Se publica como un archivo por plataforma —linux, macOS, Windows y FreeBSD en amd64, arm64 y arm— y el agente a su lado como un tar de imagen por arquitectura y como imagen de registro, en Docker Hub como `jmrplens/mikroscope-agent` y en GHCR como `ghcr.io/jmrplens/mikroscope-agent`, así que instalar solo necesita una toolchain de Go cuando quieres el agente construido desde tu propio árbol. ## Cuatro límites, dichos antes que nada 1. **Los requisitos que la herramienta no puede quitar.** RouterOS 7.24 o posterior con el paquete `container` y `device-mode container=yes` — que MikroTik protege tras una pulsación física del botón de reset o un ciclo de alimentación. El suelo es 7.24 porque el paso del contenedor escribe `privileged=`, un atributo que MikroTik añadió en esa versión; `--privileged=false` cambia su valor, no si se escribe. arm64, arm y x86_64; ni MIPS ni TILE. 2. **El suelo de resolución es el del kernel, no el de la herramienta.** `/proc/stat` avanza a 100 Hz, así que una ventana de 100 ms resuelve un núcleo en escalones del 10 %. El agente envía ticks crudos para que la ventana la elijas tú. No esperes PSI: el kernel del RB5009 no tiene ni PSI ni schedstat. Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · Linux 5.6.3 · 2026-09-11 · `/proc/pressure` y `/proc/schedstat` ausentes 3. **El contenedor ve la CPU y la memoria del router, pero su propia red.** `/proc/net/dev`, `/proc/net/snmp` y `nf_conntrack_count` son por espacio de nombres de red y describen el contenedor; la cuenta de conntrack del slab con `privileged` es la excepción, y describe el router. Los contadores de interfaz vienen de la API de RouterOS y se fusionan, no se inventan. 4. **El observador cuesta algo, y está escrito.** Un 2,85 % de un núcleo a 10 Hz en un RB5009, un 17,81 % a 100 Hz. [Lo que cuesta](/mikroscope/es/cost/) tiene la tabla completa y sus condiciones. Todas las cifras de este sitio salen de ese único equipo, un RB5009UG+S+ con RouterOS 7.24.2, arm64: las compilaciones para arm y x86_64 son cruzadas y pasan por CI, pero no han corrido nunca en hardware. **Lo que `install` escribe en tu router** - una veth - una dirección - una pertenencia a lista de interfaces - una entrada de address-list - una envlist - el tar de la imagen, salvo que `--remote-image` haga que el router se la baje - el contenedor Cada objeto lleva el comentario `mikroscope: (managed by mikroscope)` `mikroscope plan` imprime cada orden antes de escribir nada. `uninstall` elimina por etiqueta exacta más identidad, nunca por patrón, y falla nombrando el paso si queda algo. ## Lo que no hace No sustituye a la API de RouterOS: se fusiona con ella. No afirma un número que no haya medido: donde una fuente no existe en una placa, la métrica falta en vez de valer cero. Y no decide por ti qué significa un porcentaje, porque nunca calcula ninguno. ## Véase también - [El coste del observador](/mikroscope/es/cost/): el presupuesto, el resultado medido y cómo medirlo en tu propio equipo. - [El techo de muestreo](/mikroscope/es/cost/rate-ceiling/): las cinco ejecuciones detrás de las cifras del punto 4. - [Lo que los números no dicen](/mikroscope/es/cost/limits/): lo que un equipo en un día no te puede decir. --- # Cinco minutos con un router Consigue la CLI, instala el agente, graba una ventana mientras cambias algo, añade el propio log del router como marcadores y dibuja el gráfico, con una grabación real de un RB5009. Source: https://jmrplens.github.io/mikroscope/es/start/walkthrough/ La pregunta para la que existe esta herramienta: _estoy a punto de cambiar algo en el router; ¿qué hace de verdad, a una resolución en la que pueda verlo?_ Esta página es el camino más corto desde un router que nunca ha visto mikroscope hasta un gráfico que la responde: seis pasos con las órdenes tal cual son, y una grabación real del RB5009 leída línea a línea. ## Antes de empezar El router necesita lo que la herramienta no le puede dar: RouterOS 7.24 o posterior con el paquete `container` y `device-mode container=yes`, que MikroTik protege tras una pulsación del botón de reset o un ciclo de alimentación. El suelo es 7.24 porque el paso del contenedor escribe `privileged=`, un atributo que MikroTik añadió en esa versión; `--privileged=false` cambia su valor, no si se escribe, así que una 7.x anterior rechaza la orden igualmente. Todo lo de esta página se midió en 7.24.2. `doctor` informa de la versión del router en su primera línea, pero no la condiciona. [Lo que necesita el router](/mikroscope/es/install/prerequisites/) tiene la lista completa, y `mikroscope doctor` comprueba el resto en solo lectura y nombra el arreglo de lo que falte. La CLI no lee `.env` por sí misma, y solo algunas opciones toman su valor por defecto de una variable `MIKROSCOPE_*`: las de conexión y nombres (`ROUTER`, `SSH_PORT`, `SSH_KEY`, `NAME`, `VETH`, `SUBNET`, `IFACE_LIST`, `ADDR_LIST`, `DISK`, `ARCH`, `TOKEN`, `LAN_ADDRESS`), las de la API (`API_ADDR`, `API_USER`, `API_PASSWORD`), las URL de los destinos como `INFLUX_URL`, `INTERFACES` y `HOST_TAG`. El resto, entre ellas `--ephemeral`, `--rate`, `--for`, `--topics` y `--prom`, tiene valores por defecto fijos, diga lo que diga el texto de uso de la CLI. Exporta las variables que necesites, o copia `.env.example` a `.env` y carga el fichero con `set -a; . ./.env; set +a`; [variables de entorno](/mikroscope/es/reference/environment/) las lista todas. ## El camino 1. **Conseguir mikroscope.** ```sh wrap tar xzf mikroscope_1.0.0_linux_x86_64.tar.gz # un .zip en Windows ./mikroscope version ``` La versión publicada trae la CLI como un archivo por plataforma —linux, macOS, Windows y FreeBSD en amd64, arm64 y arm— con `checksums.txt`, las firmas de cosign y los SBOM al lado. El agente viaja aparte, como un tar de imagen por arquitectura (`mikroscope-agent-arm64.tar`, `mikroscope-agent-arm.tar`, `mikroscope-agent-amd64.tar`) y como imagen de registro, publicada tanto como `jmrplens/mikroscope-agent:1.0.0` en Docker Hub como `ghcr.io/jmrplens/mikroscope-agent:1.0.0` en GHCR; el paso 2 toma uno de los dos. Desde una copia del repositorio, en cambio: ```sh wrap git clone https://github.com/jmrplens/mikroscope && cd mikroscope && make build ``` Eso necesita Go 1.27 y deja la CLI en `bin/mikroscope`, que es como están escritas las órdenes de abajo. Es además el único camino que instala un agente construido desde tu propio árbol, porque `install` lo compila de forma cruzada desde la raíz del módulo. 2. **Instalar, una vez.** ```sh wrap export MIKROSCOPE_ROUTER=admin@192.168.88.1 bin/mikroscope plan --ephemeral bin/mikroscope doctor --ephemeral && bin/mikroscope install --ephemeral ``` `plan` imprime cada orden de RouterOS y no escribe nada. `install` pone la imagen del agente en el router, lista los objetos, vuelve a pasar la misma comprobación previa (`--no-doctor` se la salta), pregunta `write the objects above to the router? [y/N]` (`--yes` se salta la pregunta), escribe y después sondea el agente desde tu equipo. De dónde sale la imagen lo eliges tú, y es la única diferencia entre las tres instalaciones: - **tu toolchain de Go**, como arriba: la CLI ejecuta `go build ./cmd/mikroscope-agent` con `CGO_ENABLED=0`, `GOOS=linux` y `GOARCH` tomado de `--arch`, por lo que tiene que ejecutarse desde la raíz del módulo; - **el tar publicado**, `--agent-tar mikroscope-agent-arm64.tar`: sin toolchain de Go y sin copia del repositorio. La CLI lee el tar antes de subirlo —tiene que ser una imagen del agente de mikroscope y su arquitectura tiene que coincidir con `--arch`—, o el verbo se detiene y nombra el fichero que hay que descargar; - **el registro**, `--remote-image jmrplens/mikroscope-agent:1.0.0`: el router se descarga la imagen él mismo, no se sube nada y `uninstall` no tiene ningún fichero del que responder. RouterOS toma el host del registro del ajuste global `/container/config registry-url`, que mikroscope nunca escribe porque lo comparten todos los contenedores del equipo, y que viene puesto en `https://registry-1.docker.io`: por eso la referencia de Docker Hub de arriba funciona tal cual en un router sin tocar. La referencia de GHCR, `ghcr.io/jmrplens/mikroscope-agent:1.0.0`, nombra un host propio: `doctor` compara el ajuste con él e imprime `/container/config/set registry-url=https://ghcr.io` cuando no coincide, o remite a `--agent-tar`. La descarga necesita que el router llegue al registro y que haya RAM libre para las capas. `--arch` vale por defecto `arm64`, la del RB5009; no se detecta a partir del router, pero `doctor` la compara con la arquitectura que informa el router y nombra el valor de `--arch` con el que volver a ejecutar si no coinciden. En un router al que solo llegas por WinBox o WebFig hay una cuarta vía, sin CLI de tu lado: `mikroscope plan --rsc --remote-image jmrplens/mikroscope-agent:1.0.0 --out install.rsc` escribe las mismas órdenes, en el mismo orden y con las mismas etiquetas, como un script de RouterOS para pegar en el terminal o hacerle `/import`. [Instalar el agente](/mikroscope/es/install/) tiene esa vía y sus dos advertencias al completo. `--ephemeral` pone el tar de la imagen y la raíz del contenedor en el disco RAM tmpfs del router y crea el contenedor con `start-on-boot=no`, así que el agente no vuelve tras un reinicio. `install` borra el tar en cuanto el contenedor lo ha extraído. No se escribió nada en la flash: `write-sect-since-reboot` se quedó en 58 279 durante la instalación, la ejecución y la retirada (verificado en RB5009UG+S+, RouterOS 7.24.2, 2026-09-11). Quítalo para una instalación persistente. El disco tmpfs tiene que existir; el RB5009 tiene uno, y `doctor --ephemeral` lo comprueba e imprime el `/disk/add` que lo crea si no existe. Pasa `--ephemeral` también a `doctor`: sin él, `doctor` comprueba en su lugar la flash libre. `install` termina sondeando al agente desde tu equipo, e imprime una línea: ```text wrap direct transport ok: agent 1.0.0 (9ddd760) built 2026-09-16T08:38:27Z, 10 Hz, seq 29, 0 slipped, 7ms round trip ``` El primer campo es la identidad de compilación del agente: la versión, que una publicación graba desde el fichero `VERSION`, y después el commit y la fecha de compilación. Un `go build` sin grabar dentro de una copia del repositorio informa de esa misma versión con el commit y la hora que registra la toolchain de Go, así que el campo nunca dice `dev` ni un hash a secas. Un agente que sale del tar o del registro informa de lo que grabó la compilación publicada; uno que `install` construye desde tu árbol lleva la marca de la propia CLI, así que ambos informan de la misma cadena. El resto de la línea es el sondeo: la cadencia del muestreador, la secuencia a la que había llegado el agente, los ticks que se le escaparon y el tiempo de ida y vuelta. En el RB5009 el sondeo respondió a los tres segundos con 7 ms de ida y vuelta, y con 5 ms tras un `upgrade` ese mismo día (2026-09-12). El sondeo espera hasta 30 s. Si tu equipo no llega a la /30 del agente, primero pregunta al router si el contenedor está en marcha y solo entonces sugiere alternativas; consulta [llegar al agente](/mikroscope/es/install/reaching-the-agent/). 3. **Grabar mientras haces el cambio.** ```sh wrap bin/mikroscope record --for 70s --out burst ``` Escribe una línea y pulsa Intro cada vez que hagas algo que merezca recordarse; se convierte en un marcador con la marca de tiempo del agente. Funciona cuando la entrada estándar es un terminal, y la CLI lo dice: `type a line and press Enter to add a marker; Ctrl-C stops`. Desde otra shell, ```sh wrap bin/mikroscope mark --out burst "queue tree applied" ``` hace lo mismo: `mark` añade al final de `.markers.csv` mientras `record` mantiene el fichero abierto, así que una nota desde un segundo terminal y otra escrita en el del grabador acaban en el mismo fichero. `--for 0`, el valor por defecto, graba hasta Ctrl-C. Al final `record` imprime el número de muestras, el rango de secuencia, los huecos, los marcadores y el transporte que usó, y nombra cualquier tramo de muestras que ya no estaba en el anillo del agente. 4. **Añadir el propio log del router.** Este paso habla con la API de RouterOS, no con el agente, así que necesita una cuenta en el router con la que hablar. Basta una de solo lectura, y [el usuario de la API](/mikroscope/es/security/api-user/) trae las dos órdenes que crean el grupo y el usuario; la CLI no necesita `admin` para esto. ```sh wrap 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 ``` (O pon esas tres en `.env` —`cp .env.example .env`— y carga el fichero, como arriba.) Cada línea de log de la ventana de la grabación (desde su inicio hasta su última muestra, en el reloj del agente) cuyo tema sea `system`, `interface` o `container` se convierte en un marcador; añade `firewall` o `script` con `--topics` cuando sus líneas sean la historia. El router informa ahí de errores y de ejecuciones del planificador, y el log a menudo explica un transitorio que no provocaste tú. `MIKROSCOPE_API_ADDR` tiene la opción `--api` y `MIKROSCOPE_API_USER` la opción `--api-user`; la contraseña no tiene opción. `--router-tz` es la zona IANA que muestra el reloj del router, porque las horas del log de RouterOS no llevan zona; por defecto es la de tu máquina. `record --log-markers` hace lo mismo al final de una grabación, y `mark --log-markers` lo hace después, como aquí ([el propio log del router como marcadores](/mikroscope/es/record/#el-propio-log-del-router-como-marcadores)). 5. **Mirar.** ```sh wrap bin/mikroscope plot --in burst ``` Esto escribe `burst.svg`: tres paneles sobre un mismo eje de tiempo (proporción de ocupación por núcleo, descartes de softnet y time squeezes por segundo, memoria disponible) con cada marcador como una vertical discontinua dibujada panel a panel, de modo que ningún título de panel queda tachado. Los marcadores del log que caen en el mismo segundo se pliegan en una sola línea cuya etiqueta dice `N×` y el primer mensaje; un hueco en las muestras se dibuja como una línea roja; una etiqueta de más de 40 caracteres se corta a 37 y unos puntos suspensivos, y una etiqueta a la que no le queda sitio se acorta más hasta que cabe en vez de montarse sobre la de al lado, y se queda solo con su vertical cuando ya no queda nada legible; un marcador fuera del intervalo de la grabación no se dibuja. La misma grabación produce siempre los mismos bytes. `--title` fija el encabezado y `--svg` otra ruta de salida. 6. **Dejarlo en marcha (opcional).** ```sh wrap bin/mikroscope forward --prom :9124 --influx "$MIKROSCOPE_INFLUX_URL" --interfaces bridge,ether1 ``` Esto ejecuta el colector: la capa del kernel desde el agente y, con las variables `MIKROSCOPE_API_*` del paso 4 en el entorno, la capa de la API de RouterOS a su lado. Con el `--api-mode full` por defecto esa capa lee `/system/resource`, `/system/resource/cpu`, `/system/health`, `monitor-traffic` cada segundo para las interfaces nombradas en `--interfaces`, los contadores acumulados de cada puerto cada 10 s y qué es cada interfaz —su comentario, su tipo, sus listas de interfaces, su bridge y su MTU— al arrancar y cada 5 min; [la capa de la API de RouterOS](/mikroscope/es/sinks/api-tier/) dice cuáles de ellos puede leer el propio agente. Sin esas variables la capa de la API queda desactivada con un aviso y la capa del kernel sigue funcionando. Ambas van a una exposición de Prometheus en `:9124` y a InfluxDB 3. A partir de ahí, [importar y comprobar](/mikroscope/es/dashboards/import-and-check/) monta los dos paneles de Grafana, el campo del origen de datos que necesita una importación de InfluxDB 3 y los dos trabajos de scrape de Prometheus. ## Lo que muestra el gráfico [![RB5009UG+S+, 70 s a 10 Hz: 700 muestras en 69,9 s sobre cuatro núcleos, con tres marcadores discontinuos: «baseline, router idle» a los 12 s, «dashboards check started» a los 30 s y «check finished» a los 50 s. La ocupación por núcleo se mantiene baja, con excursiones de una sola muestra al 100 %; el panel de softnet muestra time squeezes y un cero plano en los descartes; la memoria disponible se mantiene entre 662 y 671 MiB.](https://raw.githubusercontent.com/jmrplens/mikroscope/main/site/src/assets/walkthrough/rb5009-walkthrough.svg)](https://raw.githubusercontent.com/jmrplens/mikroscope/main/site/src/assets/walkthrough/rb5009-walkthrough.svg) [Abre el gráfico a tamaño completo](https://raw.githubusercontent.com/jmrplens/mikroscope/main/site/src/assets/walkthrough/rb5009-walkthrough.svg) (SVG, 1200 × 754) para leer sus etiquetas en un móvil. Los rótulos de los paneles van en inglés porque `plot` los escribe así: el título lo pones tú con `--title`, el resto es el vocabulario del kernel. Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · 2026-09-16 · un `record` de 70 s a 10 Hz, 700 muestras en 69,9 s, el router por lo demás en reposo, tres notas escritas en el terminal de `record` Es una grabación real de un router en reposo, la forma contra la que se lee todo lo demás. Las tres líneas discontinuas son las notas escritas en el terminal de `record` durante ella; las etiquetas las llevan enteras porque caben, y una más larga se acorta en vez de montarse sobre la siguiente. - **t = 12 s, `baseline, router idle`.** No pasa nada, y los paneles lo dicen: cada núcleo promedia menos del 10 % en toda la grabación, y en el tramo tranquilo que sigue a esta nota los cuatro juntos promedian un 3,9 %. - **t = 30 a 50 s, entre `dashboards check started` y `check finished`.** Un navegador cargando los dos paneles de Grafana contra el colector de este mismo router. Los cuatro núcleos juntos promedian un 5,0 % en ese tramo, y el trabajo llega en dos ráfagas cortas justo después de la nota: el núcleo 0 al 50 % o por encima durante 0,6 s desde los 32,3 s, y otros 0,3 s a los 33,2 s. - **El trabajo está en excursiones cortas.** 76 de las 700 muestras tienen un núcleo al 50 % o por encima, en 46 tramos separados; 38 de ellos duran una sola muestra, y el más largo dura 1,4 s, al principio de la grabación y antes de la primera nota. El planificador del kernel pone cada excursión en el núcleo que esté libre. Una muestra aislada —un núcleo al 100 % durante 100 ms— mueve una media de un segundo sobre cuatro núcleos en un 2,5 %. - **softnet: `dropped` plano en cero, `time_squeeze` entre 0 y unos 20 por segundo.** No se perdió nada en 70 s. Los squeezes son el fondo de este equipo, no un suceso; lo que el colector llama microburst es un racimo de ellos —tres muestras marcadas en un mismo núcleo dentro de 60 s—, y nunca uno solo. - **Memoria disponible, de 662 a 671 MiB.** Unos 9 MiB de vaivén normal en toda la grabación, sin ningún escalón en ninguno de los dos extremos de la comprobación de los paneles. El propio `cpu-load` de RouterOS, a 1 s, da este minuto por un puñado plano de puntos porcentuales. La grabación muestra de qué está hecho ese puñado: qué núcleo se llevó cada excursión, cuánto duró y dónde caen las notas frente a ella. > **Cierto en este equipo, no en el tuyo** > > Una grabación, en un router, a 10 Hz, con el router por lo demás en reposo. Las cifras por panel > de arriba están leídas de este gráfico, no vueltas a medir. Los tres segundos hasta que respondió > el sondeo y los 7 ms de ida y vuelta son lo que mostró esa instalación en esa red, no una cifra > para la tuya. ## Lo que escribió el paso 2, y cómo quitarlo **Lo que `install` escribe en tu router** - una veth - una dirección - una pertenencia a lista de interfaces - una entrada de address-list - una envlist - el tar de la imagen, salvo que `--remote-image` haga que el router se la baje - el contenedor Cada objeto lleva el comentario `mikroscope: (managed by mikroscope)` `mikroscope plan` imprime cada orden antes de escribir nada. `uninstall` elimina por etiqueta exacta más identidad, nunca por patrón, y falla nombrando el paso si queda algo. ```sh wrap bin/mikroscope uninstall --ephemeral ``` Pasa los mismos `--ephemeral`, `--disk` y `--name` con los que instalaste: `uninstall` y `status` reconstruyen el plan a partir de sus propias opciones, y sin `--ephemeral` buscan el tar de la imagen en la flash en lugar de en tmpfs. `uninstall` deshace cada paso del plan en orden inverso, por etiqueta exacta, luego cuenta lo que queda en el router a nombre de mikroscope y falla, nombrando los objetos, si queda algo. `bin/mikroscope status --ephemeral` imprime esos mismos recuentos de propiedad en cualquier momento, más la salud del agente cuando se puede llegar a él. > **ssh no sale gratis en el router** > > Cada conexión ssh le cuesta al RB5009 un 20–27 % de CPU mientras dura. > Por eso la CLI agrupa cada lectura en una sola conexión y nunca usa ssh como camino de datos; las > muestras viajan por HTTP hasta la veth, o por el relay de la API de RouterOS. ## Véase también - [Instalar el agente](/mikroscope/es/install/): cada opción de instalación, y dónde va cada objeto. - [Grabar, marcar, dibujar](/mikroscope/es/record/): los ficheros de grabación, los transportes y los marcadores al completo. - [El usuario de la API](/mikroscope/es/security/api-user/): la cuenta de solo lectura que necesitan el paso 4 y la capa de la API, y las dos órdenes que la crean. - [El colector](/mikroscope/es/sinks/): lo que fusiona `forward` y adónde lo envía. - [Importar y comprobar](/mikroscope/es/dashboards/import-and-check/): los paneles de Grafana, sus orígenes de datos y los trabajos de scrape. - [Cómo leer lo que muestra](/mikroscope/es/playbooks/): las firmas que un fallo real y uno provocado dejan en estos paneles. --- # El coste del observador Lo que cuesta el agente en un RB5009, medido desde su propio cgroup, y cómo medirlo en un equipo que no sea este. Source: https://jmrplens.github.io/mikroscope/es/cost/ Un observador que cuesta un 20 % de aquello que observa no está midiendo el router: se está midiendo a sí mismo. Por eso aquí este número es un resultado de primera clase y no una nota al pie: el agente lo publica en cada muestra y lo expone en `/metrics`, y CI comprueba el presupuesto de tamaño de la imagen. ## El presupuesto, y lo que cuesta de verdad El presupuesto es **≤ 2 % de un núcleo, ≤ 16 MiB de RSS, ≤ 8 MiB de imagen**. La imagen son 6,1 MiB. Los otros dos dependen de la cadencia y de cuánto le pidas leer, y la respuesta honesta es una tabla, no un número. Con los valores por defecto de la instalación — 10 Hz, suelos por fuente por defecto, anillo de 300 s — el agente cuesta **un 2,85 % de un núcleo y un 31,3 MiB de RSS**, desde su propio cgroup: Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · 2026-09-15 · ventanas de 60 s en régimen estacionario (con el anillo ya lleno), conjunto completo de fuentes, colector reenviando a la vez a fichero, a una exposición Prometheus y a InfluxDB 3 Eso está por encima del 2 % que pide el presupuesto, con todas las fuentes leídas, entre ellas los tiempos de perf, buddyinfo, los contadores ECC de MTD, los eventos de cgroup y los contadores de puerto. ### Lo que cuesta la alternativa El presupuesto dice lo que se le permite costar al agente. La otra comparación, la que suele querer quien lee, es contra hacerlo de la forma obvia: un bucle de shell de busybox leyendo el mismo conjunto de ficheros a la misma cadencia. En el mismo router eso cuesta **2,4 % de un núcleo**, mientras que las lecturas en sí rondan los 0,77 ms por muestra. Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · 2026-09-11 · un bucle de shell de busybox leyendo el conjunto completo de ficheros a 10 Hz, con un fork por iteración, en un contenedor del router Así que la mayor parte del coste del bucle de shell no está en leer. Paga un fork por iteración y el agente no paga ninguno: un proceso arranca, abre sus ficheros una vez y los deja abiertos. Esa es toda la diferencia, y es la razón de que el agente sea un binario y no un script. > **De dónde sale el número** > > No de `top`, y no de una instantánea. El agente cuenta sus propios microsegundos de CPU y su RSS > desde su cgroup y los publica como `mikroscope_self_cpu_usec_total` y `mikroscope_self_rss_bytes`. > Dos lecturas de `/metrics` separadas 60 s, en régimen estacionario y con el anillo ya lleno, son > la medida. El `/tool profile` de RouterOS muestra el mismo proceso como `mikroscope-agent`. ## Dimensiona el límite de memoria según los datos El error más caro disponible aquí es dar al proceso menos memoria de la que su anillo necesita. El recolector de basura de Go responde a un límite blando ajustado ejecutándose más a menudo, y el coste de CPU se dispara por un motivo que no tiene nada que ver con la cadencia de muestreo. El anillo guarda líneas ya codificadas en vez de structs por la misma razón. Si subes `--buffer` o la cadencia, sube con ellos `--mem-limit-mb` y el `--memory-max` del contenedor; los valores usados en cada fila de las medidas están junto a ellas en [el techo de muestreo](/mikroscope/es/cost/rate-ceiling/). El análisis no es donde se va el tiempo. Analizar los siete ficheros globales de `/proc` más un delta costó 27 µs y 239 asignaciones por muestra en el equipo de desarrollo amd64 (Go 1.27.1, tres ejecuciones, 26,7–27,5 µs, 2026-09-11). En el Cortex-A72 del RB5009 un tick entero — con temporizadores, JSON y el recolector de basura incluidos — cuesta 2 856 µs a 10 Hz con los suelos por defecto, y [las cinco ejecuciones](/mikroscope/es/cost/rate-ceiling/) lo dan a cada cadencia. El análisis por sí solo no se midió en el A72. ## Medirlo en tu propio equipo Nada de esto se traslada a una placa que no sea un RB5009: otro número de núcleos, otro reloj, otro kernel y otra flash lo mueven. El procedimiento son tres órdenes y lleva un minuto: ```sh curl -s http://172.30.10.2:9123/metrics | grep -E 'self_cpu_usec_total|self_rss_bytes' sleep 60 curl -s http://172.30.10.2:9123/metrics | grep -E 'self_cpu_usec_total|self_rss_bytes' ``` La diferencia de `self_cpu_usec_total` dividida entre 60 000 000 es la fracción de un núcleo. Tómala en régimen estacionario, con el anillo lleno: un agente recién arrancado todavía lo está llenando y marcará de menos. ## Véase también - [El techo de muestreo](/mikroscope/es/cost/rate-ceiling/): las cinco ejecuciones, las opciones de memoria de cada una y lo que compra muestrear más rápido. - [Lo que los números no dicen](/mikroscope/es/cost/limits/): lo que estas cifras no pueden decirte sobre otro equipo u otra carga. - [Qué es](/mikroscope/es/start/): el agente, la CLI y los cuatro límites dichos primero. --- # El techo de muestreo Cinco ejecuciones medidas a 10, 50 y 100 Hz en un RB5009 — ninguna pierde datos — y lo que muestrear más rápido compra de verdad. Source: https://jmrplens.github.io/mikroscope/es/cost/rate-ceiling/ La pregunta que responde esta página es la que merece la pena hacer antes de fiarse de nada de lo demás: **¿a qué velocidad puede muestrear antes de empezar a perder datos?** En el equipo de referencia la respuesta es que no pierde, hasta el tope de 100 Hz de la propia CLI, con todas las fuentes leídas en cada tick. Eso no es una extrapolación desde la cifra de 10 Hz. Son cinco ejecuciones. ## Las cinco ejecuciones Cada cifra sale del propio cgroup del agente y de `/metrics`, con el conjunto completo de fuentes. Cada fila es una ventana con el anillo ya lleno: Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · 2026-09-15 · ventanas de 60 s en régimen estacionario (con el anillo ya lleno), conjunto completo de fuentes, colector reenviando a la vez a fichero, a una exposición Prometheus y a InfluxDB 3 Las ejecuciones medidas: | cadencia | suelos | CPU de un núcleo | µs/muestra | RSS | ticks retrasados | huecos / descartes | | --- | --- | --- | --- | --- | --- | --- | | 10 Hz (por defecto) | por defecto | **2,85 %** | 2 856 | 31,3 MiB | **0** | 0 / 0 | | 50 Hz | por defecto | **10,13 %** | 2 026 | 51,9 MiB | **0** | 0 / 0 | | 100 Hz | por defecto | **17,81 %** | 1 781 | 76,5 MiB | 5 (0,08 %) | 0 / 0 | | 50 Hz | `FLOOR_HZ=50` | **22,47 %** | 4 494 | 60,6 MiB | 6 (0,20 %) | 0 / 0 | | 100 Hz | `FLOOR_HZ=100` | **43,95 %** | 4 395 | 79,6 MiB | 14 (0,23 %) | 0 / 0 | `FLOOR_HZ` significa que cada fuente se lee en cada tick, sin ningún suelo por fuente — el peor caso que se le puede pedir al agente. La memoria cambia de fila en fila porque el anillo cambia. La fila de 10 Hz es la instalación por defecto (anillo de 300 s, `--mem-limit-mb 40 --memory-max 64M`); las filas de 50 Hz usaron `--buffer 120 --mem-limit-mb 64 --memory-max 96M` y las de 100 Hz `--buffer 120 --mem-limit-mb 80 --memory-max 128M`. Dale sitio al recolector de basura del agente o el coste se dispara por motivos que no tienen nada que ver con la cadencia. > **No se perdió nada a ninguna cadencia, en ninguna configuración** > > Los segundos muestreados cubrieron el reloj de pared hasta 1,0000 en las cinco ejecuciones, la > cadencia entregada fue la configurada con tres cifras, y todos los destinos — fichero, Prometheus, > InfluxDB — informaron de 0 huecos y 0 descartes. ## Dos cosas de esa tabla que merecen releerse ### El coste por muestra baja cuando sube la cadencia Una muestra cuesta 2 856 µs a 10 Hz frente a 1 781 µs a 100 Hz. No es una paradoja, son los suelos funcionando: las fuentes caras se amortizan entre más muestras. `/proc/slabinfo`, una de las fuentes caras (13,8 kB), se lee cada 2 ticks a 10 Hz y cada 17 a 100 Hz, así que una muestra media cuesta menos mientras la _frecuencia_ de lecturas de slabinfo se queda cerca de su suelo de 6 Hz en ambos casos (5 Hz a 10 Hz, unos 5,9 Hz a 100 Hz). Con `FLOOR_HZ` no hay nada que amortizar y el coste por muestra es plano — son 4 494 µs a 50 Hz y 4 395 µs a 100 Hz — así que la CPU escala linealmente con la cadencia: 22,47 %, y luego 43,95 %. ### Un tick retrasado no es una muestra perdida La muestra se produce igual y se entrega igual, llevando su `dt_ns` real, así que cualquier tasa calculada a partir de ella sigue siendo correcta. Es un emborronamiento, no un agujero, y se ve en `mikroscope_tick_interval_seconds`. A 100 Hz con todo en cada tick, el 99,5 % de los ticks cayeron aun así dentro de 11 ms de un período de 10 ms y el peor fue de 15 ms. ## Lo que limita es la lectura, no la CPU Con los suelos por defecto, todas las fuentes de un tick se leen en menos de 2 ms en el 97,5 % de las muestras a 100 Hz, holgadamente dentro de un período de 10 ms. `FLOOR_HZ` empuja un 1,4 % de las lecturas más allá de 5 ms, y esos son los ticks que se retrasan. El margen de CPU es mayor que el margen de tiempo, que es por lo que el techo es una afirmación sobre E/S y no sobre el A72. ## Lo que muestrear más rápido compra de verdad Resolución de porcentaje de CPU no. El jiffie son 10 ms, así que a 100 Hz una muestra contiene 0 o 1 ticks ocupados y la proporción de ocupación por muestra tiene dos valores posibles. Por encima de unos 20 Hz los contadores de ticks dejan de ser un porcentaje y pasan a ser un indicador de ocupación; a partir de ahí la resolución la pone el PMU. Lo que sí compra una cadencia mayor es todo lo que no está cuantizado por el jiffie — cuentas de paquetes de softnet, deltas de interrupciones, contadores del PMU, las marcas de tiempo del propio log del kernel — y una cota más estrecha sobre cuánto puede esconderse una ráfaga entre dos muestras. > **No medido, luego no afirmado** > > Cualquier cadencia en una placa que no sea esta, y qué pasa bajo una carga de tráfico mayor que la > tarde corriente de este router — unos 30 Mbit/s. Antes de citar un número > para tu equipo, vuelve a medirlo allí: dos lecturas de `/metrics` separadas 60 s en régimen > estacionario. ## Véase también - [El coste del observador](/mikroscope/es/cost/): el presupuesto contra el que se miden estas ejecuciones, y las tres órdenes para medirlo tú. - [Lo que los números no dicen](/mikroscope/es/cost/limits/): lo que `/metrics` puede y no puede recuperar a cualquiera de estas cadencias. --- # Lo que los números no dicen Las preguntas que las medidas de este sitio no pueden responder, y a qué métrica acudir en vez de adivinar. Source: https://jmrplens.github.io/mikroscope/es/cost/limits/ Todas las medidas de este sitio se tomaron en un equipo, un día y bajo una carga. Esta página es la lista de lo que por tanto no te dicen — guardada aquí, en la documentación, en vez de dejarla para que un lector la descubra equivocándose. ## Lo que `/metrics` puede recuperar y lo que no El histograma `mikroscope_cpu_busy_ticks` (ticks ocupados enteros por muestra y núcleo, lo que mantiene la fluctuación de temporización fuera de la elección de bucket) recupera el **tiempo por encima de un umbral** con resolución de una muestra. Lo que no puede recuperar es la **continuidad**: una meseta de 2 s al 30 % y veinte picos sueltos de 100 ms al 30 % se ven idénticos en él. Para eso está `mikroscope_cpu_busy_run_seconds{threshold="0.5"|"0.9"}` — la duración de cada racha de muestras consecutivas en el umbral o por encima, observada cuando la racha termina, con la racha aún en curso en `…_run_open_seconds`. Los medidores de ventana móvil (`window="1s"|"10s"|"60s"`, `stat="max"|"min"|"p95"`) muestran el pico sea cual sea tu intervalo de scrape, porque el agente los calcula sobre su propio reloj y no sobre el tuyo. Para ver la _forma_ de un transitorio en vez de su envolvente, grábalo con `record`, o deja que un disparador lo capture. ## El emborronamiento del muestreador se publica, no se esconde Cuánto tardó el muestreador en despertar tras su temporizador y cuánto tardó la lectura son `mikroscope_tick_wake_latency_seconds` y `mikroscope_tick_read_seconds`, con el intervalo realmente conseguido en `mikroscope_tick_interval_seconds`. Si sospechas del muestreador y no del router, esos tres son el primer sitio donde mirar. ## Los huecos se informan, nunca se tapan El agente guarda 300 s por defecto. Un corte del colector más corto que eso se rellena al reconectar mediante `since=`; uno más largo se informa como un hueco de longitud conocida — un marcador en una grabación, un contador en el colector. Un gráfico con un agujero es un gráfico diciendo la verdad. > **No medido, luego no afirmado** > > Cualquier placa que no sea el RB5009 [del techo de muestreo](/mikroscope/es/cost/rate-ceiling/). > Tráfico por encima de unos 30 Mbit/s. Una compilación de RouterOS de 32 bits, > que llegará con un hEX S pero todavía no ha llegado. Funcionamiento sostenido más allá de las > ventanas indicadas. Ninguna de estas es una afirmación de este proyecto, y ninguna debería > deducirse de las que sí lo son. ## Véase también - [El techo de muestreo](/mikroscope/es/cost/rate-ceiling/): las cinco ejecuciones y las condiciones en que se tomaron. - [El coste del observador](/mikroscope/es/cost/): el presupuesto, y cómo medir el coste en un equipo que no sea este. --- # Instalar el agente Qué hace `mikroscope install` en un equipo RouterOS y en qué orden, y cómo `upgrade` y `uninstall` lo cambian o lo quitan sin tocar nada que no hayan creado. Source: https://jmrplens.github.io/mikroscope/es/install/ Esta página responde a dos preguntas: qué le hace `mikroscope install` a tu router y cómo recuperas el router después. `install` pone una imagen del agente de 6,1 MiB en un contenedor del router; `uninstall` la vuelve a quitar. En RB5009UG+S+, RouterOS 7.24.2, 2026-09-12, un ciclo completo con guion doctor → install → status → upgrade → uninstall (`make roundtrip`, que pasa `--ephemeral` a doctor, install y upgrade) dejó el `/export` del router idéntico byte a byte, salvo sus líneas de cabecera `#`. Cada objeto que crea install lleva el comentario `mikroscope: (managed by mikroscope)`, salvo la envlist y el fichero de la imagen, que no lo admiten; la envlist lleva la etiqueta en su entrada `MIKROSCOPE_TAG`. No se escribe nada antes de listarlo; la eliminación selecciona por esa etiqueta más la identidad del objeto, nunca por patrón, y se verifica con recuentos de propiedad. ## Antes de la primera instalación En el router tienen que cumplirse tres cosas, y la herramienta no puede conseguir ninguna por ti: una arquitectura capaz de ejecutar contenedores con RouterOS 7.24 o posterior, el paquete `container` y `device-mode container=yes` — esto último exige pulsar un botón físico o un ciclo de alimentación. [Lo que necesita el router](/mikroscope/es/install/prerequisites/) cubre las tres. `mikroscope doctor` las comprueba en solo lectura, en una conexión ssh, e imprime la orden exacta o el paso físico para lo que falte. Hay una decisión que va antes de la primera instalación y no después: de dónde sale la imagen del agente. Un checkout la compila, `--agent-tar` toma la que publica la release, `--remote-image` hace que el router se la baje, y `plan --rsc` escribe un script que instala sin esta CLI. [Cuatro formas de instalar](/mikroscope/es/install/routes/) expone las cuatro. ## Qué hace install, en orden 1. **Consigue la imagen del agente.** Desde un checkout, la CLI la compila: `go build ./cmd/mikroscope-agent` para `linux/` (`--arch`, por defecto `arm64`) con `CGO_ENABLED=0`, empaquetado en un tar de imagen sin Docker, y la ruta de compilación es relativa, así que ejecútalo desde el checkout. `--agent-tar` toma en su lugar el tar que publica la release, y lo comprueba antes de subirlo. `--remote-image` se salta este paso entero: el router se baja la imagen él mismo y no se sube nada. [Cuatro formas de instalar](/mikroscope/es/install/routes/) es la elección, con lo que necesita cada ruta. 2. **Imprime el plan.** Una línea de opciones (un token aparece como `token=(set)`, nunca su valor), la etiqueta y luego cada orden de RouterOS numerada, con la subida por `scp` y su tamaño antes del paso del contenedor. Termina con `nothing above has been written yet`. `mikroscope plan` e `install --dry-run` se detienen aquí, antes de cualquier conexión ssh. 3. **Ejecuta `doctor`.** Cualquier requisito que falte detiene la instalación con `N prerequisite(s) missing; nothing was written`. `--no-doctor` se salta este paso. 4. **Pregunta** `write the objects above to the router? [y/N]`. `--yes` se salta la pregunta. 5. **Pregunta al router por todos los pasos a la vez.** Una conexión pregunta, para cada paso, si el objeto de mikroscope está y si el efecto existe con cualquier otro propietario. Un paso que ya es nuestro imprime `ok … (already present)` y se salta; un efecto que existe sin la etiqueta detiene la instalación, nombrándolo; uno ausente se crea, con una conexión por escritura. 6. **Sube la imagen y crea el contenedor.** El tar sube con `scp`; después una sola orden escribe la envlist, añade el contenedor, espera hasta 15 s a que el contenedor aparezca y luego 3 s más (RouterOS extrae la imagen al añadirlo), borra el tar y arranca el contenedor. Con `--remote-image` no hay subida ni tar al que esperar o que borrar: el contenedor se añade con `remote-image=` y se arranca. En cualquier caso el contenedor se añade con `privileged=`, que RouterOS conoce desde 7.24: en una 7.x anterior este es el paso que falla, y `--privileged=false` es la forma de pasar. 7. **Sondea el agente** desde tu máquina y dice qué transporte funciona: [Llegar al agente](/mikroscope/es/install/reaching-the-agent/). Un segundo `install` en un router donde todos los pasos ya son nuestros no crea nada y pasa directamente al sondeo. **Lo que `install` escribe en tu router** - una veth - una dirección - una pertenencia a lista de interfaces - una entrada de address-list - una envlist - el tar de la imagen, salvo que `--remote-image` haga que el router se la baje - el contenedor Cada objeto lleva el comentario `mikroscope: (managed by mikroscope)` `mikroscope plan` imprime cada orden antes de escribir nada. `uninstall` elimina por etiqueta exacta más identidad, nunca por patrón, y falla nombrando el paso si queda algo. Dónde vive cada uno de esos objetos, qué lleva la envlist y qué ajustes del contenedor se escriben está en [Dónde va cada cosa](/mikroscope/es/install/layout/). Por qué dos de ellos son pertenencias a listas del cortafuegos está en [Las dos trampas del cortafuegos](/mikroscope/es/install/firewall/). ## Cómo se decide la propiedad La etiqueta es lo único con lo que casa una eliminación, junto con la identidad propia del objeto: la veth por nombre, la dirección por interfaz, una pertenencia a lista por lista e interfaz, una entrada de address-list por lista y dirección. Ni `/container/envs` ni `/file` admiten comentario, así que la envlist se firma con una entrada `MIKROSCOPE_TAG` cuyo valor es la etiqueta exacta, y la imagen subida cuenta como de mikroscope solo mientras exista esa marca. El agente ignora la entrada. Cada `find` entrecomilla sus atributos de dirección y puerto. Sin comillas, RouterOS los interpreta como valores tipados y la comparación con el valor guardado sale vacía — un `dst-port=9123` sin comillas no casa con nada (verificado en RB5009UG+S+, RouterOS 7.24.2, 2026-09-11). Por ssh, RouterOS informa de un error como texto con código de salida 0 y abandona el resto de una línea unida con `;` en el primero. Por eso una escritura que imprime algo se trata como un fallo. Si el paso del contenedor falla tras la subida, el tar subido se retira (`undo removed the uploaded …`), porque sin la marca contaría como ajeno para siempre. Qué más rechaza el instalador está en [Lo que el instalador rechaza](/mikroscope/es/security/installer/). ## Actualizar **Lo que sustituye `upgrade`** - una imagen nueva y el contenedor - la envlist, reescrita con las opciones que recibe `upgrade` - los objetos de red se quedan Cada objeto lleva el comentario `mikroscope: (managed by mikroscope)` `mikroscope plan` imprime cada orden antes de escribir nada. `mikroscope upgrade` consigue la imagen igual que `install` —compilándola desde el checkout, con `--agent-tar`, o con `--remote-image` y ninguna imagen—, comprueba que todos los pasos están (si no, `nothing to upgrade: run install first`), pide confirmación, quita el paso del contenedor, espera a la eliminación asíncrona de RouterOS, vuelve a crear el paso con la imagen nueva y sondea el agente. A diferencia de `install`, no imprime ningún plan ni ejecuta `doctor`: su pregunta es la misma `write the objects above to the router? [y/N]` sin nada listado encima. `mikroscope plan` con las mismas opciones muestra la orden del contenedor que va a escribir. La envlist pertenece al paso del contenedor, así que `upgrade` la vuelve a escribir a partir de las opciones que recibe el propio `upgrade`. `--port`, `--rate`, `--buffer`, `--memory-max`, `--mem-limit-mb`, `--capture-mb`, `--triggers`, `--floor-hz`, `--privileged`, `--ephemeral` y `--expose` no leen ninguna variable de entorno: pásalas otra vez o vuelven a sus valores por defecto. Esa es también la forma de cambiarlas sin tocar los objetos de red. Dos omisiones no son inocuas. Un upgrade sin `--ephemeral` vuelve a crear el contenedor con la imagen y la raíz en la flash interna y `start-on-boot=yes`. Un upgrade de una instalación con `--expose` hecho sin `--expose` y sin token (`--token` o `MIKROSCOPE_TOKEN`) vuelve a crear el agente sin token, mientras las dos reglas del cortafuegos hacia la LAN se quedan. ## Desinstalar **Lo que elimina `uninstall`** - una veth - una dirección - una pertenencia a lista de interfaces - una entrada de address-list - una envlist - el tar de la imagen, salvo que `--remote-image` haga que el router se la baje - el contenedor Cada objeto lleva el comentario `mikroscope: (managed by mikroscope)` `mikroscope plan` imprime cada orden antes de escribir nada. `uninstall` elimina por etiqueta exacta más identidad, nunca por patrón, y falla nombrando el paso si queda algo. `mikroscope uninstall` ejecuta las eliminaciones de la más reciente a la más antigua. Una eliminación que falla o imprime algo se informa como `skip` con lo que dijo el router, y el resto continúa. Un paso cuyo selector no encuentra nada imprime `gone` igualmente, y por eso decide el recuento, no la salida de la eliminación. Después pregunta el recuento de propiedad de cada paso en una conexión, imprime una línea por paso y o bien termina con `verified: nothing mikroscope created remains on the router` o bien falla con `uninstall left objects behind`, nombrando los pasos que siguen presentes. La eliminación del contenedor espera, porque RouterOS no lo hace: `/container/remove` vuelve antes de que el contenedor haya desaparecido, y un `/file/remove` de la imagen lanzado entretanto no hizo nada, en silencio (RB5009UG+S+, RouterOS 7.24.2, 2026-09-11). Así que para el contenedor, lo quita, espera hasta 20 s a que desaparezca, reintenta la eliminación del tar durante hasta 15 s y quita la marca solo cuando el fichero ya no está. Si sigue, la marca se queda y el recuento lo dice. > **Desinstala con las opciones con las que instalaste** > > `uninstall`, `status` y `upgrade` construyen sus selectores a partir de sus propias opciones, no > de algo guardado en el router. Pasa los mismos `--name`, `--veth`, `--subnet`, `--iface-list`, > `--addr-list`, `--disk` o `--ephemeral` y `--port` que recibió `install`. Para una instalación con > `--remote-image`, pásalo también: el recuento de propiedad del contenedor se toma por aquello de > lo que se creó, y una instalación con remote-image no tiene ningún tar que contar. Para una > instalación con `--expose`, pasa también `--expose --lan-address … --token …`: sin `--expose` el > plan no tiene pasos de cortafuegos, así que `uninstall` ni quita las dos reglas ni las cuenta. ## Estado `mikroscope status` imprime el recuento de propiedad de cada paso, desde una conexión. Cuando no hay nada instalado termina con la línea `verified` y no sondea nada. Si no, sondea el `/healthz` del agente con un tiempo de espera de 3 s e imprime su versión, cadencia, secuencia y secuencia más antigua, tiempo en marcha, ticks retrasados y tiempo de ida y vuelta, y después una línea sobre la placa: si esta compilación sabe convertir en ella los nombres de puerto del kernel (`eth0`, `eth1`, …) en los de RouterOS. Donde no sabe, la línea pide la medida que añadiría la placa; [Puertos de RouterOS y nombres del kernel](/mikroscope/es/reference/port-names/) muestra cómo tomarla. Si el agente no responde, imprime `agent: not reachable from this host` con el error, y ninguna línea de placa; `status` sale igualmente con código 0. ## El día a día ```sh mikroscope doctor # solo lectura mikroscope plan # todas las órdenes, nada escrito mikroscope install [--ephemeral] # doctor, confirmación, escrituras, sondeo mikroscope status # recuentos de propiedad + salud del agente mikroscope upgrade # imagen nueva, solo el contenedor mikroscope uninstall # quita y verifica mikroscope image --arch arm64 # el tar, para cargarlo a mano mikroscope plan --rsc --out install.rsc # las mismas escrituras, como script de RouterOS ``` `--router` acepta `user@host` o un alias de la configuración de ssh y lo exige toda orden que se conecta. ## Opciones y entorno Para estas órdenes, las opciones que toman su valor por defecto de una variable son `--router`, `--ssh-port`, `--ssh-key`, `--name`, `--veth`, `--subnet`, `--iface-list`, `--addr-list`, `--disk`, `--arch`, `--token`, `--lan-address`, `--agent-tar` y `--remote-image` (`MIKROSCOPE_ROUTER`, `MIKROSCOPE_SSH_PORT` y así sucesivamente); `.env.example` las documenta. La CLI no lee `.env` por sí misma: ```sh set -a; . ./.env; set +a ``` Cada valor que llega a una orden de RouterOS se acota antes de la primera conexión: nombres, discos, la arquitectura, la sintaxis de memoria, los caracteres del token, los rangos de puerto y de cadencia, la referencia del registro, la subred, que debe ser una /30 IPv4 dada en su dirección de red, y `--triggers`, sobre el que decide el propio analizador del agente: una condición desconocida, un umbral incorrecto, una comilla o un punto y coma hacen fallar la orden con código 2 sin escribir nada. El agente vuelve a analizar `TRIGGERS` al arrancar, porque una envlist se puede editar a mano en el router; un valor que rechace allí hace que termine y registre `bad configuration`. La lista completa está en [Órdenes y opciones](/mikroscope/es/reference/cli/) y [Variables de entorno](/mikroscope/es/reference/environment/). ## Lo que ssh le cuesta al router Cada conexión ssh le cuesta al RB5009 un 20–27 % de CPU mientras dura. Por eso la CLI agrupa cada lectura en una sola conexión — `doctor` es una, `status` es una, las preguntas de estado de `install` son una — y cada escritura cuesta una más, además de la subida por `scp`. ssh nunca es un camino de datos: `record` y `forward` llegan al agente por HTTP o por la API de RouterOS. > **Sin probar** > > Que una instalación persistente sobreviva a un reinicio: el router de referencia está en > producción y no se reinicia para pruebas. El ciclo idéntico byte a byte en cualquier equipo que no > sea el RB5009, en cualquier RouterOS que no sea 7.24.2, o con install y upgrade ejecutados sin > `--ephemeral` (la ejecución con guion lo pasó a doctor, install y upgrade, no a status ni a > uninstall). El ciclo no se ha ejecutado contra los ajustes actuales del contenedor: > `privileged=yes`, `memory-max=64M` y las entradas de envlist `MEM_LIMIT_MB`, `CAPTURE_MB`, > `TRIGGERS` y `FLOOR_HZ`; se ejecutó con `memory-max=32M`. ## Véase también - [Lo que necesita el router](/mikroscope/es/install/prerequisites/): la arquitectura, el paquete y el paso de device-mode que exige tus manos en el router. - [Cuatro formas de instalar](/mikroscope/es/install/routes/): un checkout, el tar publicado, un pull del registro, o un script de RouterOS. - [Dónde va cada cosa](/mikroscope/es/install/layout/): discos, la envlist y los ajustes del contenedor. - [Llegar al agente](/mikroscope/es/install/reaching-the-agent/): el sondeo, y directo, relay y `--expose`. - [Lo que el instalador rechaza](/mikroscope/es/security/installer/): objetos sobre los que no construye ni que quita. --- # Tener la CLI en tu máquina Cómo tener la orden `mikroscope` en Linux, macOS o Windows — qué archivo descargar para tu propio ordenador y no para el router, cómo verificarlo, dónde ponerlo para que el intérprete lo encuentre y cómo comprobar que funciona. Source: https://jmrplens.github.io/mikroscope/es/install/cli/ Se publican dos programas a la vez y se ejecutan en máquinas distintas: - **`mikroscope`**, la CLI y el colector, se ejecuta en **tu ordenador** —el portátil o el servidor donde escribes las órdenes—. Esta página va de ese. - **`mikroscope-agent`** se ejecuta **en el router**, dentro de un contenedor. `mikroscope install` lo pone allí por ti; tú nunca lo ejecutas, y la única vez que lo descargas a mano es en la ruta `--agent-tar`. Así que el archivo que quieres aquí lo decide **tu** sistema operativo y tu CPU, no los del router. La arquitectura del router decide otra cosa, y esa tabla está en [Lo que necesita el router](/mikroscope/es/install/prerequisites/). ## Qué archivo | Tu máquina | Descarga | | ------------------------------------- | ---------------------------------------------- | | Linux, PC o servidor normal | `mikroscope__linux_x86_64.tar.gz` | | Linux sobre ARM (Raspberry Pi 4/5, …) | `mikroscope__linux_arm64.tar.gz` | | macOS, Apple silicon (M1 y posteriores) | `mikroscope__darwin_arm64.tar.gz` | | macOS, Intel | `mikroscope__darwin_x86_64.tar.gz` | | Windows | `mikroscope__windows_x86_64.zip` | Están en la [última publicación](https://github.com/jmrplens/mikroscope/releases/latest). Todo lo que empiece por `mikroscope-agent` es el otro programa. ## Instalarla - **Linux** 1. Descarga el archivo y las sumas de verificación: ```sh VERSION=1.0.4 curl -fsSLO https://github.com/jmrplens/mikroscope/releases/download/v$VERSION/mikroscope_${VERSION}_linux_x86_64.tar.gz curl -fsSLO https://github.com/jmrplens/mikroscope/releases/download/v$VERSION/checksums.txt ``` 2. Compruébalo contra la lista antes de descomprimirlo: ```sh sha256sum --ignore-missing -c checksums.txt ``` 3. Descomprime y ponlo donde el intérprete mira: ```sh tar xzf mikroscope_${VERSION}_linux_x86_64.tar.gz mikroscope sudo install -m 0755 mikroscope /usr/local/bin/mikroscope ``` Sin `sudo`, `mkdir -p ~/.local/bin && install -m 0755 mikroscope ~/.local/bin/` también vale, siempre que `~/.local/bin` esté en tu `PATH`. - **macOS** 1. Descarga el archivo de tu CPU —`darwin_arm64` para Apple silicon, `darwin_x86_64` para Intel— y las sumas de verificación: ```sh VERSION=1.0.4 curl -fsSLO https://github.com/jmrplens/mikroscope/releases/download/v$VERSION/mikroscope_${VERSION}_darwin_arm64.tar.gz curl -fsSLO https://github.com/jmrplens/mikroscope/releases/download/v$VERSION/checksums.txt ``` 2. Compruébalo: ```sh shasum -a 256 --ignore-missing -c checksums.txt ``` 3. Descomprime, quita la marca de cuarentena que le puso la descarga e instálalo: ```sh tar xzf mikroscope_${VERSION}_darwin_arm64.tar.gz mikroscope xattr -d com.apple.quarantine mikroscope 2>/dev/null || true sudo install -m 0755 mikroscope /usr/local/bin/mikroscope ``` El binario no está firmado ni notarizado, así que sin esa línea de `xattr` macOS se niega a ejecutarlo y dice que «no se puede abrir porque no se puede verificar al desarrollador». - **Windows** 1. Descarga `mikroscope__windows_x86_64.zip` y `checksums.txt` de la página de la publicación. 2. Compruébalo en PowerShell, contra la línea de tu archivo en `checksums.txt`: ```powershell Get-FileHash .\mikroscope_1.0.4_windows_x86_64.zip -Algorithm SHA256 ``` 3. Descomprímelo en un sitio permanente y pon esa carpeta en tu `PATH`: ```powershell Expand-Archive .\mikroscope_1.0.4_windows_x86_64.zip -DestinationPath $HOME\mikroscope $env:PATH += ";$HOME\mikroscope" ``` Esa línea dura lo que la sesión. Para conservarla, añade la carpeta en **Configuración → Sistema → Acerca de → Configuración avanzada del sistema → Variables de entorno**, o: ```powershell [Environment]::SetEnvironmentVariable("PATH", "$env:PATH;$HOME\mikroscope", "User") ``` > **ssh en Windows** > > Tres de las cuatro rutas de instalación llegan al router por ssh, y la CLI usa el `ssh` y el `scp` > que haya en tu `PATH`. Windows 10 y 11 traen OpenSSH: `Get-Command ssh` debería encontrarlo, y > **Configuración → Sistema → Características opcionales** lo instala si no. La cuarta ruta, > [un script de RouterOS](/mikroscope/es/install/routes/#un-script-de-routeros), no necesita ssh. - **Con Go** Si tienes Go 1.27 o posterior y prefieres compilarla: ```sh go install github.com/jmrplens/mikroscope/cmd/mikroscope@latest ``` Eso deja `mikroscope` en `$(go env GOPATH)/bin`, que es `~/go/bin` salvo que lo hayas movido, y ese directorio tiene que estar en tu `PATH`. Un binario compilado así informa de la versión del módulo y no del sello de una publicación. Un checkout compila los dos programas de una vez, que es la ruta de quien contribuye: ```sh git clone https://github.com/jmrplens/mikroscope cd mikroscope make build # deja bin/mikroscope y bin/mikroscope-agent ``` ## Comprobarla ```sh mikroscope version ``` Imprime la versión, el commit y la fecha de compilación. Después, con un router al que apuntar: ```sh mikroscope doctor --router user@192.168.88.1 ``` `doctor` no escribe nada. Lee el equipo, imprime qué es y marca cada requisito como `ok` o `MISSING` con la orden que lo arregla —incluido el que nadie puede hacer en remoto—. Es lo primero que hay que ejecutar, antes de cualquier ruta de instalación. > **Las opciones tienen variables de entorno** > > `--router`, `--ssh-port`, `--ssh-key`, `--arch` y casi todas las demás leen su valor por defecto de > una variable `MIKROSCOPE_*`, así que un intérprete que las exporte convierte cada orden de aquí en > `mikroscope doctor`. [Variables de entorno](/mikroscope/es/reference/environment/) es la lista, y > `.env.example` en el repositorio es una plantilla. ## Véase también - [Lo que necesita el router](/mikroscope/es/install/prerequisites/): los tres requisitos en el equipo, y la tabla de arquitecturas **del router**. - [Cuatro formas de instalar](/mikroscope/es/install/routes/): cómo llega la imagen del agente al router una vez tienes la CLI. - [Órdenes y opciones](/mikroscope/es/reference/cli/): cada verbo y cada opción. --- # Lo que necesita el router La arquitectura, el paquete container y el paso de device-mode que exige una mano en el router, más lo que necesita tu propia máquina, y cómo comprueba `doctor` cada cosa. Source: https://jmrplens.github.io/mikroscope/es/install/prerequisites/ Esta página enumera lo que tiene que estar listo antes de que `mikroscope install` pueda escribir nada: tres cosas en el router que la herramienta no puede hacer por ti, los recursos y las listas del cortafuegos que comprueba, y lo que necesita la máquina desde la que lo ejecutas. `mikroscope doctor` lo comprueba todo en solo lectura, en una conexión ssh, e imprime la orden exacta o el paso físico para lo que falte. > **Un paso exige tus manos en el router** > > `device-mode container=yes` no se puede activar en remoto. Tras la orden, RouterOS espera cinco > minutos a que alguien pulse el botón de reset o el de modo, o haga un ciclo de alimentación del > equipo. Ninguna sesión ssh, llamada a la API ni opción de esta herramienta puede dar ese paso. > Cuenta con estar junto al router. ## Un equipo capaz de ejecutar contenedores El router debe ejecutar RouterOS 7.24 o posterior en una de tres arquitecturas: arm64, arm (RouterOS de 32 bits en la línea renovada hEX) o x86_64. Ni MIPS ni TILE. 7.24 es el suelo porque el paso del contenedor escribe `privileged=`, un atributo que RouterOS añadió en esa versión. `doctor` imprime la versión y no comprueba nada contra ella, así que en una 7.x anterior la instalación llega hasta el contenedor y falla ahí con el error de RouterOS sobre un parámetro `privileged` desconocido —y una escritura que imprime algo cuenta como fallo, de modo que la instalación se detiene y se lleva de vuelta el tar que había subido—. `--privileged=false` es la forma de pasar, al precio de todo lo que oculta el espacio de nombres de usuario del contenedor: [Lo que aporta privileged](/mikroscope/es/limits/privileged/) lo enumera. Dile a la CLI cuál con `--arch`: `arm64` (por defecto), `arm` o `amd64`. `doctor` la compara con el `architecture-name` del router y, si no coinciden, nombra la opción con la que volver a ejecutar. | Tu equipo | `architecture-name` | `--arch` | La compilación del agente | | -------------------------------------------------- | ------------------- | -------- | --------------------------- | | RB5009, CCR2004, hAP ax³ y otros ARM de 64 bits | `arm64` | `arm64` | `linux/arm64` | | hEX Refresh / hEX S (2025), cualquier placa EN7562CT | `arm` | `arm` | `linux/arm/v5` | | Otros ARM de 32 bits (hAP ac², hAP ax², …) | `arm` | `arm` | `linux/arm/v5` o `v7` | | CHR y RouterOS x86 | `x86_64` | `amd64` | `linux/amd64` | **El ARM de 32 bits no es una cosa, son dos.** La documentación de contenedores de MikroTik dice que los equipos con CPU EN7562CT —la serie hEX Refresh— «solo admiten imágenes de contenedor arm32v5», mientras que sus demás placas ARM de 32 bits ejecutan un espacio de usuario ARMv7. Un binario ARMv5 funciona en las dos; uno ARMv7 no arranca en las primeras, y falla con un `exec format error` en el registro del contenedor después de una instalación correcta. Por eso `--goarm` vale **5** por defecto, que es el nivel que arranca en todas partes, y la imagen declara la variante que le corresponde. `--goarm 7` compila la ARMv7 para una placa donde se quiera ese juego de instrucciones; lo que cuesta la diferencia no se ha medido, porque este proyecto no tiene hardware ARM. `--remote-image` hace desaparecer la pregunta: el índice publicado lleva `linux/amd64`, `linux/arm64`, `linux/arm/v7` y `linux/arm/v5`, y el router elige el suyo. ## El paquete container Descarga el paquete `container` para tu arquitectura y tu versión de RouterOS desde mikrotik.com, súbelo al router y reinicia; después `/system/package/enable container`. Esa es la solución que imprime `doctor`, y solo da el paquete por presente cuando está instalado y no deshabilitado. ## device-mode container=yes MikroTik protege los contenedores tras un paso físico: 1. Ejecuta, en la consola del router: ```text /system/device-mode/update container=yes ``` 2. La consola responde: ```text update: please activate by turning power off or pressing reset or mode button in 5m00s ``` 3. En esos cinco minutos, pulsa el botón de reset o el de modo, o haz un ciclo de alimentación del router. Si nadie lo hace, el cambio se cancela. Tras tres intentos fallidos el router dice `too many unsuccessful attempts … to reset attempt-count` y necesita un ciclo de alimentación antes de aceptar otro. ## Lo que comprueba doctor `doctor` imprime `device:` con la placa, la versión de RouterOS y la arquitectura, y después una línea por comprobación marcada `ok` o `MISSING`, con lo que encontró entre paréntesis y, si falta algo, una línea `fix:`. Termina con `doctor: every prerequisite is met`, o falla con `N prerequisite(s) missing; nothing was written`. `install` ejecuta primero las mismas comprobaciones salvo que pases `--no-doctor`. Las comprobaciones que ejecuta doctor: | Comprobación, tal como se imprime | Pasa cuando | La solución que nombra | | --- | --- | --- | | registry-url is https:// | con `--remote-image`, `/container/config registry-url` nombra el host de registro de la referencia. Sin `--remote-image` doctor no lo pregunta: el ajuste es global del equipo y mikroscope nunca lo escribe | `/container/config/set registry-url=https://` en el router, que afecta a todos sus contenedores, o instalar desde un tar con `--agent-tar` | | container package installed and enabled | existe un paquete `container` con `disabled=no` | descargar, subir, reiniciar; después `/system/package/enable container` | | device-mode container=yes | `/system/device-mode` informa `container=yes` | `/system/device-mode/update container=yes` y, en menos de 5 minutos, el botón reset o mode, o un ciclo de alimentación | | architecture matches --arch | el `architecture-name` del router es el que corresponde a `--arch` (`arm64`, `arm`, `x86_64`) | volver a ejecutar con el `--arch` que nombra | | free memory ≥ <--memory-max> | `free-memory` es al menos lo que pide `--memory-max`, 64 MiB por defecto | liberar memoria en el router, o pedir menos con `--memory-max` | | free flash ≥ (image tar + extracted root) | sin `--disk`: `free-hdd-space` es al menos el doble de la imagen más 4 MiB | liberar flash, o instalar con `--disk tmpfs` o `--ephemeral` donde exista un disco tmpfs | | disk exists | con `--disk` o `--ephemeral`: existe un disco con ese slot; su espacio libre no se comprueba | `/disk/add type=tmpfs tmpfs-max-size=64M slot=tmpfs` para un disco en RAM, o nombrar un disco existente con `--disk` | | interface list exists (raw rule trap) | existe la lista `--iface-list` (por defecto `LAN`) | `/interface/list/add name=…`, o pasar la lista que usa tu regla de descarte `in-interface-list=!…` | | address list has entries (raw rule trap) | la lista `--addr-list` (por defecto `LANs`) tiene al menos una entrada | pasar la lista que usa tu regla `drop local if not from default IP range`; una lista vacía vale solo si no hay tal regla | | veth name is free or ours | siempre se informa `ok`, con el recuento encontrado | ninguna: una colisión la detecta el propio `install` | La comprobación de flash usa el tamaño real del tar dentro de `install`. `doctor` por sí solo supone una imagen de 7 MiB, así que pide 18.0 MiB. El doble de la imagen porque el tar y la raíz extraída de él conviven en el disco hasta que `install` borra el tar; con `--remote-image` no se sube ningún tar, así que la comprobación pide solo los 4 MiB de margen. El umbral de memoria sigue a `--memory-max`: pide al menos lo que pide esa opción, que por defecto son 64 MiB, de modo que `--memory-max 128M` en un router con 70 MiB libres se detecta aquí y no en un contenedor que no arranca. La comprobación de `registry-url` solo se ejecuta con `--remote-image`, y solo cuando la referencia lleva un host de registro. `/container/config` es global al equipo y compartido con cualquier otro contenedor que tenga, así que mikroscope lee ese ajuste y nunca lo escribe. RouterOS lo trae puesto en `https://registry-1.docker.io`, así que la referencia de Docker Hub, `--remote-image jmrplens/mikroscope-agent:1.0.0`, no necesita fijar nada ahí en un router sin tocar, y la de GHCR es la que exige cambiar antes el ajuste. [Cuatro formas de instalar](/mikroscope/es/install/routes/#un-pull-del-registro) tiene la orden para ponerlo a mano. Las dos comprobaciones de listas existen por dos reglas raw del cortafuegos que descartan cada paquete que envía un contenedor; [Las dos trampas del cortafuegos](/mikroscope/es/install/firewall/) las explica. `doctor` marca como ausente una address-list vacía incluso en un router que no tiene esa regla; ahí, `--no-doctor` es la forma de pasar, y con ella te saltas también todas las demás comprobaciones. ## Lo que necesita tu máquina - **ssh al router con un usuario que pueda escribir**, tu propio acceso de administrador. La CLI ejecuta el `ssh` del sistema con `BatchMode=yes` y `ConnectTimeout=15`, así que no puede responder a una petición de contraseña: usa una clave o un agente ssh. `--router` acepta `user@host` o un alias de la configuración de ssh; `--ssh-port` y `--ssh-key` (`MIKROSCOPE_SSH_PORT`, `MIKROSCOPE_SSH_KEY`) recurren a tu configuración de ssh cuando no tienen valor. La imagen sube con `scp`, en las dos rutas que suben una. La ruta del script de RouterOS no necesita ssh en absoluto. - **Una imagen del agente, por una de cuatro rutas.** La CLI puede compilar una (`go build ./cmd/mikroscope-agent` desde un checkout, lo que exige Go 1.27), tomar el tar que publica la release (`--agent-tar`, sin toolchain y sin checkout), o dejar que el router se baje la imagen él mismo (`--remote-image`, sin subir nada). La cuarta ruta no necesita ni CLI en tu máquina: `plan --rsc` escribe un script de RouterOS que pegas en el router. [Cuatro formas de instalar](/mikroscope/es/install/routes/) tiene las órdenes, lo que necesita cada ruta y cómo verificar una descarga. - **Una forma de llegar al agente** una vez en marcha: una ruta a la /30 del contenedor a través del router, el relay por la API de RouterOS, o `--expose`. Consulta [Llegar al agente](/mikroscope/es/install/reaching-the-agent/). - **Un usuario de la API de RouterOS**, solo para el transporte relay, `--log-markers` y la capa de la API del colector. Se queda en tu máquina; su política está en [El usuario de la API](/mikroscope/es/security/api-user/). > **Sin probar** > > Una instalación en arm o x86_64: todas las instalaciones hasta ahora se hicieron en un único > RB5009 arm64, y el hEX S que probará RouterOS de 32 bits no ha llegado. Cualquier RouterOS que no > sea 7.24.2. El paso del contenedor escribe `privileged=`, que RouterOS añadió en 7.24, y entradas > de envlist con `key=`, donde `name=` falla en 7.24.2; no se probó cómo acepta una 7.x anterior > cualquiera de las dos. ## Véase también - [Instalar el agente](/mikroscope/es/install/): lo que hace `install` una vez esto está listo. - [Cuatro formas de instalar](/mikroscope/es/install/routes/): las cuatro rutas por las que la imagen del agente llega al router, y cómo verificar una descarga. - [Las dos trampas del cortafuegos](/mikroscope/es/install/firewall/): por qué existen las dos comprobaciones de listas. - [Dónde va cada cosa](/mikroscope/es/install/layout/): el disco al que se refiere la comprobación de flash, y `--ephemeral`. --- # Cuatro formas de instalar Las cuatro rutas por las que la imagen del agente llega al router — un checkout con Go, el tar publicado, un registro del que tira el router, o un script de RouterOS que pegas — qué necesita cada una, qué escribe cada una, y cómo verificar una descarga de la release. Source: https://jmrplens.github.io/mikroscope/es/install/routes/ El agente es una imagen de contenedor, y las cuatro rutas de abajo se diferencian en una sola cosa: cómo llega esa imagen al router. Todo lo demás que escribe `install` —la veth, la dirección, las dos pertenencias a listas, la envlist, el contenedor y su etiqueta— es igual tomes la ruta que tomes, y también lo son los requisitos previos: [Lo que necesita el router](/mikroscope/es/install/prerequisites/) va primero en las cuatro, porque `device-mode container=yes` exige una mano en el equipo y ninguna ruta lo esquiva. ## Cuál elegir | Ruta | Necesita | Prefiérela cuando | | ------------------------------------------------------ | ------------------------------------------------------------------- | ---------------------------------------------------------------------- | | **[Un pull del registro](#un-pull-del-registro)** — empieza por aquí | que el router llegue a Docker Hub; nada más | casi siempre: una orden, nada que elegir, nada que subir | | [Un script de RouterOS](#un-script-de-routeros) | una terminal en el router; `--remote-image` | llegas al router por WinBox o WebFig y no por ssh | | [Un checkout, con Go](#un-checkout-con-go) | Go 1.27 y el repositorio; ssh al router | trabajas en mikroscope y quieres el agente de tu propio árbol | | [El tar publicado](#el-tar-publicado) | los artefactos de la release, el correcto para la placa; ssh al router | el router no llega a ningún registro | **Toma la primera salvo que algo te lo impida.** Un pull del registro es una sola orden sin nada que elegir: el índice de imágenes publicado lleva todas las plataformas que puede ser un contenedor de MikroTik, así que el router reconoce la suya y nadie tiene que saber si la placa es ARM de 64 bits o una de las dos clases de 32 bits. Nada aterriza en la flash, y `uninstall` no tiene ningún fichero del que dar cuenta. El tar va el último a propósito. Es la ruta correcta para un router sin salida a un registro, y es la única en la que **tú** eliges la arquitectura —y la forma en que eso sale mal es una imagen que se instala, arranca y muere con `exec format error` en el registro del contenedor—. Si la tomas, lee [qué tar](#qué-tar) antes de descargar nada. ## Un pull del registro La ruta recomendada, y la más corta. Una orden, nada que descargar, nada que subir: ```sh mikroscope install --router usuario@192.168.88.1 \ --remote-image jmrplens/mikroscope-agent:1.0.4 ``` No se sube nada, ningún tar aterriza en el equipo, y `uninstall` no tiene ningún fichero del que dar cuenta: el paso del contenedor pasa a ser `/container/add remote-image="jmrplens/mikroscope-agent:1.0.4" …` y el plan imprime `the router pulls … (nothing is uploaded)` donde iría la línea de subida. La release publica la imagen dos veces, como `jmrplens/mikroscope-agent:1.0.4` en Docker Hub y como `ghcr.io/jmrplens/mikroscope-agent:1.0.4` en GHCR. Las dos llevan `linux/amd64`, `linux/arm64`, `linux/arm/v7` y `linux/arm/v5`, y RouterOS toma la que necesita su arquitectura —que es la razón de que esta ruta no pregunte nada sobre la placa: las dos clases de ARM de 32 bits que vende MikroTik están en el índice. La referencia de arriba es la de Docker Hub, y no lleva host de registro: el router se la descarga del que ya nombre `/container/config registry-url`, y RouterOS trae ese ajuste puesto en `https://registry-1.docker.io`. En un router donde nadie lo haya tocado, la orden de arriba no necesita fijar nada antes —en la RB5009 sobre la que se mide este proyecto, ese ajuste vale `https://registry-1.docker.io`—. La referencia de GHCR es la alternativa, y necesita antes `/container/config/set registry-url=https://ghcr.io` en el equipo, que es un cambio para todos sus contenedores. Necesita dos cosas que las otras rutas no: que el router llegue al registro, y que tenga sitio en RAM para las capas mientras las extrae. > **El host del registro es un ajuste global del router** > > RouterOS toma el host del registro de `/container/config registry-url`, que es global al equipo y > compartido con cualquier otro contenedor que tenga, y solo el resto de la referencia va a > `remote-image=`. mikroscope nunca escribe ese ajuste: apuntar el registro de tu router a otro > sitio para instalar una sonda sería cambiar los contenedores de otra persona. `doctor` lo lee en > su lugar y, cuando la referencia nombra un host que no cuadra con el ajuste, nombra la única > orden que hay que ejecutar: `/container/config/set registry-url=https://ghcr.io` para la > referencia de GHCR. Instala con `--agent-tar` si prefieres no tocarlo. Una referencia sin host —`jmrplens/mikroscope-agent:1.0.4`— deja el registro a lo que el router ya tenga configurado, y entonces `doctor` no comprueba nada al respecto. `--remote-image` toma su valor por defecto de `MIKROSCOPE_REMOTE_IMAGE`, y `upgrade` también lo acepta; `image` lo rechaza, porque no hay ningún tar que escribir. ## Un script de RouterOS Para un router al que llegas por WinBox o WebFig, o donde no quieres ssh desde otra máquina en absoluto: ```sh mikroscope plan --rsc \ --remote-image jmrplens/mikroscope-agent:1.0.4 \ --out install.rsc ``` El fichero lleva las mismas órdenes que ejecuta `install`, en el mismo orden y con cada objeto etiquetado igual, así que `status` y `uninstall` desde la CLI los reconocen después. Léelo, y luego pégalo en la terminal del router, o súbelo y hazle `/import`. Sin `--out` sale por la salida estándar. Lleva su propia cabecera: la etiqueta que escribe, qué comprobar antes de ejecutarlo y, al final, `/container/print where name~"mikroscope"` y la URL `/healthz` en la que responde el agente. Dos advertencias, ambas escritas en el propio script: - **No puede subir nada.** Un script que se ejecuta en el router no tiene forma de poner la imagen ahí, así que acompáñalo de `--remote-image`. Sin él, la cabecera dice en su lugar qué nombre de fichero hay que dejar antes en el equipo —el nombre que espera el paso del contenedor— y cómo regenerar el script para un pull del registro. - **Con `--token`, el fichero es una credencial.** La línea de la envlist lleva el token en claro, porque el router lo necesita en claro. La CLI escribe el fichero con permisos `0600`; lo que hagas con él después es la exposición. Nada dentro del script comprueba nada. No hay `doctor`, ni pregunta sobre con qué chocarían los objetos que crea, ni confirmación: escribe. Ejecuta `mikroscope doctor` desde una máquina que pueda, o lee [Lo que necesita el router](/mikroscope/es/install/prerequisites/) y comprueba a mano los tres requisitos, antes de pegarlo. Las cuatro rutas se ejecutaron de extremo a extremo contra el RB5009UG+S+ de referencia (RouterOS 7.24.2, arm64) el 2026-09-17, una tras otra, cada una instalando bajo su propio nombre, veth y `/30` para no tocar nada de lo que ya había en el equipo, y cada una retirada antes de la siguiente. En todas ellas el agente respondió a `/healthz` desde el equipo del colector: la compilación desde el checkout y el `mikroscope-agent-arm64.tar` publicado con 2 ms de ida y vuelta, la descarga que el propio router hizo de `jmrplens/mikroscope-agent:1.0.4` desde Docker Hub con 2 ms, y el script de `plan --rsc` — subido e importado con `/import`, sin CLI ninguna en la instalación misma — con 15 ms en sus primeras muestras. `uninstall` verificó después por recuento de propiedad en cada caso, y el `/export` del router tras las cuatro era idéntico byte a byte al tomado antes de ellas. > **Sin probar** > > `--remote-image` contra **GHCR**. `/container/config registry-url` es un único ajuste global de > RouterOS que mikroscope lee y nunca escribe, y el router de referencia apunta a Docker Hub; > apuntarlo a ghcr.io para probar ese camino cambiaría el registro para todos los demás > contenedores del equipo. La imagen se publica en los dos registros y la CI la arranca desde GHCR > en las tres arquitecturas, pero ningún router se la ha bajado de allí. Tampoco se ha ejecutado > ninguna ruta en hardware arm ni x86_64. ## Un checkout, con Go ```sh git clone https://github.com/jmrplens/mikroscope cd mikroscope make build bin/mikroscope install --router usuario@192.168.88.1 ``` `plan`, `install`, `upgrade` e `image` compilan el agente ellos mismos: `go build ./cmd/mikroscope-agent` para `linux/` (`--arch`, por defecto `arm64`) con `CGO_ENABLED=0`, empaquetado en un tar de imagen sin Docker. La ruta de compilación es relativa, así que ejecuta la CLI desde el checkout. Es la única ruta que instala un agente compilado de tu propio árbol, y por eso es la que se usa mientras se cambia el agente. `make build` deja la CLI en `bin/mikroscope`. Sin una toolchain de Go en `PATH`, el verbo se detiene antes de escribir nada y nombra las otras dos rutas y la versión de Go que quería. ## El tar publicado La ruta para un router que no llega a ningún registro. Dos artefactos: el archivo de la CLI para la máquina desde la que la ejecutas —que es [Tener la CLI](/mikroscope/es/install/cli/), y no tiene nada que ver con el router— y un tar de imagen del agente, para la arquitectura **del router**. Sin toolchain de Go y sin checkout. ### Qué tar | Tu MikroTik | `architecture-name` | Tar de imagen del agente | | ------------------------------------------------ | ------------------- | ------------------------------- | | RB5009, CCR2004, hAP ax³, otros ARM de 64 bits | `arm64` | `mikroscope-agent-arm64.tar` | | hEX Refresh / hEX S (2025), cualquier EN7562CT | `arm` | `mikroscope-agent-armv5.tar` | | Otros ARM de 32 bits (hAP ac², hAP ax², …) | `arm` | `mikroscope-agent-armv7.tar`, o el v5 | | CHR, RouterOS x86 | `x86_64` | `mikroscope-agent-amd64.tar` | `mikroscope doctor --router …` imprime la arquitectura leída del equipo, así que ejecútalo primero y deja que te lo diga. **Si no sabes qué placa ARM de 32 bits tienes, toma el tar v5**: la documentación de contenedores de MikroTik dice que las placas EN7562CT «solo admiten imágenes de contenedor arm32v5», y una imagen ARMv5 funciona en todos los ARM de 32 bits que vende MikroTik, mientras que una ARMv7 no arranca en aquellas. 1. Descarga `mikroscope_1.0.4__.tar.gz` (`.zip` en Windows) y el tar de la imagen del agente de la tabla de arriba, junto con `checksums.txt` y `checksums.txt.sigstore.json`. 2. Verifícalos, más abajo, antes de desempaquetar nada. 3. Desempaqueta la CLI e instala: ```sh tar xzf mikroscope_1.0.4_linux_x86_64.tar.gz ./mikroscope install --router usuario@192.168.88.1 \ --arch arm64 --agent-tar mikroscope-agent-arm64.tar ``` La CLI lee el tar antes de subirlo, que es lo que caza la descarga equivocada: imprime lo que leyó, como `using mikroscope-agent-arm64.tar: linux/arm64, agent KiB`, y con la imagen ARMv7 añade la nota de que una placa EN7562CT necesita la v5. Quiere un `manifest.json` de una sola imagen, la configuración que ese manifiesto nombra, una capa y `/mikroscope-agent` como entrypoint; cualquier otra cosa falla con `this is not a mikroscope agent image`. Después, la arquitectura de la imagen tiene que ser la que dice `--arch`, o el verbo falla nombrando el artefacto que hay que descargar en su lugar: una imagen `amd64` en una placa arm64 se instalaría, arrancaría y moriría con `exec format error` en el log del contenedor. De un tar que acepta imprime lo que ha leído, así: `using mikroscope-agent-arm64.tar: linux/arm64, agent KiB`. `--agent-tar` toma su valor por defecto de `MIKROSCOPE_AGENT_TAR`, y `upgrade` e `image` también lo aceptan. De ahí en adelante la instalación es la ruta de subida: el tar sube con `scp`, RouterOS lo extrae al añadir el contenedor, e `install` lo borra. > **Dos artefactos con nombres parecidos** > > `mikroscope-agent-arm64.tar` es la imagen de contenedor para cargar de lado, la que quiere > `--agent-tar`. `mikroscope-agent_1.0.4_linux_arm64.tar.gz` es un archivo con el binario del agente > a secas, para leerlo o ejecutarlo fuera de un contenedor; `--agent-tar` lo rechaza. ### Verificar la descarga `checksums.txt` cubre todos los archivos y todos los tar de imagen del agente, y es el único fichero por el que responde la firma. La firma es sin claves: la identidad es la ejecución del workflow que la produjo, registrada en un registro público de transparencia, así que no hay ninguna clave que buscar. ```sh cosign verify-blob \ --certificate-identity-regexp 'https://github.com/jmrplens/mikroscope/.github/workflows/release.yml@refs/tags/.*' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ --bundle checksums.txt.sigstore.json \ checksums.txt sha256sum --ignore-missing -c checksums.txt ``` `--ignore-missing` es lo que permite comprobar los dos ficheros que has descargado contra una lista que cubre toda la release. Cada archivo lleva además un SBOM en SPDX (`.spdx.json`) con su propio paquete de firma, que se verifica igual. ## Véase también - [Lo que necesita el router](/mikroscope/es/install/prerequisites/): los tres requisitos que comparten todas las rutas, y lo que comprueba `doctor`. - [Instalar el agente](/mikroscope/es/install/): lo que hace `install` una vez decidida la imagen. - [Dónde va cada cosa](/mikroscope/es/install/layout/): el disco que usan el tar y la raíz del contenedor, y lo que `--remote-image` deja de poner en él. - [Llegar al agente](/mikroscope/es/install/reaching-the-agent/): la sonda que se ejecuta después. --- # Las dos trampas del cortafuegos Las dos reglas raw del cortafuegos por defecto de MikroTik que descartan en silencio cada paquete que envía un contenedor, lo que añade `install` para que no lo hagan y qué pasar cuando tus listas tienen otros nombres. Source: https://jmrplens.github.io/mikroscope/es/install/firewall/ Esta página responde a por qué un agente que está en marcha puede seguir siendo inalcanzable en un router con el cortafuegos por defecto de MikroTik, y qué cambia `install` en ese cortafuegos para que no lo sea. Añade dos pertenencias a listas y nada más; sin `--expose` no escribe ninguna regla de cortafuegos. ## Dos reglas que descartan todo lo que envía un contenedor El cortafuegos por defecto de MikroTik lleva dos reglas raw que descartan en silencio cada paquete que envía un contenedor: - `drop the rest (in-interface-list=!LAN)` casa con un paquete que entra por una interfaz fuera de la lista de interfaces `LAN`, y una veth nueva está fuera de ella. - `drop local if not from default IP range (src-address-list=!LANs)` casa con una dirección de origen fuera de la address-list `LANs`, y la /30 del contenedor está fuera de ella. El descarte es silencioso. Desde tu máquina el agente no responde, y eso se ve igual que un contenedor que no está en marcha — por eso el sondeo tras `install` pregunta al router si el contenedor está en marcha antes de sugerir nada más. ## Lo que añade install `install` añade la veth a tu lista de interfaces `LAN` y la /30 del contenedor a tu address-list `LANs`. Las dos son añadidos a listas que ya existen, las dos llevan la etiqueta, y `uninstall` quita las dos por esa etiqueta más la lista y el miembro. Con los valores por defecto, las dos escrituras son: | Paso | Orden | | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | pertenencia a interface-list | `/interface/list/member/add list="LAN" interface="veth-mikroscope" comment="mikroscope:mikroscope (managed by mikroscope)"` | | pertenencia a address-list | `/ip/firewall/address-list/add list="LANs" address=172.30.10.0/30 comment="mikroscope:mikroscope (managed by mikroscope)"` | En RB5009UG+S+, RouterOS 7.24.2, 2026-09-11 estas dos pertenencias bastaron para que una máquina de la LAN llegara al agente directamente a través del router. Una pertenencia no se limita a mikroscope. Cualquier otra regla de tu router que case con la lista de interfaces `LAN` o con la address-list `LANs` casa también con el tráfico del contenedor, mientras esté instalado. Lee tus propias reglas teniéndolo en cuenta. ## Cuando tus listas tienen otros nombres Pasa los nombres que usan tus reglas: - `--iface-list` (`MIKROSCOPE_IFACE_LIST`): la lista que usa tu regla de descarte `in-interface-list=!…`. - `--addr-list` (`MIKROSCOPE_ADDR_LIST`): la lista que usa tu regla `drop local if not from default IP range`. `doctor` comprueba las dos antes de que `install` escriba nada. Una lista de interfaces que no existe se informa con la solución `/interface/list/add name=…`, o la opción. Una address-list sin entradas también se informa; el texto de su solución dice que una lista vacía vale solo si no existe esa regla, pero la comprobación sigue contando como ausente, y solo `--no-doctor` la supera, saltándose con ella todas las demás comprobaciones. `uninstall`, `status` y `upgrade` necesitan otra vez las mismas dos opciones, porque los selectores se construyen a partir de ellas; con otros nombres, `upgrade` no encuentra las pertenencias y se niega con `nothing to upgrade: run install first`. ## Cuando la pertenencia ya existe Si la veth ya está en la lista de interfaces, o la /30 ya está en la address-list, y esa entrada no lleva la etiqueta de mikroscope, `install` se detiene en ese paso y lo nombra: el efecto existe, pero mikroscope no lo creó y no lo quitará después. Elige otro `--veth` u otro `--subnet`, o quita tu entrada a mano si es tuya. `uninstall` nunca la toca. ## Las reglas que añade `--expose` Solo `--expose` escribe reglas de cortafuegos: un dst-nat en la dirección LAN del router y un accept en forward colocado antes del primer drop de forward, las dos etiquetadas y las dos quitadas por `uninstall --expose --lan-address --token …`: el selector del dst-nat casa con la dirección LAN, y `--expose` se rechaza sin token aunque ningún selector lo use. Qué abre eso y por qué el token pasa a ser obligatorio está en [Lo que abre --expose](/mikroscope/es/security/expose/); cómo usarlo está en [Llegar al agente](/mikroscope/es/install/reaching-the-agent/). > **Cierto en este equipo, no en el tuyo** > > Se midió un único cortafuegos: el del RB5009, el 2026-09-11, donde las dos pertenencias bastaron. > Un cortafuegos con otras reglas de descarte en `raw`, `input` o `forward` puede descartar el > tráfico del contenedor en otro punto, e `install` no añade nada para ese caso más allá de las dos > pertenencias. ## Véase también - [Llegar al agente](/mikroscope/es/install/reaching-the-agent/): qué hacer cuando las pertenencias no bastan. - [Lo que necesita el router](/mikroscope/es/install/prerequisites/): las comprobaciones de `doctor` para las dos listas. - [Lo que abre --expose](/mikroscope/es/security/expose/): las dos reglas, y quién llega al agente después. --- # Dónde va cada cosa Dónde vive en el router cada objeto que crea `install`, qué disco guarda la imagen y la raíz, qué lleva la envlist y los ajustes del contenedor que escribe install. Source: https://jmrplens.github.io/mikroscope/es/install/layout/ Esta página responde a dónde pone las cosas `install`: las direcciones, el disco que guarda la imagen y la raíz del contenedor, lo que va en la envlist y nada más, y los ajustes con los que se crea el contenedor. Cada valor de aquí es lo que imprime `mikroscope plan` para tus opciones, así que el plan es la forma de comprobarlo para tu router antes de escribir nada. ## Los objetos y sus valores por defecto | Objeto | Por defecto | Opción | | -------------------- | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | | veth + /30 | `veth-mikroscope`, `172.30.10.0/30` (router `.1`, agente `.2`) | `--veth`, `--subnet` | | dirección del router | `172.30.10.1/30` en la veth | derivada de `--subnet` | | tar de la imagen | `.tar`, subido con `scp`, **borrado justo después de la extracción**; ninguno con `--remote-image` | `--disk`, `--ephemeral`, `--remote-image` | | raíz del contenedor | `mikroscope/` en la flash interna | `--disk tmpfs` para un disco en RAM, `--ephemeral` | | envlist | `-env` | `--rate`, `--buffer`, `--port`, `--token`, … | | etiqueta | `mikroscope: (managed by mikroscope)` en cada objeto que admite comentario; como `MIKROSCOPE_TAG` en la envlist | `--name`, por defecto `mikroscope` | `--subnet` debe ser una /30 IPv4 dada en su dirección de red; el router toma la `.1` y el agente la `.2`. `--name` admite hasta 32 caracteres y `--veth` hasta 64, letras, dígitos, `_`, `.` y `-`, empezando por letra o dígito. La /30 y el nombre de veth por defecto se eligieron para no chocar con un muestreador instalado a mano en el equipo de referencia; si `172.30.10.0/30` está en uso en el tuyo, elige otra. Con `--disk`, la imagen y la raíz se mueven juntas: `/.tar` y `/mikroscope/`. El valor es un slot de disco de RouterOS: vacío para la flash interna, `tmpfs`, `disk1`, `usb1` y así sucesivamente. ## Persistente o efímera **Persistente es lo predeterminado**: la raíz en la flash interna, `start-on-boot=yes` y `restart-policy=on-failure` limitado a cinco reintentos separados diez segundos, para que una imagen rota no entre en bucle al arrancar. **`--ephemeral`** pone el tar y la raíz en el disco tmpfs del router, si lo tiene — el RB5009 lo tiene — con `start-on-boot=no`: cero escrituras en flash, y nada sobrevive a un reinicio. Verificado en RB5009UG+S+, RouterOS 7.24.2, 2026-09-11: `write-sect-since-reboot` marcaba 58 279 antes de la instalación y 58 279 después del borrado — el mismo valor, no un incremento pequeño. Con `--ephemeral` en un router sin disco tmpfs, `doctor` lo marca como ausente y nombra la orden que añade uno. `--disk tmpfs` sin `--ephemeral` también pone las dos cosas en el disco tmpfs, pero mantiene `start-on-boot=yes`; solo `--ephemeral` cambia el ajuste de arranque. > **Sin probar** > > Que sobreviva a un reinicio, en cualquiera de los dos modos: el router de referencia está en > producción y no se reinicia para pruebas. ## Por qué se borra el tar al instalar RouterOS extrae la imagen cuando se añade el contenedor, así que una vez que el contenedor existe el tar ya no sirve para nada. Un tar que se queda en el equipo es lo que `uninstall` tendría que encontrar después en un índice de `/file` que iba minutos por detrás tras quitar un contenedor. Así que `install` espera hasta 15 s a que el contenedor aparezca y luego 3 s más, borra el tar y solo entonces arranca el contenedor. No comprueba que la extracción haya terminado; en el RB5009 un tar de 1.8 MiB se extrajo en el mismo segundo del alta (RouterOS 7.24.2, 2026-09-11). El contenedor se crea con `ignore-remote-image-change=yes`. Con el `no` por defecto, RouterOS vigilaba la imagen y, en cuanto se quitaba el tar, paraba y quitaba el contenedor y lo volvía a extraer minutos después (RB5009UG+S+, RouterOS 7.24.2, 2026-09-11). Hasta que se borra el tar, este y la raíz extraída de él comparten disco, y por eso `doctor` pide el doble de la imagen más 4 MiB de flash libre. Con `--remote-image` no pasa nada de esto. RouterOS se baja las capas él mismo, ningún tar aterriza en el equipo, no hay nada a lo que esperar ni nada que borrar, `doctor` pide solo los 4 MiB, y `uninstall` no tiene ningún fichero del que dar cuenta: el recuento de propiedad del contenedor son el contenedor y la envlist. La raíz del contenedor sigue yendo donde digan `--disk` y `--ephemeral`. ## Lo que lleva la envlist La envlist guarda la configuración del agente y la marca de propiedad, y nada más: Las entradas que install escribe en la envlist del agente: | Clave | Se escribe | Viene de | Contiene | | --- | --- | --- | --- | | `MIKROSCOPE_TAG` | siempre | `--name` | la marca de propiedad `mikroscope: (managed by mikroscope)`, que se escribe la primera y se borra la última; el agente la ignora | | `RATE_HZ` | siempre | `--rate`, por defecto `10`, 1–100 | la cadencia del muestreador, en Hz | | `BUFFER_S` | siempre | `--buffer`, por defecto `300`, 10–3600 | la longitud del anillo, en segundos | | `PORT` | siempre | `--port`, por defecto `9123`, 1–65535 | el puerto HTTP del agente | | `ADDR` | siempre | `--subnet` | la dirección del agente, la `.2` de la /30; el agente solo escucha ahí | | `MEM_LIMIT_MB` | siempre | `--mem-limit-mb`, por defecto `40`, 8–1024 | el límite blando de memoria de Go del agente, en MiB | | `FLOOR_HZ` | solo cuando es mayor que 0 | `--floor-hz`, por defecto `0`, 0–1000 | una sola cadencia para todas las fuentes de nivel, en Hz | | `CAPTURE_MB` | siempre | `--capture-mb`, por defecto `4`, 0–256 | el presupuesto de capturas por disparo, en MiB; `0` las desactiva | | `TRIGGERS` | solo cuando se da | `--triggers` | las condiciones de disparo; sin ella, el agente usa su conjunto por defecto | | `TOKEN` | solo cuando se da | `--token` | el token bearer que exige el agente, de `--token` o `MIKROSCOPE_TOKEN`, con o sin `--expose` | No va en ella ninguna dirección de destino, ningún token de destino ni ninguna credencial de la API. Cualquier usuario de RouterOS con `read` puede listar la envlist de cualquier contenedor por la API (verificado en RB5009UG+S+, RouterOS 7.24.2, 2026-09-11), así que lo que se escribe aquí lo pueden leer todos ellos — el token incluido, si pones uno. El plan imprime la envlist con el token enmascarado como `(token)`. [Qué se ejecuta dónde](/mikroscope/es/security/) dice qué credencial vive dónde; el agente lee más variables de las que escribe `install`, y [Variables de entorno](/mikroscope/es/reference/environment/) las enumera. ## Los ajustes del contenedor El contenedor se añade con: - `file=` el tar subido, o `remote-image=` la referencia sin su host de registro cuando el router se baja la imagen; - `interface=` la veth, `root-dir=` la raíz, `envlist=` la envlist, y la etiqueta como comentario; - `logging=yes`, para que lo que imprime el agente llegue al log del router; - `start-on-boot=yes`, o `no` con `--ephemeral`; - `restart-policy=on-failure restart-max-count=5 restart-interval=10s`; - `memory-max=64M`, aplicado como límite del cgroup del contenedor (`--memory-max`); - `privileged=yes` (`--privileged=false` para renunciar a ello), que quita el espacio de nombres de usuario del contenedor para que el log del kernel, `/proc/slabinfo` y los contadores ECC de la MTD se puedan leer, y no amplía su espacio de nombres de red ni el de PID — [Lo que aporta privileged](/mikroscope/es/limits/privileged/) tiene el detalle; - `ignore-remote-image-change=yes`, por el motivo de arriba. El agente captura SIGTERM: RouterOS mata al instante un contenedor que no lo hace. Respeta el tiempo de parada por defecto de 10 s. ## Dimensiona la memoria al anillo `--memory-max` y `--mem-limit-mb` tienen que moverse con `--rate` y `--buffer`. El anillo guarda `rate × buffer` líneas de unos 2,4 kB cada una; el límite blando de Go quiere más o menos el doble y tiene que quedar holgadamente por debajo de `memory-max`. La comprobación de arranque del agente cuenta 2 560 bytes por línea más `--capture-mb`: por encima de `memory-max` se niega a arrancar, y por encima de la mitad de `--mem-limit-mb` avisa. Los valores por defecto, 40 MiB bajo `64M`, están dimensionados para 10 Hz y un anillo de 300 s. El presupuesto de la captura por disparo cuenta contra los dos límites en esa comprobación, igual que el anillo. En las ejecuciones medidas se usó `--mem-limit-mb 40 --memory-max 64M` a 10 Hz, `--buffer 120 --mem-limit-mb 64 --memory-max 96M` a 50 Hz y `--buffer 120 --mem-limit-mb 80 --memory-max 128M` a 100 Hz. Lo que cuesta un límite ajustado está en [El coste del observador](/mikroscope/es/cost/); las ejecuciones en sí están en [El techo de muestreo](/mikroscope/es/cost/rate-ceiling/). ## Véase también - [Instalar el agente](/mikroscope/es/install/): el orden en que se crean estos objetos, y cómo se quitan. - [El techo de muestreo](/mikroscope/es/cost/rate-ceiling/): lo que cuesta cada cadencia con las opciones de memoria de arriba. - [Lo que aporta privileged](/mikroscope/es/limits/privileged/): el único ajuste del contenedor que es una concesión de privilegio real. - [Qué se ejecuta dónde](/mikroscope/es/security/): qué más puede leer la envlist. --- # Llegar al agente Cómo llega la máquina que ejecuta `record` o `forward` a un agente que solo escucha en la dirección de su veth, qué te dice el sondeo tras `install` cuando no puede y qué cuesta cada vía, directa, relay y `--expose`. Source: https://jmrplens.github.io/mikroscope/es/install/reaching-the-agent/ El agente escucha solo en la dirección de la veth — `http://172.30.10.2:9123` con los valores por defecto — y no abre ninguna conexión saliente, nunca. Algo tiene que ir hasta él. Esta página responde a cómo llega tu máquina, qué comprueba `install` por ti y cuál de las tres vías usar cuando la primera no funciona. ## Lo que te dice el sondeo tras install Después de `install` y `upgrade`, la CLI sondea el agente desde tu máquina: una conexión TCP a la dirección y el puerto del agente, luego `GET /healthz`, reintentado un segundo después de cada intento fallido (cada intento agota su tiempo a los 2 s) durante hasta 30 s. Cuando responde, la CLI imprime la versión del agente, la cadencia, el número de secuencia, los ticks retrasados y el tiempo de ida y vuelta, como en `direct transport ok: agent 1.0.0, 10 Hz, seq 29, 0 slipped, 7ms round trip`. La versión es la de la propia CLI: `install` graba en el agente que compila su `internal/version.Version`, y tanto el Makefile como la release lo toman del fichero VERSION, así que un agente puesto ahí por 1.0.0 informa `1.0.0`. Un agente que se bajó el router informa la etiqueta con la que se publicó. En el RB5009 (RouterOS 7.24.2, 2026-09-12) el agente respondió 3 s después de la instalación, con un tiempo de ida y vuelta de 5–7 ms. Cuando no responde, la CLI pregunta al router — una conexión más — si el contenedor que lleva la etiqueta está en marcha, porque una veth solo está levantada mientras su contenedor está en marcha: - **No está en marcha**: lo dice y señala el log del router, `/log/print where topics~"container"`. El cortafuegos todavía no es el problema. - **Está en marcha**: esta máquina no llega a la dirección del agente. Sugiere ejecutar el colector en una máquina desde la que el router enrute hacia la veth, o `install --expose --lan-address --token …`. El relay no está en ese mensaje; es la tercera vía, más abajo. En los dos casos la orden falla con `agent installed but not reachable from this host`, y todo lo que creó se queda en el router. ## Directa, la predeterminada Tu máquina llega a la /30 a través del router, con HTTP plano a la dirección del agente. En RB5009UG+S+, RouterOS 7.24.2, 2026-09-11 bastaron las dos pertenencias a listas que añade `install`; [Las dos trampas del cortafuegos](/mikroscope/es/install/firewall/) las explica. La máquina necesita que sus paquetes para la /30 vayan al router: una máquina cuya puerta de enlace por defecto es el router ya los manda ahí. Con un token puesto, el transporte directo envía `Authorization: Bearer ` a partir de `--token` o `MIKROSCOPE_TOKEN`. `/healthz` nunca lo necesita. ## Relay, a través de la API de RouterOS `record` y `forward` con `--transport relay` ejecutan `/tool fetch` en el router por la API binaria, y el router, que sí llega a su propia veth, pide los datos al agente. - Necesita un usuario de RouterOS con la política `read,api,test`: en RouterOS 7.24.2 `/tool fetch` exige `test`, y sin ella la llamada responde `not enough permissions (9)` en vez de venir vacía. [El usuario de la API](/mikroscope/es/security/api-user/) tiene las órdenes. La CLI acepta `--api host:port` (`MIKROSCOPE_API_ADDR`), `--api-user` (`MIKROSCOPE_API_USER`) y la contraseña solo desde `MIKROSCOPE_API_PASSWORD`, nunca desde una opción. - Cada llamada devuelve como mucho 64 512 B; RouterOS trunca en silencio lo que sea más largo. Por eso el relay pide como mucho 18 muestras por petición y rechaza una respuesta que llegue al tope en vez de interpretarla truncada. `forward` avisa al arrancar cuando eso no puede seguir la cadencia del agente. - Cada llamada tarda o ~3 ms o ~1 s; más o menos la mitad de las llamadas tardaron ~1 s (RB5009, RouterOS 7.24.2, 2026-09-11). - El relay de esta compilación solo le pasa la URL a `/tool fetch`, sin cabeceras, así que no presenta un token. Todas las rutas salvo `/healthz` devuelven 401 sin él: un agente instalado con token necesita el transporte directo. `--transport auto`, el valor por defecto, prueba primero la vía directa; si `/healthz` no responde y `--api` y `--api-user` están puestos, prueba el relay; si no, falla nombrando `install --expose`. ## --expose, en la dirección LAN del router **Lo que añade `install --expose`** - dos reglas de cortafuegos, etiquetadas - el token pasa a ser obligatorio - `uninstall` y `status` solo ven las dos reglas si se les vuelve a dar `--expose` Cada objeto lleva el comentario `mikroscope: (managed by mikroscope)` `mikroscope plan` imprime cada orden antes de escribir nada. `install --expose --lan-address --token …` añade un dst-nat desde la dirección LAN del router en el puerto del agente hacia la veth, y un accept en forward para ese flujo colocado antes del primer drop de forward (añadido al final cuando la cadena forward no tiene drop). Cualquier máquina de la LAN puede entonces llegar al agente, así que el token es obligatorio: `install` rechaza `--expose` sin token o sin una dirección LAN IPv4. El token puede contener letras, dígitos, `_`, `.` y `-`, hasta 128 caracteres. `uninstall` quita las dos reglas — si recibe otra vez `--expose`, como advierte [Instalar el agente](/mikroscope/es/install/). Verificado en RB5009UG+S+, RouterOS 7.24.2, 2026-09-11: el par funciona, y las dos reglas se pueden quitar por etiqueta. Lo usa un cliente al que apuntes a `http://:9123`: Prometheus, `curl`. Los transportes de la propia CLI no. `record`, `forward` y el sondeo tras `install` construyen la dirección del agente a partir de `--subnet` y `--port`, y esta compilación no tiene ninguna opción que los dirija a la dirección expuesta. Lo que abre un servicio escuchando para toda la LAN está en [Lo que abre --expose](/mikroscope/es/security/expose/). ## Elegir | Vía | Necesita | Costes y límites | | ---------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | directa | una ruta desde tu máquina a la /30 a través del router | solo las dos pertenencias a listas que ya añade `install` | | relay | un usuario de la API con `read,api,test`; `--transport relay` o `auto` | 64 512 B y 18 muestras por llamada, ~1 s en más o menos la mitad de las llamadas, sin token | | `--expose` | `--lan-address` y un token; dos reglas de cortafuegos en el router | alcanzable desde toda la LAN; no lo usan `record`, `forward` ni el sondeo | > **Cierto en este equipo, no en el tuyo** > > Todos los resultados de alcance de esta página son del RB5009, con su propio cortafuegos y su LAN, > en septiembre de 2026. El tope de 64 512 B y la fracción de ~1 s del > relay se midieron en RouterOS 7.24.2 y pueden ser distintos en otra versión. ## Véase también - [Las dos trampas del cortafuegos](/mikroscope/es/install/firewall/): las pertenencias de las que depende el transporte directo. - [El usuario de la API](/mikroscope/es/security/api-user/): el usuario que necesita el relay, y dónde restringirlo. - [Lo que abre --expose](/mikroscope/es/security/expose/): las dos reglas y el token. - [El colector](/mikroscope/es/sinks/): lo que tira del agente una vez que es alcanzable. --- # Grabar, marcar, dibujar Cómo traer a tu máquina una ventana de muestras a cadencia completa, marcar los momentos que contiene, añadir el propio log del router y dibujarla como un SVG determinista. Source: https://jmrplens.github.io/mikroscope/es/record/ El colector mantiene un router vigilado; `record` es para la otra pregunta — _estoy a punto de cambiar algo, ¿qué hace de verdad?_ Esta página responde cómo tomar una grabación desde tu máquina, qué contienen los cuatro ficheros que escribe, cómo entran los marcadores y en qué reloj, cómo el log del router pasa a formar parte de ella y qué dibuja `plot`. ## Tres verbos ```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 ``` Los tres verbos comparten un mismo juego de opciones, así que cada uno acepta todas las opciones de abajo; las notas dicen sobre qué verbo actúa cada una. | Opción | Por defecto | Qué hace | | --------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--out` | `capture-` | Prefijo de salida: `.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 18. | | `--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` | `.svg` | `plot`: el fichero de salida. | | `--title` | el prefijo | `plot`: el título del gráfico. | ## Lo que se ha medido que entrega una grabación Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · 2026-09-12 · un `record` de 60 s a 10 Hz, 600 muestras en 59,9 s, un bucle de script de RouterOS lanzado por ssh y el log del router añadido como marcadores Esa grabación dio exactamente 600 muestras, 0 huecos y un desfase de reloj de −7 ms. En ella, un bucle de RouterOS por script (`:for … 400 000`) se vio como la carga de un núcleo entero al 100 % de t = 21,0 a 25,8 s, con la muestra de 25,8 s marcando alrededor del 60 %, y el inicio y el final resueltos a 100 ms, y los marcadores del log del router explicaron una meseta de 2 s entre 15 y 17 s que nadie había provocado: el planificador `ensure-ipv6-nd-prefix`. [Cinco minutos con un router](/mikroscope/es/start/walkthrough/) recorre `record` y `plot` de principio a fin sobre otra captura: 70 s a 10 Hz con el router por lo demás en reposo, con tres notas escritas en el terminal de `record`. > **No medido, luego no afirmado** > > Una grabación por encima de 10 Hz. Las ejecuciones sin pérdidas a 50 Hz y 100 Hz del [techo de > muestreo](/mikroscope/es/cost/rate-ceiling/) fueron del colector, no de `record`. Ambos usan el > mismo tamaño de lote, pero una grabación a esas cadencias no se ha medido por separado. Tampoco > una grabación a través del relay, a ninguna cadencia. ## Cómo llega `record` al agente `--transport auto` prueba primero el camino directo: un `GET /healthz` a `http://<.2 de --subnet>:<--port>`. Si no responde, abre la API de RouterOS y pide al router que haga él mismo la petición al agente. Si falta `--api` o `--api-user`, 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 : …`. La sugerencia de `--expose` sirve a otros clientes HTTP, no a `record`: `record` siempre llama a la `.2` de `--subnet` y no tiene opción para la dirección LAN del router. `direct` y `relay` fuerzan un camino y fallan en vez de recurrir al otro. - **direct** es HTTP plano de tu máquina a la veth. Envía `--token` como token bearer. - **relay** ejecuta `/tool fetch output=user` en el router por la API binaria, así que el usuario de la API necesita las políticas `read,api,test`. 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 18 muestras, y una respuesta que llega al límite se rechaza en vez de analizarse truncada. La petición desde el router no lleva cabecera: un agente instalado con token solo se puede grabar por el camino directo. > **Una petición por relay llena aún puede llegar al límite** > > Por aritmética, leído del código el 2026-09-15 y no medido. El límite de 18 muestras sale del tope > de fetch de 64 512 B, una línea media de 2 560 B y un margen del > 134 %; la media son los 2 439 B medidos en el RB5009 (RouterOS 7.24.2, > 2026-09-12), redondeados hacia arriba. Una petición llena de líneas medias ocupa unos 46 kB. Una > petición llena cuyas líneas midan de media más del 134 % de esa media seguiría llegando al tope y > se rechazaría, y cuánto miden las líneas con los suelos por fuente por defecto de hoy no se ha > medido. Con 18 muestras por petición y el `--poll` por defecto de 500 ms, el relay lleva 36 > muestras por segundo; por encima, usa el camino directo. [Llegar al agente](/mikroscope/es/install/reaching-the-agent/) explica qué camino permite una red. El usuario de la API se describe en [su propia página](/mikroscope/es/security/api-user/). Una vez conectado, `record` lee `/healthz`. El reloj de pared del agente menos el de tu máquina es el **desfase de reloj**, que se 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. La grabación empieza entonces en directo desde la muestra más reciente, o desde la más antigua que guarda el anillo con `--from-start`. Cada `--poll` pide `/snapshot?since=&max=`. 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. 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. Cuando vence `--for` o pulsas Ctrl-C, una última petición recoge lo que llegó entretanto, y `record` imprime cuántas muestras conservó, su rango de secuencia, los huecos, los marcadores, el transporte y los ficheros. ## Los cuatro ficheros Cada fichero se crea con modo `0600`, y un fichero existente con el mismo prefijo se sobrescribe. - **`.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. - **`.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. - **`.markers.csv`** — `wall_ns,wall_utc,seq,kind,label`, una fila por marcador. - **`.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` — lo que el agente estableció sobre la placa — cuando el transporte pudo obtenerlo. 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_busy`, `c_user`, `c_nice`, `c_system`, `c_idle`, `c_iowait`, `c_irq`, `c_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_processed`, `softnet_dropped`, `softnet_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 del CSV 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 %. [El suelo de resolución](/mikroscope/es/limits/) explica por qué, y por qué la proporción está limitada a 1. ## Los marcadores, y en qué reloj están Un marcador es una fila de `.markers.csv` de uno de tres tipos: - **`note`** — una línea de texto que añadiste tú. Mientras `record` corre en un terminal imprime `type a line and press Enter to add a marker; Ctrl-C stops`, y cada línea no vacía que escribes se convierte en una nota. Cuando la entrada estándar no es un terminal, no se lee nada de ella. - **`gap`** — se escribe cuando el agente informa de que unas muestras ya no estaban en su anillo, con la etiqueta `samples .. lost`. Los mismos huecos aparecen en el resumen que imprime `record` al final. - **`log`** — una línea del propio log del router, añadida con `--log-markers` (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, no del de tu máquina. `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. Sobre una grabación terminada, la nota cae por tanto después de la última muestra, y `plot` no la dibuja. `mark` necesita ese fichero y un `cap.markers.csv` existente, y su fila lleva `seq` 0. > **Marca mientras corre la grabación, no después** > > `record` no mantiene abierto `.markers.csv`: lo abre, añade y lo cierra con cada marcador, > que es exactamente lo que hace `mark --out "text"` desde un segundo terminal. Así que un > marcador tecleado en una terminal y otro añadido desde otra caen en el fichero en el orden en que > se hicieron, igual en Linux, macOS y Windows. > Lo que un `mark` de texto no puede hacer es marcar un momento de una grabación ya detenida: sella > la hora actual, que cae después de la última muestra, y `plot` no dibuja nada ahí. ## El propio log del router como marcadores El log explica a menudo un transitorio que no provocaste tú: ejecuciones del planificador, errores, eventos de interfaz. `--log-markers` convierte las líneas del log de la ventana de la grabación en marcadores `log`, con la etiqueta `: `. ```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 acaban en `.markers.csv` y el total de marcadores del resumen cuenta lo que contiene el fichero. Si la propia 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`: el RB5009 tenía 66 217 filas de log, porque su tema `dns` registra a disco. Después conserva las líneas dentro de la ventana cuyos temas incluyen alguno de `--topics`. Un router con varias acciones de registro sobre un mismo tema lleva cada evento 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 de RouterOS no llevan zona. RouterOS 7.24.2 imprime la fecha completa por la API; 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. Se leen en `--router-tz` y se sellan como reloj de pared del agente — 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. ## El 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. 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 para separarlas; una etiqueta de más de 40 caracteres se acorta. Los marcadores de log consecutivos, en orden de tiempo, que caen en el mismo segundo se pliegan en una sola etiqueta, `× : `, para que un planificador parlanchín no entierre el gráfico; 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/` 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. ## Disparadores durante una grabación 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](/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 - [Cinco minutos con un router](/mikroscope/es/start/walkthrough/): una grabación real del RB5009, sus marcadores y su gráfico, paso a paso. - [Captura por disparo](/mikroscope/es/record/triggers/): las muestras alrededor de una condición, guardadas a cadencia completa por el agente sin ninguna grabación en marcha. - [Llegar al agente](/mikroscope/es/install/reaching-the-agent/): cuál de los caminos directo y relay permite tu red. - [El usuario de la API](/mikroscope/es/security/api-user/): el usuario de RouterOS que necesitan el relay y `--log-markers`. --- # Captura por disparo Cómo guarda el agente las muestras a cadencia completa alrededor de una condición que configuraste, qué dispara, cómo traer una captura y por qué el conjunto de capturas es una muestra de los eventos y no un censo. Source: https://jmrplens.github.io/mikroscope/es/record/triggers/ Todo lo que hay en `/metrics` es un resumen con pérdidas, y una grabación solo existe si alguien la empezó antes del momento. Esta página responde qué hace el agente en su lugar: qué condiciones le hacen guardar a cadencia completa las muestras alrededor de un momento, cómo configurarlas, cómo traer lo que guardó, qué hace el colector con el aviso y qué no te puede decir una captura. ## Qué es una captura, y qué no es Lo único sin pérdidas que el agente puede hacer por su cuenta es guardar las muestras que ya existen, a cadencia completa, alrededor del momento en que importan — y solo el agente puede, porque solo el agente tiene todas las muestras. El anillo ya guarda los últimos 300 s por defecto, así que conservar los segundos anteriores a un disparo no cuesta nada; los posteriores solo cuestan la espera. No decide nada sobre el significado. Una condición es una comparación que configuraste tú. El campo que comparó y el valor que la hizo saltar viajan en la cabecera de la captura, así que puedes ver qué se comparó. La captura son las mismas líneas de deltas crudos que envía `/snapshot`. Nada se convierte en un porcentaje ni en un veredicto. Una captura no copia esas líneas. El anillo guarda cada muestra como una línea precodificada e inmutable, y una captura fija las líneas que necesita, así que disparar cuesta una copia de las cabeceras de las entradas una vez por disparo y nada por tick. El diseño lo estima en unos 3 µs para una ventana de 10 s a 10 Hz; no se ha medido en el equipo. Las condiciones se evalúan en el propio bucle del muestreador, entre leer una muestra y meterla en el anillo, nunca en una segunda goroutine. No hay lenguaje de expresiones, a propósito: un analizador es una dependencia y una superficie de ataque, y una expresión que el operador puede escribir en el camino caliente del muestreador es una forma de volver lento el router. ## Configurarla El agente lee seis variables de la envlist de su contenedor. Dos de ellas tienen una opción de `install`. | Variable del agente | Opción de `install` | Por defecto | Valores aceptados | Qué ajusta | | ---------------------- | ------------------- | -------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------ | | `TRIGGERS` | `--triggers` | `softnet-drop,oom,kmsg<=3,reset,irq-err,flash-bad` | las condiciones de abajo, separadas por comas | Qué condiciones arman una captura. | | `CAPTURE_MB` | `--capture-mb` | `4` | `0`–`256` | El presupuesto de bytes fijados del anillo, en MiB. `0` desactiva la función. | | `CAPTURE_PRE_S` | ninguna | `5` | `1`–`60` | Segundos que se guardan antes de la muestra que disparó. | | `CAPTURE_POST_S` | ninguna | `5` | `1`–`60` | Segundos que se guardan después. | | `CAPTURE_POLICY` | ninguna | `first` | `first`, `last` | Con el presupuesto lleno: `first` rechaza la captura nueva, `last` expulsa la más antigua. | | `TRIGGER_REFRACTORY_S` | ninguna | `10` | `0`–`3600` | Tiempo de silencio por condición después de disparar. | `install` siempre escribe `CAPTURE_MB`, y escribe `TRIGGERS` solo cuando se da `--triggers`; sin ella el agente usa su conjunto por defecto. No escribe ninguna de las otras cuatro, así que un agente instalado funciona con sus valores por defecto. `mikroscope plan` muestra las entradas de la envlist antes de escribir nada. `--triggers` pasa por el propio analizador del agente antes de la primera conexión: `Finish` se la pasa a `agent.ParseTriggers`, así que una condición desconocida, un umbral fuera de rango, una comilla o un punto y coma hacen fallar la orden con código de salida 2 sin escribir nada. El agente vuelve a analizar la lista al arrancar, porque una envlist se puede editar a mano en el router; un valor que rechace allí hace que se niegue a funcionar, con una línea en su salida estándar, que RouterOS lleva a su log. ## Las condiciones El conjunto por defecto son las condiciones sin umbral salvo `squeeze` — `softnet-drop`, `oom`, `reset`, `irq-err`, `flash-bad`, cada una de las cuales dispara cuando el kernel cuenta algo que normalmente no cuenta — más `kmsg<=3`. Las condiciones de nivel no están en él: sus umbrales los eliges tú. | Condición | Dispara cuando | `field` en la cabecera | Umbral | En el conjunto por defecto | | -------------- | ----------------------------------------------------------------------------------------- | ---------------------------- | ------------ | -------------------------- | | `softnet-drop` | alguna cola softnet descartó un paquete en la muestra | `softnet[N].dropped` | ninguno | sí | | `squeeze` | alguna cola softnet se quedó sin presupuesto (`time_squeeze`) en la muestra | `softnet[N].time_squeeze` | ninguno | no | | `oom` | el kernel mató algo por falta de memoria (`vm.oom_kill` se movió) | `vm.oom_kill` | ninguno | sí | | `reset` | un contador retrocedió de una forma que no es un desbordamiento de 32 bits | `resets` | ninguno | sí | | `irq-err` | la fila `Err` de `/proc/interrupts` se movió | `irq_err` | ninguno | sí | | `flash-bad` | el recuento de bloques defectuosos de una partición YAFFS subió desde la muestra anterior | `flash[].bad_blocks` | ninguno | sí | | `kmsg<=N` | un registro del log del kernel de severidad N o más grave (0 es emergencia, 3 error) | `events.level` | `0`–`7` | `kmsg<=3` | | `busy>=X` | la proporción de ocupación de algún núcleo está en X o por encima | `cpu[N].busy_ratio` | `0.05`–`1` | no | | `slip>=X` | el intervalo de la muestra fue de al menos X periodos del muestreador | `dt_ns/period` | `1.1`–`100` | no | | `memfall>=N` | `MemAvailable` cayó N o más en un tick | `mem.MemAvailable fall (MB)` | `1`–`100000` | no | Cuando una condición abarca varios núcleos, colas o particiones, la cabecera nombra el primero que coincidió. `memfall` compara los kB de `/proc/meminfo` divididos entre 1 024, así que su N está en MiB aunque el campo diga MB. `kmsg<=N` necesita el log del kernel, que el agente solo puede leer en un contenedor privilegiado ([lo que aporta privileged](/mikroscope/es/limits/privileged/)); `flash-bad` necesita una partición YAFFS. Una condición cuya fuente no existe nunca dispara. `squeeze` está disponible pero no es de las de por defecto. En el RB5009 de referencia los time squeezes son ruido de fondo: una regla que salte ante cualquier squeeze salta allí 92 veces en veinte minutos (RouterOS 7.24.2, 2026-09-15). Por eso la regla de microrráfagas del colector exige un episodio de tres desviaciones. Una captura también se puede armar a mano, con `POST /capture` (abajo). Su causa es `manual` y su `field` es el motivo que diste. ## Cómo un disparo se convierte en captura 1. Una condición es cierta en la muestra S. Si esa condición disparó hace menos de `TRIGGER_REFRACTORY_S` × cadencia muestras (esos segundos a la cadencia nominal), el disparo se **suprime** (motivo `refractory`). Si otra captura aún está recogiendo su ventana, el disparo se **suprime** (motivo `pending`): se recoge una captura cada vez, sea cual sea la condición que la armó. 2. Si no, se arma una captura para la ventana desde S − `CAPTURE_PRE_S` × cadencia hasta S + `CAPTURE_POST_S` × cadencia, y se pone en cola una línea `{"trigger":{…}}` para el flujo. 3. Cuando la muestra del final de la ventana está en el anillo, la captura fija las líneas del anillo de esa ventana. Si el anillo ya no guarda ninguna de ellas, la captura se **rechaza** (`empty`). 4. Si los bytes de la ventana por sí solos superan el presupuesto, se **rechaza** (`budget`). Si el presupuesto está lleno, `first` la rechaza (`budget`) y `last` expulsa las capturas más antiguas hasta que quepa. Una captura cuya ventana tiene menos de `(pre + post) × rate + 1` muestras se conserva con `complete: false` en vez de quedarse corta en silencio. Eso ocurre cuando el anillo no guardaba la ventana entera: un disparo dentro de los `CAPTURE_PRE_S` primeros segundos tras arrancar el agente, o un anillo (`BUFFER_S`) más corto que la ventana. Cuando el agente se detiene, recoge una captura pendiente con lo que guarda el anillo, pero no puede servirla: las capturas están en memoria, y el servidor HTTP se detiene con el agente. Las capturas viven en la memoria del agente. Nada las escribe a disco, así que un reinicio del contenedor, un `upgrade` o un reinicio del router pierde las que aún no se han descargado. ### Lo que pesa una captura El tamaño de una captura es el número de muestras de su ventana por el tamaño de línea. La línea media medida en el RB5009 (RouterOS 7.24.2, 10 Hz, todas las fuentes de esa fecha, 2026-09-12) fue de 2 439 B; las líneas con los suelos por defecto no se han medido. Eso da, por aritmética y no midiendo capturas: | Cadencia | Ventana por defecto (5 s + 5 s) | Capturas en los 4 MiB por defecto | | -------- | ------------------------------- | --------------------------------- | | 10 Hz | 101 muestras, unos 250 kB | 17 | | 50 Hz | 501 muestras, unos 1,2 MB | 3 | | 100 Hz | 1 001 muestras, unos 2,4 MB | 1 | Una placa mayor — más núcleos, más líneas de interrupción — tiene líneas más largas. El campo `bytes` de cada captura es la cifra real. El presupuesto es memoria que el agente retiene además de su anillo: una línea fijada sigue viva cuando el anillo ya la ha dejado atrás. Por eso el agente la cuenta al arrancar en la misma comprobación que el anillo. Cuando el agente puede leer el `memory.max` del contenedor y unos `rate × buffer × 2.56 kB` más `CAPTURE_MB` lo superan, se niega a arrancar, nombrando los tres ajustes que bajar o `--memory-max` para subir. Cuando `MEM_LIMIT_MB` es mayor que 0, avisa si el doble de eso supera su límite blando de memoria de Go; el valor por defecto del propio agente para `MEM_LIMIT_MB` es 14, e `install` escribe 40. [El coste del observador](/mikroscope/es/cost/) explica por qué importa esa segunda proporción. ## Leer capturas por HTTP Cuatro endpoints en el agente. Cada uno necesita el token bearer cuando el agente lo tiene, y cada uno responde `404` con `captures disabled (CAPTURE_MB=0)` cuando la función está desactivada. | Petición | Respuesta | | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GET /captures` | El índice, en JSON. | | `GET /captures/` | Una línea de cabecera `{"capture":{…}}`, y después las líneas de muestra tal cual, como NDJSON. | | `DELETE /captures/` | Libera la parte del presupuesto de esa captura; `204`. | | `POST /capture?reason=…` | Arma una captura manual en la muestra más reciente: `{"id":N,"armed":true}`, o `409` cuando hay una captura pendiente o el disparo manual está en su ventana refractaria. El motivo por defecto es `operator`. | ```sh curl -s -H "Authorization: Bearer $MIKROSCOPE_TOKEN" http://172.30.10.2:9123/captures curl -s -H "Authorization: Bearer $MIKROSCOPE_TOKEN" http://172.30.10.2:9123/captures/3 > cap3.jsonl mikroscope plot --in cap3.jsonl curl -s -X DELETE -H "Authorization: Bearer $MIKROSCOPE_TOKEN" http://172.30.10.2:9123/captures/3 ``` El índice lleva la `policy`, `budget_bytes`, los `bytes` retenidos, la captura aún `pending` si la hay, los `triggers` configurados y una entrada por captura: `id`, `cause`, `condition`, `field`, `value`, `threshold`, `fire_seq`, `fire_mono_ns`, `fire_wall_ns`, `first_seq`, `last_seq`, `samples`, `bytes` y `complete`. Las líneas de muestra de `GET /captures/` son idénticas byte a byte a lo que sirve `/snapshot` para las mismas muestras, así que una herramienta que lee un snapshot no necesita un analizador nuevo; `plot` salta la línea de cabecera. La CLI no tiene verbo para las capturas: usa cualquier cliente HTTP. > **Las capturas necesitan el camino directo** > > El transporte relay tira a través de `/tool fetch`, que devuelve como mucho 64 512 B y no envía token. Una captura con los valores por defecto ocupa unos 250 kB. Trae las capturas > desde un equipo que llegue al agente directamente, o a través de `--expose`: [llegar al > agente](/mikroscope/es/install/reaching-the-agent/) cubre ambos. ## La línea de disparo, y qué hace con ella el colector Cuando se arma una captura, el agente coloca una línea antes de la muestra en la que disparó, en `/stream` y en `/snapshot?since=` (no en `/snapshot?seconds=`): ```text {"trigger":{"id":3,"cause":"busy>=0.95","field":"cpu[2].busy_ratio","value":1,"threshold":0.95,"seq":48213,"wall_ns":1789000000000000000}} ``` Los valores de arriba son ilustrativos. La línea es de un tipo propio, como la línea `{"gap":…}`, no un campo de la muestra, así que el esquema de la muestra no cambia. El agente guarda las últimas 64 para quien tire de él, así que quien va más de 64 disparos por detrás nunca ve las más antiguas; un disparo suprimido no produce ninguna. Una captura manual dispara sobre la muestra más reciente que ya está en el anillo, así que quien ya ha recibido esa muestra no recibe línea de disparo para ella; lee `/captures` en su lugar. `forward` reconoce la línea, nunca la confunde con una muestra, la cuenta y se la entrega a cada destino como anotación: la medida `mikroscope_trigger` en InfluxDB y la tabla en SQL, `mikroscope_collector_triggers_total{cause}` en la exposición Prometheus del colector, y la propia línea en el destino de fichero. Los dos paneles de Grafana llevan una anotación `triggers`, desactivada por defecto en la barra de conmutadores: en InfluxDB lee las filas de `mikroscope_trigger`, en Prometheus el `mikroscope_trigger_fired_total` del agente. La captura en sí se queda en el agente, en `/captures/`. `record` aún no reconoce la línea — [Grabar, marcar, dibujar](/mikroscope/es/record/#disparadores-durante-una-grabación) dice qué hace con ella. ## Contar lo que no se capturó El `/metrics` del agente lleva las familias que dicen cuánto no vieron las capturas. Cada par de condición y motivo se expone desde el principio, a 0 hasta que ocurre, así que un panel puede mostrar «0 hasta ahora». | Familia | Tipo | Significado | | ------------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------- | | `mikroscope_trigger_fired_total{condition}` | counter | Veces que cada condición armó una captura; `condition="manual"` aparece cuando se ha armado una captura manual. | | `mikroscope_trigger_suppressed_total{condition,reason}` | counter | Veces que una condición fue cierta y no se armó nada: `refractory` o `pending`. | | `mikroscope_capture_refused_total{reason}` | counter | Capturas recogidas y después no conservadas: `budget` o `empty`. | | `mikroscope_captures_held` | gauge | Capturas retenidas en este momento. | | `mikroscope_capture_bytes` | gauge | Bytes del anillo que fijan las capturas retenidas. | | `mikroscope_capture_budget_bytes` | gauge | El presupuesto, a partir de `CAPTURE_MB`. | | `mikroscope_capture_bytes_served_total` | counter | Bytes entregados por `/captures/`. | El colector no puede recalcularlas a partir de las muestras, así que el panel de Prometheus espera un trabajo de scrape sobre el propio agente que conserve solo las familias exclusivas del agente; [Prometheus](/mikroscope/es/sinks/prometheus/) tiene el trabajo. ## Lo que no puede hacer Dicho porque cada una de estas cosas va a ocurrir: - **El conjunto de capturas es una muestra de los eventos, nunca un censo.** La ventana refractaria y el presupuesto de bytes acotan una tormenta de disparos, y se recoge una captura cada vez. `mikroscope_trigger_suppressed_total` y `mikroscope_capture_refused_total` son cuánto no se vio. - **Cadencia completa no es detalle completo.** Una captura guarda muestras a la cadencia del muestreador: a 10 Hz nada más corto que 100 ms es visible con fiabilidad, y una proporción de ocupación sigue moviéndose en los escalones de tick del kernel. [El suelo de resolución](/mikroscope/es/limits/) fija ese límite, no la captura. - **Una ventana puede quedarse corta.** Una que el anillo no guardaba entera se sirve con `complete: false`. Una cortada porque el agente se detuvo se recoge pero nunca se sirve, porque las capturas se detienen con el agente. - **Descargar le cuesta al router.** Una descarga corre en el mismo núcleo que el muestreador, y se contabiliza en `mikroscope_capture_bytes_served_total` igual que se le carga a cualquiera que tire del agente. > **No medido, luego no afirmado** > > El coste de un disparo en el equipo — 3 µs es la estimación del diseño. Cómo se comporta el agente > bajo una tormenta sostenida de disparos. Cualquier captura a 50 Hz o 100 Hz. Los tamaños de > captura de la tabla de arriba, que son aritmética a partir del tamaño de línea, no tamaños de > capturas tomadas en el RB5009. ## Véase también - [Grabar, marcar, dibujar](/mikroscope/es/record/): una grabación que empiezas tú, con marcadores, y el gráfico que `plot` dibuja también a partir de una captura. - [Los endpoints HTTP del agente](/mikroscope/es/reference/http/): `/captures`, `/stream` y `/snapshot` junto al resto. - [Familias de métricas de Prometheus](/mikroscope/es/reference/metrics/): las familias de disparo y captura con todas las demás que expone el agente. - [El suelo de resolución es del kernel](/mikroscope/es/limits/): lo que la cadencia completa puede y no puede resolver. --- # El colector Lo que hace `mikroscope forward` entre el agente y tus almacenes — extraer, fusionar, derivar, repartir — y lo que promete cuando un almacén va lento. Source: https://jmrplens.github.io/mikroscope/es/sinks/ _De dónde vienen los datos y a dónde van_ — El router ejecuta el agente en un contenedor que lee el kernel compartido y lo sirve por un veth. El colector de tu máquina tira de ahí, le une la capa de la API de RouterOS, deriva y escribe en cada destino que hayas nombrado. `mikroscope forward` es el colector. Tira de la capa del kernel del agente, muestrea la capa de la API de RouterOS, marca las dos con el reloj del agente, pasa la etapa de derivación sobre ellas y escribe la línea temporal fusionada en cada destino que indiques. Esta página responde a qué hace una ejecución, cómo sigue el ritmo del agente, qué reloj lleva cada registro y qué significa «descartado» cuando un destino deja de responder. ```sh mikroscope forward --prom :9124 --influx "$MIKROSCOPE_INFLUX_URL" --interfaces bridge,ether1 ``` `forward` sin ningún destino es un error, no algo que no hace nada en silencio: leería el router y tiraría los datos. Hace falta al menos uno de `--file`, `--prom`, `--influx`, `--loki`, `--otlp`, `--graphite`, `--elastic`, `--sql`, `--telegraf` o `--stdout`, y lo normal es usar más de uno a la vez. ## Lo que hace una ejecución 1. **Pide al agente su estado.** La respuesta trae el reloj de pared del agente, su cadencia, su número de secuencia más reciente y su hash de capacidades. La diferencia entre el reloj del agente y el del colector es el desfase; la cadencia dimensiona el lote de extracción y las referencias móviles de la etapa de derivación. 2. **Entrega a cada destino el flujo de datos del equipo.** Las `/capabilities` del agente — placa, kernel, techos, cadencias — salen una vez como registro propio. Consulta [el flujo de datos del equipo](/mikroscope/es/sinks/device-info/). 3. **Tira del anillo cada `--poll`.** La primera extracción empieza después de la muestra más reciente del agente, así que `forward` no reproduce lo que el anillo tenía antes de arrancar. Cada extracción pide las muestras posteriores al último número de secuencia visto. Un marcador de disparo viaja entre las muestras en orden de secuencia y se reenvía como anotación, nunca se decodifica como muestra. 4. **Deriva y luego reparte.** Cada muestra del kernel pasa por [la etapa de derivación](/mikroscope/es/sinks/derive/), y la muestra, sus valores derivados y cualquier [detección](/mikroscope/es/sinks/detections/) que haya provocado van a cada destino en el mismo orden. 5. **Muestrea la capa de la API cada `--api-every`** (1 s por defecto) cuando hay credenciales de la API configuradas. Consulta [la capa de la API de RouterOS](/mikroscope/es/sinks/api-tier/). 6. **Vuelve a medir el desfase cada minuto.** Un salto de más de 50 ms se registra — un paso del reloj del router, una corrección de NTP — y se cuenta como salto de desfase. La misma lectura de estado vuelve a comprobar el hash de capacidades. 7. **Con Ctrl-C o al final de `--for`**, extrae una última vez, cierra cada destino con un vaciado final e imprime lo que hizo cada uno. En ese bucle no hay interpolación en ningún sitio: cada consumidor ve la cadencia que tiene de verdad cada fuente. ## Extraer lo bastante rápido `--poll` (500 ms por defecto) es cada cuánto se tira del anillo y `--batch` cuántas muestras pide una extracción. El lote por defecto es el doble de lo que produce un intervalo de sondeo a la cadencia del agente, y nunca menos de 20. Un lote fijo de 20 muestras por sondeo de 500 ms limitaría una extracción a 40 Hz y perdería 1 − 40/50 de las muestras de un agente a 50 Hz, y por eso el lote se dimensiona a partir de la cadencia del agente. Una extracción se repite mientras vuelva llena — hasta 100 veces — para que el cursor se ponga al día dentro de un sondeo en vez de avanzar un lote por sondeo. Una respuesta corta es el borde del anillo. El transporte por relay limita una extracción a 18 líneas, y el límite se calcula en vez de elegirse: `/tool fetch` devuelve como mucho 64 512 B, la línea media del anillo se toma como 2 560 B (los 2 439 B medidos, redondeados hacia arriba) y el límite deja un 134 % de esa media para que también quepa un lote de líneas por encima de la media: 18 líneas, unos 46 kB. Una respuesta que aun así llega al límite de fetch se rechaza con `relay reply hit the 64512-byte fetch limit; lower the batch` en vez de analizarse truncada. Con el sondeo por defecto de 500 ms eso son 36 muestras/s, y por encima el colector se queda atrás. `forward` calcula lo que el lote efectivo y el sondeo permiten por segundo y avisa al arrancar cuando queda por debajo de la cadencia del agente — para el relay contra un agente a 100 Hz, la aritmética da: ```text warning: at most 18 samples per pull every 500ms is 36/s, below the agent's 100 Hz; the collector will fall behind and report gaps. Raise --batch, lower --poll, or use the direct transport ``` El límite y el aviso están leídos del código el 2026-09-15, no vueltos a medir en un equipo. Un colector que se queda más atrás que el anillo del agente (300 s por defecto) recibe una línea de hueco en vez de las muestras, y cada destino registra el hueco. ## Qué reloj lleva cada registro | Registro | Marca de tiempo | | ------------------------------------- | ----------------------------------------------------------------------------- | | Muestra del kernel, valores derivados | el propio reloj de pared del agente, tal como lo lleva la muestra | | Detección | el reloj de pared de la muestra que la provocó | | Marcador de disparo | el reloj de pared del agente en el momento del disparo | | Muestra de la capa de la API | el reloj del colector más el desfase medido | | Hueco | el reloj del colector cuando volvió la extracción que lo encontró | | Registro de datos del equipo | el reloj del colector: los datos de la placa no tienen marca de tiempo propia | ## ¿Cuál debería usar? diez destinos, y la respuesta honesta es que casi todo el mundo quiere uno de los dos primeros. Los demás existen para que mikroscope encaje con lo que ya tienes, en vez de pedirte que montes algo nuevo. | Si… | Usa | Lleva | Dashboard | | -------------------------------------------------------------- | -------------- | -------------------------------------------------- | --------- | | quieres todo, con los dashboards, y no tienes nada montado | `--influx` | todas las medidas, como protocolo de línea | **sí**, generado | | ya tienes Prometheus | `--prom` | todas las familias, recalculadas de las muestras | **sí**, generado | | quieres capturar una ventana y mirarla después | `--file` | la línea temporal unida como JSONL, sin instalar nada | no | | guardas datos a largo plazo en PostgreSQL o TimescaleDB | `--sql` | DDL e INSERTs para `psql`, sin driver | **sí**, generado | | quieres el registro del kernel y las detecciones con tus logs | `--loki` | **solo eventos**: kmsg, detecciones, huecos | no | | ya tienes una tubería de OpenTelemetry | `--otlp` | métricas como OTLP/HTTP | no | | ya tienes Graphite o Elasticsearch | `--graphite`, `--elastic` | todas las medidas, con la forma de ese producto | **sí**, uno más pequeño | | ya tienes Telegraf | `--telegraf` | todas las medidas, como protocolo de línea | no | | quieres canalizarlo hacia algo tuyo | `--stdout` | protocolo de línea o NDJSON por la salida estándar | no | Nada impide nombrar varios a la vez, y esa es la disposición normal: `--file` junto a un almacén te deja una captura a la que volver, y `--loki` junto a `--influx` pone el registro del kernel donde puede alcanzarlo una consulta de logs mientras los números van al almacén que leen los dashboards. Dos de ellos no llevan lo mismo que el resto. **Loki recibe eventos, no métricas** —los registros del kernel, las detecciones y los huecos—, así que una ejecución solo con Loki no tiene ni un número de CPU o memoria. Y **`--prom` se consulta, no se envía**: `forward` sirve `/metrics` y Prometheus viene a por él, lo que significa que el colector tiene que ser alcanzable desde la máquina de Prometheus. ## Los diez destinos | Opción | Destino | URL o credencial desde el entorno | Forma | Página | | ------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------ | ---------- | ----------------------------------------------------------------------- | | `--file path.jsonl` | fichero JSONL | — | síncrono | [el fichero](/mikroscope/es/sinks/other/#el-fichero) | | `--prom :9124` | `/metrics` de Prometheus en la máquina del colector | — | en memoria | [Prometheus](/mikroscope/es/sinks/prometheus/) | | `--influx URL` | protocolo de líneas de InfluxDB 3 | `MIKROSCOPE_INFLUX_URL`, `MIKROSCOPE_INFLUX_TOKEN` | en cola | [InfluxDB 3](/mikroscope/es/sinks/influxdb/) | | `--sql path` o `--sql -` | sentencias PostgreSQL / TimescaleDB, para `psql` | — | síncrono | [SQL](/mikroscope/es/sinks/other/#sql-para-postgresql-y-timescaledb) | | `--stdout lp` o `--stdout json` | salida estándar | — | en cola | [stdout](/mikroscope/es/sinks/other/#salida-estándar) | | `--loki URL` | API push de Loki: eventos, no métricas | `MIKROSCOPE_LOKI_URL`, `MIKROSCOPE_LOKI_TOKEN`, `MIKROSCOPE_LOKI_TENANT` | en cola | [Loki](/mikroscope/es/sinks/other/#loki) | | `--otlp URL` | métricas OTLP/HTTP, codificación JSON | `MIKROSCOPE_OTLP_URL`, `MIKROSCOPE_OTLP_TOKEN` | en cola | [OTLP](/mikroscope/es/sinks/other/#otlp) | | `--graphite host:port` | texto plano de carbon sobre TCP | `MIKROSCOPE_GRAPHITE_ADDR` | en cola | [Graphite](/mikroscope/es/sinks/other/#graphite) | | `--elastic URL` | `_bulk` de Elasticsearch u OpenSearch | `MIKROSCOPE_ELASTIC_URL`, `MIKROSCOPE_ELASTIC_AUTH` | en cola | [Elasticsearch](/mikroscope/es/sinks/other/#elasticsearch-y-opensearch) | | `--telegraf URL` | un listener de Telegraf por HTTP, TCP o UDP | `MIKROSCOPE_TELEGRAF_URL`, `MIKROSCOPE_TELEGRAF_TOKEN` | en cola | [Telegraf](/mikroscope/es/sinks/other/#telegraf) | Las credenciales de los destinos nunca vienen de una opción: una opción se ve en `ps` y en el historial del shell. Cada token de un destino se lee solo de su variable `MIKROSCOPE_*`. El token bearer del propio agente es la excepción: `forward` lo toma como `--token`, con `MIKROSCOPE_TOKEN` por defecto. `--host-tag` (`MIKROSCOPE_HOST_TAG`, `router` por defecto) pone la misma etiqueta de host en cada punto de cada destino. Un destino que se pidió y no se puede construir — un puerto ya ocupado, un fichero que no se puede abrir — hace fallar la ejecución. Un destino ausente en silencio es peor que no tener datos, porque la ausencia no se ve. ## Un destino lento nunca para el bucle El bucle de extracción del colector nunca debe esperar a un destino. Cada destino que habla con un remoto renderiza en memoria y entrega los bytes a una cola acotada que vacía una goroutine propia una vez por segundo: - La cola guarda `--queue-seconds` (60 por defecto) segundos de un presupuesto de bytes: 64 KiB por segundo para InfluxDB, Loki, OTLP, Elasticsearch, Telegraf y stdout, y 256 KiB por segundo para Graphite, cuyo formato de una línea por valor ocupa más. - Pasado el presupuesto se expulsa el lote **más antiguo** y se cuenta; el más nuevo se conserva siempre, porque la telemetría fresca vale más que la rancia. - Una entrega fallida espera 2 s, doblando hasta 60 s, y registra como mucho una línea por minuto. Todo lo demás está en los contadores. - Cada POST HTTP lleva un tiempo de espera de 10 s, y una conexión reutilizada muerta es un error que se reintenta y se cuenta, no un reenvío silencioso. Tres destinos no van en cola. Los destinos de fichero y SQL escriben de forma síncrona a través de un búfer de 64 KiB, porque un fichero local no se atasca como un remoto; un error de escritura cuenta un error y un descarte. El destino de Prometheus actualiza un estado en memoria bajo un cerrojo y lo sirve en cada scrape. > **Una tubería hacia psql puede bloquear el colector** > > El destino SQL no tiene cola, así que con `--sql -` alimentando `| psql`, un `psql` que se retrasa > llena la tubería y la siguiente escritura bloquea el bucle de extracción en vez de descartar. Cada > `INSERT` es su propia transacción, que es la forma realista de que `psql` se quede atrás frente a > un agente a 10 Hz. No medido. Escribe a un fichero y aplícalo después. ### Lo que cuentan los contadores `forward` imprime `written`, `dropped` y `errors` por destino, y la unidad depende de la forma: - **Los destinos en cola cuentan lotes.** `written` es un lote que el destino aceptó, `dropped` un lote expulsado por el presupuesto de bytes, `errors` un intento fallido — un lote que falla tres veces y luego llega son 3 errores y 1 escrito. Elasticsearch suma un `dropped` por cada documento que el clúster rechazó dentro de una respuesta 200. - **Fichero, SQL y Prometheus cuentan eventos**: uno por muestra, disparo, lectura de la API, hueco, detección o registro del equipo aceptado. ## Lo que imprime `forward` Al arrancar, por la salida de error: una línea `sink: ` por destino, la configuración de la capa de la API, y la versión, cadencia, número de secuencia, desfase, transporte y lote efectivo del agente. Cada minuto, por la salida de error, un informe en curso: ```text forwarded kernel, api, gap(s), trigger(s), detection(s), last seq ; : written, dropped, errors ``` Al salir, por la **salida estándar**, los totales y una línea por destino: ```text forwarded kernel samples, api samples, gap(s), skew jump(s) : written, dropped, errors ``` Dos propiedades de esa salida pueden sorprender a un consumidor: el resumen de salida va al mismo flujo en el que escribe el destino `--stdout`, así que `forward --stdout=lp | telegraf` termina cada ejecución con líneas que el consumidor no puede interpretar; y un `--token` erróneo se registra en cada extracción como `401 Unauthorized` mientras la ejecución termina igualmente en su plazo `--for` con código de salida 0 y `forwarded 0 kernel samples`. ## Una dimensión, un nombre Un procesador es `cpu` en todas partes — etiqueta de InfluxDB, columna SQL, etiqueta de Prometheus, atributo OTLP — nunca `core`. El cable lleva la unidad del propio kernel y la nombra en el campo (`_khz`, `_kb`, `_ticks`, `_pages`, `_sectors`); cada destino convierte una sola vez, a la convención de ese almacén, y convierte un valor y su techo de la misma manera. Por eso la temperatura es `celsius` junto a `critical_celsius`, y el tiempo ocupado de un dispositivo de bloques es `io_s`. ## Lo que se ha medido En el RB5009 de referencia el 2026-09-12, esta ejecución de ocho minutos reenvió 4 800 muestras del kernel y 479 de la API con 0 huecos y 0 descartes: ```sh mikroscope forward --for 8m --prom :9124 --influx … --interfaces bridge,ether1,PPPoE_DIGI --conntrack-every 10s ``` En las cinco ejecuciones de cadencia del 2026-09-15 el colector escribió en tres destinos a la vez, y todos informaron de 0 huecos y 0 descartes a 10, 50 y 100 Hz: Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · 2026-09-15 · ventanas de 60 s en régimen estacionario (con el anillo ya lleno), conjunto completo de fuentes, colector reenviando a la vez a fichero, a una exposición Prometheus y a InfluxDB 3 > **No medido, luego no afirmado** > > Loki, OTLP, Graphite, Elasticsearch, Telegraf, SQL y stdout se han probado contra receptores > locales que comprueban los bytes que acepta cada protocolo (máquina de desarrollo, amd64, > 2026-09-12), no alimentados desde el RB5009 hacia un backend en marcha. Los tamaños en bytes que > se citan para ellos en [los demás destinos](/mikroscope/es/sinks/other/) salen de fixtures de > pruebas, no de un equipo. ## Véase también - [Prometheus](/mikroscope/es/sinks/prometheus/): el `/metrics` del colector y los dos trabajos de scrape que espera el panel. - [InfluxDB 3](/mikroscope/es/sinks/influxdb/): la URL de escritura, las medidas y lo que rechaza InfluxDB 3 Core. - [La capa de la API de RouterOS](/mikroscope/es/sinks/api-tier/): lo que el colector sigue preguntando al router, y cómo preguntar menos. - [Llegar al agente](/mikroscope/es/install/reaching-the-agent/): los transportes directo y por relay sobre los que va la extracción. --- # Prometheus El `/metrics` propio del colector — cada familia del agente recalculada a partir de las muestras, más la capa de la API, la etapa de derivación y los contadores del colector — y los dos trabajos de scrape que lo acompañan. Source: https://jmrplens.github.io/mikroscope/es/sinks/prometheus/ `--prom :9124` hace que el colector sirva texto de Prometheus en `GET /metrics` en esa dirección. Esta página responde a qué lleva esa exposición, qué no puede llevar y tiene que venir del agente, y cómo hacer scrape de los dos sin contar nada dos veces. ## Recalculado a partir de las muestras Las familias de la capa del kernel en el colector las renderiza el mismo código que ejecuta el agente — sus contadores acumulados, su histograma de ticks ocupados y sus ventanas móviles — alimentado con las muestras que recibió el colector. Un despliegue cuyo colector solo llega al agente por el relay sigue teniendo métricas que no dependen de quién hace scrape ni de cuándo. Por encima de ellas el colector añade lo que solo tiene él: los gauges de la capa de la API de RouterOS, los valores de la etapa de derivación, sus contadores de detecciones y de huecos, y las familias de datos del equipo que salen de las `/capabilities` del agente. > **Dimensionado para 10 Hz, vaya el agente a lo que vaya** > > El colector dimensiona su histograma `mikroscope_cpu_busy_ticks` y su anillo de muestras a partir > de 10 Hz fijos, no del agente conectado, para que la disposición de los buckets no cambie cuando > se reconecta a un agente configurado de otra forma. Leído del código, no medido, la misma > constante fija más cosas, y frente a un agente por encima de 10 Hz cada una se desvía en la razón > de cadencias. El anillo que hay detrás de las ventanas móviles sí sigue al agente: el colector lee > la cadencia de su comprobación de salud y dimensiona el anillo a 60 s de ella, así que > `window="60s"` abarca un minuto a cualquier cadencia. La media móvil de softnet que hay detrás de > `mikroscope_softnet_burst_samples_total` no: su peso es 1/600, una memoria de 60 s a 10 Hz y menos > por encima, así que la referencia de ráfagas del colector se estrecha cuanto más rápido muestrea > el agente; el agente dimensiona la suya a partir de su cadencia real. Una línea de interrupción se > poda de las familias top-K tras 36 000 muestras fuera de todo top-K: una hora a 10 Hz, 12 min a 50 > Hz, 6 min a 100 Hz. ## Dos trabajos de scrape El panel de Prometheus espera dos trabajos: el colector, que tiene todas las familias del agente más las suyas, y el propio agente, con un relabel `keep` que lo deja en las familias que solo el muestreador puede producir — sus histogramas de temporización de ticks, los contadores de disparos y capturas, los ticks perdidos: ```yaml - job_name: "mikroscope" scrape_interval: 5s static_configs: [{ targets: [":9124"] }] - job_name: "mikroscope-agent" scrape_interval: 5s static_configs: [{ targets: ["172.30.10.2:9123"] }] metric_relabel_configs: - source_labels: [__name__] regex: "mikroscope_(tick_.*|trigger_.*|capture.*|captures_held|slipped_total)" action: keep ``` Hacer scrape del agente sin la lista `keep` duplicaría cada contador que el colector también expone. Apunta Prometheus a la máquina del colector, o directamente al agente si puede llegar a la veth. ### Lo que solo puede decir el agente El colector no renderiza `mikroscope_slipped_total` ni `mikroscope_tick_interval_seconds`, `mikroscope_tick_wake_latency_seconds` o `mikroscope_tick_read_seconds`: nunca ejecutó el muestreador, y un 0 ahí sería una afirmación sobre un temporizador que no es suyo. El índice de capturas del agente y sus contadores `mikroscope_trigger_*` también viven en el agente; el colector cuenta los marcadores de disparo que vio en `mikroscope_collector_triggers_total{cause}`. ## Lo que añade el colector ### Los contadores propios del colector | Familia | Tipo | Lleva | | --------------------------------------------- | ------- | -------------------------------------------------------------------------------------- | | `mikroscope_collector_gaps_total` | counter | huecos del anillo que vio el colector: muestras perdidas entre extracciones | | `mikroscope_collector_triggers_total{cause}` | counter | disparos de captura que lanzó el agente, por causa; presente en cuanto se ha visto uno | | `mikroscope_collector_detections_total{rule}` | counter | eventos de detección por regla, cada una de las once reglas a 0 desde el primer scrape | | `mikroscope_collector_bursts_total` | counter | muestras que la etapa de derivación marcó como ráfaga por debajo de la muestra | Las detecciones y las ráfagas son contadores para que quien solo usa Prometheus se entere de un evento aunque se pierda un scrape, y cada regla se renderiza a 0 desde el principio porque una familia que solo aparece tras su primer evento no se puede leer como «ninguno hasta ahora». ### La etapa de derivación | Familia | Tipo | Lleva | | -------------------------------------------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_derived_memory_pressure` | gauge | la escalera de escalada del asignador en la muestra más reciente, de 0 a 4 | | `mikroscope_derived_cycles_per_packet` | gauge | ciclos de PMU por paquete procesado, sumados sobre los núcleos; ausente sin PMU, en una muestra sin paquetes o tras un reinicio de contador | | `mikroscope_derived_instructions_per_packet` | gauge | instrucciones de PMU por paquete, mismas condiciones | | `mikroscope_derived_cache_misses_per_packet` | gauge | fallos de caché de PMU por paquete, mismas condiciones | | `mikroscope_derived_packets_per_interrupt` | gauge | paquetes por interrupción de dispositivo; ausente cuando la fila del temporizador no estaba en el top-K de la muestra | | `mikroscope_derived_fastpath_share{interface,direction}` | gauge | proporción por fast path del tráfico que la interfaz entrega a la CPU, entre las dos últimas lecturas de contadores; no es una proporción del cable; solo `rx` mientras `fp-tx-byte` no haya contado nunca | Son los valores de la muestra más reciente, un nivel en el momento del scrape; la serie completa está en los almacenes que guardan cada muestra. Lo que significa cada uno, y cuándo se omite, está en [lo que deriva el colector](/mikroscope/es/sinks/derive/). ### La capa de la API Solo presentes cuando la capa de la API ha entregado una muestra; con `--api-mode off` o sin credenciales de la API no existe ninguna de estas familias. | Familia | Tipo | Lleva | | ------------------------------------------------------------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `mikroscope_api_up` | gauge | 1 mientras la capa de la API entrega muestras | | `mikroscope_api_cpu_load` | gauge | el `cpu-load` de RouterOS desde `/system/resource` | | `mikroscope_api_memory_bytes{kind}` | gauge | `free` y `total` desde `/system/resource` | | `mikroscope_api_uptime_seconds` | gauge | uptime de RouterOS | | `mikroscope_api_core_percent{cpu,kind}` | gauge | porcentaje `load`, `irq` y `disk` por núcleo desde `/system/resource/cpu` | | `mikroscope_api_health{name}` | gauge | cada lectura de `/system/health` | | `mikroscope_api_interface{interface,kind}` | gauge | `rx_bps`, `tx_bps`, `rx_pps`, `tx_pps` de `monitor-traffic`, más cada tasa de pérdidas que devolvió el router | | `mikroscope_api_interface_info{interface,label,type,role,bridge,default_name}` | gauge | siempre 1; una serie por interfaz salida del inventario de configuración: su comentario, el tipo de RouterOS, sus listas de interfaces, su bridge y su nombre de fábrica | | `mikroscope_api_interface_counter_total{interface,counter}` | counter | cada contador acumulado por puerto que devolvió el router, con el nombre de contador del propio RouterOS | | `mikroscope_api_conntrack_entries` | gauge | el número de conexiones, cuando `--conntrack-every` lo pide | `mikroscope_api_up` nunca se renderiza como 0: antes de la primera muestra de la API, y cuando la capa está apagada, la familia no existe. El número de conexiones, los contadores de puerto y las proporciones del fast path llegan con cadencias más lentas que el scrape. El colector guarda el último valor de cada uno entre lecturas, así que un scrape que cae entre dos lecturas sigue viendo la familia en vez de una serie que aparece y desaparece. ### Qué es cada interfaz La capa de la API lee qué es cada interfaz — su comentario, el tipo de RouterOS, sus listas de interfaces, el bridge del que es puerto, su nombre de fábrica y su MTU — con tres lecturas solo de configuración al arrancar el colector y de nuevo cada `--labels-every` (5 min por defecto). En `/metrics` ese inventario es una serie info por interfaz. Nada de eso es etiqueta de las series de tasas ni de contadores: el comentario lo edita una persona, y una etiqueta cambiada abriría una serie nueva para cada tasa y para cada uno de los sesenta y pico contadores de ese puerto en cada edición. Únelo en la consulta: ```text mikroscope_api_interface_counter_total * on(interface) group_left(label, type, role) mikroscope_api_interface_info ``` Toda interfaz del inventario tiene serie, tenga comentario o no; un valor vacío de `label` es como Prometheus escribe «ninguno», así que el juego de etiquetas es el mismo en todas. `type` dice qué significan los contadores de esa interfaz: un puerto `ether` de un bridge cuenta su cable, incluidas las tramas que el chip de switching reenvió por hardware, mientras que el `bridge` cuenta su lado de CPU. Ninguno es un subconjunto del otro — en el RB5009 de referencia (RouterOS 7.24.2, 2026-09-16) ether1 recibió 255,8 GB en el cable y entregó 29,7 GB a la CPU — así que no sumes un puerto y su bridge. `mikroscope_api_interface_counter_total` tiene una serie por puerto y contador que el router informa: 9 puertos × unos 60 contadores en el RB5009 de referencia. Un contador que un puerto no informa no tiene serie, y una tasa de pérdidas que el router no devolvió no tiene `kind`. Las claves que se leen como enteros pero no cuentan nada — `mtu`, `actual-mtu`, `l2mtu`, `max-l2mtu`, `sfp-shutdown-temperature` — son tamaños y configuración y no tienen serie de contador; el MTU forma parte del inventario. En el RB5009 con RouterOS 7.24.2 (2026-09-15) `monitor-traffic` devuelve `rx-drops`, `tx-drops` y `tx-queue-drops` y ninguna clave de errores. ## Familias de datos del equipo La exposición del colector lleva las mismas familias de datos del equipo que el propio `/metrics` del agente, a partir de lo que obtuvo de `/capabilities`: `mikroscope_device_info`, los techos que publica la placa (`mikroscope_thermal_critical_celsius`, `mikroscope_thermal_polling_seconds`, `mikroscope_cpu_frequency_limit_hertz`, `mikroscope_cpu_frequency_step_hertz`, `mikroscope_cpu_frequency_governor_info`, `mikroscope_cpu_frequency_cluster`, `mikroscope_self_cgroup_memory_max_bytes`) y `mikroscope_source_cadence_hz{source,reason}`. Consulta [el flujo de datos del equipo](/mikroscope/es/sinks/device-info/). > **No medido, luego no afirmado** > > Un Prometheus 3.14 ha hecho scrape de la exposición del colector cada 5 s con el RB5009 > alimentando el colector, el 2026-09-12 y de nuevo el 2026-09-15. No consta ningún otro intervalo > de scrape ni ninguna otra versión de Prometheus. ## Véase también - [Familias de métricas de Prometheus](/mikroscope/es/reference/metrics/): cada familia que renderizan el agente y el colector, con sus etiquetas. - [Importar y comprobar](/mikroscope/es/dashboards/import-and-check/): el panel que alimentan estos dos trabajos de scrape, y cómo comprobarlo panel a panel. - [Lo que deriva el colector](/mikroscope/es/sinks/derive/): qué significan los gauges `mikroscope_derived_*` y cuándo faltan. - [El colector](/mikroscope/es/sinks/): lo que hace una ejecución de `forward` antes de que nada llegue a `/metrics`. --- # InfluxDB 3 El destino de protocolo de líneas de InfluxDB 3 — la URL de escritura y el token, cómo se entregan y se descartan los lotes, cada medida que escribe y lo que rechaza InfluxDB 3 Core. Source: https://jmrplens.github.io/mikroscope/es/sinks/influxdb/ `--influx URL` escribe la línea temporal fusionada como protocolo de líneas de InfluxDB en el `/api/v3/write_lp` de InfluxDB 3. Es el destino que conserva cada muestra a la cadencia del agente, y el que lee el panel de InfluxDB. Esta página responde a cómo apuntarlo a una base de datos, qué hace cuando la base de datos va lenta o rechaza, y qué medidas llegan allí. ## La URL de escritura y el token ```sh export MIKROSCOPE_INFLUX_URL="http://host:8181/api/v3/write_lp?db=mikroscope&precision=nanosecond" export MIKROSCOPE_INFLUX_TOKEN=… mikroscope forward --influx "$MIKROSCOPE_INFLUX_URL" --host-tag rb5009 ``` La opción toma su valor por defecto de `MIKROSCOPE_INFLUX_URL`. El token se lee solo de `MIKROSCOPE_INFLUX_TOKEN`, nunca de una opción, y se envía como `Authorization: Bearer `. > **Pon la URL entre comillas** > > Cuando la URL está en un fichero que cargas con `source`, ponla entre comillas: `&` es un operador > del shell, y un `…?db=mikroscope&precision=nanosecond` sin comillas se corta en el `&`. ## Entrega - **Un lote por segundo.** Cada evento se renderiza en el lote actual según llega; una goroutine lo pasa a la cola y lo envía una vez por segundo, con un tiempo de espera de 10 s por envío. - **Una cola acotada.** `--queue-seconds` (60 por defecto) × 64 KiB. Pasado eso se descarta y se cuenta el lote más antiguo; el más nuevo nunca se descarta. - **Espera entre reintentos.** Un envío fallido espera 2 s, doblando hasta 60 s, y registra como mucho una línea por minuto. Una respuesta que no es 2xx es un error, con los primeros 512 bytes del cuerpo en esa línea. - **Sin reenvío silencioso.** Una petición cuya conexión reutilizada resulta estar muerta se informa como error y la reintenta el destino, en vez de reenviarla el cliente HTTP por su cuenta. Ese reenvío automático de un lote que el servidor ya había confirmado es la explicación principal, sin verificar, de las filas duplicadas de la ejecución nocturna del 2026-09-13: una ejecución a 50 Hz que escribió números de secuencia duplicados en InfluxDB. No se ha medido si el reintento del propio destino los evita. Una muestra del kernel a 10 Hz se renderiza en unos 1,2 KiB de protocolo de líneas, medido en el RB5009 (RouterOS 7.24.2, kernel 5.6.3, 2026-09-12). Así que un segundo de presupuesto guarda unas 50 muestras — unos 5 s de atraso a 10 Hz — y los 60 s por defecto guardan unos 5 minutos. No medido por encima de 10 Hz, ni vuelto a medir contra el conjunto de fuentes actual. Los contadores van en lotes: `written` un lote que InfluxDB aceptó, `dropped` un lote expulsado, `errors` un intento fallido. ## Medidas Cada medida se llama `mikroscope_` y lleva `host=<--host-tag>`. Las filas de la capa del kernel se marcan con el reloj de pared del agente en nanosegundos; la tabla indica las excepciones. Las listas de campos están en [medidas de InfluxDB y SQL](/mikroscope/es/reference/measurements/). ### La capa del kernel | Medida | Tags | Lleva | | --------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_cpu` | `cpu` | los deltas de ticks por modo, `busy_ratio` y el intervalo real de la muestra `dt_ns` | | `mikroscope_cpufreq` | `cpu` | `khz`, con el `max_khz` del núcleo en la misma fila donde se publica | | `mikroscope_softnet` | `cpu` | deltas de `processed`, `dropped`, `time_squeeze` | | `mikroscope_irq` | `irq`, `name` | la cuenta de interrupciones sumada sobre las CPU, para las fuentes del top-K | | `mikroscope_irq_cpu` | `irq`, `name`, `cpu` | la misma cuenta por CPU, solo filas con delta distinto de cero | | `mikroscope_softirq` | `kind`, `cpu` | deltas de softirq por vector y CPU, solo distintos de cero | | `mikroscope_sample` | — | `seq`, `dt_ns`, `mono_ns` | | `mikroscope_stat` | — | los deltas de `/proc/stat`: `ctxt`, `intr`, `forks`, `irq_total`, `irq_err` | | `mikroscope_mem` | — | niveles de `/proc/meminfo`, en `_kb` | | `mikroscope_load` | — | medias de carga, `running`, `threads`, `procs_blocked` | | `mikroscope_vm` | — | deltas de contadores de `/proc/vmstat`: fallos, escaneos y robos de reclaim, bloqueos, `oom_kill`, swap | | `mikroscope_vm_level` | — | niveles de `/proc/vmstat`: `nr_free_pages`, `nr_dirty`, `nr_writeback`, páginas de slab | | `mikroscope_buddy` | `node`, `zone` | bloques libres por orden y `free_pages`, en las muestras en las que cambiaron las listas libres | | `mikroscope_self` | — | la CPU, el RSS, la memoria de cgroup y `memory.max` del propio agente; sus eventos de cgroup donde se leyó cgroup2; `resets`; `kmsg_dropped` | | `mikroscope_psi` | — | microsegundos de bloqueo, solo donde el kernel tiene PSI — no en el RB5009 | | `mikroscope_thermal` | `zone` | `celsius`, con el `critical_celsius` de la zona donde declara uno | | `mikroscope_slab` | `cache` | objetos activos, con `limit` donde el kernel publica uno (`nf_conntrack`) | | `mikroscope_mtd` | `device`, `partition` | contadores ECC de la flash tal como se leen, con los umbrales de la partición donde se publican | | `mikroscope_flash` | `device` | escrituras, lecturas y borrados de páginas YAFFS, GC; niveles `bad_blocks` y `free_chunks` | | `mikroscope_disk` | `device` | deltas de lectura y escritura del dispositivo de bloques, `io_s`, `inflight` | | `mikroscope_perf` | `counter`, `cpu` | cuenta de PMU, con `enabled_ns` y `running_ns` en la misma fila | | `mikroscope_kmsg` | `level`, `port`, `kind`, `label`, `role` | una cuenta de registros del log del kernel por nivel, no el texto; un registro que nombra un puerto se cuenta por nivel, puerto y tipo, con su `label` y su `role` donde se conocen | | `mikroscope_derived` | — | los valores de la etapa de derivación junto a la muestra | Los contadores se escriben como deltas sin signo; los niveles, como el valor absoluto que informó el kernel. Son medidas separadas donde una fuente tiene ambos (`mikroscope_vm` frente a `mikroscope_vm_level`), porque un delta es una tasa de eventos y un nivel es una profundidad, y una sola medida invita a un panel a sumar un nivel o a sacar la tasa de un gauge. Una fuente que el despliegue no puede leer no escribe ninguna fila, nunca una fila a cero: PSI no existe en el kernel de referencia, y las filas de slab, del log del kernel, de MTD y de PMU necesitan `privileged=yes`. Las fuentes de nivel que el agente guarda al cambiar solo aparecen en las muestras que las llevan; consulta [cada fuente a su propio suelo](/mikroscope/es/limits/source-floors/). Un `running_ns` por debajo de `enabled_ns` en una fila de `mikroscope_perf` significa que esa cuenta es una estimación multiplexada y escalada a la baja. El texto del log del kernel va a [Loki](/mikroscope/es/sinks/other/#loki); lo que puede responder un almacén de métricas es cuándo empezó el router a producir avisos, en qué puerto y de qué tipo. `kind` es la clasificación de un registro que nombra un puerto: `link-up`, `link-down`, `stp-blocking` y sus hermanos (`listening`, `learning`, `forwarding`, `disabled`), `own-address` — el bridge recibió una trama con su propia MAC como dirección de origen, la firma de un bucle de capa 2 — u `other`. Un registro que no nombra ningún puerto mantiene la forma sin tags y lleva solo `level`. Un link-up normal va seguido de `stp-blocking`, `stp-learning` y `stp-forwarding` en su puerto del bridge: cuatro registros, no cuatro fallos. ### La capa de la API, y lo que añade el colector | Medida | Tags | Lleva | Reloj | | --------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------- | | `mikroscope_api_system` | — | `cpu_load`, `free_memory`, `total_memory`, `free_hdd`, `uptime_s` | colector + desfase | | `mikroscope_api_core` | `cpu` | el porcentaje `load`, `irq`, `disk` por núcleo de RouterOS | colector + desfase | | `mikroscope_api_health` | `name` | cada valor de `/system/health` | colector + desfase | | `mikroscope_api_iface` | `interface`, `label`, `type`, `role`, `bridge` | tasas de `monitor-traffic`, y cada tasa de pérdidas que devolvió el router | colector + desfase | | `mikroscope_api_ifcounters` | `interface`, `label`, `type`, `role`, `bridge` | cada contador acumulado por puerto que devolvió el router, con `-` convertido en `_` | colector + desfase | | `mikroscope_api_ifinfo` | `interface`, `label`, `type`, `role`, `bridge` | `default_name` y `mtu`: qué es cada interfaz, al arrancar y cada `--labels-every` | colector + desfase | | `mikroscope_api_conntrack` | — | `entries`, cuando `--conntrack-every` lo pide | colector + desfase | | `mikroscope_derived_iface` | `interface` | la proporción del fast path de lo que la interfaz entrega a la CPU, junto a los deltas de bytes de los que salió | colector + desfase | | `mikroscope_gap` | — | `from`, `to`: el rango de secuencia que nunca llegó | colector, al notarlo | | `mikroscope_detection` | `rule`, `key` | `value`, `threshold`, `seq`, `message` | la muestra que la provocó | | `mikroscope_trigger` | `cause` | `id`, `seq`, `value`, `threshold`, `field`: la captura está en el agente | el agente, al dispararse | | `mikroscope_device` | `board`, `kernel` | `cores`, `privileged`, `cgroup`, `sources`, `hash`, y los techos que publica la placa | colector | | `mikroscope_device_thermal` | `zone` | `critical_celsius`, `polling_ms` | colector | | `mikroscope_device_cpufreq` | `cpu` | `cluster`, `min_khz`, `max_khz`, `governor`, `steps` | colector | | `mikroscope_device_cadence` | `source`, `reason` | `hz` | colector | Cada fila de una interfaz lleva qué es esa interfaz: `label` es su comentario de RouterOS, `type` es el tipo propio de RouterOS (`ether`, `bridge`, `vlan`, `pppoe-out`, `wg`, `veth`, `loopback`), `role` son sus listas de interfaces ordenadas y unidas por comas (`WAN`, `LAN,VPN` — un miembro de un bridge que no está en ninguna lista propia toma las listas de su bridge, que es como las reglas de firewall de RouterOS lo emparejan) y `bridge` es el bridge del que es puerto. Cada uno se omite en vez de enviarse vacío, para que la clave de la serie de una interfaz que no lo tiene siga estable, y para que un panel pueda separar los puertos de cable (`type=ether`) del lado de CPU del bridge (`type=bridge`), o la WAN de la LAN. Un campo de pérdidas solo se escribe cuando el router devolvió esa clave. `mikroscope_api_ifinfo` lleva esos cinco tags para cada interfaz que leyó el colector, esté o no en la lista de `--interfaces`, con `default_name` (el nombre de fábrica de un puerto físico, una cadena vacía para un bridge, una VLAN o un túnel) como campo de texto y `mtu` donde el router informa de uno mayor que cero. Se escribe una vez antes del primer sondeo del kernel y de nuevo en cada relectura de `--labels-every`, 5 minutos por defecto, y es la tabla con la que un panel cruza para decir qué es una interfaz. `mikroscope_api_ifcounters` lleva solo contadores: `mtu`, `l2mtu`, `max-l2mtu` y `sfp-shutdown-temperature` se leen como enteros, pero son tamaños y configuración, no cuentas, así que no son campos ahí; la MTU está en `mikroscope_api_ifinfo`. El `fp_rx_share` de `mikroscope_derived_iface` es la proporción del fast path del tráfico que una interfaz entrega a la CPU: `fp-rx-byte` sobre `driver-rx-byte` en un puerto del switch, cuyo `rx-byte` es el total del cable, y sobre `rx-byte` en una interfaz por software. No es una proporción del cable: las tramas que el chip del switch reenvía por hardware no están en ninguno de los dos números. `fp_tx_share` se retiene, y el denominador `tx_bytes` que lo acompaña se escribe como 0, mientras el `fp-tx-byte` acumulado sea 0, que es lo que era en todas las interfaces del RB5009 de referencia el 2026-09-16 tras cientos de GB transmitidos. ## InfluxDB 3 Core rechaza cosas Aprendido a base de golpes, contra InfluxDB 3 Core: - **Un nodo guarda como mucho cinco bases de datos.** Una sexta escritura falla con 422 — el primer destino real, el 2026-09-12, respondió `422: would exceed limit of 5 databases`. El destino espera y sigue intentándolo, así que el síntoma es una cuenta de `errors` que sube, no una ejecución fallida. Las ejecuciones medidas usaron una instancia de InfluxDB 3 Core propia. - **Toda consulta debe estar acotada en el tiempo.** - **El tipo de una columna no se puede cambiar una vez escrito.** - **Una columna solo existe cuando una fila la ha llevado.** `mikroscope_kmsg` no tiene columna `kind` hasta que se escribe el primer registro del log del kernel que nombra un puerto, y una consulta que filtre por ella antes falla al planificarse — por eso la forma SQL de la regla de alerta del bucle de capa 2 necesita un almacén que ya haya guardado un registro de puerto clasificado. El datasource de Grafana para InfluxDB 3 necesita dos campos seguros, no uno; consulta [importar y comprobar](/mikroscope/es/dashboards/import-and-check/). > **No medido, luego no afirmado** > > Todas las ejecuciones registradas escribieron en InfluxDB 3 Core; no consta ninguna escritura en > el `/api/v2/write` de InfluxDB 2 ni en InfluxDB 3 Enterprise. Las filas duplicadas del 2026-09-13 > tienen una explicación principal y sin verificar, y desactivar el reenvío propio del cliente HTTP > no se ha medido contra una repetición de aquella ejecución. ## Véase también - [Medidas de InfluxDB y SQL](/mikroscope/es/reference/measurements/): cada campo de cada medida, con su unidad. - [Importar y comprobar](/mikroscope/es/dashboards/import-and-check/): el panel de InfluxDB, su datasource y la sonda del almacén. - [El colector](/mikroscope/es/sinks/): la cola, la espera entre reintentos y los contadores que comparten todos los destinos en cola. - [El flujo de datos del equipo](/mikroscope/es/sinks/device-info/): lo que guardan las cuatro medidas `mikroscope_device*`. --- # El fichero y los demás destinos El fichero JSONL, la salida estándar, SQL, Loki, OTLP, Graphite, Elasticsearch y Telegraf — qué lleva cada uno, cómo entrega y lo que su protocolo no puede prometer. Source: https://jmrplens.github.io/mikroscope/es/sinks/other/ Además de [Prometheus](/mikroscope/es/sinks/prometheus/) e [InfluxDB 3](/mikroscope/es/sinks/influxdb/), `forward` escribe en ocho destinos más. Esta página responde, para cada uno, qué partes de la línea temporal recibe, cómo se le entregan y qué no te puede decir por culpa del protocolo del destino. La cola, la espera entre reintentos y los contadores que comparten están en [el colector](/mikroscope/es/sinks/). ## Lo que recibe cada destino | Destino | Muestras del kernel | Texto del log del kernel | Capa de la API | Derivados y detecciones | Disparos, huecos, equipo | | ------------- | -------------------------- | ------------------------------------------ | ------------------------------------- | ------------------------------------------------------ | ------------------------- | | file | líneas del agente tal cual | dentro de las líneas de muestra | líneas `{"api":…}` | `{"derived":…}`, `{"detection":…}` | los tres, como líneas | | stdout `json` | líneas del agente tal cual | dentro de las líneas de muestra | líneas `{"api":…}` | como el fichero | los tres, como líneas | | stdout `lp` | las medidas de InfluxDB | cuentas por nivel, puerto y tipo de evento | las medidas de InfluxDB | las medidas de InfluxDB | los tres | | SQL | una tabla por fuente | filas `mikroscope_event` | tablas, más `mikroscope_api_error` | `mikroscope_derived`, `mikroscope_detection` | los tres | | Loki | no | una línea por registro | solo errores por orden | solo detecciones | los tres, como líneas | | OTLP | sums y gauges | no | gauges, y una cuenta de errores | gauges, un sum de detecciones | los tres | | Graphite | una ruta por valor | no | rutas | rutas | solo las partes numéricas | | Elasticsearch | un documento por tick | un documento por registro | un documento por lectura, sin errores | en el documento del kernel, un documento por detección | los tres | | Telegraf | las medidas de InfluxDB | cuentas por nivel, puerto y tipo de evento | las medidas de InfluxDB | las medidas de InfluxDB | los tres | Los registros del log del kernel solo existen cuando el contenedor corre con `privileged=yes`; consulta [lo que aporta privileged](/mikroscope/es/limits/privileged/). ## El fichero ```sh mikroscope forward --file timeline.jsonl ``` El fichero se abre y se **trunca**, con modo `0600`. Cada línea es un objeto JSON, y su primera clave dice qué es: - una muestra del kernel — la propia línea del agente, byte a byte, tal como la sirvió `/snapshot`; - `{"trigger":…}` — el marcador de captura del agente, también tal cual; - `{"derived":…}` — los valores de la etapa de derivación, en la línea siguiente a la muestra a la que pertenecen, para que un lector que solo quiere muestras en bruto se salte ese tipo; - `{"detection":…}`, `{"device":…}`, `{"api":…}` y `{"gap":…}`. Las escrituras son síncronas a través de un búfer de 64 KiB, y los contadores van en eventos. Una escritura que el sistema de ficheros rechaza cuenta un error y un descarte. ## Salida estándar ```sh mikroscope forward --stdout=lp | telegraf --config … mikroscope forward --stdout=json | jq ``` `--stdout lp` renderiza protocolo de líneas de InfluxDB con el propio codificador del destino de InfluxDB, así que una tubería muestra exactamente lo que enviaría `--influx`. `--stdout json` escribe los mismos tipos de línea que el fichero. Cualquier otro valor se rechaza antes de que empiece la ejecución. La salida estándar se puede atascar — un lector lento llena el búfer de la tubería y la escritura se bloquea sin nada que la acote — así que este destino va en cola: un lote por segundo, cada lote escrito en una sola llamada de líneas completas para que un lector nunca vea un registro a medias, 64 KiB × `--queue-seconds` de presupuesto, descartando primero lo más antiguo. Un lote que un lector atascado deja crecer por encima del presupuesto entero se cierra antes. Ese presupuesto se dimensionó para una muestra en protocolo de líneas de unos 1,2 KiB; **no** se ha medido para `json`, cuyas líneas llevan fuentes que el protocolo de líneas omite y son más grandes. - Una tubería rota no llega a los contadores: Go deja `SIGPIPE` sin capturar en la salida estándar, así que el proceso termina. - Al cerrar, el destino espera como mucho 3 s a que el lector acepte el último lote, así que un lector que dejó de leer no puede colgar `forward`. - `forward` imprime también su resumen de salida por la salida estándar, así que una tubería hacia `telegraf` termina cada ejecución con líneas que el consumidor no puede interpretar. ## SQL para PostgreSQL y TimescaleDB ```sh mikroscope forward --sql out.sql --for 10m && psql -f out.sql mikroscope forward --sql - | psql # mira el aviso de abajo ``` `--sql` escribe texto PostgreSQL — una cabecera DDL y luego un `INSERT` por fila — a un fichero, o a la salida estándar con `-`. No usa driver a propósito: hablar el protocolo de red de PostgreSQL necesita un driver de terceros, así que el texto SQL es la interfaz y `psql` es dueño de la conexión. El coste es que el destino no puede saber si una fila se guardó; cuenta los eventos que escribió. La cabecera se puede aplicar por sí sola y es idempotente: - `SET standard_conforming_strings = on;` — para que una barra invertida en un mensaje del kernel nunca se convierta en un escape y se trague las sentencias siguientes; - `CREATE TABLE IF NOT EXISTS` para cada tabla, cada una con una clave primaria que empieza por `time, host`; - con `--sql-hypertable`, una llamada `create_hypertable` de TimescaleDB sobre `time` para cada tabla, con `if_not_exists => TRUE`. Cada `INSERT` termina en `ON CONFLICT DO NOTHING`, así que aplicar el mismo fichero dos veces no hace nada en vez de abortar por clave duplicada. Una fila es un instante inmutable de un delta de contador, nunca un total acumulado que un fichero posterior corrija. | Tabla | Clave tras `time, host` | Guarda | | ---------------------------------------------------------------------------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------- | | `mikroscope_cpu` | `cpu` | `user_ticks` … `steal_ticks`, `busy_ratio`, `dt_ns` | | `mikroscope_softnet` | `cpu` | `processed`, `dropped`, `time_squeeze` | | `mikroscope_irq` | `irq` | `name`, `count` sumado sobre las CPU | | `mikroscope_mem` | — | niveles: `free_kb`, `available_kb`, `cached_kb`, `slab_kb`, `sunreclaim_kb` | | `mikroscope_load` | — | niveles: medias de carga, `running`, `threads`, `procs_blocked` | | `mikroscope_stat` | — | deltas: `ctxt`, `intr`, `forks`, `irq_total`, `irq_err`, `pgfault`, `pgmajfault` | | `mikroscope_self` | — | delta `cpu_us`; niveles `rss`, `cgroup_mem`; eventos de cgroup, NULL sin cgroup2; `seq` | | `mikroscope_buddy` | `node, zone, block_order` | `free_blocks`, una fila por zona y orden | | `mikroscope_mtd` | `device` | `partition`, contadores ECC tal como se leen, umbrales NULL donde no se publican | | `mikroscope_psi` | — | microsegundos de bloqueo; ninguna fila donde el kernel no tiene PSI | | `mikroscope_thermal` | `zone` | `celsius`, `critical_celsius` | | `mikroscope_slab` | `cache` | `active_objs`, `limit_objs` (NULL para toda caché salvo `nf_conntrack`) | | `mikroscope_disk` | `device` | deltas de lectura y escritura, `io_s`; `inflight` es un nivel | | `mikroscope_flash` | `device` | deltas de desgaste; `bad_blocks` y `free_chunks` son niveles | | `mikroscope_event` | `kernel_seq` | un registro del log del kernel: `level`, `facility`, `time_usec`, `message`, y `port`, `kind` | | `mikroscope_api_system` | — | `cpu_load`, memoria, `free_hdd`, `uptime_s`, `version` | | `mikroscope_api_core` | `cpu` | el porcentaje `load`, `irq`, `disk` de RouterOS | | `mikroscope_api_health` | `name` | `value` | | `mikroscope_api_iface` | `interface` | `label`, tasas, y cinco columnas de pérdidas que son NULL donde el router no devolvió la clave | | `mikroscope_api_conntrack` | — | `entries`, el último valor repetido a la cadencia de la API | | `mikroscope_api_ifinfo` | `interface` | qué es cada interfaz: `default_name`, `type`, `role`, `bridge`, `label`, `mtu` | | `mikroscope_api_ifcounter` | `interface, counter` | `value`, en formato largo, con el nombre de contador del propio RouterOS | | `mikroscope_api_error` | `message` | una por cada orden de la API que falló | | `mikroscope_gap` | `seq_from, seq_to` | el rango perdido, con el reloj del colector | | `mikroscope_trigger` | `id` | `cause`, `field`, `value`, `threshold`, `seq` | | `mikroscope_derived` | — | `seq`, `mem_pressure`, `burst`, `suspect`, valores por paquete NULL donde no se calcularon | | `mikroscope_derived_iface` | `interface` | los cuatro deltas de bytes y las dos proporciones del fast path | | `mikroscope_detection` | `rule, key` | `seq`, `value`, `threshold`, `message` | | `mikroscope_device`, `mikroscope_device_thermal`, `mikroscope_device_cpufreq`, `mikroscope_device_cadence` | —, `zone`, `cpu`, `source` | [el flujo de datos del equipo](/mikroscope/es/sinks/device-info/) | Las columnas nunca necesitan comillas: las columnas de ticks son `user_ticks` y compañía porque `user` es una palabra reservada, y `block_order` porque `order` lo es. `dt_ns` va solo en `mikroscope_cpu`, así que una tasa sobre cualquier otra tabla de deltas se une a `mikroscope_cpu` por `(time, host)` para tener el intervalo real en vez de suponer el periodo nominal. `mikroscope_api_ifinfo` guarda una fila por interfaz, escrita al arrancar el colector y en cada relectura de `--labels-every` (5 minutos por defecto), así que una consulta la une por `interface` para darle a cualquier serie de interfaz un tipo, un rol y el comentario del puerto. `default_name` es el nombre de fábrica de un puerto físico y está vacío para un bridge, una VLAN o un túnel; `mtu` es el `actual-mtu` de RouterOS, NULL donde el router no lo publica. En `mikroscope_event`, `port` es el puerto que nombra el registro — su nombre actual en RouterOS cuando el inventario de la capa de la API lo da, y el nombre por defecto de la placa si no — y `kind` es lo que le pasó: `link-up`, `link-down`, los estados `stp-*`, `own-address` (el bridge recibió una trama con su propia MAC como origen, la firma de un bucle de capa 2) u `other`. Ambas son NULL para un registro que no nombra ningún puerto. Una fuente que el despliegue no puede leer no emite ninguna fila. Un valor que no se midió es NULL, nunca 0: RouterOS 7.24.2 no devuelve ninguna clave de errores de interfaz, y un 0 ahí afirmaría una medida que nunca se hizo. El texto se fuerza donde PostgreSQL lo rechazaría — un byte NUL se elimina, el UTF-8 inválido pasa a U+FFFD — y un float NaN o infinito pasa a NULL. `TIMESTAMPTZ` resuelve a 1 µs, así que dos muestras más cercanas que eso chocarían en la clave primaria; a 10 Hz están a 100 ms. Comparado con InfluxDB, el destino SQL lleva menos fuentes: no hay tablas de frecuencia de CPU, PMU, softirq, niveles de `/proc/vmstat`, interrupciones por CPU ni cuentas del log del kernel; no hay tabla de contadores de `/proc/vmstat` más allá de `pgfault` y `pgmajfault` (que van en `mikroscope_stat`, así que faltan los deltas de `pgscan_*`, `pgsteal_*`, `pgalloc`, `pgfree`, `allocstall`, `compact_stall`, `oom_kill`, `pswpin` y `pswpout`); no hay `mikroscope_sample`, y `mikroscope_mem` es más estrecha. Lleva dos que InfluxDB no tiene: el texto del log del kernel en `mikroscope_event`, y los errores de la capa de la API. > **Una tubería hacia psql puede bloquear el colector** > > El destino SQL es síncrono, sin cola. Con `--sql -` alimentando `psql`, un `psql` que se retrasa — > cada `INSERT` es su propia transacción y su propio commit — llena la tubería, y la siguiente > escritura bloquea el bucle de extracción del colector en vez de descartar. No medido. Escribe un > fichero y aplícalo después, o envuelve el fichero en `BEGIN`/`COMMIT` a mano. Tamaño, según el propio fixture de pruebas del destino el 2026-09-12 — dos núcleos, una cola softnet, una interrupción, sin fuentes privilegiadas — no según el equipo: un evento del kernel se renderiza en 1 375 B de SQL y un evento de la API en 1 138 B, así que 10 Hz más la capa de la API a 1 Hz son unos 14 KiB/s de fichero tras una cabecera de 5,6 KiB. Los mismos dos eventos en protocolo de líneas son 716 B y 608 B, unas 1,9× más pequeños, aunque parte de eso es contenido que las filas SQL llevan y el protocolo de líneas no. Con las fuentes privilegiadas presentes el evento del kernel crece hasta 2 749 B. Esa cabecera es la del fixture: la de las treinta y dos tablas que declara el destino, calculada a partir de las cadenas del esquema y no medida, son 7 757 B, unos 7,6 KiB. ## Loki ```sh export MIKROSCOPE_LOKI_URL=http://host:3100/loki/api/v1/push mikroscope forward --loki "$MIKROSCOPE_LOKI_URL" --loki-tenant team-a ``` Loki recibe los **eventos** de la línea temporal, no sus muestras. Una muestra es una medida y pertenece a un almacén de métricas; 10 Hz de números en un almacén de logs son una copia más lenta y más grande. Lo que llega es lo que pasó una vez, en un momento conocido. Un token bearer sale de `MIKROSCOPE_LOKI_TOKEN`, y `--loki-tenant` (`MIKROSCOPE_LOKI_TENANT`) fija `X-Scope-OrgID` para un Loki multi-tenant. Los streams llevan tres etiquetas y ninguna más — `host`, `source` y `level` — porque Loki indexa las etiquetas y la cardinalidad es un coste: | `source` | `level` | Una línea por | | ----------- | ----------------------------------------- | ------------------------------------------------------------------------------------- | | `kmsg` | el del propio registro: `emerg` … `debug` | registro del log del kernel, marcado en su tick | | `gap` | `warn` | rango de secuencia perdido, marcado cuando el colector lo notó | | `api` | `err` | orden de la capa de la API que falló | | `detection` | `warn` | [detección](/mikroscope/es/sinks/detections/), en la muestra que la provocó | | `trigger` | `info` | disparo de captura, en el momento del disparo; la captura se queda en el agente | | `device` | `info` | registro de datos del equipo: board, kernel, cores, privileged, cgroup, sources, hash | La línea de un registro del kernel es el mensaje seguido de pares logfmt — `level`, `facility`, `prio`, `kseq`, `us` (los microsegundos del propio kernel desde el arranque), `seq` (la muestra) y, cuando el registro nombra un puerto, `iface` (el nombre del kernel), `ros_iface` (el nombre en RouterOS), `port_event` (lo que pasó: `link-up`, `link-down`, un estado `stp-*`, `own-address` u `other`), `label` (el comentario del puerto, entre comillas) y `role` (sus listas de interfaces). `ros_iface` es el nombre actual del puerto en RouterOS cuando el inventario de la capa de la API lo da, y el nombre por defecto de la placa si no; `label` y `role` salen también de ese inventario, así que una ejecución sin capa de la API no lleva ninguno de los dos. El puerto va en la línea, no en una etiqueta, porque un stream por puerto multiplica el número de streams por un campo que LogQL extrae bajo demanda: ```text {source="kmsg"} | logfmt | ros_iface="ether2" {source="kmsg"} | logfmt | port_event="own-address" ``` Cada registro se marca con el reloj de pared de su tick, nunca con su propia marca desde el arranque, que lo fecharía en 1970 más el uptime y Loki lo rechazaría. Los registros de un mismo tick están separados 1 ns, en orden creciente, porque un stream de Loki se ordena solo por marca de tiempo y en el equipo de referencia una pareja «blocking state» y luego «learning state» llega dentro de un mismo tick de 100 ms con el mismo nivel: su orden es la señal. El desplazamiento es como mucho 63 ns, acotado por el límite del agente de 64 registros por tick. Un push por segundo, 64 KiB × `--queue-seconds` de presupuesto. En el RB5009 (RouterOS 7.24.2, kernel 5.6.3, 2026-09-12) el log del kernel iba a 1,49 /s mientras el reflejo de capa 2 estaba activo y a 0,03 /s después de arreglarlo, y una línea renderizada mide unos 150 B con su envoltorio JSON, así que un segundo de presupuesto guarda horas de ese tráfico. No medido durante una tormenta del log del kernel. ## OTLP ```sh mikroscope forward --otlp http://collector:4318/v1/metrics ``` `--otlp` envía métricas de OpenTelemetry a un receptor OTLP/HTTP en la codificación JSON, una petición por segundo. Un token bearer sale de `MIKROSCOPE_OTLP_TOKEN`. JSON en vez de protobuf es una decisión de dependencias: protobuf añadiría un generador de código y un runtime, y todo receptor OTLP acepta `application/json` en el mismo endpoint. El recurso lleva `host.name` (la etiqueta de host) y `service.name=mikroscope`; el scope lleva la versión del colector. El mapeo es la razón de que este destino sea barato de consumir. mikroscope envía deltas en bruto, y OTLP tiene un sitio exacto para ellos: cada contador es un **Sum** con `AGGREGATION_TEMPORALITY_DELTA` e `isMonotonic=true`. En los contadores de la muestra del kernel, `startTimeUnixNano` = el reloj de pared de la muestra menos su intervalo real, y `timeUnixNano` = su reloj de pared — así al receptor se le dice el intervalo que cubre cada delta en vez de que adivine el nominal. Las demás sumas no llevan intervalo: `mikroscope.api.errors`, `mikroscope.collector.gaps` y `mikroscope.collector.gap.samples` no tienen hora de inicio, y `mikroscope.trigger.fired` y `mikroscope.detection` tienen una hora de inicio igual a su hora. Cada nivel es un **Gauge**. De la capa del kernel no hay nada dividido de antemano salvo `mikroscope.cpu.busy_ratio`; los gauges `mikroscope.derived.*` son la [etapa de derivación](/mikroscope/es/sinks/derive/) del colector. | Tipo | Métrica | Atributos | | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | Sum | `mikroscope.cpu.ticks` | `cpu`, `mode` | | Sum | `mikroscope.context_switches`, `mikroscope.interrupts`, `mikroscope.forks`, `mikroscope.self.cpu.time` | — | | Sum | `mikroscope.self.throttled_periods`, `mikroscope.self.throttled_time`, `mikroscope.self.oom_kills` (solo con cgroup2) | — | | Sum | `mikroscope.irq.count`; `mikroscope.irq.total`, `mikroscope.irq.errors` | `irq`, `name`; — | | Sum | `mikroscope.softnet`; `mikroscope.sched` | `cpu`, `kind` | | Sum | `mikroscope.softirq`; `mikroscope.vm.events` | `kind` | | Sum | `mikroscope.psi.stalled` | `resource`, `scope` | | Sum | `mikroscope.flash`, `mikroscope.disk`; `mikroscope.disk.io_time` (ms) | `device`, `kind`; `device` | | Sum | `mikroscope.api.errors`; `mikroscope.collector.gaps`, `mikroscope.collector.gap.samples` | `tier`; — | | Sum | `mikroscope.trigger.fired`; `mikroscope.detection` | `cause`; `rule` | | Gauge | `mikroscope.cpu.busy_ratio`, `mikroscope.cpu.frequency` | `cpu` | | Gauge | `mikroscope.sample.dt`, `mikroscope.sample.seq`, `mikroscope.threads`, `mikroscope.procs_blocked` | — | | Gauge | `mikroscope.memory` (KiB), `mikroscope.self.memory`, `mikroscope.vm.pages` | `kind` | | Gauge | `mikroscope.load` | `window` | | Gauge | `mikroscope.thermal.temperature` | `zone` | | Gauge | `mikroscope.slab.objects`, `mikroscope.slab.limit` | `cache` | | Gauge | `mikroscope.memory.buddy_free_blocks` | `node`, `zone`, `order` | | Gauge | `mikroscope.mtd.ecc`; `mikroscope.mtd.bitflip_threshold`, `mikroscope.mtd.ecc_strength` | `device`, `partition`, `kind`; `device`, `partition` | | Gauge | `mikroscope.flash.blocks`; `mikroscope.disk.io_in_progress` | `device`, `kind`; `device` | | Gauge | `mikroscope.api.cpu_load`, `mikroscope.api.uptime`; `mikroscope.api.memory` | —; `kind` | | Gauge | `mikroscope.api.core`; `mikroscope.api.health` | `cpu`, `kind`; `name` | | Gauge | `mikroscope.api.interface` | `interface`, `kind`, y `label`, `type`, `role` donde el inventario los tiene | | Gauge | `mikroscope.api.interface.counter`; `mikroscope.api.conntrack.entries` | `interface`, `counter`, y `label`, `type`, `role` donde el inventario los tiene; — | | Gauge | `mikroscope.derived.memory_pressure`, `mikroscope.derived.cycles_per_packet`, `….instructions_per_packet`, `….cache_misses_per_packet`, `….packets_per_irq` | — | | Gauge | `mikroscope.derived.fastpath_share` | `interface`, `direction` | | Gauge | `mikroscope.device.cores`; `mikroscope.device.thermal.critical`, `mikroscope.device.thermal.polling` (s) | `board`, `kernel`, `hash`; `zone` | | Gauge | `mikroscope.device.cpu.frequency_max`, `mikroscope.device.cpu.frequency_min`; `mikroscope.device.source_cadence` | `cpu`; `source`, `reason` | Los miembros de reclaim y swap de `mikroscope.vm.events` se omiten cuando valen cero: en un Sum de deltas, un punto ausente y un punto a cero significan lo mismo. Los contadores por puerto son gauges de un total acumulado, no Sums, porque el destino no tiene tiempo de inicio para un contador que RouterOS lleva desde el arranque. Un gauge cuyo valor es NaN o infinito se descarta. Los registros del log del kernel no se emiten: su sitio son los logs OTLP en `/v1/logs`, que este destino no implementa. Un **éxito parcial** de OTLP — un 2xx cuyo cuerpo rechaza algunos puntos — cuenta como escrito y se registra, no se reintenta. El rechazo es determinista (un receptor OTLP de Prometheus que rechaza un punto más antiguo que su ventana es el caso habitual), así que el mismo lote se rechazaría igual. Tamaño, según un fixture de pruebas de dos núcleos el 2026-09-12 y no según el equipo: una muestra del kernel se renderiza en 8 132 B de JSON OTLP frente a 716 B de protocolo de líneas, unas 11×, el precio de repetir las claves de los atributos y de poner entre comillas cada entero de 64 bits. Un segundo de muestras a 10 Hz más una muestra de la API son 68 868 B, así que el presupuesto de 64 KiB por segundo guarda aproximadamente un segundo de atraso por segundo y los 60 s por defecto unos 57 lotes. El RB5009 de cuatro núcleos, con su top-K real de interrupciones, renderiza más; no medido. ## Graphite ```sh mikroscope forward --graphite carbon:2003 --graphite-prefix mikroscope ``` `--graphite` (`MIKROSCOPE_GRAPHITE_ADDR`) escribe el protocolo de texto plano de carbon — `path value timestamp`, una línea por valor — sobre una única conexión TCP persistente. Graphite no tiene etiquetas, así que cada dimensión es un nodo de ruta bajo `..`; `--graphite-prefix` vale `mikroscope` por defecto. En un nodo solo sobreviven letras ASCII, dígitos, `_`, `-` y `:`, cualquier otro byte pasa a `_`, y un valor vacío pasa a `none`, así que la profundidad de una ruta nunca cambia. | Rutas | De | | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | `sample.{seq,dt_ns}`, `stat.{ctxt,intr,forks,procs_blocked,irq_total,irq_err}` | la muestra | | `cpu..{user,nice,system,idle,iowait,irq,softirq,steal,busy_ratio,freq_khz}` | `/proc/stat`, cpufreq | | `softnet..{processed,dropped,time_squeeze}`, `irq...count`, `softirq..count` | softnet, interrupts, softirqs | | `mem.{total,free,available,cached,slab,sunreclaim,dirty,writeback}_kb`, `load.{load1,load5,load15,running,threads}` | meminfo, loadavg | | `vm.{pgfault,pgmajfault,pgscan_kswapd,pgscan_direct,pgsteal_kswapd,pgsteal_direct,allocstall,oom_kill}`, `vmg.{nr_free_pages,nr_dirty,nr_writeback}` | vmstat | | `self.{cpu_us,rss_bytes,cgroup_mem,throttled,throttled_us,oom_kill}` | el coste propio del agente | | `psi.*_us`, `sched..{run_ns,wait_ns}` | solo donde el kernel los tiene | | `thermal..celsius`, `slab..{active_objs,limit_objs}`, `buddy...order_` | thermal, slab, buddyinfo | | `mtd..`, `flash..`, `disk..{reads,read_sectors,writes,write_sectors,io_s,inflight}` | flash y dispositivos de bloques | | `api.system.`, `api.core..{load,irq,disk}`, `api.health.`, `api.conntrack.entries` | la capa de la API | | `api.iface..{rx_bps,tx_bps,rx_pps,tx_pps,,fp_rx_share,fp_tx_share}`, `api.ifcounter..` | la capa de la API y la etapa de derivación | | `derived.{mem_pressure,cycles_per_packet,instructions_per_packet,cache_misses_per_packet,packets_per_irq}` | la etapa de derivación | | `trigger.`, `detection.` — el valor 1 en cada evento | disparos y detecciones | | `device.{cores,conntrack_max,cgroup_mem_max}`, `device.thermal..*`, `device.cpufreq..*`, `device.cadence..hz` | el flujo de datos del equipo | | `collector.gap.{samples,from,to}` | huecos, con el reloj del colector | Lo que el protocolo no puede prometer, dicho porque cada punto cambia lo que significa un panel de Graphite: - **Marcas de tiempo en segundos enteros.** La retención más fina de Whisper es un segundo, así que a 10 Hz nueve de cada diez muestras caen en una casilla que ya tiene valor y carbon se queda con el último escrito. Es una lectura válida para un nivel (`mem`, `load`, `thermal`, `freq_khz`, `slab`, `vmg`) y se queda corta para un delta: una suma sobre `cpu.0.user` ve más o menos una décima parte de los ticks que envió el agente. Sumar los deltas de cada segundo en el destino arreglaría las rutas de deltas y no las de niveles; ese intercambio no se ha hecho. Un consumidor que necesite cada tick tiene el destino de InfluxDB o el de fichero. - **Sin respuesta.** `written` cuenta lotes entregados al socket, no puntos que carbon guardó. Una escritura en un socket que carbon ya cerró funciona una vez y falla en la siguiente, así que el destino reintenta una vez con una conexión nueva; el lote que entró en el socket cerrado se pierde, uno por cada reinicio de carbon. - **Zonas térmicas por índice**, no por nombre, porque la cadena de tipo de una zona no es única; el nombre se conserva en los destinos de fichero e InfluxDB. - **Menos detalle**: se descartan las cuentas de interrupciones por CPU, los softirqs se suman sobre las CPU, no se emite `cpu.total` (`sumSeries` sobre `cpu.*.user` lo da), se omiten `pgalloc`, `pgfree` y los contadores de swap, y se descartan los registros del log del kernel porque Graphite solo guarda números. Las cadenas de placa, kernel y governor no tienen forma en Graphite. El presupuesto de bytes es de 256 KiB por segundo en cola, cuatro veces el de los demás: un tick con forma de RB5009 de cuatro núcleos con todas las fuentes presentes se renderiza en 6 411 B en 131 líneas en el fixture de pruebas del destino (máquina de desarrollo, 2026-09-12, no el equipo), así que 10 Hz son unos 63 KiB/s. No medido por encima de 10 Hz. ## Elasticsearch y OpenSearch ```sh export MIKROSCOPE_ELASTIC_AUTH=elastic:… # o una clave de API mikroscope forward --elastic http://opensearch:9200 --elastic-index 'mikroscope-%Y.%m.%d' ``` `--elastic` (`MIKROSCOPE_ELASTIC_URL`) escribe a través de la API bulk que comparten los dos productos. `/_bulk` se añade a la raíz de un clúster, conservando cualquier query string. Un `MIKROSCOPE_ELASTIC_AUTH` con `user:password` se envía como autenticación básica, y cualquier otra cosa como `Authorization: ApiKey`; las credenciales incrustadas en la URL se ocultan en el nombre del destino que se imprime. Un documento por evento, con `kind` para distinguirlos: - `kernel` — un tick del agente: ticks por CPU con `busy` y `busy_ratio`, `stat`, `mem`, `load`, `vm`, `self`, cada fuente opcional solo cuando se leyó (sin clave, nunca un cero), y los valores de la etapa de derivación bajo `derived`; - `event` — un registro del log del kernel: `priority`, `level`, `facility`, `seq`, `time_usec`, `message` y, cuando el registro nombra un puerto, `iface`, `ros_iface`, `port_event` (lo que le pasó al puerto), `label` y `role`; - `api` — una lectura de la API: `system`, `cores`, `health`, `ifaces`, `iface_counters`, el último número de conexiones, las proporciones del fast path bajo `fastpath`, e `inventory` en las rondas que leen qué es cada interfaz. Cada entrada de `ifaces` lleva `label`, `type`, `role` y `bridge`, y cada entrada de `iface_counters` lleva `comment`, `type`, `role` y `bridge`, donde el inventario los tiene. Los errores por orden de la capa de la API no se escriben; - `gap` (`from`, `to`, `lost`), `device`, `detection` y `trigger`. Cada documento lleva `@timestamp` con el reloj del agente (el del colector para huecos y registros del equipo) y `host`. El nombre del índice expande `%Y`, `%m` y `%d` — solo esos — contra esa marca de tiempo, así que un lote que queda en cola pasada la medianoche cae en el día en que se muestreó, y se pasa a minúsculas porque el clúster rechaza la petición entera si el nombre de índice tiene mayúsculas. La acción es `index` con un `_id` construido a partir del tipo, el host, la marca de tiempo del documento en nanosegundos y, donde existe, el número de secuencia que distingue documentos del mismo instante, así que un lote que el clúster aplicó pero cuya respuesta se perdió se reenvía sin duplicar nada. La marca de tiempo forma parte de la identidad porque la secuencia del agente vuelve a empezar desde 1 en cada arranque: sin ella, las muestras de un agente reiniciado sobrescribirían las anteriores del día, cada una con un 201. Una petición bulk responde 200 aunque se hayan rechazado todos sus elementos. El destino lee el veredicto de cada elemento, suma uno a `dropped` por cada documento rechazado, y registra el primer motivo una vez por minuto — por separado de los fallos de entrega, porque un conflicto de mapeo y un clúster inalcanzable piden acciones distintas. Los lotes se cierran una vez por segundo o al llegar a 1 MiB, lo que ocurra primero. Tamaño, según el fixture de dos núcleos del destino el 2026-09-12 y no según el equipo: 1 077 B de NDJSON para una muestra del kernel sin fuentes opcionales, 1 952 B repartidos en dos documentos con las fuentes opcionales y un registro del log del kernel. Una muestra de cuatro núcleos del RB5009 no se ha renderizado en este formato. ## Telegraf ```sh mikroscope forward --telegraf http://host:8186/telegraf mikroscope forward --telegraf tcp://host:8094 ``` `--telegraf` (`MIKROSCOPE_TELEGRAF_URL`) envía los mismos registros de protocolo de líneas que el destino de InfluxDB — llama a ese codificador en vez de copiarlo — para que las propias salidas de Telegraf los repartan a sistemas para los que este repositorio no tiene destino. Telegraf deja pasar la marca de tiempo sin cambiarla. El esquema del endpoint elige el transporte: - `http://` o `https://` — envía a una entrada `http_listener_v2` o `influxdb_v2_listener`. Un `host:port` sin más se interpreta como HTTP, y a un endpoint HTTP sin ruta se le da `/telegraf`, el valor por defecto de `http_listener_v2`: un listener responde 404 en `/` y el cuerpo no dice por qué. Un `MIKROSCOPE_TELEGRAF_TOKEN` con `user:password` se envía como autenticación básica, y cualquier otra cosa como `Authorization: Token …`. - `tcp://` — registros delimitados por saltos de línea a un `socket_listener`, con una conexión nueva por lote. - `udp://` — datagramas de como mucho 1 432 bytes, cortados en los límites de registro para que ningún datagrama lleve media línea. No hay confirmación: `written` cuenta lotes que aceptó el kernel local, un datagrama perdido por el camino es invisible, y un reintento tras un fallo a mitad de lote puede entregar algunos registros dos veces. Usa `http://` o `tcp://` para cualquier cosa que importe. El presupuesto es el del destino de InfluxDB, 64 KiB por segundo en cola: a unos 1,2 KiB por muestra a 10 Hz (RB5009, 2026-09-12), unos 5 minutos de atraso con los 60 s por defecto. El codificador compartido escribe las lecturas de `/system/health` en el orden de un map de Go, así que los registros dentro de un lote no tienen un orden estable — 12 renderizados de un map de 8 nombres dieron 7 órdenes (2026-09-12). Cada registro lleva su propia marca de tiempo, así que no se pierde ni se desfasa nada. > **No medido, luego no afirmado** > > Salvo el destino de fichero, que fue uno de los tres destinos de las ejecuciones de cadencia del > 2026-09-15, ninguno de estos destinos formó parte de las ejecuciones de cadencia medidas, y > ninguno se ha alimentado desde el RB5009 hacia un Loki, receptor OTLP, carbon, Elasticsearch, > OpenSearch, Telegraf o TimescaleDB en marcha en una ejecución registrada. Cada uno de estos se ha > probado contra un receptor local que comprueba los bytes que acepta su protocolo (máquina de > desarrollo, amd64, 2026-09-12). ## Véase también - [El colector](/mikroscope/es/sinks/): la cola, la espera entre reintentos y los contadores que comparten estos destinos. - [InfluxDB 3](/mikroscope/es/sinks/influxdb/): las medidas que llevan `--stdout lp` y `--telegraf`. - [Detecciones](/mikroscope/es/sinks/detections/): lo que dicen las líneas, filas y documentos de detección. - [Variables de entorno](/mikroscope/es/reference/environment/): cada `MIKROSCOPE_*` que lee un destino. --- # La capa de la API de RouterOS Lo que el colector sigue preguntando a RouterOS por su API binaria, por qué casi todo es opcional, y las opciones y preajustes que deciden cuánto preguntar. Source: https://jmrplens.github.io/mikroscope/es/sinks/api-tier/ Además de la capa del kernel que extrae del agente, `forward` puede mantener una sesión persistente con la API binaria de RouterOS para la capa de la API (una segunda cuando la propia capa del kernel llega por el relay) y leer lo que el contenedor no ve. Esta página responde a qué lee esa capa, qué parte ya cubre el agente, cómo elegir cuánto de ella ejecutar y qué significa un valor que falta. ## Lo que el agente ya lee, y lo que no puede Casi todo lo que la capa de la API puede obtener lo lee el propio agente: - `/system/resource` y `/system/resource/cpu` son la media de un segundo que hace RouterOS de los mismos jiffies de `/proc/stat` que el agente diferencia a 10 Hz. - La temperatura de `/system/health` es `/sys/class/thermal`, legible desde el contenedor. - El número de conexiones es la caché slab global `nf_conntrack`, que el agente lee con `privileged=yes`; consulta [conntrack sin la API](/mikroscope/es/playbooks/conntrack/). Lo que queda son **los bytes y paquetes por interfaz**. Viven en el espacio de nombres de red del router y quedan fuera de alcance se le dé lo que se le dé al contenedor; consulta [la CPU del router, la red del contenedor](/mikroscope/es/limits/namespaces/). ## Qué promedia de verdad `cpu-load` `/system/resource` publica `cpu-load` como un porcentaje entero, y esta página lo llama media de un segundo. Eso está medido, no supuesto: la serie de la API se correlacionó con la propia proporción de ocupación por núcleo del agente, que son los mismos jiffies de `/proc/stat` leídos a 10 Hz, en dos horas distintas. El mejor ajuste es una **media móvil de 1,0 s con 0,6 s de retardo, r = 0,9825** sobre 3.499 muestras de la API, y **1,1 s con 0,1 s de retardo, r = 0,9734** sobre 3.594 en la segunda hora. Ensanchar la ventana solo empeora el ajuste: 1,5 s da 0,955; 2 s da 0,919; 5 s da 0,822; y 8 s da 0,791. Una media de sesenta segundos queda descartada por partida doble. Su correlación es 0,238, y la respuesta al escalón no tiene rampa: en el mayor salto de carga del día el kernel pasó de 5 % a 27 % en un segundo y `cpu-load` pasó de 5 a 26 en ese mismo segundo, y de 22 % a 6 % al bajar con la misma rapidez. Una media de un minuto habría necesitado un minuto para recorrer cualquiera de los dos caminos. Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · Linux 5.6.3 · 2026-09-15 · `/system/resource` consultado a 1 Hz frente a la proporción de ocupación por núcleo del agente a 10 Hz, en dos horas distintas Así que el número es lo que dice ser. Lo que sigue sin poder hacer es resolver nada más corto que su propio segundo, que es justo la razón por la que este proyecto lee el kernel. ## Elegir cuánto preguntar `--api-mode` elige un preajuste. Es `full` salvo que digas otra cosa, y un `--api-every`, `--no-health` o `--conntrack-every` explícito sigue mandando sobre él. | Modo | Lo que hace | Úsalo cuando | | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | | `off` | sin capa de la API: `--api-every 0` | en producción, cuando el tráfico por interfaz ya viene de otro sitio | | `slow` | una ronda cada 10 s, sin `/system/health` y sin el número de conexiones; `/system/resource`, `monitor-traffic` y los contadores de puerto siguen ejecutándose en cada ronda | en producción, cuando también quieres las tasas de las interfaces | | `full` | una ronda por segundo, con `/system/health`; contadores de puerto cada 10 s; el número de conexiones solo si `--conntrack-every` lo pide | experimentos y ejecuciones de `record` | `off` es el modo que renuncia al tráfico por interfaz; `slow` es el que lo conserva a bajo coste. Con `slow` hay dos paneles vacíos por configuración y no por el equipo — `/system/health` y el número de conexiones de RouterOS — y nombran la opción que los rellena. > **El preajuste off para la capa de la API, no toda sesión de API** > > El transporte por relay extrae el anillo del agente a través de `/tool fetch` sobre la misma API > binaria. Con `--transport auto`, un colector que no puede llegar directamente al agente recurre al > relay y abre una sesión de API para ello diga lo que diga `--api-mode`. `--transport direct` nunca > lo hace. ## Lo que pregunta una ronda Cada orden de la capa de la API se ejecuta en la única sesión de esa capa, de una en una, con un tiempo de espera de 15 s cada una. Las cadencias más lentas se comprueban en cada ronda, así que ninguna se ejecuta más a menudo que `--api-every`. | Orden | Pide | Se ejecuta | Apagada cuando | | ------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------- | | `/system/resource/print` | `cpu-load`, `free-memory`, `total-memory`, `free-hdd-space`, `uptime`, `version` | en cada ronda | la capa está apagada | | `/system/health/print` | `name`, `value` | en cada ronda | `--no-health`, o `slow` | | `/interface/monitor-traffic` con `interface=` y `once` | las cuatro tasas de tráfico y las tasas de pérdidas que devuelva el router | en cada ronda, una llamada para todas las interfaces | `--interfaces` está vacío | | `/interface/print` con `.proplist=name,default-name,type,comment,actual-mtu` | qué es cada interfaz: su nombre actual, el nombre de fábrica de la placa, su tipo, su comentario y su MTU | al arrancar, antes de la primera extracción del kernel, y luego cada `--labels-every` (5 min) | la capa está apagada | | `/interface/list/member/print` con `.proplist=list,interface` | qué listas de interfaces nombran cada interfaz, que es su rol | con la lectura de arriba | la capa está apagada | | `/interface/bridge/port/print` con `.proplist=interface,bridge` | a qué bridge pertenece cada puerto | con la lectura de arriba | la capa está apagada | | `/interface/ethernet/print stats` y `/interface/print stats-detail` | cada contador numérico de **todas** las interfaces | cada `--counters-every` (10 s) | `--counters-every 0` | | `/ip/firewall/connection/print count-only` | el número de conexiones | cada `--conntrack-every` | `--conntrack-every 0`, el valor por defecto | Una orden que falla deja vacía su parte de la muestra y registra por qué; el resto de la ronda sigue en pie. Los fallos se registran en la salida de error como `api tier: …`, se escriben como líneas en Loki y filas en SQL, y se cuentan en OTLP — nunca se convierten en un valor. Una lectura de `/interface` que falla conserva el inventario que ya tenía — un error transitorio no deja en blanco la etiqueta de todos los paneles — y se informa como `inventory: …`; las lecturas de listas y de bridges son de mejor esfuerzo, y sin ellas el inventario sigue llevando nombres, tipos y comentarios. La muestra de la capa de la API se marca con el reloj del colector más el desfase medido frente al agente, así que cae en la línea temporal del agente. ## Qué es cada interfaz Las tres lecturas del inventario responden a lo que los contadores no pueden: a qué pertenecen los números. De cada interfaz dan su nombre actual, el nombre por defecto de la placa (el `ether5` de fábrica de un puerto físico, vacío en un bridge, una VLAN o un túnel), el tipo propio de RouterOS (`ether`, `bridge`, `vlan`, `pppoe-out`, `wg`, `veth`, `loopback`), el comentario, las listas de interfaces a las que pertenece — ordenadas y unidas por comas, `WAN` o `LAN,VPN` —, el bridge del que es puerto y el MTU. Un miembro de un bridge que no está en ninguna lista propia hereda las de su bridge, porque así es como lo empareja una regla de firewall de RouterOS, y un puerto de bridge que nombra una lista de interfaces en vez de una interfaz no se etiqueta. En el RB5009 de referencia (RouterOS 7.24.2, 2026-09-16) las tres lecturas devuelven 17 interfaces. `ether1` es un `ether` de la lista `LAN`, puerto de `bridge`, etiquetado «TrueNAS - High Performance Storage», MTU 9000; `ether5` es un `ether` de `WAN` que no está en ningún bridge, etiquetado «DIGI ONT»; `PPPoE_DIGI` es `pppoe-out`, `WAN`, MTU 1480; `VLAN_DIGI` es `vlan`, `WAN`; `wg_devices` y `wg_trastero` son `wg` en `LAN,VPN`; `ether6` y `ether7` llevan el comentario «Unused». Esto es configuración, no telemetría, así que se lee una vez al arrancar — antes de la primera extracción del kernel, para que un registro del log del kernel lleve etiqueta desde la primera línea — y luego con la cadencia lenta de `--labels-every`, nunca en cada sondeo. Un comentario editado, o un puerto que cambia de lista, llega al dashboard en minutos y no en el siguiente reinicio del colector, y un comentario borrado en RouterOS desaparece también aquí: la lectura sustituye el inventario entero. Ninguna de las cinco propiedades que pide puede llevar un secreto. Cada fila de tasas de `monitor-traffic` y cada fila de contadores por puerto llevan entonces `label` (el comentario), `type`, `role` y `bridge`, para que un panel diga lo que hay enchufado en vez de un número de puerto, y diga qué números se pueden comparar. Prometheus es la excepción a propósito: un comentario lo edita una persona, y una etiqueta que cambia crearía una serie nueva en cada edición, así que el `/metrics` del colector lleva una serie informativa por interfaz — `mikroscope_api_interface_info{interface,label,type,role,bridge,default_name} 1`, para cada interfaz tenga o no comentario — y una consulta las une: ```text mikroscope_api_interface_counter_total * on(interface) group_left(label, type, role) mikroscope_api_interface_info ``` El inventario también le da sus nombres al log del kernel. Un registro del kernel nombra un puerto como `eth1`, la tabla de puertos de la placa lo traduce al nombre por defecto de RouterOS y el inventario traduce ese nombre por defecto al actual, así que quien haya renombrado `ether5` a `WAN` lee `WAN` en el panel, con la etiqueta y el rol del puerto al lado. Sin la capa de la API un registro del kernel conserva el nombre por defecto de la placa y no recibe etiqueta. ## Opciones | Opción | Por defecto | Significado | | ------------------- | ------------------------------ | ---------------------------------------------------------------------------------------- | | `--api host:port` | `MIKROSCOPE_API_ADDR` | la API binaria de RouterOS, p. ej. `192.168.88.1:8728` | | `--api-mode` | `full` | `off`, `slow` o `full`, como arriba | | `--api-every` | `1s` | la cadencia de la ronda; `0` desactiva la capa | | `--interfaces` | `MIKROSCOPE_INTERFACES`, vacío | interfaces separadas por comas para `monitor-traffic`, p. ej. `bridge,ether1` | | `--counters-every` | `10s` | cada cuánto leer los contadores acumulados de cada puerto; `0` nunca | | `--labels-every` | `5m` | cada cuánto releer qué es cada interfaz — comentario, tipo, listas de interfaces, bridge; `0` significa el valor por defecto | | `--conntrack-every` | `0` | cada cuánto pedir el número de conexiones; `0` nunca, porque es un recorrido de tabla | | `--no-health` | desactivada | omite `/system/health` | Sin `--api`, `--api-user` y `MIKROSCOPE_API_PASSWORD`, o cuando la sesión no se puede abrir en 10 s, `forward` registra `api tier disabled: …` y ejecuta solo la capa del kernel. Es un aviso, no un fallo: la capa del kernel es lo importante. ## El usuario que necesita En cualquier modo las órdenes de la capa de la API son lecturas: basta un usuario con las políticas `read` y `api`. `test` solo hace falta para `/tool fetch`, que usa el transporte por relay y nada más de la capa de la API. El grupo, la restricción de dirección y por qué la credencial nunca vive en el router están en [el usuario de la API](/mikroscope/es/security/api-user/). ## Un valor ausente está ausente La capa de la API guarda exactamente lo que devolvió el router y no se inventa nada para lo que no devolvió. **Tasas de pérdidas.** `monitor-traffic` puede devolver `rx-drops`, `tx-drops`, `tx-queue-drops`, `rx-errors` y `tx-errors` por segundo, y un destino escribe cada una solo cuando volvió. En el RB5009 con RouterOS 7.24.2 (2026-09-15) devuelve las tres tasas de descartes y **ninguna clave de errores**. **Contadores de puerto.** Los contadores por puerto son un mapa con los nombres de campo del propio RouterOS, no un conjunto fijo de campos, y solo se conservan los enteros sin signo simples que cuentan algo. `mtu`, `actual-mtu`, `l2mtu`, `max-l2mtu` y `sfp-shutdown-temperature` se leen como enteros pero son tamaños y configuración, y todo destino representa este mapa como una familia de contadores, así que se descartan aquí en vez de dejar que cada consumidor lo sepa; el MTU que importa viaja en el inventario. Las dos órdenes se fusionan por interfaz: `/interface/ethernet/print stats` aporta los errores tipados de la MAC, la familia de colisiones, los intervalos de tamaño de trama y los contadores del driver; `/interface/print stats-detail` aporta los contadores `fp-*` del fast path, `link-downs`, `tx-queue-drop` y los totales del lado del kernel. Una placa sin contadores de colisiones no produce entradas de colisiones en vez de una fila de ceros que se lee como «sin colisiones». El motivo es una medida. El 2026-09-15 el `ether1` del router de referencia tenía 652 364 eventos `rx-overflow`, en aumento, y `monitor-traffic` no devuelve ninguna clave de errores para ese puerto: esa cuenta solo llega a un consumidor por los contadores de puerto. Qué resultaron ser esos desbordamientos está [más abajo](#qué-es-el-rx-overflow-del-puerto-del-nas). Los contadores cubren **todas** las interfaces que lista el router, no solo `--interfaces`: el puerto en el que vive una avería suele ser uno que nadie pensó en vigilar, y el bucle de capa 2 del 2026-09-12 estaba en un puerto que no figuraba en la lista de `monitor-traffic`. Son acumulados desde el arranque o desde el último reinicio del puerto, así que un consumidor los diferencia. La derivación que permiten, medida en `ether1` (2,5 GbE hacia un NAS, 2026-09-16, desde el último reinicio del contador del puerto): 255,8 GB de `rx-bytes` en el cable, de los cuales 29,7 GB de `driver-rx-byte` llegaron a la CPU — el resto lo reenvió el chip de conmutación en hardware, y ningún contador de dentro del contenedor tiene un número para eso. El colector convierte los contadores del fast path que van al lado en [una proporción del tráfico que cada interfaz entrega a la CPU](/mikroscope/es/sinks/derive/#junto-a-cada-lectura-de-contadores). **Un puerto de switch y un bridge cuentan cosas distintas**, y por eso el tipo viaja con cada fila. Un `ether` dentro de un bridge cuenta su cable, incluidas las tramas que el chip de conmutación reenvió sin la CPU; el `bridge` cuenta su propio lado de CPU; una VLAN o un enlace PPPoE cuentan lo que la CPU envió y recibió. `ether1` y `bridge` son dos planos, ninguno subconjunto del otro: dibujados uno al lado del otro sin su tipo parecen iguales, y sumados cuentan dos veces. No los sumes nunca. **Etiquetas de interfaz.** Con qué se etiqueta cada fila — el comentario, el tipo, el rol y el bridge — es el inventario de arriba, y una interfaz que el inventario no lista no lleva ninguna de ellas en vez de una identidad en blanco inventada. ## Qué es el `rx-overflow` del puerto del NAS Los contadores de puerto son el único sitio donde aparecen los desbordamientos del `ether1` del router de referencia, y en quince horas dicen qué clase de suceso son. Medido el 2026-09-15 entre las 07:13 y las 22:20 UTC, sobre 4.471 lecturas consecutivas de los contadores cada 10 s en `ether1` (2,5 Gbps hacia un NAS, MTU 9000): - 126 443 sucesos `rx-overflow` en total, presentes en el 40 % de los intervalos; por intervalo la mediana es 29, el p99 unos 1.036 y el mayor 3.747. En toda la tirada eso es el 0,53 % de los paquetes que envió el NAS. - Correlación de rangos sobre los deltas de 10 s: 0,85 con la parte de lo que recibe el NAS que el switch reenvió en hardware (`rx-bytes` menos `driver-rx-byte`), 0,00 con la parte que mandó a la CPU (`driver-rx-byte`). - Hacia dónde iba: `ether8` (NGINX, 1 Gbps) se lleva casi todo el volumen y `ether4` (Mastodon, 1 Gbps) es el destino más frecuente; la jaula SFP+, `ether2`, `ether3` y el camino de la CPU no muestran nada. - Las tramas eran grandes: el intervalo de tamaño de trama de 1024 en adelante en `ether1` tiene una mediana de 9.331 por intervalo con desbordamiento frente a 1.336 por intervalo sin él. - La carga no era alta: la mediana de lo que recibe el NAS en un intervalo con desbordamiento son unos 9 Mbit/s de media en 10 s. Ráfagas, no carga sostenida. - Nada del lado de la CPU: softnet descartó 0, `time_squeeze` correlaciona 0,04 y las interrupciones de `switch0` 0,05 con los desbordamientos, con los datos a 10 Hz del agente agrupados en intervalos de 10 s. Ninguna trama de pausa en `ether1` en ningún sentido. Leído en conjunto, encaja con ráfagas a la velocidad de línea de 2,5 Gbps conmutadas dentro del chip hacia puertos de 1 Gbps sin control de flujo en juego. Sin verificar: la semántica exacta de los contadores del chip de conmutación y la cuenta de retransmisiones del propio NAS — los contadores son del puerto, no de la conversación. La capa del kernel no puede ver nada de esto por construcción. Una trama que el chip de conmutación reenvía en hardware nunca llega a la CPU, así que ningún fichero de `/proc` del router tiene un número para ella; hacen falta los contadores por puerto, y solo la API los tiene. ## Lo que le cuesta al router Medido en el RB5009 de referencia (RouterOS 7.24.2, 2026-09-16) con `/tool profile duration=60s cpu=total`, una vez con el colector parado y otra con él en marcha con `--interfaces bridge,ether1,PPPoE_DIGI --counters-every 10s --api-every 1s`. Las cinco primeras instantáneas de un segundo de cada perfil se descartan: llevan la conexión SSH que lo pidió. | Fila del perfil | Colector parado | Colector en marcha | | ---------------- | --------------- | ------------------ | | total | 5,93 % | 6,04 % | | `interface-mgmt` | 0,40 % | 0,87 % | | `config-db` | unos 0 % | 0,15 % | El total se mueve 0,11 puntos, que está dentro del ruido del tráfico de un minuto; las dos filas que responden a las preguntas de la capa se mueven juntas medio punto. En ninguno de los dos perfiles aparece una fila de proceso `api`: el proceso de la API hace de intermediario y el trabajo cae en el subsistema que responde. Así que una capa que hace una ronda por segundo le cuesta al router en torno al 0,5 % de su CPU total. Es poco, y aun así el proyecto sigue tratando la API como el camino caro. Los datos por puerto salen del contenedor siempre que el contenedor pueda verlos, y la configuración se lee al arrancar y con la cadencia lenta de las etiquetas, nunca en cada sondeo. ## El número de conexiones `--conntrack-every 10s` pregunta `/ip/firewall/connection/print count-only` con esa cadencia: 1,3 ms con 6 212 entradas en el RB5009 (fecha no registrada). Está apagado por defecto porque es un recorrido de tabla sobre una sesión de API, y con `privileged=yes` la cuenta slab `nf_conntrack` del agente es la misma población leída de un fichero a la cadencia del muestreador. Las dos no coinciden exactamente — se muestrean en instantes distintos, y el slab cuenta objetos que el asignador todavía retiene — pero se siguen: la API dijo 6 212 el día antes de que el slab dijera 6 287. > **No medido, luego no afirmado** > > La capa de la API ha funcionado contra una sola versión de RouterOS, la 7.24.2, en una sola placa. > Qué claves de pérdidas y qué contadores devuelve otra versión u otra placa lo tiene que decir ese > router; los destinos llevan lo que vuelva y nada más. Tampoco dice la correlación de arriba si > RouterOS calcula la ventana de un segundo de `cpu-load` sobre un reloj de pared o sobre jiffies. ## Véase también - [El usuario de la API](/mikroscope/es/security/api-user/): el grupo de RouterOS y la restricción de dirección que necesita el usuario de la capa. - [El colector](/mikroscope/es/sinks/): dónde se une la muestra de la capa de la API a la línea temporal del kernel. - [Puertos de RouterOS y nombres del kernel](/mikroscope/es/reference/port-names/): cómo emparejar el `ether2` de la API con el `eth1` del kernel. - [Conntrack sin la API](/mikroscope/es/playbooks/conntrack/): leer el número de conexiones del slab en su lugar. --- # Lo que deriva el colector Los valores que el colector calcula junto a las muestras en bruto — presión de memoria, coste de PMU por paquete, paquetes por interrupción, la marca de ráfaga y la proporción del fast path — y cuándo se omite cada uno. Source: https://jmrplens.github.io/mikroscope/es/sinks/derive/ El colector tiene una etapa de derivación. Esta página responde a qué calcula, a partir de qué entradas, cuándo un valor se omite en vez de escribirse y dónde lo pone cada destino. Los eventos discretos que provoca la misma etapa están en [detecciones](/mikroscope/es/sinks/detections/). ## Por qué el colector, y no el agente ni el panel La etapa de derivación existe para las derivaciones que necesitan estado entre muestras, que unen la capa del kernel con la capa de la API, o que deben calcularse una sola vez para que cada destino que las lleva lleve los mismos números (qué destinos tienen sitio para ellas está en la tabla de abajo) — nada de lo cual hace una consulta de un panel, y nada de lo cual debería pagar el agente. El agente envía deltas de ticks en bruto y nunca divide; el colector puede hacerlo. Nada de esta etapa se ejecuta en el router. La rigen dos reglas: - **Un valor derivado se escribe junto a sus entradas, nunca en su lugar**, para que el almacén pueda recalcularlo si más adelante se descubre que la derivación estaba mal. - **Una detección es un evento discreto** en la línea temporal, un «mira aquí» — nunca una serie continua y nunca un veredicto. ## Junto a cada muestra del kernel ### `mem_pressure` La propia escalera de escalada del asignador como un único ordinal. Cada entrada ya se envía y se dibuja por separado; lo que añade el ordinal es que la escalera está ordenada, así que una sola serie dice hasta dónde llegó. Gana el peldaño más alto alcanzado en los deltas de `/proc/vmstat` de la muestra: | Valor | Peldaño | A partir de los deltas de `/proc/vmstat` de la muestra | | ----- | ----------------------------------------------------- | ------------------------------------------------------ | | 0 | ninguno | nada de lo de abajo | | 1 | kswapd escaneó | `pgscan_kswapd` > 0 | | 2 | reclaim directo: un hilo escaneó por sí mismo | `pgscan_direct` > 0 | | 3 | una asignación se bloqueó o se sacó una página a swap | `allocstall` > 0 o `pswpout` > 0 | | 4 | actuó el OOM killer | `oom_kill` > 0 | ### Las razones por paquete del PMU `cycles_per_packet`, `instructions_per_packet` y `cache_misses_per_packet`: cuentas de PMU sumadas sobre los núcleos, divididas entre los paquetes que softnet procesó en la muestra, sumados sobre los núcleos: el coste de reenvío del router en la única unidad que permite comparar dos configuraciones. Un cambio de reglas que reduce a la mitad los ciclos por paquete es una mejora real; uno que reduce a la mitad el tiempo ocupado mientras el tráfico también se reducía a la mitad no lo es. Ausentes sin PMU, en una muestra sin paquetes, y en una muestra con un reinicio de contador (`suspect`), donde un delta es una cota inferior y no una medida. ### `packets_per_irq` Paquetes procesados por interrupción de dispositivo — todas las filas de `/proc/interrupts` salvo el temporizador y las interrupciones entre procesadores — que es la profundidad de agrupamiento de NAPI. La cuenta de dispositivo es el total de interrupciones de la muestra menos las filas del temporizador y las filas `IPI` presentes en su top-K. Ausente en una muestra sin paquetes o con un reinicio de contador (`suspect`), y cuando la fila del temporizador no está en el top-K de la muestra, porque entonces la cuenta de dispositivo no se puede separar del total. ### `burst` Un paquete descartado, o más squeezes de softnet de los que esa CPU suele tener — por encima de su percentil 90 móvil **y** al menos 3 — en una muestra cuya cuenta de paquetes estaba en su mediana móvil o por debajo. Es la prueba del propio kernel de una ráfaga más corta que el intervalo de muestreo, que es la única forma en que esta herramienta puede ver dentro de uno. La marca es verdadera cuando cualquier CPU cumple la condición. Las referencias móviles abarcan diez segundos de reloj de pared a cualquier cadencia: 100 muestras a 10 Hz, 500 a 50 Hz, 1 000 a 100 Hz, acotadas entre 10 y 2 000 muestras. No se marca ninguna muestra hasta que su CPU tiene al menos diez muestras de historia. Por qué un percentil y no «cualquier squeeze», medido en el RB5009 de referencia sobre 3 476 muestras el 2026-09-15: alrededor del 11,2 % de las muestras llevan un squeeze de fondo y el 2 % llevan dos o más. «Cualquier squeeze» marcaría la normalidad del equipo varias veces por minuto. Tres muestras marcadas en una CPU en 60 s es lo que provoca la [detección](/mikroscope/es/sinks/detections/#microburst) `microburst`; una muestra marcada se queda en un dato. ### `suspect` Verdadero cuando el agente informó de un reinicio de contador en la muestra: un contador que retrocedió sin un desbordamiento de 32 bits. La fila en bruto se conserva; los valores por paquete de arriba — los tres cocientes de la PMU y `packets_per_irq` — se omiten. ## Junto a cada lectura de contadores En cada ronda de la API que trajo los contadores por puerto, el colector calcula, sobre los deltas desde la lectura anterior, la proporción del fast path del tráfico que cada interfaz entrega a la CPU: de los bytes que llegaron a la CPU en esa interfaz, la parte que RouterOS contó por el fast path y no por el camino lento. El denominador no es el mismo contador en todas las interfaces, porque RouterOS no cuenta lo mismo en todos los tipos — gana el primer contador presente en las dos lecturas: | Interfaz | `fp_rx_share` es | Por qué ese denominador | | ------------------------------------------------ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | un puerto del switch (`ether…`, `sfp-sfpplus1`) | Δ`fp-rx-byte` / Δ`driver-rx-byte` | su `rx-byte` es el total del cable, incluidas las tramas que el chip del switch reenvió por hardware; `driver-rx-byte` es lo que llegó a la CPU | | una interfaz software (bridge, VLAN, PPPoE) | Δ`fp-rx-byte` / Δ`rx-byte` (o `rx-bytes`) | no tiene contadores del driver, y su `rx-byte` ya es lo que la CPU envió y recibió | `fp_tx_share` es el mismo cociente en el otro sentido — Δ`fp-tx-byte` entre Δ`driver-tx-byte`, Δ`tx-byte` o Δ`tx-bytes` — y en el equipo de referencia se omite, más abajo. Cada proporción está limitada a 1, con los cuatro deltas de bytes escritos en la misma fila; ahí `rx_bytes` y `tx_bytes` son los denominadores de la proporción, no los totales del cable. **No es una proporción del cable.** Una trama que el chip del switch reenvió por hardware no está en ninguno de los dos números: nunca llegó a la CPU. La parte de los bytes del cable de un puerto que se queda la CPU — `driver-rx-byte` frente a `rx-byte` — es otra pregunta, y la responde a partir de los contadores en bruto el panel apilado "Where a port's receive bytes went"; la etapa de derivación no la calcula. Medido en el RB5009 de referencia (RouterOS 7.24.2, 2026-09-16) sobre los contadores acumulados desde el arranque: todos los puertos del switch dan alrededor del 100 %, porque allí `fp-rx-byte` es igual a `driver-rx-byte` con unos pocos kB de diferencia — cada byte que un puerto entrega a la CPU se cuenta en el driver, capaz de fast path, así que la línea de un puerto dice poco. Las interfaces software son las líneas sobre las que la proporción informa: `bridge` pasó por el fast path 211,9 GB de 663,0 GB (32 %), `PPPoE_DIGI` el 99,97 %. **La proporción de tx se omite mientras `fp-tx-byte` no haya contado nunca.** En ese router `fp-tx-byte` está a 0 en todas las interfaces tras cientos de GB transmitidos, lo que se lee como un contador que RouterOS no mantiene aquí y no como un fast path que no reenvió nada; una proporción calculada a partir de él sería un 0 % fabricado. Mientras el `fp-tx-byte` acumulado de una interfaz sea 0, el colector no escribe `fp_tx_share` para ella, y sus `tx_bytes` y `fp_tx_bytes` son 0. También falta una proporción para un sentido que no movió bytes, cuyo contador retrocedió, o cuyo contador del fast path el puerto no informa, y la fila entera falta cuando no se pudo diferenciar el denominador de ninguno de los dos sentidos. La primera lectura de cada interfaz siembra y no escribe nada. Un delta que no se pudo calcular — su contador ausente, que retrocedió, u omitido junto con la proporción de tx — se escribe como 0 junto a la proporción ausente: las filas de InfluxDB y SQL y el objeto `fastpath` de Elasticsearch llevan `rx_bytes`, `fp_rx_bytes`, `tx_bytes` y `fp_tx_bytes` siempre. Leído en el código (`internal/derive/derive.go`), no observado. En ese caso, lee la proporción, no el delta. La proporción es por lectura, y en una interfaz que mueve pocos paquetes oscila: el repaso del panel del 2026-09-15 la vio ir de 0 a 100 % entre lecturas en interfaces así, por lo que el panel "Fast-path share of the traffic each interface hands the CPU" la pondera por bytes en cada intervalo — los bytes por fast path del intervalo entre los bytes del intervalo — en vez de dibujar el valor de cada lectura. La forma de Prometheus de ese panel es el gauge por lectura del colector y conserva ese ruido. ## Dónde lo pone cada destino | Destino | Junto a las muestras del kernel | Junto a las lecturas de contadores | | ------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | | InfluxDB, Telegraf, stdout `lp` | `mikroscope_derived`: `mem_pressure`, `burst`, `suspect`, y los cocientes que se pudieron calcular | `mikroscope_derived_iface{interface}` | | SQL | `mikroscope_derived`, cocientes NULL donde no se calcularon | `mikroscope_derived_iface` | | file, stdout `json` | una línea `{"derived":…}` tras su muestra | no se escribe; los contadores en bruto están en la línea `{"api":…}` | | Prometheus | gauges `mikroscope_derived_*` de la muestra más reciente; `mikroscope_collector_bursts_total` | `mikroscope_derived_fastpath_share{interface,direction}` | | OTLP | gauges `mikroscope.derived.*` | `mikroscope.derived.fastpath_share{interface,direction}` | | Graphite | rutas `derived.*` | `api.iface..fp_rx_share`, `fp_tx_share` | | Elasticsearch | `derived` en el documento del kernel | `fastpath` en el documento de la API | | Loki | — | — | > **No medido, luego no afirmado** > > No consta ninguna comparación del coste de PMU por paquete entre dos configuraciones del router en > el equipo de referencia, y esa comparación es el uso para el que existe. La referencia de ráfagas > está ajustada contra un RB5009 en un solo día; en otra placa o con otra mezcla de tráfico su > percentil será el de ese equipo, y no se ha medido si diez segundos son el intervalo adecuado > allí. Ninguna interfaz de ese router cuenta `fp-tx-byte`, así que nunca se ha producido una > proporción de tx a partir de contadores en vivo, y no está establecido por qué RouterOS deja ese > contador a 0 allí. ## Véase también - [Detecciones](/mikroscope/es/sinks/detections/): las once reglas que ejecuta la misma etapa, y lo que no puede afirmar cada una. - [La capa de la API de RouterOS](/mikroscope/es/sinks/api-tier/): los contadores de puerto a partir de los que se calcula la proporción del fast path. - [El suelo de resolución es del kernel](/mikroscope/es/limits/): por qué la PMU es la resolución por debajo del jiffie. - [Una inundación de paquetes](/mikroscope/es/playbooks/packet-flood/): descartes y squeezes de softnet en un router real. --- # Detecciones Las once reglas que ejecuta la etapa de derivación del colector, cada una con su condición exacta, las pruebas que necesita del despliegue y lo que no puede afirmar. Source: https://jmrplens.github.io/mikroscope/es/sinks/detections/ Una detección es un evento discreto que el colector pone en la línea temporal: un «mira aquí», nunca una serie continua y nunca un veredicto. Esta página responde, para cada una de las once reglas, exactamente cuándo salta, qué tiene que aportar el despliegue para que pueda saltar y qué no te puede decir. Los valores derivados en los que se apoyan algunas reglas están en [lo que deriva el colector](/mikroscope/es/sinks/derive/). ## Lo que lleva una detección Toda detección tiene los mismos campos: `rule`; `key` — la CPU, el núcleo, la zona o el puerto al que se refiere, vacío para una regla de todo el equipo; `seq` y `wall_ns` de la muestra que la provocó; `value`, la cantidad que comparó la regla; `threshold`, contra qué la comparó; y un `message` en palabras. Los umbrales son los de cada regla y se escriben en cada evento. **Una vez por regla y clave cada 10 s.** Tras saltar una regla para una clave, la misma regla y clave quedan suprimidas durante 10 s del reloj de pared de las muestras, así que una condición que persiste salta cada 10 s en vez de en cada muestra. La etapa cuenta lo que suprimió, pero ningún destino exporta esa cuenta. Las reglas se ejecutan en el proceso del colector y su historia vive allí. Un colector que se reinicia empieza desde cero cada ventana móvil, cada intervalo y cada valor anterior. ## Dónde caen las detecciones | Destino | Forma | | ------------------------------- | ------------------------------------------------------------------------------------ | | InfluxDB, Telegraf, stdout `lp` | `mikroscope_detection{rule,key}` con `value`, `threshold`, `seq`, `message` | | SQL | una fila de `mikroscope_detection` | | file, stdout `json` | una línea `{"detection":…}` | | Prometheus | `mikroscope_collector_detections_total{rule}`, cada regla a 0 desde el primer scrape | | Loki | una línea en el stream `source="detection"`, `level="warn"` | | OTLP | un sum de deltas `mikroscope.detection{rule}` de 1 | | Graphite | `detection.` = 1 en el segundo del evento | | Elasticsearch | un documento con `kind: detection` | Los paneles dibujan cada detección como una anotación, y una de las [reglas de alerta](/mikroscope/es/dashboards/alerts/) salta con cualquier detección. ## Las reglas | Regla | Clave | Salta cuando | Necesita | | ----------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | | `counter-reset` | — | la muestra informa de un contador que retrocedió sin un desbordamiento de 32 bits | cualquier despliegue | | `agent-restart` | — | el número de secuencia retrocedió | cualquier despliegue | | `agent-oom` | — | el cgroup propio del contenedor registró un OOM kill | cgroup2 en el contenedor | | `microburst` | `cpu` | tres muestras `burst` en una CPU en 60 s | softnet | | `reboot` | — | el reloj desde el arranque de un registro del log del kernel es menor que el del registro anterior | [requiere `privileged=yes`](/mikroscope/es/limits/privileged/) | | `link-flap` | puerto | dos o más registros de enlace up/down en un puerto en 60 s | [requiere `privileged=yes`](/mikroscope/es/limits/privileged/) | | `conntrack-cliff` | — | `nf_conntrack` cayó por debajo de la mitad de su valor guardado anterior | [requiere `privileged=yes`](/mikroscope/es/limits/privileged/) | | `conntrack-high` | — | ocupación por encima del 80 % de `nf_conntrack_max` **y** en aumento durante los últimos 60 s | [requiere `privileged=yes`](/mikroscope/es/limits/privileged/) | | `thermal-high` | zona | una zona a menos del 15 % de su propio disparo crítico declarado | una zona térmica que declare un disparo | | `thermal-rising` | zona | tres subidas consecutivas de un minuto de más de 1 °C cada una | una zona térmica | | `ipc-collapse` | `core` | el IPC de un segundo de un núcleo por debajo de la mitad de su mediana móvil **mientras** su tasa de ciclos está por encima de su mediana | la PMU | ### `counter-reset` **Salta cuando** el `resets` de la muestra del kernel es mayor que 0: el agente encontró un contador más bajo que su lectura anterior sin un desbordamiento de 32 bits que lo explique, y usó el valor del contador tras el reinicio como delta de ese tick, una cota inferior. `value` es el número de esos contadores, `threshold` 0. **Necesita** solo una muestra. La misma condición marca la muestra como `suspect`, y los valores derivados por paquete se omiten para ella. **No puede afirmar** qué contador se reinició, ni por qué. Cada delta de esa muestra es una cota inferior. ### `agent-restart` **Salta cuando** el número de secuencia de una muestra es menor que el de la muestra anterior. `value` es el nuevo número de secuencia, `threshold` el anterior. **Necesita** que el colector haya visto al menos una muestra antes del reinicio. La secuencia del agente vuelve a empezar desde 1 en cada arranque, así que así es como se ve un reinicio desde fuera. > **Un forward en marcha no lo ve hoy** > > Leído en el código, no observado en una ejecución: `forward` guarda su cursor de lectura, el último > número de secuencia que recibió, y nunca lo reinicia. El anillo de un agente reiniciado responde a > `/snapshot?since=` con nada, y sin hueco, hasta que su nueva secuencia supera > el cursor antiguo; para entonces cada muestra que devuelve tiene un número de secuencia mayor que > el anterior. Así que un `forward` que sigue en marcha durante el reinicio de un agente no recibe > nada del agente nuevo durante tanto tiempo como llevaba en marcha el anterior — un día a 10 Hz > para un agente que llevaba un día — y esta regla no puede saltar en él. La regla solo la ejercitan > las pruebas unitarias de la etapa de derivación; ninguna prueba de forward ni de extremo a extremo > la cubre. Reiniciar `forward` después de que se reinicie el agente recupera los datos, pero > entonces no hay número de secuencia anterior y la regla tampoco salta. **No puede afirmar** por qué se reinició el agente. Un colector reiniciado a la vez no tiene número de secuencia anterior y no ve nada. ### `agent-oom` **Salta cuando** el cgroup propio del contenedor registra un OOM kill en la muestra — el kernel mató un proceso dentro del contenedor de mikroscope. `value` es el número de kills. **Necesita** cgroup2 legible en el contenedor; sin él, el agente no informa de eventos de cgroup y esta regla no puede saltar. **No puede afirmar** nada sobre los números que la rodean: todos los números de esa ventana son sospechosos. Cómo dimensionar la memoria del contenedor según el anillo del agente está en [el coste del observador](/mikroscope/es/cost/). ### `microburst` **Salta cuando** la muestra de una CPU lleva la marca [`burst`](/mikroscope/es/sinks/derive/#burst) — un descarte, o squeezes por encima del percentil 90 móvil de esa CPU y al menos 3, mientras su cuenta de paquetes estaba en su mediana móvil o por debajo — y esa CPU tiene ya al menos tres muestras marcadas en los últimos 60 s. `value` es el número de muestras marcadas en la ventana, `threshold` 3; el mensaje lleva los squeezes, descartes y paquetes de la última muestra y la mediana móvil. **Necesita** `/proc/net/softnet_stat`, que lee todo despliegue, y diez muestras de historia por CPU antes de su primera marca. Las referencias abarcan diez segundos de reloj de pared a cualquier cadencia del muestreador. **Falsos positivos, medidos.** En este equipo el squeeze es el fondo, no un suceso. En el RB5009 de referencia `time_squeeze` es 0 en el 87,3 % de las muestras por CPU, 1 en el 11,2 %, 2 en el 1,2 % y 3 en el 0,21 %, mientras que softnet no descartó nada en esas mismas 24 h. Una ventana móvil de una distribución que es siete octavos ceros tiene un percentil 90 de 1, así que «por encima de p90» lo cumple cualquier 2 — por eso quien manda es el suelo y no el percentil. Reejecutada sobre 6 h de muestras almacenadas, con suelo 2 salta 77,7 /h y con suelo 3 salta 0,5 /h, y sigue marcando 88 muestras para el contador de ráfagas y para `derived.burst`. Un descarte marca por sí solo, con cualquier cuenta de squeezes. **No puede afirmar** el tamaño de la ráfaga, ni el flujo ni la interfaz que la causó. Dice que el kernel se quedó sin presupuesto más de lo habitual mientras llevaba menos paquetes de lo habitual — prueba de algo más corto que el intervalo de muestreo. ### `reboot` **Salta cuando** la marca de tiempo de un registro del log del kernel, en microsegundos desde el arranque, es menor que la del registro anterior. `value` y `threshold` son las marcas nueva y anterior en segundos. **Necesita** `privileged=yes`, que exige el log del kernel, y un colector que siga en marcha durante el reinicio mientras el agente vuelve. No necesita credenciales de la API de RouterOS. Que el colector siga en marcha no basta por sí solo: el agente que vuelve tras el reinicio es un proceso nuevo, y sus muestras solo llegan a `forward` cuando su secuencia supera el cursor antiguo (ver [`agent-restart`](#agent-restart)). La regla solo puede saltar entonces con un registro del log del kernel cuyo tiempo desde el arranque siga por debajo del último visto antes del reinicio. Esto está leído en el código, no observado. **No puede afirmar** que se vea cada reinicio. El agente lee el log del kernel desde el final al arrancar, así que el primer registro tras un reinicio es uno escrito después de que el agente arrancara; si el tiempo desde el arranque de ese registro es posterior al del último registro antes del reinicio, el reloj no retrocedió y no salta nada. ### `link-flap` **Salta cuando** un registro del log del kernel que nombra una interfaz se clasifica como `link-up` o `link-down` —el mismo clasificador que pone un `kind` en cada registro de puerto— y ese puerto tiene ya dos o más de esos registros en los últimos 60 s. `key` es el nombre actual del puerto en RouterOS cuando el inventario de interfaces de la capa de la API lo da, el nombre por defecto de la placa cuando la tabla de puertos del agente traduce el nombre del kernel, y el nombre del kernel en otro caso. `value` es el número de registros en la ventana, `threshold` 2. **Necesita** `privileged=yes`. Un nombre de RouterOS necesita que la placa esté en la tabla de puertos del agente, y el nombre actual necesita además la capa de la API; consulta [puertos de RouterOS y nombres del kernel](/mikroscope/es/reference/port-names/). **No puede afirmar** una avería. Un cable desenchufado y vuelto a enchufar en menos de un minuto son un registro de down y uno de up, y salta. No se ha capturado ningún flap provocado con esta regla en marcha; los flaps medidos para la tabla de puertos el 2026-09-15 no lo fueron. ### `conntrack-cliff` **Salta cuando** la cuenta de objetos activos de la caché slab `nf_conntrack` está por debajo de la mitad de su valor guardado anterior. `value` es la nueva cuenta, `threshold` la anterior. **Necesita** `privileged=yes`, para `/proc/slabinfo`. El slab se lee a unos 6 Hz y se guarda al cambiar, así que «anterior» es la muestra guardada anterior, no el tick anterior. **Tampoco puede afirmar** una avería: el mensaje dice «a flush or a reset», y un vaciado deliberado de la tabla de conexiones la hace saltar. ### `conntrack-high` **Salta cuando** los objetos activos de `nf_conntrack` están por encima de 0,8 del `nf_conntrack_max` del kernel, **y** la cuenta es mayor que el valor guardado más antiguo de los últimos 60 s. `value` es la ocupación como fracción, `threshold` 0,8. **Necesita** `privileged=yes`, el techo publicado por el kernel y al menos dos muestras guardadas en los últimos 60 s. **No puede afirmar** cuándo se llenará la tabla: a propósito, no se adjunta ningún tiempo hasta el llenado. Como escala, la tabla del router de referencia estaba al 0,63 % de su techo de 966 656 el 2026-09-12. ### `thermal-high` **Salta cuando** la lectura de una zona está en 0,85 o más del punto de disparo crítico declarado más bajo de esa misma zona. `value` es la lectura en °C, `threshold` 0,85 × el disparo. **Necesita** una zona térmica que declare un disparo crítico. Una zona que no declara ninguno nunca salta; no se compara nada con un número compilado. **No puede afirmar** que haya fallado la refrigeración, ni nada sobre una zona que la placa no informa. ### `thermal-rising` **Salta cuando** las últimas cuatro medias de un minuto completas de una zona superan cada una a la anterior en más de 1 °C — tres subidas consecutivas. `value` es la subida desde la primera de las cuatro medias hasta la última, `threshold` 3. Un intervalo de un minuto se cierra con la primera muestra al menos 60 s después de abrirse, y la regla se comprueba cada vez que se cierra uno, así que lo antes que puede saltar es tras unos cuatro minutos de lecturas. **Necesita** una zona térmica. Las medias son sobre las lecturas que llevaban las muestras; la temperatura se lee a la cadencia de sondeo declarada por la zona, 1 Hz en el equipo de referencia. **Resolución.** El sensor del equipo de referencia cuantiza a unos 0,42 °C, así que 1 °C por minuto son 2,4 escalones y se puede resolver. **No puede afirmar** una causa, ni una subida más lenta que 1 °C por minuto. ### `ipc-collapse` **Salta cuando**, en un núcleo, se cierra un intervalo de un segundo con instrucciones por ciclo por debajo de la mitad de la mediana de los intervalos móviles de ese núcleo **mientras** su tasa de ciclos está por encima de la mediana de sus tasas móviles. `key` es `core`, `value` el IPC del intervalo, `threshold` la mitad de la mediana. **Necesita** los `cycles` e `instructions` de la PMU por CPU (con `privileged=yes`), y veinte intervalos de un segundo completos de historia para ese núcleo antes de poder saltar; la historia móvil guarda hasta sesenta. **No puede afirmar** inactividad, ni nada agregado: la conjunción con la tasa de ciclos es lo que separa un régimen de bloqueo por memoria de un núcleo que se queda quieto, y la regla es por núcleo, nunca entre núcleos. > **Deliberadamente no provocado** > > De las once reglas, solo `microburst` tiene un comportamiento registrado en el equipo de > referencia. Las demás las ejercitan las pruebas unitarias de la etapa de derivación contra > muestras construidas. No se ha provocado en el RB5009, con estas reglas en marcha, ningún OOM kill > dentro del contenedor, reinicio, flap de enlace, vaciado o tormenta de conntrack, excursión > térmica ni colapso de IPC: es el router de producción del propietario, los reinicios esperan a una > ventana de mantenimiento, y una tormenta de conntrack provocada puede dejar sin acceso el camino > por el que se está trabajando. ## Véase también - [Lo que deriva el colector](/mikroscope/es/sinks/derive/): la marca `burst` y los demás valores que se escriben junto a las muestras. - [Reglas de alerta](/mikroscope/es/dashboards/alerts/): las reglas de Grafana construidas sobre las detecciones y los contadores de averías. - [Un bucle que solo veía el kernel](/mikroscope/es/playbooks/loop/): lo que cazó el log del kernel en el router de producción. - [Lo que aporta privileged](/mikroscope/es/limits/privileged/): las fuentes de las que dependen la mitad de estas reglas. --- # El flujo de datos del equipo Lo que el agente establece sobre la placa sin la API de RouterOS — identidad, techos, cadencias — y cómo el colector lo entrega a cada destino como hechos y no como muestras. Source: https://jmrplens.github.io/mikroscope/es/sinks/device-info/ Al arrancar, el agente averigua lo que puede sobre la placa en la que corre, sin la API de RouterOS y sin ninguna credencial, y lo sirve en `/capabilities`. El colector lo entrega a cada destino como registro propio. Esta página responde a qué guarda el registro, cuándo se envía y dónde lo pone cada destino. ## Lo que lleva - **Identidad**: la cadena de modelo del device tree (la placa), el kernel, el número de núcleos, si el contenedor es privilegiado y si tiene cgroup2, las fuentes activas, la procedencia de la tabla de puertos, y un hash del kernel, el número de núcleos y las fuentes activas. - **Los techos propios del equipo**, leídos una vez al arrancar el agente: el punto de disparo crítico y el retardo de sondeo de cada zona térmica; el rango cpufreq, la escalera de frecuencias, el governor y el cluster de cada núcleo; el techo de conntrack del kernel; el `memory.max` del propio contenedor. - **Cadencias**: la cadencia a la que se lee y guarda cada fuente de nivel, y el motivo con nombre por el que no es la cadencia del muestreador. Un techo que la placa no publica se omite, nunca se rellena con un número sacado de otro sitio — el techo de conntrack de 966 656 y el límite de 64 MiB del contenedor son los de la propia placa. ### Los motivos que da una cadencia Los motivos de cadencia que puede publicar una fuente: | `reason` | Qué significa | | --- | --- | | `rate` | se lee a la cadencia completa del muestreador; nada de lo que declara el equipo justifica menos | | `declared` | el equipo publica su propia cadencia de refresco, y leer más rápido devuelve el mismo valor con un temblor nuevo | | `policy` | un ajuste dice que el valor no puede moverse por sí solo: un gobernador cpufreq `userspace` | | `budget` | un coste de análisis medido | | `change` | se lee en cada tick y se guarda solo cuando se mueve | | `override` | `FLOOR_HZ` está fijado, y todas las fuentes de nivel van a su única cadencia | Los contadores nunca tienen suelo; solo las fuentes de nivel llevan cadencia. Los suelos en sí y cómo se midieron están en [cada fuente a su propio suelo](/mikroscope/es/limits/source-floors/). ## Cuándo se envía Cuando arranca `forward`, cada vez que cambia el hash de capacidades del agente — un agente reiniciado con otro conjunto de fuentes, por ejemplo — y, si no, cada cinco minutos. El colector comprueba el hash en la lectura de estado que hace cada minuto para volver a medir el desfase de reloj, así que un cambio llega a los destinos en más o menos un minuto. Tanto el transporte directo como el relay pueden obtener `/capabilities`; cuando la obtención falla, el colector lo registra, no envía nada y lo vuelve a intentar en la siguiente lectura de estado. Que no se envíe nada es ausencia, no una placa sin datos. **Por qué se repite.** Estos datos son filas con la marca de tiempo del colector, así que un almacén solo los tiene en los instantes en que se enviaron, y una ventana de panel que no contiene ningún envío no contiene ningún dato. Enviados una sola vez al arrancar, los cuatro paneles del equipo muestran «No data» en toda ventana posterior: medido en el despliegue de referencia el 2026-09-17, donde la última fila de equipo tenía 26 horas y esos paneles llevaban vacíos otro tanto. Cinco minutos pone los datos dentro de cualquier ventana en la que valga la pena leerlos y cuesta doce filas por envío — una de identidad, una por zona térmica, una por núcleo con datos de cpufreq, una por fuente de nivel — frente a las 864 000 filas de muestra al día que produce `--hz 10`. La repetición va marcada como tal, y los destinos se reparten según eso: los almacenes la escriben como cualquier otra fila, que es justo para lo que está, y los flujos pensados para que alguien los lea — Loki, stdout, una grabación — la saltan, porque un registro es para lo que cambia. Así Loki sigue llevando exactamente una línea `device` por conjunto de datos. El hash solo cubre la cadena del kernel, el número de núcleos y los nombres de las fuentes activas; quedan fuera la placa, `privileged`, cgroup, la procedencia de la tabla de puertos, cada techo y cada cadencia. Un agente reiniciado cuyo único cambio es un techo o una cadencia — un `--memory-max` nuevo, o un `FLOOR_HZ` con el mismo conjunto de fuentes — conserva su hash, así que el cambio no es lo que dispara el envío; llega a los destinos en la siguiente repetición de cinco minutos, y la fila que lo lleva queda marcada entonces y no cuando ocurrió. Esto está leído en el código (`capsHash` en `internal/agent/source.go`), no observado. Son hechos, no muestras. No tienen reloj propio, así que los destinos que ponen marca de tiempo a sus registros los marcan con el reloj del colector en el momento en que se enviaron; las líneas del fichero y de stdout `json` no llevan marca de tiempo, y Prometheus los genera al hacer el scrape. Nunca se mezclan en una fila de muestra. ## Dónde lo pone cada destino | Destino | Forma | | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | InfluxDB, Telegraf, stdout `lp` | `mikroscope_device{board,kernel}`, `mikroscope_device_thermal{zone}`, `mikroscope_device_cpufreq{cpu}`, `mikroscope_device_cadence{source,reason}` | | SQL | las tablas `mikroscope_device`, `mikroscope_device_thermal`, `mikroscope_device_cpufreq`, `mikroscope_device_cadence` | | file, stdout `json` | una línea `{"device":…}` con las capacidades tal como se obtuvieron | | Elasticsearch | un documento con `kind: device` | | Graphite | los datos numéricos bajo `device.*`; placa, kernel y governor no tienen forma en Graphite | | OTLP | gauges: `mikroscope.device.cores` con placa, kernel y hash como atributos, los techos térmicos y de cpufreq, `mikroscope.device.source_cadence` | | Loki | una línea `source="device"`, `level="info"`: board, kernel, cores, privileged, cgroup, sources, hash | | Prometheus | las mismas familias de datos del equipo que lleva el propio `/metrics` del agente | ### InfluxDB y SQL | Medida o tabla | Identificada por | Guarda | | --------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_device` | `board`, `kernel` | `cores`, `privileged`, `cgroup`, `sources` (unidas por comas, ordenadas), `hash`, `conntrack_max`, `cgroup_mem_max`, `ports_from` | | `mikroscope_device_thermal` | `zone` | `critical_celsius`, `polling_ms` | | `mikroscope_device_cpufreq` | `cpu` | `cluster` (el núcleo de número más bajo que cambia de frecuencia con este), `min_khz`, `max_khz`, `governor`, `steps` | | `mikroscope_device_cadence` | `source`, `reason` | `hz` | En InfluxDB `board` y `kernel` son tags, `unknown` cuando están vacíos; en SQL son columnas, NULL cuando están vacías, y un techo que no se publicó es NULL. `steps` es la escalera de frecuencias como lista de kHz separada por espacios. ### Prometheus | Familia | Lleva | | ------------------------------------------------------------------------------ | ----------------------------------------------------- | | `mikroscope_device_info{board,kernel,cores,privileged,cgroup,ports_from,hash}` | siempre 1 | | `mikroscope_thermal_critical_celsius{zone}` | el disparo crítico más bajo de la zona | | `mikroscope_thermal_polling_seconds{zone}` | el retardo de sondeo de la zona | | `mikroscope_cpu_frequency_limit_hertz{cpu,bound}` | el rango del reloj hardware, `min` y `max` | | `mikroscope_cpu_frequency_step_hertz{cpu,step}` | cada frecuencia que usará el driver | | `mikroscope_cpu_frequency_governor_info{cpu,governor}` | siempre 1 | | `mikroscope_cpu_frequency_cluster{cpu}` | el cluster, nombrado por su núcleo de número más bajo | | `mikroscope_self_cgroup_memory_max_bytes` | el `memory.max` del propio contenedor | | `mikroscope_source_cadence_hz{source,reason}` | la cadencia de cada fuente de nivel | En el RB5009 de referencia, leídos de `related_cpus` y `affected_cpus` dentro del contenedor el 2026-09-14 (RouterOS 7.24.2), los clusters de cpufreq son `{0,1}` y `{2,3}`, así que un panel de frecuencia por núcleo son en realidad dos series. Esa es la topología de esta placa y de ninguna otra: el agente lee los clusters por equipo, y no se ha medido ninguna otra placa. El techo de conntrack llega a Prometheus como `mikroscope_slab_limit_objects` de la caché `nf_conntrack` y no como una familia de datos del equipo. ## Véase también - [Los endpoints HTTP del agente](/mikroscope/es/reference/http/): `/capabilities`, de donde sale este registro. - [Cada fuente a su propio suelo](/mikroscope/es/limits/source-floors/): las cadencias y las medidas que hay detrás. - [InfluxDB 3](/mikroscope/es/sinks/influxdb/): las demás medidas junto a las que están los registros del equipo. --- # Cinco dashboards, una sola lista Qué contienen los dashboards de InfluxDB 3, Prometheus, PostgreSQL, Graphite y Elasticsearch, sección a sección y en el orden en que se leen, y por qué los cinco almacenes no llevan los mismos paneles. Source: https://jmrplens.github.io/mikroscope/es/dashboards/ `dashboards/` contiene un dashboard de Grafana por almacén, los dos generados por `mikroscope dashboards gen` a partir de una única lista de paneles en `internal/dashboards`. Esta página responde a qué hay en ellos: qué secciones, qué paneles en cada una, qué muestra el Overview antes de desplegar nada y qué paneles existen solo en uno de los dos almacenes. Importarlos y comprobarlos contra un Grafana en marcha está en [Importar y comprobar](/mikroscope/es/dashboards/import-and-check/); las reglas de alerta que se generan a su lado, en [Reglas de alerta](/mikroscope/es/dashboards/alerts/). Los dashboards están en inglés. Los títulos de filas y paneles se citan aquí tal como los muestra Grafana, sin traducir, para que puedas encontrarlos. ## Lo que escribe `gen` - dashboards/ - mikroscope-influxdb.json 171 paneles, InfluxDB 3 (SQL) - mikroscope-prometheus.json 133 paneles, Prometheus - mikroscope-postgres.json 156 paneles, PostgreSQL / TimescaleDB - mikroscope-graphite.json 41 paneles, Graphite - mikroscope-elasticsearch.json 30 paneles, Elasticsearch - mikroscope-alerts-influxdb.yaml 10 reglas - mikroscope-alerts-prometheus.yaml 11 reglas - mikroscope-alerts-postgres.yaml 8 reglas ```sh mikroscope dashboards gen # escribe los cuatro ficheros en ./dashboards mikroscope dashboards gen --out /tmp/dash # o en otro directorio ``` Los cinco dashboards están en el formato de exportación compartible de Grafana: la fuente de datos es un marcador `${DS_MIKROSCOPE}` declarado en `__inputs`, no hay `id` y el `uid` es fijo — `mikroscope-influxdb` y `mikroscope-prometheus` —, así que una reimportación actualiza el mismo dashboard en su sitio en vez de crear un segundo. Un test comprueba que dos generaciones del dashboard de InfluxDB son idénticas, y regenerar el 2026-09-15 reprodujo byte a byte los cuatro ficheros del repositorio. El dashboard de InfluxDB consulta InfluxDB 3 en SQL, y toda consulta está acotada por `$__timeFilter`, porque InfluxDB 3 Core rechaza los recorridos sin límite; donde una columna se llama `cluster` el SQL la entrecomilla, porque `cluster` es una palabra reservada en DataFusion. El dashboard de Prometheus consulta un Prometheus que hace scrape del `/metrics` del colector — que lleva todo lo que lleva el del agente, recalculado a partir de las muestras, más las familias derivadas y de detección propias del colector — y del propio agente para las pocas familias que solo produce el muestreador. Prometheus 3 representa el borde del bucket cero de un histograma como `le="0.0"`, así que una consulta que lee ese bucket casa con `le=~"0|0.0"`. Los dos trabajos de scrape están en [Importar y comprobar](/mikroscope/es/dashboards/import-and-check/#prometheus-dos-trabajos-de-scrape). ## Qué aspecto tiene Una captura por sección del dashboard de InfluxDB, en el orden en que las pone el dashboard. Cada una enlaza al fichero a tamaño completo. > **Esto es una base de demostración, no un router** > > Todas las capturas de abajo son el dashboard de InfluxDB sobre una ejecución del mismo agente > simulado enlatado que usan las baterías de extremo a extremo, escrita en un almacén en contenedor > por el paso de llenado de `test/e2e/docker` y fotografiada por > `site/scripts/gen-dashboard-captures.mjs`. Nada en ellas viene de un equipo real, el host se llama > `rb5009` porque el agente simulado imita el `/proc` capturado de esa placa, y las cifras son las > que publique el simulador: léelas como «esta es la forma de la página», nunca como una medición. > Un panel vacío en una captura es uno para el que el agente simulado no produce nada; en un router > real con la capa de la API en marcha, varios se llenan, y vuelven dos secciones enteras que el > sondeo del almacén movió a «no disponible». Una captura por sección del dashboard de InfluxDB, sobre una base de demostración llenada por el agente simulado, en [la página](/mikroscope/es/dashboards/): - Overview (12) - CPU and scheduler (11) - Memory and load (9) - Connections (9) - Interface traffic (13) - Detections and captures (5) - Network receive path (9) - Forwarding cost (derived) (4) - Interrupts and softirqs (12) - Temperature and clock (10) - Kernel log (7) - CPU: how long a core stayed busy (3) - Memory: fragmentation (3) - Memory: reclaim and page faults (8) - Memory: detail and cross-checks (5) - Hardware counters (PMU) (11) - Flash wear (7) - NAND health (ECC) (2) - RouterOS API cross-checks — CPU and memory (9) - The observer (11) - The observer: sampler timing and self events (3) - This device (3) - Not available on this device (5) ## Una lista, dos almacenes Cada panel se declara una sola vez, con su SQL y su PromQL uno al lado del otro, de modo que un panel añadido a un almacén queda añadido al otro. Cuando un panel no tiene consulta para un almacén, el generador **lo quita del dashboard de ese almacén** en vez de publicarlo para que muestre "No data" para siempre. Una sección a la que se le quitan todos los paneles no emite ninguna fila. Por eso difieren los recuentos, y difieren en los dos sentidos. Casi toda la familia térmica y de reloj, la sección de desgaste de la flash, el censo de slab y varios paneles de memoria tienen SQL y no PromQL, así que solo existen en InfluxDB. Los histogramas de temporización del muestreador, las supresiones de disparadores y el presupuesto de capturas viven solo en el `/metrics` del agente. La racha de ocupación aún en curso y la antigüedad de cada lectura retenida son familias de la exposición de Prometheus, también en el `/metrics` del colector, sin campo en InfluxDB. Ninguno de los dos grupos se escribe en InfluxDB, así que los paneles que los leen solo existen en Prometheus. Cada panel de abajo que está en un solo almacén dice en cuál. Paneles por sección y por almacén: | Sección (fila en Grafana) | InfluxDB 3 | Prometheus | PostgreSQL | Graphite | Elasticsearch | | --- | --- | --- | --- | --- | --- | | Overview | 12 | 12 | 12 | 10 | 6 | | CPU and scheduler | 11 | 9 | 11 | 1 | sin fila | | Memory and load | 9 | 9 | 9 | 7 | 5 | | Connections | 9 | 3 | 9 | 1 | 1 | | Interface traffic | 13 | 11 | 9 | 2 | sin fila | | Detections and captures | 5 | 4 | 5 | sin fila | sin fila | | Network receive path | 9 | 7 | 9 | 3 | 3 | | Forwarding cost (derived) | 4 | 4 | 4 | sin fila | sin fila | | Interrupts and softirqs | 12 | 10 | 12 | 2 | 2 | | Temperature and clock | 10 | 2 | 10 | 2 | 2 | | Kernel log | 7 | 6 | sin fila | sin fila | sin fila | | CPU: how long a core stayed busy | 3 | 4 | 3 | sin fila | sin fila | | Memory: fragmentation | 3 | 1 | sin fila | sin fila | sin fila | | Memory: reclaim and page faults | 8 | 6 | 8 | 2 | 1 | | Memory: detail and cross-checks | 5 | 3 | 5 | 1 | 1 | | Hardware counters (PMU) | 11 | 7 | 11 | sin fila | sin fila | | Flash wear | 7 | sin fila | 7 | 1 | 1 | | NAND health (ECC) | 2 | 2 | 2 | sin fila | sin fila | | RouterOS API cross-checks — CPU and memory | 9 | 9 | 9 | 1 | sin fila | | The observer | 11 | 9 | 10 | 3 | 3 | | The observer: sampler timing and self events | 3 | 7 | 3 | sin fila | sin fila | | This device | 3 | 3 | 3 | sin fila | sin fila | | Not available on this device | 5 | 5 | 5 | 5 | 5 | | **Total** | **171** | **133** | **156** | **41** | **30** | Los recuentos son los de los ficheros del repositorio, que llevan los valores compilados por defecto. `import` y `check` preguntan primero a la fuente de datos qué contiene y pueden meter paneles en la última fila o sacarlos de ella; consulta [No disponible en este equipo](#no-disponible-en-este-equipo) más abajo. ## Orden de lectura Las secciones están ordenadas por la frecuencia con que se abren, no por taxonomía (propietario, 2026-09-13: "normalmente lo primero que se quiere ver es cpu, RAM, conexiones y cosas así"). Primero las cuatro preguntas con las que llega un operador — cuánto trabaja, cuánta memoria le queda, cuántas conexiones mantiene, cuánto tráfico mueve —, después lo que marcaron el colector y el agente, después las familias que explican las cuatro primeras cuando una tiene mala pinta, después los niveles profundos a los que un lector va a propósito, y al final lo que mikroscope le cuesta al router que está midiendo. Todas las secciones salvo el Overview se publican **plegadas**. Grafana guarda los paneles de una fila plegada dentro del objeto de la fila y no ejecuta ninguna de sus consultas hasta que alguien la despliega, así que el primer renderizado pide al almacén los doce paneles del Overview y no los 171. Los valores por defecto son un rango de 3 horas (`now-3h`) y un refresco de 5 minutos. El refresco de 5 minutos se mantiene para cuando alguien despliega una sección: el censo de slab y los mapas de calor de la PMU y del coste por muestra devuelven una fila por muestra, y `maxDataPoints` no se aplica al SQL en bruto. El rango de 3 horas se mantiene porque `now-15m` abría 108 paneles vacíos con el agente parado, y con el agente en marcha una ventana de 15 minutos de muestras de 100 ms dibujaba muros de ruido ilegibles. Una investigación en vivo fija su propio rango y su propio refresco. ## El Overview La única sección abierta. Responde a "¿está sano este router ahora mismo?" y, antes de eso, a "¿puedo creerme estos números?". Ningún panel de ella es un mapa de calor ni una combinación de varias medidas, y todos devuelven un agregado; tres paneles ("Reboots in the window", "Sample continuity" y "Ticks never delivered, this window") lo calculan con una función de ventana sobre las muestras en bruto de la ventana. La mayoría son copias de paneles que también viven en una sección posterior, porque un panel de Grafana pertenece exactamente a una fila. "Connections tracked right now", "Load average (1 min) against the core count", "Reboots in the window" y "Detections in the window" viven solo aquí, igual que "OOM kills in the window" en Prometheus, donde la copia de la sección de recuperación de memoria no tiene PromQL y se quita. Las copias comparten su SQL; en Prometheus, el "OOM kills in the window" del Overview y el "Ticks never delivered, this window" del observador difieren de sus homólogos (el del observador lleva una tercera consulta, los ticks perdidos por retraso del muestreador). 1. **CPU busy per core**, **Memory in use, against the kernel's own total** (un indicador de `(MemTotal − MemAvailable) / MemTotal`, naranja al 75 %, rojo al 90 %) y **Connections tracked right now** (los objetos activos del slab `nf_conntrack`; en blanco en un contenedor sin privilegios, y el panel lo dice). 2. **Interface throughput — rx above, tx below** (capa de la API; en blanco con `--api-mode off`, y el panel nombra la opción), **Die temperature by zone** y **Load average (1 min) against the core count**, donde el número de núcleos se mide en el almacén en vez de suponerse. 3. **Sample continuity**, a todo lo ancho: cada intervalo, nunca más estrecho que un minuto, clasificado como continuo, con ticks perdidos o con el agente reiniciado, a partir de la primera diferencia del número de secuencia de las muestras. En Prometheus, que no tiene número de secuencia, la franja lo aproxima a partir del contador de huecos del colector y de los reinicios del contador de muestras del agente, y su descripción lo dice. Todos los demás paneles del dashboard deben leerse contra esta franja. 4. **Packets dropped in the kernel RX path (window total)**, **OOM kills in the window**, **Reboots in the window** y **Detections in the window** — cada uno a cero en un equipo sano y coloreado según sus propios umbrales. 5. **Ticks never delivered, this window**, junto al recuento de reinicios del agente. > **Una pantalla, medida una vez** > > El 2026-09-14 un Overview de once paneles midió 1 052 px de alto en una ventana de navegador de > 1 080 px — una pantalla de escritorio sin nada cortado. El Overview tiene doce paneles y la > cuadrícula pasa "Ticks never delivered" a su propia línea a todo lo ancho; esa altura no se ha > medido en un navegador, aunque el JSON de los dos paneles del repositorio le da 31 unidades de > cuadrícula, cabecera de fila incluida. En un móvil no es una pantalla: Grafana apila una fila de > 24 columnas en una sola columna por debajo de unos 768 px, y un Overview de ocho paneles se > renderizó con 2 188 px de alto en una ventana de 844 px el 2026-09-12. ## Las cuatro preguntas de todos los días ### CPU y planificador Ticks de `/proc/stat` y la cadencia del propio muestreador. Cada serie por núcleo sale de los datos (`GROUP BY cpu`, `by (cpu)`), así que una placa de 2 núcleos y una de 8 dibujan cada una sus propias líneas. - CPU busy per core - Busy per core, p95 of one-second means - Worst sample interval, relative to the window's median - Per-core busy as states — which core paid, and when - Where the ticks went — device share by mode - softirq share of busy time, per core - Busy-tick distribution per sample - Share of samples the tick counter called completely idle - Cycles retired in samples /proc/stat called idle — solo InfluxDB - nice, irq and iowait ticks in the window - Tick accounting closes — solo InfluxDB ### Memoria y carga Los niveles de `/proc/meminfo` y `/proc/loadavg`, promediados o con el máximo sobre un intervalo y nunca sumados. Todo "porcentaje de RAM" divide por el total que emite el propio kernel. - Memory in use, against the kernel's own total - Load average, all three windows - Runnable threads out of total - Memory by category, as a share of total - Free memory — three definitions, against the ceiling - Commit headroom — Committed_AS as a share of CommitLimit - Load average (1 min) per core - Threads on the whole router - Slab — the conntrack and route tables the netns hides ### Conexiones La tabla de conexiones del router, sacada del asignador de slab global (`/proc/slabinfo`, solo con privilegios), junto al recuento de la capa de la API cuando se consulta. Una sección vacía aquí significa un agente sin privilegios, no un router en reposo; el panel "Slab caches reporting" existe para decir cuál de los dos es. - Connection table, two ways — slab objects against the RouterOS API count - Connection churn floor — peak-to-trough swing inside each bin — solo InfluxDB - Packet-buffer and large-allocation caches — solo InfluxDB - Every slab cache, normalized to its own window minimum — solo InfluxDB - Connection count distribution over time (nf_conntrack) — solo InfluxDB - Slab census — latest, min, max and spread per cache — solo InfluxDB - Connection table occupancy - Connections as RouterOS counts them (API poll) - Slab caches reporting (is this a privileged deployment?) — solo InfluxDB ### Tráfico por interfaz `/interface/monitor-traffic`, los contadores por puerto del MAC y `/system/health` desde la API de RouterOS — los únicos contadores por interfaz que tiene mikroscope, porque el `/proc/net/dev` del contenedor describe su propio veth — junto a la configuración que la capa de la API lee al arrancar y cada `--labels-every`: qué es cada interfaz. Las tasas llegan ya por segundo y nunca se suman, y los contadores por puerto tampoco, porque RouterOS cuenta una cosa distinta en cada tipo. Un puerto del switch cuenta su cable, tramas reenviadas por hardware incluidas; el bridge cuenta su lado de CPU; una VLAN o un PPPoE cuentan lo que la CPU envió y recibió. En el RB5009 de referencia (RouterOS 7.24.2, 2026-09-16) ether1 había recibido 255,8 GB por el cable y 29,7 GB de ellos habían llegado a la CPU: ether1 y bridge son planos distintos, y ninguno es un subconjunto del otro. El conjunto de interfaces de los paneles de tasas es el que seleccionó `--interfaces`, así que una interfaz que falta en uno puede estar en reposo, sin configurar o sin seleccionar, y las tres cosas se ven igual. - Interface throughput — rx above, tx below - Interface packet rate — rx above, tx below - Mean packet size per interface - CPU cost of forwarding: cpu-load against the busiest interface's packet rate — solo InfluxDB - Port errors per bin — typed, from the MAC counters - Where a port's receive bytes went — switched in hardware, CPU fast path, CPU slow path — solo InfluxDB - Link flaps — link-downs per interface, per bin - Frame size mix over the window, per Ethernet port - Interface drops — rx, tx and tx-queue - What each interface is: type, role, bridge and label - Interface inventory and window summary - /system/health sensors, as RouterOS reads them - API tier coverage — which sources delivered, and when "What each interface is: type, role, bridge and label" es la tabla que hay que leer antes de comparar dos series de interfaz: una fila por interfaz que tiene el router — el tipo de RouterOS, sus listas de interfaces, el bridge del que es puerto, su comentario, su nombre de fábrica y su MTU — a partir de tres lecturas de configuración, nunca por consulta. "Frame size mix over the window, per Ethernet port" es una fila por puerto y no lleva total: cada contador `tx-rx-*` guarda los dos sentidos de su propio puerto, así que una trama conmutada de ether1 a ether8 se cuenta en los dos, y una suma por puertos cuenta dos veces cada trama conmutada. "Interface inventory and window summary" no lleva columnas de errores, porque `monitor-traffic` en RouterOS 7.24.2 no devuelve ninguna tasa de error; los contadores de error tipificados del MAC tienen su propio panel. ## Lo que se marcó ### Detecciones y capturas Sucesos, no niveles: lo que la [etapa de derivación](/mikroscope/es/sinks/derive/) del colector y la [captura por disparo](/mikroscope/es/record/triggers/) del agente dijeron de la ventana. Vacío es el estado sano, y los paneles de sucesos están marcados como vacíos conocidos para que `check` no falle por ello. - Detections per bin, by rule - Memory pressure state - Detections in this window — solo InfluxDB - Trigger fires and suppressions per bin - Captures held on the agent, and the budget they pin — solo Prometheus - Trigger markers in this window — solo InfluxDB En "Trigger fires and suppressions per bin", las supresiones solo están en Prometheus: viven en el `/metrics` del agente. ### Las rayas rojas discontinuas de todos los paneles Un dashboard con detecciones en su ventana dibuja una raya vertical roja discontinua, con un pequeño triángulo al pie del eje, cruzando **todos** los paneles en el instante de cada una. Son anotaciones, no datos: no son un hueco en el registro, ni un tick perdido, ni un corte de la serie. Están para que el panel que estés mirando, sea cual sea, se pueda leer al lado de lo que el colector señaló en ese momento — un régimen de atasco en memoria en un núcleo, una microrráfaga en una cola, un enlace que parpadeó — sin tener que bajar a la sección de detecciones para enterarte de que pasó algo. Cada dashboard trae dos capas: | Capa | Color | Por defecto | Qué es cada marca | | -------------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------ | | **detections** | rojo | activada | una fila de `mikroscope_detection`: la regla, su clave y su mensaje, de la etapa de derivación del colector | | **triggers** | naranja | desactivada | un marcador de captura del agente: la condición que se disparó, el campo y el valor | Al pasar el ratón por una marca sale el mensaje que lleva la fila, así que la raya responde al *qué* además de al *cuándo*. La capa de disparos viene apagada para que un dashboard tranquilo siga tranquilo; actívala cuando estés trabajando con [capturas disparadas](/mikroscope/es/record/triggers/). **Cómo apagarlas.** Las dos capas son casillas en la fila de submenú de debajo del título del dashboard — la misma fila en la que van las variables de un dashboard, que en los de InfluxDB, Prometheus y PostgreSQL no lleva nada más. Pulsa el nombre de la capa para ocultar sus marcas. Es un ajuste de vista: dura la sesión, y guardar el dashboard lo conserva. No cambia nada de las filas subyacentes, y la sección de detecciones las sigue contando. En InfluxDB cada anotación es una fila de suceso con su mensaje; en Prometheus es `increase(…[1m]) > 0` con un paso de 1 minuto, así que allí una anotación marca el minuto, no el instante. Las capturas por sección de más arriba están tomadas con las dos capas apagadas, porque el agente simulado que llena la base de demostración dispara una detección cada pocos segundos y veinte minutos de eso salen fotografiados como una mancha roja. Esta es la misma vista general con la capa de detecciones activada, sobre una base de demostración que lleva tres — la densidad que produce un despliegue real, y el recuadro de abajo a la derecha cuenta esas mismas tres: *La capa de detecciones, activada: una raya roja discontinua por detección, en todos los paneles a la vez.* ## Por qué una de las cuatro tiene el aspecto que tiene ### Ruta de recepción de red `/proc/net/softnet_stat`, que es global incluso dentro del espacio de nombres de red del contenedor. Aquí no sobrevive ninguna banda absoluta de tasa de paquetes: el régimen de squeeze se clasifica como un múltiplo de la mediana de la propia ventana. - RX path: packets processed per second, per core - Squeeze rate: softirq budget exhaustions per second, per core - Squeeze pressure: budget exhaustions per 1 000 packets - Receive-path balance across cores - Packets dropped in the kernel RX path (window total) - Squeeze regime per core, as a multiple of this window's median - Burst distribution: packets per sample (all cores) — solo InfluxDB - Squeeze against throughput (1 s points, whole window) — solo InfluxDB - Packets per NET_RX poll, per core ### Coste de reenvío (derivado) Los cocientes por paquete y la proporción de ruta rápida que el colector deriva cruzando subsistemas — la PMU contra softnet, softnet contra las interrupciones, los contadores de puerto de RouterOS entre sí. La proporción de ruta rápida es la proporción del tráfico que una interfaz le entrega a la CPU, no una proporción del cable: `fp-rx-byte` sobre `driver-rx-byte` en un puerto del switch, sobre `rx-byte` en una interfaz software, con las tramas conmutadas por hardware fuera de los dos números. Ese reparto lo enseña el panel "Where a port's receive bytes went", a partir de los contadores en bruto. En el RB5009 de referencia (RouterOS 7.24.2, 2026-09-16) los puertos del switch leen en torno al 100 % — allí `fp-rx-byte` iguala a `driver-rx-byte` con unos pocos kB de diferencia —, mientras que bridge había llevado por la ruta rápida 211,9 GB de 663,0 GB desde el arranque (32 %) y PPPoE_DIGI el 99,97 %. La línea de tx suele estar ausente: `fp-tx-byte` se quedó en 0 en todas las interfaces de ese router después de cientos de GB transmitidos, así que el colector retiene la proporción de tx mientras el contador acumulado sea 0 en vez de dibujar un 0 % que no ha medido. Por intervalo, la proporción está ponderada por bytes — los bytes de ruta rápida del intervalo sobre los bytes del intervalo — porque una interfaz que mueve unos pocos paquetes por consulta oscila entre 0 y 100 % de una consulta a otra, como mostró ese mismo router el 2026-09-15. La forma de Prometheus es el medidor por consulta del colector y conserva ese ruido. - Cycles, instructions and cache misses per packet - Packets per device interrupt (NAPI coalescing depth) - Fast-path share of the traffic each interface hands the CPU - Sub-sample bursts: squeezed samples whose packet count looked ordinary ### Interrupciones y softirqs Deltas de `/proc/interrupts` y `/proc/softirqs`. El nombre del dispositivo se recupera en la consulta a partir del texto en bruto de `/proc/interrupts`, no comparándolo con un nombre de driver. - Interrupts per second by source (top-K) - Per-line interrupt load on the device with the most lines - Receive-path IRQ imbalance (max / mean across a device's lines) - Which core takes each interrupt - Top-K interrupt coverage - Interrupt mix right now - Top-K membership — which interrupt sources the agent was watching — solo InfluxDB - NET_RX softirq invocations per core - Softirq invocations by kind (all CPUs) - NET_RX burst size distribution per sample — solo InfluxDB - µs of softirq CPU per invocation - NET_TX and TASKLET softirq invocations per second ### Temperatura y reloj `/sys/class/thermal` y cpufreq. Todo umbral térmico se mide desde el punto de disparo crítico de la propia zona, leído de la placa: los umbrales del panel de margen están a 10 °C y 5 °C de margen por debajo de él. Ningún panel lleva una temperatura que la placa no haya publicado. - Die temperature by zone - Temperature by source — kernel zones, and the RouterOS sensor when it is polled — solo InfluxDB - Thermal slope, °C per minute by zone - Sensor step dwell — where each zone actually sat — solo InfluxDB - Latest temperature by zone — solo InfluxDB - Thermal headroom — how far each zone is from its own critical trip — solo InfluxDB - Gap between the hottest and coolest thermal zone — solo InfluxDB - Core clock per core — governor state — solo InfluxDB - Is the clock pinned? — solo InfluxDB - Clock-weighted work: effective Hz per core — solo InfluxDB ### Log del kernel `/dev/kmsg`, solo con privilegios. Cuando el log está vacío no está ausente, está en silencio, y el silencio es el estado sano. - Worst kernel-log severity in each bin - Kernel-log records per second by severity - Warning-or-worse kernel-log rate now - Kernel records in the window, by severity - Kernel records per sample — burst distribution against the per-tick cap — solo InfluxDB - Port events from the kernel log, per port and kind - Port events in the window, per port Dos de ellos leen los registros que nombran un puerto de red. Cada registro así lleva una clase — link-up, link-down, `stp-`, own-address (el bridge recibiendo una trama con su propia MAC como origen, la firma de un bucle de capa 2) u otra —, clasificada por el agente mientras lee `/dev/kmsg`, y por el colector para los registros de un agente que no la clasifica. El colector sustituye después el nombre de fábrica del puerto por el nombre actual de la interfaz en RouterOS y le añade su comentario y sus listas de interfaces desde el inventario de la capa de la API; sin la capa de la API el registro conserva el nombre de fábrica y no lleva etiqueta. "Port events from the kernel log, per port and kind" los cuenta por intervalo, una serie por puerto y clase, y "Port events in the window, per port" es el censo que va a su lado: una fila por puerto, con la etiqueta, el rol y una columna por clase. Esta es la vista por puerto que el contenedor tiene gratis — el log del kernel no le cuesta nada al router y fecha cada transición al microsegundo, mientras que los contadores por puerto de RouterOS necesitan una lectura de la API en cada consulta — y es ciega a todo lo que el kernel nunca llega a oír, las tramas conmutadas por hardware incluidas. A un link-up le siguen stp-blocking, stp-learning y stp-forwarding en su puerto del bridge: cuatro registros, no cuatro fallos. Los dos paneles están marcados como vacíos conocidos, porque un conjunto de puertos tranquilo es el estado sano. La forma de Prometheus lee el `/metrics` del colector, donde todo registro de puerto lleva un `kind` porque el colector clasifica lo que le llega sin clasificar; la exposición del propio agente lleva la etiqueta solo cuando es el agente quien clasifica, cosa que el del router de referencia no hace a fecha del 2026-09-16. En InfluxDB la tabla `mikroscope_kmsg` se crea con el primer registro del kernel que se escribe, y su columna `kind` con el primer registro de puerto que se escribe ya clasificado. En un router cuyo kernel no ha dicho nada desde que arrancó el colector, la tabla no existe e InfluxDB 3 rechaza la consulta al planificarla; la sección plegada impide que esa consulta se ejecute hasta que alguien la abre. El código lo llama una mitigación, no un arreglo. `import` con el sondeo lleva esos paneles a la fila de no disponibles. ## Los niveles profundos ### CPU: cuánto tiempo siguió ocupado un núcleo La contigüidad, que ningún histograma por muestra puede recuperar: una meseta de 2 s y veinte picos sueltos caen en los mismos intervalos. En InfluxDB las longitudes de racha son una consulta sobre las muestras en bruto; en Prometheus son el borde superior del bucket poblado más alto de `mikroscope_cpu_busy_run_seconds`, recortado a 60 s, así que 60 se lee como "más de un minuto", no como una medida. - Longest run at or above 90 % busy, per cpu - Longest run at or above 50 % busy, per cpu - Busy run in progress right now — solo Prometheus - Blocked tasks and forks ### Memoria: fragmentación `/proc/buddyinfo`, el único número de memoria que `/proc/meminfo` no puede dar. - Free memory by block order (pages) — solo InfluxDB - Free blocks per order (count) - Largest block order with any free block — solo InfluxDB ### Memoria: recuperación y fallos de página Los deltas de `/proc/vmstat`, sumados o convertidos en tasa sobre un intervalo y nunca promediados — en una sección separada de los niveles para que los dos tipos de reductor no puedan mezclarse. - Reclaim efficiency — pgsteal ÷ pgscan - Did the kernel have to reclaim at all? - Allocation distress — stalls and swap - OOM kills in the window — solo InfluxDB - Page churn — allocate, free, and the net - Page faults — minor and major per second - Minor-fault bursts — distribution per sample — solo InfluxDB - Context switches and all interrupts per second ### Memoria: detalle y comprobaciones cruzadas La escritura diferida, la LRU, los niveles pequeños y dos paneles que validan el propio instrumento. Se leen una vez por equipo nuevo, no durante un incidente. - Writeback backlog — dirty pages and pages in flight - LRU balance — active vs inactive - Mapped, kernel stacks and page tables - vmstat pages vs meminfo kB — unit cross-check — solo InfluxDB - Kernel stack per thread — solo InfluxDB ### Contadores hardware (PMU) `perf_event_open` desde el contenedor privilegiado. Cada panel divide dos recuentos en bruto; los normalizados por reloj dividen por la frecuencia que el kernel informó para ese núcleo en esa muestra. - IPC per core (instructions retired / cycles) - Beneath the tick floor: PMU cycles against /proc/stat busy ticks — solo InfluxDB - Instructions retired while /proc/stat reported the core idle — solo InfluxDB - Work the jiffie threw away (selected range) — solo InfluxDB - Core cycles per bus cycle - Unhalted-cycle fraction of the clock, per core - Distribution of cycles per sample, as a share of the clock (all cores pooled) — solo InfluxDB - Cache-miss rate per core (misses / references) - Cache misses per 1 000 instructions (MPKI), per core - Branch mispredictions per 1 000 instructions per core - PMU counters this CPU actually opened ### Desgaste de la flash `/proc/yaffs`, la única señal de desgaste de la NAND en una RouterBOARD. Solo InfluxDB: la sección no tiene fila en el dashboard de Prometheus. Ningún panel puede mostrar una proporción de la partición, porque el agente no analiza el rango de bloques de la partición, así que no hay denominador para ello. - Flash page traffic per YAFFS partition (pages/s) - Write amplification — GC copies per page write - Flash housekeeping — erasures, garbage collections and GC copies per bin - YAFFS free-chunk drift within the window (chunks, relative to the first sample) - YAFFS partition state over the window - Bad blocks retired during the window, per YAFFS partition - Page writes and erasures per day, at the window's rate ### Salud de la NAND (ECC) Los contadores ECC de MTD bajo `/sys/class/mtd`, solo con privilegios — el indicador adelantado de la flash, mientras que el recuento de bloques defectuosos de YAFFS es la autopsia. - ECC corrections since boot, per partition, against the bitflip threshold - Uncorrectable ECC failures and bad blocks, per partition ### Comprobaciones cruzadas con la API de RouterOS — CPU y memoria `/system/resource` y `/system/resource/cpu`: la capa independiente contra la que se comprueban las cifras del kernel. RouterOS las recalcula una vez por segundo, así que cada campo es de 1 Hz como mucho y está cuantizado en enteros; ningún panel de aquí pretende una lectura por debajo del segundo. - RouterOS cpu-load vs kernel busy — do the two tiers agree? - Cross-tier residual: cpu-load − kernel busy, distribution - Per-core load, RouterOS's own accounting - Per-core mean over the window: RouterOS load next to kernel busy - Per-core IRQ time as RouterOS accounts it, against kernel softirq+system - Per-core disk time (RouterOS) — max over the window - RAM used, as RouterOS accounts it - Free memory: RouterOS free-memory vs the kernel's two answers - RouterOS uptime ## Lo que cuesta observar, y si llegó a funcionar ### El observador Lo que mikroscope le cuesta al router que está midiendo, y si estaba funcionando. En InfluxDB, la continuidad se deriva del número de secuencia de cada muestra (Prometheus la aproxima, como en el Overview), lo que encontró 4 493 ticks perdidos y un reinicio en la captura del 2026-09-11/12 de los que el registro de huecos del colector no decía nada. - Agent CPU cost against its 2 % budget - Observer effect: agent share of all busy CPU on the router - CPU per sample: mean and worst tick - Where the agent's cost actually lives (per-sample distribution over time) — solo InfluxDB - Agent memory against the container cap - Headroom under the container memory cap - CPU budget used (window mean) - Ticks the agent took vs ticks the store received - Sample continuity - Ticks never delivered, this window - Gaps and restarts in this window — solo InfluxDB ### El observador: temporización del muestreador y sucesos propios El emborronamiento del propio muestreador — con cuánto retraso despertó y cuánto tardó la lectura — y los sucesos de cgroup que el agente registra sobre sí mismo. Los tres mapas de calor leen los histogramas del agente, que nunca se envían como muestras, así que solo existen en Prometheus. - Tick interval distribution, relative to the nominal period — solo Prometheus - Wake latency: how late the sampler ran after its ticker — solo Prometheus - Read duration: how long every source took to read — solo Prometheus - Counter resets the agent saw - The container's own throttling and OOM kills - How each level source is read - Age of each held reading — solo Prometheus ### Este equipo El [flujo de datos del equipo](/mikroscope/es/sinks/device-info/): lo que el agente estableció sobre la placa al arrancar sin la API de RouterOS — identidad, techos, la escalera de frecuencias. - This device, as the agent established it - Thermal zones: the board's own trip points and polling cadence - CPU clock: range, ladder, governor and clusters ## No disponible en este equipo La última fila se titula "Not available on this device — measurements this kernel or board does not produce (open to read why)" y está plegada. Un panel cuya medida no está en el almacén se saca de su sección y se lleva a esta fila, donde su descripción dice qué está esperando. En los ficheros del repositorio — los valores compilados por defecto, que son lo que escribe `gen` a secas y lo que recibe una subida manual a Grafana — la fila contiene los cinco paneles que el RB5009 de referencia (RouterOS 7.24.2, kernel 5.6.3 arm64) no puede producir: - Pressure stall (PSI), where the kernel exposes it - Block-device queue depth (requests in flight) - Block-device requests per second (reads and writes completed) - Block-device busy percent (io_s / wall time) - Block-device throughput (sectors → bytes per second) El panel de PSI está vacío allí porque ese kernel no tiene `/proc/pressure`. Los paneles de dispositivos de bloques están vacíos allí porque el agente descarta un dispositivo de bloques cuyas lecturas, escrituras y peticiones en curso son todas cero en un tick, y en ese router todos los dispositivos listados se quedan a cero, así que ningún destino llega a crear la tabla. Para estos paneles hay declaradas dos secciones, **Pressure stall (PSI)** y **Block devices**, que no emiten fila mientras todos sus paneles estén ausentes. En un kernel compilado con PSI, o en una placa con almacenamiento USB o eMMC que se mueva, `import` encuentra la medida y la sección aparece en su sitio sin tocar el generador. `import` también funciona en el otro sentido: en un almacén al que le falta una medida que sí tenía el equipo de referencia — o un campo añadido después de que ese almacén se escribiera por primera vez — el panel pasa a esta fila con su consulta eliminada, así que muestra su explicación y no una insignia roja de error. Cómo decide el sondeo está en [Importar y comprobar](/mikroscope/es/dashboards/import-and-check/#el-sondeo). ## Cómo tratan los dashboards los cocientes, los huecos y las cifras - **Los cocientes nunca son del agente.** El agente solo envía contadores en bruto, nunca porcentajes. Un cociente de estos dashboards se calcula o bien en la consulta del propio panel o bien en la etapa de derivación del colector (la sección de coste de reenvío y las familias `mikroscope_derived*`). Las tasas de interfaz de la capa de la API son la excepción: llegan de RouterOS ya calculadas. - **No dibujan los huecos cortos.** Una línea se corta donde dos puntos vecinos están separados más de 5 minutos. El umbral tiene que superar el intervalo más ancho que seleccione un lector — con un rango de 2 días el intervalo de un panel de 12 columnas es de unos 4 minutos —, así que un hueco de menos de 5 minutos se sigue dibujando como una línea interpolada. Lee los huecos en Sample continuity, no en la forma de una línea. - **Sus cifras son las de un router.** Las descripciones que citan una cifra la atribuyen al RB5009 de referencia con la fecha en que se midió y dicen que las tuyas serán distintas. Ningún título, consulta ni umbral nombra un equipo, su número de núcleos, sus interfaces o su tamaño de memoria; los únicos números fijos son los objetivos de presupuesto propios de mikroscope (2 % de un núcleo, 16 MiB). > **Versiones de Grafana** > > Los dashboards se comprobaron en Grafana 12.3.2 (2026-09-12) y Grafana 13.2.1 (pasadas en el > navegador el 2026-09-12 y el 2026-09-14, `check` y renderizado el 2026-09-15, y `check` otra vez el > 2026-09-16). Las opciones de los > paneles están escritas según el esquema que espera Grafana 13.2.1 — la marca del xychart, por > ejemplo, cambió de sitio entre Grafana 11 y 13 — y `__requires` declara Grafana 11.0.0. No se ha > probado ninguna versión aparte de esas dos. ## Véase también - [Importar y comprobar](/mikroscope/es/dashboards/import-and-check/): la fuente de datos, los dos trabajos de scrape de Prometheus y lo que `check` verifica y lo que no. - [Reglas de alerta](/mikroscope/es/dashboards/alerts/): las reglas que se generan junto a los dashboards, y de dónde sale cada umbral. - [Detecciones](/mikroscope/es/sinks/detections/): los sucesos detrás de la sección de detecciones y de las anotaciones. - [Familias de métricas de Prometheus](/mikroscope/es/reference/metrics/): lo que lee cada panel de Prometheus. --- # Importar y comprobar Cómo enlazar los dashboards a una fuente de datos, qué preguntan primero import y check al almacén, y qué demuestra check contra un Grafana en marcha y qué no puede ver. Source: https://jmrplens.github.io/mikroscope/es/dashboards/import-and-check/ Esta página responde a cómo meter los cinco dashboards en un Grafana, qué necesita la fuente de datos antes de que puedan devolver algo y qué te dice `mikroscope dashboards check` una vez están allí. La versión corta de lo último: `check` demuestra que la consulta de cada panel devuelve filas a través de la propia API de consultas de Grafana. No demuestra que un lector pueda leer el resultado. ## La fuente de datos de InfluxDB 3 Lo primero es la base de datos: `influxdb3 create database mikroscope` en el nodo de InfluxDB 3, con un token que pueda leerla y escribirla — la misma base de datos y el mismo token en los que escribe el destino, en [InfluxDB 3](/mikroscope/es/sinks/influxdb/). La fuente de datos es de tipo `influxdb`, `version: SQL`, `dbName: mikroscope`, con **los dos** campos seguros rellenos: - `httpHeaderValue1` = `Bearer ` (la ruta HTTP) - `token` = `` (la ruta FlightSQL) Sin el segundo, los paneles fallan con `flightsql: Unauthenticated` (Grafana 12.3.2, 2026-09-12). ## Prometheus: dos trabajos de scrape El dashboard de Prometheus espera dos trabajos de scrape. El primero hace scrape del colector (`mikroscope forward --prom :9124`), que lleva todas las familias que tiene el agente, recalculadas a partir de las muestras que recibió, más las familias derivadas y de detección propias del colector. El segundo hace scrape del agente directamente y se queda solo con las familias que únicamente puede producir el muestreador: sus histogramas de temporización de ticks, los contadores de disparos y capturas, y los ticks perdidos por retraso. ```yaml - job_name: "mikroscope" scrape_interval: 5s static_configs: [{ targets: [":9124"] }] - job_name: "mikroscope-agent" scrape_interval: 5s static_configs: [{ targets: ["172.30.10.2:9123"] }] metric_relabel_configs: - source_labels: [__name__] regex: "mikroscope_(tick_.*|trigger_.*|capture.*|captures_held|slipped_total)" action: keep ``` Hacer scrape del agente sin la lista `keep` duplicaría cada contador que también expone el colector. `172.30.10.2:9123` es la dirección del agente en la instalación por defecto; cómo llega a ella un host de Prometheus está en [Llegar al agente](/mikroscope/es/install/reaching-the-agent/). ## La fuente de datos de PostgreSQL El destino SQL escribe un **guion**, no filas: `forward --sql out.sql` y después `psql -f out.sql`, o `--sql - | psql`. Así que la base de datos tiene el esquema y los datos solo después de aplicar ese guion —una fuente de datos apuntada a una base vacía responde a cada panel con «relation does not exist»—. La fuente de datos es el `grafana-postgresql-datasource` de Grafana, con la base y el usuario con los que se cargó el guion. El `sslmode` es cosa tuya; `postgresVersion` solo decide qué sintaxis puede emitir el complemento, y todas las consultas de este dashboard son SQL llano. Los paneles son los de InfluxDB, reescritos: la macro de agrupación, los percentiles, los casts y los nombres de columna que el destino SQL tuvo que cambiar porque `user`, `from` y `to` son palabras reservadas. Diez consultas no se reescriben y lo dicen: `mikroscope_buddy` y los contadores de interfaz de RouterOS son anchos en InfluxDB y largos en el esquema SQL, y un pivote es otra pregunta. ## La fuente de datos de Graphite Graphite no tiene etiquetas: cada dimensión es un nodo de la ruta, así que una consulta ES una ruta —y los dos primeros nodos son tuyos—. `--graphite-prefix` (por defecto `mikroscope`) y `--host-tag` son por eso **variables del dashboard**, leídas del propio árbol de métricas de Graphite, y el dashboard las pregunta arriba en vez de quedar fijado a quien lo generó. La fuente de datos es de tipo `graphite`; no hace falta nada más. Desde la CLI, `check` no puede leer el selector de variables de un navegador, así que se las pasas: ```sh mikroscope dashboards check --store graphite --datasource-uid \ --var prefix=mikroscope --var host=rb5009 ``` ## La fuente de datos de Elasticsearch De tipo `elasticsearch`, con el índice que produzca el `--elastic-index` con el que reenvíes y `@timestamp` como campo de tiempo. Este dashboard es el más pequeño de los cinco, y la razón está en los documentos y no en las consultas: el destino escribe las lecturas por núcleo y por dispositivo de una muestra como **arrays** —`cpu` es un array de cuatro objetos— y un array mapeado dinámicamente es un campo multivaluado sin correspondencia entre sus miembros. `avg(cpu.busy_ratio)` es la media entre los núcleos, que es un número real; «la proporción de ocupación del núcleo 2» no es expresable sin un mapeo nested que el destino no declara. Así que los paneles de Elasticsearch son los agregados escalares, y los de por núcleo están ausentes en vez de equivocados. ## Importar a mano Grafana → Dashboards → New → Import, sube `dashboards/mikroscope-influxdb.json` o `mikroscope-prometheus.json` y elige la fuente de datos cuando Grafana pida `DS_MIKROSCOPE`. Un fichero subido así lleva los **valores compilados por defecto**: los cinco paneles que el equipo de referencia no puede producir están en la fila de no disponibles, y todos los demás paneles se publican con su consulta, tenga o no tu almacén su medida. En InfluxDB, un panel al que le falta la tabla o la columna muestra entonces el error de planificación de InfluxDB 3, como una insignia roja, cuando se abre su sección. `import` desde la CLI lo evita. ## Importar desde la CLI ```sh export GRAFANA_URL=http://grafana:3000 GRAFANA_TOKEN=… mikroscope dashboards import --store influxdb --datasource-uid mikroscope dashboards check --store influxdb --datasource-uid --window 15m ``` | Opción | Por defecto | La usa | Qué hace | | ------------------ | -------------------- | ------------- | --------------------------------------------------------------------------------------------- | | `--store` | `influxdb` | import, check | `influxdb` o `prometheus`; también el id del plugin de fuente de datos que se envía a Grafana | | `--grafana` | `$GRAFANA_URL` | import, check | la URL base de Grafana | | `--datasource-uid` | ninguno, obligatoria | import, check | la fuente de datos a la que se enlaza `DS_MIKROSCOPE` | | `--no-probe` | desactivada | import, check | no preguntar a la fuente de datos qué contiene; usar los valores compilados por defecto | | `--window` | `15m` | check | longitud de la ventana de consulta | | `--end` | ahora | check | el borde derecho de la ventana, en RFC 3339 | | `--out` | `dashboards` | gen | el directorio en el que `gen` escribe los cuatro ficheros | El token solo se lee de `GRAFANA_TOKEN`; no hay opción para él. Es un token de cuenta de servicio de Grafana con permiso para escribir dashboards. El UID de la fuente de datos es el último segmento de la URL de sus ajustes en Grafana, `/connections/datasources/edit/`. Sin una URL de Grafana, un token y un UID de fuente de datos, las dos órdenes se detienen con `import/check need --grafana, GRAFANA_TOKEN and --datasource-uid`. `import` envía el dashboard a `/api/dashboards/import` de Grafana con la entrada de la fuente de datos resuelta a tu UID, `overwrite` activado, a la carpeta General (`folderId` 0). El `uid` del dashboard es fijo, así que importar de nuevo sustituye el mismo dashboard en la misma URL. Imprime esa URL. ## El sondeo Antes de generar, `import` y `check` preguntan a la fuente de datos cuáles de las medidas de mikroscope contiene, a través de `/api/ds/query` de Grafana: - **InfluxDB 3** ```sql SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = 'iox' ``` Columnas, y no solo tablas: InfluxDB 3 rechaza al planificar una consulta que nombra una columna inexistente exactamente igual que rechaza una tabla inexistente, y un almacén escrito antes de que existiera un campo tiene la tabla pero no el campo. Un panel que lee un campo añadido más tarde lo declara, y el sondeo lo comprueba. - **Prometheus** ```text group by(__name__) ({__name__=~"mikroscope_.+"}) ``` Una consulta instantánea que devuelve una serie por cada nombre de métrica que existe, sin muestras que transferir. Un histograma cuenta como presente cuando lo está su serie `_bucket`, `_count` o `_sum`. Imprime `datasource holds N measurements`; en InfluxDB, N cuenta las tablas más los pares `tabla.columna`, así que es mayor que el número de medidas. Después genera según la respuesta: - Un panel cuyas medidas y campos obligatorios están todos presentes se publica en su propia sección con su consulta — incluido un panel que el equipo de referencia no podía producir. - Un panel al que le falta cualquier cosa pasa a la fila plegada "Not available on this device" **con su consulta eliminada**. No ejecuta nada, así que no puede pintar una insignia roja `table … not found`; su texto de sin valor nombra lo que este almacén no contiene. - Un sondeo que falla — un error de Grafana, o una respuesta sin ningún nombre `mikroscope_` — es un aviso, no un error. `import` y `check` imprimen `warning: could not ask which measurements it holds`, con el motivo, y siguen con los valores compilados por defecto, de modo que se te dice qué dashboard has recibido. `--no-probe` se salta la pregunta y usa los valores compilados por defecto, que es también lo que hace `gen` a secas, ya que no tiene fuente de datos a la que preguntar. ## Lo que verifica `check` `check` genera el dashboard exactamente como lo haría `import` — sondeo incluido — y después, para cada panel, incluidos todos los paneles anidados dentro de una fila plegada, envía cada una de sus consultas a través de `/api/ds/query` de Grafana contra tu fuente de datos sobre la ventana, y cuenta las filas que vuelven. La petición lleva el paso que Grafana calcularía para ese panel: la ventana dividida entre 900 puntos de datos, o el intervalo mínimo propio del panel si lo tiene y es mayor. Sin ese paso, un objetivo `increase(x[$__interval])` devuelve un frame vacío, porque un paso por debajo del intervalo de scrape deja menos de dos puntos en el rango. Imprime una línea por panel y un veredicto: ```text ok rows= frames= none rows= frames= FAIL rows= frames= every panel returns data ( known-empty tolerated) ``` - **ok**: el panel devolvió al menos una fila y ninguna consulta informó de un error. - **none**: el panel está marcado como vacío conocido y no calificó como ok. Llevan la marca dos tipos de panel: aquellos cuyo vacío es el estado sano (por ejemplo, los paneles de detecciones y de disparos, los dos paneles de eventos de puerto, la tabla de huecos, la consulta opcional de conntrack, el panel de ráfagas por debajo de la muestra y la línea temporal de la peor severidad del log del kernel) y los de la fila de no disponibles. Un panel vacío conocido se tolera tanto si no devolvió filas como si devolvió un error. - **FAIL**: cualquier otra cosa — ninguna fila, o un error en cualquiera de las consultas del panel aunque otra devolviera filas. Con uno o más fallos `check` sale con código distinto de cero y `N panel(s) return no data (K known-empty tolerated)`. Un dashboard no está terminado hasta que cada panel que no es un vacío conocido devuelve filas. ## Lo que `check` no verifica > **Más allá del recuento de filas** > > `check` cuenta filas. No ve una leyenda, un eje, una unidad, un color, un umbral ni la cuadrícula. > El 2026-09-12 `dashboards check --store influxdb --window 12h` pasó en los 125 paneles que > recorrió mientras unos 90 de ellos eran ilegibles en un navegador: leyendas que decían "value core > 0", dos xycharts atascados en "Loading plugin panel…", una franja de continuidad que seguía en > verde sobre 2 170 ticks perdidos. La legibilidad se establece renderizando el dashboard en > Chromium; que `check` pase no dice nada de ella. Qué más queda fuera de su alcance, según el código: - **El dashboard guardado en Grafana.** `check` regenera el dashboard en local y ejecuta esas consultas. No relee lo que guardó `import`, así que un dashboard editado en la interfaz de Grafana no es lo que comprueba. - **Si un número es correcto.** Una fila es un aprobado. Una aritmética errónea que devuelve filas aprueba. - **Las anotaciones.** Solo se recorren los paneles; las consultas de las anotaciones de detecciones y de disparos no se ejecutan. - **Las reglas de alerta.** Los ficheros de aprovisionamiento que escribe `gen` no se cargan ni se evalúan. - **Lo que el navegador le hace a una consulta.** Algunas variables se sustituyen en el navegador, no en el servidor con el que habla `check`. El 2026-09-14 la fuente de datos de InfluxDB escapó `$__interval_ms` en cinco paneles, en el navegador, convirtiéndolo en un SQL que InfluxDB 3 no podía analizar; el dashboard renderizado lo mostró. - **La anchura real del panel.** `check` supone que todo panel tiene 900 puntos de datos de ancho, el valor que el navegador envió para las gráficas de este dashboard el 2026-09-12. Un panel más estrecho recibe un intervalo más ancho. ## Comprobar contra una captura terminada `--window` es la longitud de la ventana de consulta y `--end` mueve su borde derecho, así que los paneles pueden comprobarse contra una captura que ya ha terminado en vez de contra un ahora en reposo: ```sh mikroscope dashboards check --store influxdb --datasource-uid \ --window 1h --end 2026-09-13T08:30:00Z ``` Un panel responde de forma distinta sobre una ventana con datos que sobre una sin ellos, y una comprobación vale lo que vale la ventana a la que apunta. ## Lo que se ha verificado **2026-09-16**, en el Grafana 13.2.1 del propietario, contra el despliegue de referencia: el agente en el RB5009 de referencia (RouterOS 7.24.2, privilegiado, los disparadores por defecto); `forward --prom :9124 --influx … --interfaces bridge,ether1,PPPoE_DIGI --counters-every 10s` durante 30 minutos hacia un InfluxDB 3 Core aislado; un Prometheus 3.14 haciendo scrape del colector cada 5 s, y del agente directamente para las familias que el colector no puede recalcular. | Almacén | Ventana | Paneles | Fallan | Vacíos conocidos tolerados | | ---------- | ---------- | ------: | -----: | --------------------------------------------------------------------------------------------------------------------------------------- | | InfluxDB 3 | 30 minutos | 171 | 0 | 10 (los dos paneles de eventos de puerto, la consulta opcional de conntrack, los dos paneles de disparos, PSI, los cuatro dispositivos de bloques en reposo) | | Prometheus | 30 minutos | 133 | 0 | 9 (los dos paneles de eventos de puerto, los dos paneles de conntrack de la API, PSI, los cuatro dispositivos de bloques) | El SQL de los dos paneles de eventos de puerto se validó ese mismo día contra una tabla sintética en ese mismo InfluxDB 3, porque el almacén en vivo no tiene columna `kind` hasta que se le escribe el primer registro de puerto ya clasificado. El recorrido fila a fila sin interfaz de los dos dashboards en Chromium a 1600x1000 — 0 insignias de error y 0 "No data" — es del **2026-09-15** y cubre 168 paneles de InfluxDB y 130 de Prometheus. No se ha repetido, así que no se afirma nada del renderizado de los tres paneles que no cubre: los dos de eventos de puerto y "What each interface is: type, role, bridge and label". **2026-09-12**, en el Grafana 12.3.2 del propietario. El dashboard de InfluxDB contra un InfluxDB 3 Core aislado alimentado por `mikroscope forward` desde un RB5009: todos los paneles devolvieron filas, entre 158 y 316 por panel en 10 minutos. El dashboard de Prometheus contra un Prometheus 3.14 haciendo scrape del colector cada 5 s: todos los paneles devolvieron filas, entre 228 y 2 052 en 5 minutos. > **Vacío por configuración no es vacío conocido** > > Los 10 y 9 de arriba son ese despliegue sobre esa ventana. Solo se toleran los paneles marcados en > el generador o los que el sondeo lleva a la fila de no disponibles. Un panel vacío por cómo se > ejecutó el colector — los paneles de interfaces con `--api-mode off`, los paneles de slab en un > agente sin privilegios — no está marcado: en un almacén que nunca tuvo su medida el sondeo lo > lleva a la fila de no disponibles, y en un almacén que la tuvo antes pero no dentro de la ventana, > `check` lo da por fallido. ## Véase también - [Cinco dashboards, una sola lista](/mikroscope/es/dashboards/): cada sección y cada panel, y qué almacén lleva cada uno. - [Reglas de alerta](/mikroscope/es/dashboards/alerts/): los ficheros de aprovisionamiento que escribe `gen`, que `check` no ejecuta. - [Prometheus](/mikroscope/es/sinks/prometheus/): el `/metrics` del colector que lee el primer trabajo de scrape. - [InfluxDB 3](/mikroscope/es/sinks/influxdb/): la URL de escritura, el token y los límites propios del almacén. --- # Reglas de alerta Las reglas de alerta de Grafana que se generan junto a los dashboards, con qué salta cada una, de dónde sale su umbral y qué no se ha probado de ellas. Source: https://jmrplens.github.io/mikroscope/es/dashboards/alerts/ `mikroscope dashboards gen` escribe, junto a cada dashboard, un fichero de aprovisionamiento de las alertas unificadas de Grafana con las reglas que se desprenden de los propios contadores de fallos de los dashboards, del registro del kernel que lee el agente y de las detecciones del colector. Esta página responde a qué reglas son, con qué salta cada una y qué significa para ella el silencio, cómo instalar el fichero y de dónde sale cada umbral. Todo umbral es cero (un contador que no debería moverse), una muestra (la regla del agente en silencio) o una proporción de un techo que publicó el propio equipo. Ninguno es un número compilado para un router concreto. ## Los ficheros | Fichero | Reglas | Lenguaje de consulta | | ---------------------------------------------- | -----------------------------------------------: | -------------------- | | `dashboards/mikroscope-alerts-influxdb.yaml` | 10 | SQL de InfluxDB 3 | | `dashboards/mikroscope-alerts-prometheus.yaml` | 11 | PromQL | El fichero de InfluxDB tiene una regla menos porque "The sampler is slipping ticks" no tiene forma en SQL: el contador de ticks perdidos por retraso se expone en el `/metrics` del agente y no se escribe en InfluxDB. Cada fichero es `apiVersion: 1` con un grupo de reglas, `mikroscope`, en una carpeta llamada `mikroscope`, organización 1, evaluado cada minuto. Las reglas se aprovisionan en vez de ir dentro de los dashboards, así que un operador que no quiera ninguna no copia nada. ## Instalarlas Los ficheros de aprovisionamiento no resuelven la entrada `${DS_MIKROSCOPE}` de un dashboard, así que la fuente de datos es un marcador literal, `DS_UID_PLACEHOLDER`, que tienes que sustituir por el UID de tu fuente de datos antes de que Grafana lea el fichero: ```sh sed 's/DS_UID_PLACEHOLDER//g' dashboards/mikroscope-alerts-influxdb.yaml \ > /etc/grafana/provisioning/alerting/mikroscope-alerts-influxdb.yaml ``` El directorio de aprovisionamiento es cosa de Grafana; `/etc/grafana/provisioning/alerting/` es la ruta que nombra la cabecera del propio fichero generado. Usa el fichero que corresponda a la fuente de datos a la que pertenece el UID. Todas las reglas tienen la misma forma, la que escribe el propio editor de reglas de Grafana: 1. **A** — la consulta, contra tu fuente de datos, con un rango de tiempo relativo de los últimos 600 s. Toda consulta SQL y toda consulta de Prometheus sobre un contador acota además su propia ventana (2 minutos, 5 minutos o 1 hora, más abajo); las dos reglas de Prometheus sobre gauges, la térmica y la de conntrack, leen el último valor. 2. **B** — reduce A a un número por serie con `last`, descartando los valores no numéricos. 3. **C** — compara B con el umbral. C es la condición de la regla. Cada regla lleva las etiquetas `severity` (`critical` o `warning`) y `source: mikroscope`, una anotación `summary` y `execErrState: Error`. Lo que hace Grafana después con una regla cuya consulta falla es comportamiento de Grafana, descrito en su propia documentación, y aquí no se ha probado. ## Las reglas Las reglas de alerta: | Regla (uid) | Salta cuando | Umbral (C) | Severidad | `for` | Sin datos significa | Almacenes | | --- | --- | --- | --- | --- | --- | --- | | `mikroscope-agent-silent` | llegó al almacén menos de 1 muestra nueva en los últimos 2 minutos | < 1 | critical | 2m | Alerting | solo InfluxDB | | `mikroscope-softnet-drops` | `softnet_stat` descartó un paquete en los últimos 5 minutos | > 0 | critical | 0s | OK | solo InfluxDB | | `mikroscope-oom-kill` | `oom_kill` de `/proc/vmstat` se movió en los últimos 5 minutos | > 0 | critical | 0s | OK | solo InfluxDB | | `mikroscope-detections` | cualquier detección en los últimos 5 minutos | > 0 | warning | 0s | OK | solo InfluxDB | | `mikroscope-thermal-near-critical` | una zona al 85 % o más de su propio disparo crítico | > 0 | critical | 1m | OK | solo InfluxDB | | `mikroscope-conntrack-near-limit` | objetos activos de `nf_conntrack` por encima de 0,8 del límite del kernel | > 0,8 | warning | 5m | OK | solo InfluxDB | | `mikroscope-ticks-slipped` | el muestreador perdió un tick por retraso en los últimos 5 minutos | > 0 | warning | 5m | OK | solo Prometheus | | `mikroscope-agent-oom` | el propio cgroup del agente registró un OOM kill en los últimos 5 minutos | > 0 | critical | 0s | OK | solo InfluxDB | | `mikroscope-l2-loop` | un registro de own-address en cualquier puerto en los últimos 5 minutos | > 0 | critical | 0s | OK | los dos | | `mikroscope-port-link-down` | un registro de link-down en cualquier puerto en los últimos 5 minutos | > 0 | warning | 0s | OK | los dos | | `mikroscope-ecc-failure` | la NAND informó de un fallo ECC no corregible en la última hora | > 0 | critical | 0s | OK | solo InfluxDB | La forma de InfluxDB de `mikroscope-conntrack-near-limit` está rota; la nota de lo no probado, al final de esta página, explica por qué. "Sin datos significa" es el `noDataState` de la regla. La regla del agente en silencio es la única en la que el silencio es el fallo, así que la ausencia de datos la hace saltar; para todas las demás, no tener datos es la lectura sana. El título de cada regla, en inglés como aparece en Grafana, y debajo una traducción de su anotación `summary` (el texto generado está en inglés): - **mikroscope agent stopped delivering samples.** No llegaron muestras nuevas al almacén en los últimos dos minutos: se paró el agente, se paró el colector o se cortó el camino entre ellos. Todas las demás reglas están ciegas mientras esta salta. - **Packets dropped in the kernel receive path.** `softnet_stat` descartó un paquete: una cola por CPU estaba llena. Pérdida inequívoca dentro del router, invisible para cualquier contador SNMP o de RouterOS. Cero es la lectura esperada. - **The kernel OOM-killed a process.** `oom_kill` de `/proc/vmstat` se movió: el kernel mató un proceso para recuperar memoria. Qué proceso no se puede saber desde el contenedor (no hay espacio de nombres de PID). - **The collector's derive stage flagged an event.** Saltó una regla de detección (counter-reset, agent-restart, agent-oom, microburst, reboot, link-flap, conntrack-cliff, conntrack-high, thermal-high, thermal-rising, ipc-collapse). La regla, la clave, el valor y el umbral están en la sección de detecciones y en el dashboard como anotación. - **A thermal zone is within 15 % of its own critical trip.** La lectura está al 85 % o más del punto de disparo crítico declarado por la zona (105 C en el RB5009 de referencia). El techo es el de la propia placa, leído de `/sys`, no un número compilado. - **The connection table is above 80 % of nf_conntrack_max.** Objetos activos de `nf_conntrack` por encima del propio techo del kernel. Pasado el techo, el router descarta las conexiones nuevas. El límite es el sysctl que leyó el agente, no un número compilado. - **The sampler is slipping ticks.** Hubo ticks que terminaron después de que tocara el siguiente. La cadencia no se está cumpliendo: el equipo va falto de CPU, el conjunto de fuentes es demasiado caro para la cadencia, o la cuota de CPU del contenedor limitó al agente (consulta el contador de limitación del observador). - **mikroscope's own container was OOM-killed.** El kernel mató un proceso dentro del cgroup del agente: el anillo de captura y las capturas se han perdido, y todos los números de la ventana son sospechosos. Sube `--memory-max` o baja `RATE_HZ`, `BUFFER_S` o `CAPTURE_MB`. - **The bridge received its own address back: a layer-2 loop signature.** El registro del kernel informó de `received packet on with own address as source address`: una trama que envió el router volvió a entrar, que es el aspecto que tiene un bucle a través de un switch o un punto de acceso aguas abajo. La etiqueta del puerto dice de qué cable se trata. Se lee de `/dev/kmsg` con el agente, sin API. En el RB5009 de referencia esto corrió a 1,49 registros/s durante horas el 2026-09-12 mientras todos los contadores de RouterOS parecían sanos. La forma de InfluxDB necesita un almacén que haya tenido al menos un registro de puerto clasificado por `kind`. - **A port's link went down.** El registro del kernel informó de un link-down en un puerto: un cable desconectado, un par que se reinició o se apagó, una renegociación. La detección de link-flap del colector cubre el caso repetido; esto es el suceso único. Se lee de `/dev/kmsg` con el agente, sin API; el puerto, su comentario y su rol están en los sucesos de puerto de la sección del registro del kernel. - **The NAND reported an uncorrectable ECC failure.** `ecc_failures` subió en una partición MTD: una lectura que la corrección de errores no pudo arreglar, es decir, pérdida de datos en la flash. Cualquier incremento es un incidente. ## Las consultas - **Prometheus** ```text # mikroscope-agent-silent (< 1) sum(increase(mikroscope_samples_total[2m])) # mikroscope-softnet-drops (> 0) sum(increase(mikroscope_softnet_total{kind="dropped"}[5m])) # mikroscope-oom-kill (> 0) sum(increase(mikroscope_vm_events_total{event="oom_kill"}[5m])) # mikroscope-detections (> 0) sum(increase(mikroscope_collector_detections_total[5m])) # mikroscope-thermal-near-critical (> 0) count(mikroscope_thermal_celsius >= on(zone) 0.85 * mikroscope_thermal_critical_celsius) # mikroscope-conntrack-near-limit (> 0.8) max(mikroscope_slab_active_objects{cache="nf_conntrack"} / mikroscope_slab_limit_objects{cache="nf_conntrack"}) # mikroscope-ticks-slipped (> 0) sum(increase(mikroscope_slipped_total[5m])) # mikroscope-agent-oom (> 0) sum(increase(mikroscope_self_oom_kills_total[5m])) # mikroscope-l2-loop (> 0) sum(increase(mikroscope_kmsg_port_records_total{kind="own-address"}[5m])) # mikroscope-port-link-down (> 0) sum(increase(mikroscope_kmsg_port_records_total{kind="link-down"}[5m])) # mikroscope-ecc-failure (> 0) sum(increase(mikroscope_mtd_ecc_failures_total[1h])) ``` `mikroscope_slipped_total` viene del trabajo de scrape del agente, el que tiene la lista `keep` en [Importar y comprobar](/mikroscope/es/dashboards/import-and-check/#prometheus-dos-trabajos-de-scrape); las otras diez leen el `/metrics` del colector. `mikroscope_kmsg_port_records_total` está entre ellas: las dos exposiciones las escribe el mismo renderizador, y la copia del colector es la que la lista `keep` deja pasar, la que clasifica el registro que el agente no clasificó y nombra cada puerto como lo nombra RouterOS ahora. - **InfluxDB 3** ```sql -- mikroscope-agent-silent (< 1) SELECT count(1) AS value FROM mikroscope_cpu WHERE time >= now() - interval '2 minutes' -- mikroscope-softnet-drops (> 0) SELECT coalesce(sum(dropped), 0) AS value FROM mikroscope_softnet WHERE time >= now() - interval '5 minutes' -- mikroscope-oom-kill (> 0) SELECT coalesce(sum(oom_kill), 0) AS value FROM mikroscope_vm WHERE time >= now() - interval '5 minutes' -- mikroscope-detections (> 0) SELECT count(1) AS value FROM mikroscope_detection WHERE time >= now() - interval '5 minutes' -- mikroscope-thermal-near-critical (> 0) SELECT count(1) AS value FROM (SELECT zone, max(celsius) AS c, max(critical_celsius) AS crit FROM mikroscope_thermal WHERE time >= now() - interval '2 minutes' AND critical_celsius IS NOT NULL GROUP BY zone) WHERE c >= 0.85 * crit -- mikroscope-conntrack-near-limit (> 0.8) SELECT max(active) * 1.0 / nullif(max(limit_objs), 0) AS value FROM mikroscope_slab WHERE time >= now() - interval '2 minutes' AND cache = 'nf_conntrack' AND limit_objs IS NOT NULL -- mikroscope-agent-oom (> 0) SELECT coalesce(sum(oom_kill), 0) AS value FROM mikroscope_self WHERE time >= now() - interval '5 minutes' AND oom_kill IS NOT NULL -- mikroscope-l2-loop (> 0) SELECT coalesce(sum(count), 0) AS value FROM mikroscope_kmsg WHERE time >= now() - interval '5 minutes' AND kind = 'own-address' -- mikroscope-port-link-down (> 0) SELECT coalesce(sum(count), 0) AS value FROM mikroscope_kmsg WHERE time >= now() - interval '5 minutes' AND kind = 'link-down' -- mikroscope-ecc-failure (> 0) SELECT coalesce(sum(delta), 0) AS value FROM (SELECT max(ecc_failures) - min(ecc_failures) AS delta FROM mikroscope_mtd WHERE time >= now() - interval '1 hour' AND ecc_failures IS NOT NULL GROUP BY "partition") ``` ## De dónde salen los umbrales - **Cero, para un contador que no debería moverse.** Descartes de RX del kernel, OOM kills del kernel, detecciones, ticks perdidos por retraso, OOM kills del propio agente, fallos ECC no corregibles y los registros de puerto del kernel cuyo `kind` es `own-address` o `link-down`. Un equipo sano lee cero en cada uno. - **Una muestra, para el agente en silencio.** Menos de una muestra en dos minutos es ninguna, a cualquier cadencia configurada. - **Una proporción del propio disparo térmico de la placa.** El 85 % del punto de disparo crítico más bajo que declara cada zona, que el agente lee de `/sys/class/thermal` y envía junto a cada lectura. Una zona que no declara disparo crítico queda fuera de la consulta en vez de compararse con un techo inventado. - **Una proporción del propio límite de conexiones del kernel.** 0.8 del límite de `nf_conntrack` que leyó el agente. La ocupación sale de `/proc/slabinfo`, que necesita un contenedor privilegiado. Las reglas no alertan por squeezes de softnet. Medido en el RB5009 de referencia el 2026-09-15 sobre 3 476 muestras, alrededor del 11,2 % de las muestras llevan un squeeze, y alertar con "squeeze > 0" te despertaría para siempre. Lo que llega en su lugar a la alerta de detecciones es la detección `microburst`: tres muestras de ráfaga en una CPU en 60 s. Una muestra de ráfaga es aquella en la que una cola de softnet descartó un paquete, o agotó su presupuesto más veces que el percentil 90 móvil de esa CPU y al menos tres, mientras el recuento de paquetes de la muestra estaba en su mediana móvil o por debajo. Consulta [Detecciones](/mikroscope/es/sinks/detections/). ## Lo que las reglas no pueden ver - **Qué proceso.** La regla de OOM dice que el kernel mató algo. Desde dentro del contenedor no hay espacio de nombres de PID que diga qué. - **Qué puerto.** Las dos consultas de sucesos de puerto suman sobre los puertos, así que una regla que salta dice que hubo una firma de bucle o un link-down, no en qué cable. El puerto, su comentario y sus listas de interfaces están en los dos paneles de sucesos de puerto de la sección del registro del kernel. - **Los puntos ciegos de un agente sin privilegios.** Las reglas de la tabla de conexiones y de ECC leen fuentes que necesitan un contenedor privilegiado. Sin él esas medidas nunca llegan al almacén, y en Prometheus una consulta sobre una métrica inexistente no devuelve datos — lo que estas reglas leen como OK. - **Nada mientras salta la regla del agente en silencio.** Todas las demás reglas salvo la de ticks perdidos por retraso, que lee directamente el agente, leen el mismo flujo; sin muestras llegando leen cero o ningún dato, y las dos cosas son OK. > **Lo que no se ha probado** > > Estos ficheros no se han cargado en Grafana para observar una regla evaluarse, saltar o > resolverse; `dashboards check` ejecuta las consultas de los paneles de los dashboards y no estas. > Del código se siguen varias consecuencias que no se han observado. **Tablas de InfluxDB que solo > aparecen tras su primer suceso:** el colector crea `mikroscope_detection` con la primera detección > que escribe, e InfluxDB 3 rechaza al planificarla una consulta que nombra una tabla inexistente, > así que en un almacén que nunca ha tenido una detección la consulta de la regla de detecciones > debería fallar, y se aplica su `execErrState: Error` en vez de OK; lo mismo vale para cualquier > tabla o columna que el despliegue no haya escrito nunca, como `mikroscope_mtd` en un agente sin > privilegios. **A las dos reglas de sucesos de puerto no se las ha visto saltar:** ninguna se ha > observado contra un bucle real ni contra un link-down real, en ninguno de los dos almacenes. Su > forma de InfluxDB lee la columna `kind` de `mikroscope_kmsg`, que un almacén solo tiene una vez > que el colector ha escrito un primer registro de puerto clasificado por `kind` — en el despliegue > de referencia, el 2026-09-16 el almacén todavía no tenía columna `kind`, así que allí esa consulta > falla al planificarse por la misma razón que el caso de la tabla inexistente de más arriba. Su > forma de Prometheus lee `mikroscope_kmsg_port_records_total{kind=…}` del `/metrics` del colector, > que lleva un `kind` en todo registro de puerto sea cual sea la versión del agente; la exposición > del propio agente lleva la etiqueta solo cuando es él quien clasifica, y el del router de > referencia no lo hace, así que un Prometheus que raspe solo al agente no ve allí ningún `kind` y > la consulta no devuelve datos, lo que estas reglas leen como OK. **La regla de conntrack de InfluxDB nombra una columna que ningún almacén InfluxDB > tiene:** su consulta lee `limit_objs`, que es el nombre de la columna en el destino SQL > (Postgres/Timescale), mientras que el destino InfluxDB escribe el techo del slab como el campo > `limit` (el propio panel de ocupación de los dashboards lee `limit`). Así que en todo almacén > InfluxDB, con o sin privilegios, esa consulta debería fallar al planificarse y la regla no puede > evaluarse. Es un defecto del generador, no una propiedad del equipo. **La regla ECC de InfluxDB a > través de particiones:** su consulta toma la mayor lectura de `ecc_failures` de la hora menos la > menor, sobre todas las particiones juntas, sin agrupar por partición — así que en una placa cuyas > particiones estén en niveles distintos de cero la diferencia no es cero sin que haya ningún fallo > nuevo. Todas las particiones leen cero en el RB5009 de referencia, así que ese equipo no puede > mostrarlo. La propia descripción del panel de la flash añade que una placa que sale de fábrica con > bloques marcados como defectuosos muestra un nivel distinto de cero que es normal para ella, y que > el suceso es el cambio, no el nivel. ## Véase también - [Detecciones](/mikroscope/es/sinks/detections/): las once reglas detrás de la alerta de detecciones, y lo que cada una no puede afirmar. - [Importar y comprobar](/mikroscope/es/dashboards/import-and-check/): el UID de fuente de datos que necesitan estos ficheros, y los trabajos de scrape de Prometheus. - [Cinco dashboards, una sola lista](/mikroscope/es/dashboards/): los paneles de cuyos contadores de fallos salen estas reglas. - [Familias de métricas de Prometheus](/mikroscope/es/reference/metrics/): las familias que leen las consultas de Prometheus. --- # Cómo leer lo que muestra Siete lecturas de datos del kernel de un RB5009 — un fallo de producción encontrado por casualidad, tres sucesos provocados a propósito, dos que no hubo que provocar y la forma en reposo contra la que se leen. Source: https://jmrplens.github.io/mikroscope/es/playbooks/ Esta sección responde a la pregunta que viene después de instalar el agente: _los números están llegando — ¿qué aspecto tiene un fallo en ellos?_ Cada página es una forma, leída en datos que el agente recogió en el router de referencia, con las órdenes que la produjeron para que puedas reproducirla en tu propio equipo. Todos los números de estas páginas salen de una sola campaña en el router de referencia, un kernel aarch64 en una placa con 1 GB de RAM: Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · Linux 5.6.3 · 2026-09-12 · agente a 10 Hz en un contenedor privilegiado efímero Cuando una página añade una cifra de otra fecha, lo dice junto a la cifra. Cuando un fallo se provocó a propósito, la página dice exactamente cómo. ## Antes de provocar nada Los escenarios se eligieron de forma que nada de lo que hacen pueda romper el enlace de subida del router ni cortar el propio camino de un administrador hasta él. Conserva esa propiedad en tu equipo: averigua primero por qué puerto estás conectado, ```text /interface/bridge/host/print where mac-address="" ``` y deja en paz ese puerto, el puerto WAN y cualquiera que lleve un servicio. ## Las siete lecturas Lee primero la forma en reposo. Sin ella, cualquier otra página parece una anomalía. | Página | Cómo surgió | Dónde se ve | Firma | | -------------------------------------------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | [La forma de un router en reposo](/mikroscope/es/playbooks/idle/) | 60 s en reposo | ocupación por núcleo, `time_squeeze`, `events` | 6–8 % de ocupación en los cuatro núcleos, el squeeze nunca a cero, cero sucesos del kernel | | [Un bucle que solo veía el kernel](/mikroscope/es/playbooks/loop/) | real, encontrado por casualidad en el router de producción | `events` (la fuente `kmsg`) | `events` a 1,49 /s, separados 2,00–2,01 s; la API informaba de un equipo sano | | [Puertos de RouterOS y nombres del kernel](/mikroscope/es/reference/port-names/) | un puerto muerto apagado y vuelto a encender | `events` | el kernel escribe `eth5` donde RouterOS dice `ether6` | | [Una carga limitada por CPU](/mikroscope/es/playbooks/cpu/) | un bucle de consola que termina solo | ocupación por núcleo, temperatura, frecuencia | ~29 % en total, un núcleo al 99,8 %, ~20 s de migración antes de asentarse | | [Una inundación de paquetes](/mikroscope/es/playbooks/packet-flood/) | `ping -f` a la propia dirección LAN del router, 10 s | interrupciones de `switch0`, softirqs, `time_squeeze` | `switch0` de 5,5 k a 34 k por tramo de 5 s, todo en el único núcleo al que está fijada la IRQ | | [Desgaste de la flash](/mikroscope/es/playbooks/flash-wear/) | nada provocado; el router escribe por su cuenta | la fuente `yaffs`, contadores ECC de MTD | 2 escrituras de página cada 30 s en reposo, rastreadas hasta el tema `dns` que registra en disco | | [Conntrack sin la API](/mikroscope/es/playbooks/conntrack/) | una comprobación cruzada contra la API, sin tormenta | la caché slab `nf_conntrack` | el número real de conexiones del router donde el propio namespace del contenedor informa 0 | ## Dos comprobaciones antes de fiarte de una lectura **Al muestreador no le faltó CPU.** `mikroscope_slipped_total` debería ser 0 en la ventana que estés leyendo. Un tick perdido es uno cuya lectura terminó después de que tocara el siguiente tick, y entonces la propia contabilidad del muestreador es lo primero de lo que hay que desconfiar. **El log del kernel se conservó entero.** `mikroscope_kmsg_dropped_total` cuenta episodios de pérdida, no registros: uno por cada tick que llegó al tope de 64 registros del agente, y uno por cada desbordamiento del búfer circular del kernel, que puede suponer muchos registros. Mientras no sea cero, los recuentos por nivel de `mikroscope_kmsg_records_total` son una cota inferior, y también lo es una tasa de sucesos sacada de ellos. > **Las órdenes de estas páginas no llevan token** > > Los ejemplos con `curl` hablan con el agente en `http://172.30.10.2:9123` sin credenciales, que es > como responde una instalación por defecto. Un agente arrancado con token — obligatorio con > `--expose` — devuelve 401 en todas las rutas salvo `/healthz` hasta que cada petición lleve la > cabecera `Authorization: Bearer `. ## Lo que cuesta el agente mientras haces esto Mide al observador, y mídelo con honradez — incluida la parte en la que medir cambia la respuesta. Descargar un `/snapshot` de 60 segundos no sale gratis: el agente tiene que entregar ~600 líneas ya codificadas, unos 1,5 MB, y el `self.cpu_us` de esas muestras _incluye el coste de servirlas_. Leer el coste del agente en una instantánea grande lo exagera, por tanto, y hacerlo repetidamente lo exagera más. Usa `/metrics` en su lugar. Es pequeño, sus contadores son acumulados y no depende de quién lo lea ni de cuándo — léelo dos veces y divide: ```sh U=http://172.30.10.2:9123/metrics get() { curl -s "$U" | awk -v k="$1" '$1==k{print $2}'; } c0=$(get mikroscope_self_cpu_usec_total); t0=$(date +%s) sleep 180 c1=$(get mikroscope_self_cpu_usec_total); t1=$(date +%s) echo "$c0 $c1 $t0 $t1" | awk '{printf "%.2f %% of one core\n", 100*($2-$1)/1e6/($4-$3)}' ``` Dos cosas que cabe esperar: - **El coste y la memoria suben hasta que el anillo se llena.** Con el anillo por defecto de 300 s a 10 Hz, el agente guarda 3 000 muestras ya codificadas; una cifra tomada en el primer minuto tras la instalación se mide sobre un heap casi vacío y saldrá optimista. Espera a que pase `BUFFER_S` antes de citar un número de régimen estacionario. - **`mikroscope_slipped_total` es el número que de verdad importa.** Un muestreador que cuesta algo más pero nunca pierde ticks te está diciendo la verdad; uno que los pierde, no. Como referencia: con los valores por defecto de la instalación, el agente cuesta un 2,85 % de un núcleo y un 31,3 MiB de RSS. Esa cifra se midió el 2026-09-15 con el conjunto completo de fuentes y tres destinos a la vez, no durante la campaña de la que vienen estas lecturas: Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · 2026-09-15 · ventanas de 60 s en régimen estacionario (con el anillo ya lleno), conjunto completo de fuentes, colector reenviando a la vez a fichero, a una exposición Prometheus y a InfluxDB 3 ## Véase también - [El coste del observador](/mikroscope/es/cost/): el presupuesto, el coste medido y el procedimiento de arriba completo. - [El suelo de resolución es del kernel](/mikroscope/es/limits/): por qué el porcentaje de ocupación de un núcleo en una muestra de 100 ms se mueve en escalones del 10 %, y qué se lee por debajo de eso. - [Detecciones](/mikroscope/es/sinks/detections/): las reglas que el colector ejecuta sobre estas mismas señales. - [Cinco minutos con un router](/mikroscope/es/start/walkthrough/): una grabación con marcadores, la otra forma de leer un transitorio. --- # La forma de un router en reposo Sesenta segundos del RB5009 de referencia sin hacer nada en particular — el suelo de ocupación, el squeeze que nunca llega a cero y el silencio de un log del kernel sano. Source: https://jmrplens.github.io/mikroscope/es/playbooks/idle/ Esta página responde a qué aspecto tiene lo "normal", para que las demás páginas tengan algo respecto a lo que ser anómalas. Conoce esta forma antes de salir a buscar anomalías, o las encontrarás en todas partes. Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · Linux 5.6.3 · 2026-09-12 · agente a 10 Hz en un contenedor privilegiado efímero ## Sesenta segundos en reposo Tres tramos de 20 segundos: ```text t busy% per-core busy% ctxt sirq squeeze temp MHz events 0 7.1 10.5 7.9 4.3 5.8 41793 325 296 36.2 1400 0 1 8.4 8.0 7.8 9.9 7.8 60518 251 223 36.2 1400 0 2 6.5 7.1 5.2 6.7 6.8 58259 199 179 36.3 1400 0 ``` Los porcentajes de esa tabla se calcularon a posteriori a partir de los deltas de ticks en bruto que envía el agente; el agente nunca los convierte en porcentajes. ## Qué leer - **Un 6–8 % de ocupación en los cuatro núcleos es el suelo de este router**, no un problema. Son DNS, DHCP, WireGuard, el bridge y su propio mantenimiento. - **`time_squeeze` nunca es cero** (~200 cada 20 s). Un squeeze distinto de cero es normal; lo que importa es el _cambio_ bajo carga, que muestra [la inundación de paquetes](/mikroscope/es/playbooks/packet-flood/). Una alerta con "squeeze > 0" te despertaría para siempre. - **`arch_timer` y `switch0` son siempre las dos fuentes de interrupciones con más actividad.** - **Cero sucesos del kernel es el estado sano.** Cualquier flujo constante de `events` en reposo merece el tratamiento del [caso del bucle](/mikroscope/es/playbooks/loop/) — así es exactamente como empezó. ## El squeeze de fondo y la marca de ráfaga El suelo del squeeze se volvió a medir el 2026-09-15, en el mismo equipo, sobre 3 476 muestras: alrededor del 11,2 % de las muestras llevan un squeeze de fondo, y un 2 % llevan dos o más. Medido en este router ese día: una regla que marque cualquier squeeze salta 92 veces en veinte minutos, y no significa nada. Así que la marca `burst` del colector es una desviación respecto a la norma del propio equipo: un paquete descartado, o más squeezes de los que esa CPU suele tener (por encima de su percentil 90 móvil y al menos 3) en una muestra cuyo número de paquetes estuvo en su mediana móvil o por debajo. Las líneas base móviles abarcan diez segundos de reloj de pared a cualquier cadencia. Tres muestras marcadas en una CPU en 60 s disparan la detección `microburst`; una sola muestra marcada se queda en un dato. > **Cierto en este equipo, no en el tuyo** > > Este suelo es un RB5009 con el reloj fijado a 1400 MHz, que ejecuta el DNS, el DHCP, el WireGuard > y el bridge de este propietario. Un router con otros servicios, un reloj que escala o una placa > distinta tiene otra forma en reposo. Toma los mismos sesenta segundos en el tuyo antes de leer > cualquier otra página contra ella. ## Véase también - [Una inundación de paquetes](/mikroscope/es/playbooks/packet-flood/): lo que hace el squeeze cuando sí cambia. - [Un bucle que solo veía el kernel](/mikroscope/es/playbooks/loop/): lo que resultó ser un flujo constante de sucesos en reposo. - [Detecciones](/mikroscope/es/sinks/detections/): la regla `microburst` y las demás, con lo que cada una no puede afirmar. --- # Un bucle que solo veía el kernel Un bucle de capa 2 en la red de producción del router de referencia que ni el log de RouterOS ni su monitor de puertos mostraron nunca, encontrado por la fuente de log del kernel del agente, diagnosticado a partir de una tasa y corregido con una actualización de firmware confirmada de dos formas independientes. Source: https://jmrplens.github.io/mikroscope/es/playbooks/loop/ Este fallo no se provocó. Se encontró por casualidad el 2026-09-12, mientras se medía otra cosa, en el RB5009UG+S+ de producción del propietario (RouterOS 7.24.2, kernel 5.6.3), y se diagnosticó y corrigió ese mismo día. Es la respuesta más clara que tiene este proyecto a _"¿por qué no consultar sin más la API de RouterOS?"_: durante toda su vida, la API informó de un equipo sano. La página sigue el diagnóstico en el orden en que ocurrió, porque el orden es el método. ## El síntoma `/snapshot` traía un flujo constante de `events` — la fuente `kmsg` del agente — a **1,49 /s**: ```text [6] br0: port 2(eth1) entered blocking state [4] br0: received packet on eth1 with own address as source address (addr:00:00:5e:00:53:5d, vlan:0) [6] br0: port 2(eth1) entered learning state ``` La dirección de la segunda línea es la del propio router: la MAC de `sfp-sfpplus1`, volviendo a entrar en el bridge. Aquí aparece como `00:00:5e:00:53:5d`, del bloque que la RFC 7042 reserva para documentación; en tu equipo será la de tu bridge, y así es como reconoces la línea. ## Paso 1 — establecer que es real, y obtener un número Una sola línea de log alarmante es una anécdota. Una _tasa_ es una medida, y una tasa es lo que te dice después si una corrección funcionó. Cuenta sucesos por segundo en una ventana lo bastante larga como para ser estable: ```sh curl -s "http://172.30.10.2:9123/snapshot?seconds=120" \ | python3 -c ' import sys, json, collections rows=[json.loads(l) for l in sys.stdin if l.strip()] secs=sum(r["dt_ns"] for r in rows)/1e9 ev=[e for r in rows for e in r.get("events",[])] print(f"{len(ev)/secs:.2f} events/s over {secs:.0f}s") c=collections.Counter(e["msg"].split("(")[0][:60] for e in ev) for k,v in c.most_common(): print(f" {v:5d} {k}")' ``` `?seconds=` admite de 1 a 3600, y el agente solo puede devolver lo que su anillo aún guarda — 300 s con el `BUFFER_S` por defecto. La longitud de la ventana sale de sumar el `dt_ns` de cada muestra, no del número que pediste. ## Paso 2 — leer los tiempos, no solo el texto Los intervalos entre las tramas reflejadas eran de **2,00–2,01 s**, siempre. Eso no es una coincidencia que anotar y dejar atrás: 2 s es el _hello interval_ de STP. El mecanismo era, por tanto, "el router emite una BPDU y recibe de vuelta su propia BPDU", lo que es un bucle, no un cliente que se porta mal. Los tiempos suelen ser donde de verdad está el diagnóstico: ```sh # intervalos entre apariciones consecutivas de un mismo mensaje ... | python3 -c ' import sys, json ts=sorted(e["us"]/1e6 for l in sys.stdin if l.strip() for e in json.loads(l).get("events",[]) if "own address" in e["msg"]) print([round(ts[i+1]-ts[i],2) for i in range(len(ts)-1)][:12])' ``` `us` es la marca de tiempo del propio kernel para el registro, en microsegundos desde el arranque sobre el reloj monótono — no el momento en que el agente lo leyó —, así que los intervalos son los del kernel, no los del muestreador. ## Paso 3 — confirmar que la API de verdad no lo ve Merece la pena hacerlo de forma explícita, porque decide dónde gastas la hora siguiente: ```text /log/print where topics~"bridge" or topics~"stp" or topics~"interface" /interface/bridge/port/monitor [find] once ``` Las dos volvieron limpias: cero filas de log en esos temas, todos los puertos `designated-port` / `in-bridge`. RouterOS no estaba ocultando el fallo; de verdad no muestra esta clase de sucesos del kernel. ## Paso 4 — buscar evidencia que lo corrobore fuera del log del kernel Una sola fuente, por convincente que sea, es una fuente. Dos observaciones independientes que apuntan en la misma dirección son un diagnóstico. La MAC del mensaje pertenecía a `sfp-sfpplus1`, así que la sospecha obvia era el segmento SFP+ — pero las propias tablas del bridge decían otra cosa: ```text # hosts aprendidos por puerto :foreach p in=[/interface/bridge/port/find] do={ \ :local n [/interface/bridge/port/get $p interface]; \ :put ($n . " hosts=" . [:len [/interface/bridge/host/find interface=$n]]) } ``` | Puerto | MAC aprendidas | Tráfico | `edge` de RSTP | | -------------- | -------------- | -------------------------------- | -------------- | | `sfp-sfpplus1` | 60 | 23,8 GB rx | true | | **`ether2`** | **0** | **9,15 GB rx / 22,4 M paquetes** | **false** | | el resto | 0–2 | — | true | `ether2` estaba pasando 22 millones de paquetes por un enlace sano de 1 Gbps y el bridge no había aprendido **nada** detrás de él — que es lo que hace un bridge en un puerto por el que no deja de ver sus propias direcciones. Era además el único puerto que recibía BPDU. Las dos anomalías, en el mismo puerto. El log del kernel había dicho `eth1`, no `ether2`. Establecer que son el mismo puerto costó tiempo de verdad aquel día; [Puertos de RouterOS y nombres del kernel](/mikroscope/es/reference/port-names/) explica cómo hacerlo en un solo paso seguro, y por qué el agente lo hace por ti en esta placa. ## Paso 5 — hacer un cambio que ponga a prueba la hipótesis La hipótesis era "el equipo de `ether2` tiene un segundo camino hasta el router a través de los otros AP, que cuelgan del switch". Primero se hicieron dos cambios en el switch, y el resultado honesto es que **no sirvieron de nada**: | Cambio | Tasa de sucesos | | --------------------- | ----------------------------------- | | línea base | 1,49 /s | | cambio en el switch 1 | 1,67 /s | | cambio en el switch 2 | 1,47 /s | | (banda de ruido) | ±0,2 /s | Una lectura más de la misma serie, 1,50 /s, también quedó dentro de esa banda. Es un resultado útil, no un paso perdido: descartó el switch con evidencia. La lección para un playbook es **volver a medir siempre después de un cambio, incluso uno que esperas que funcione**, y conocer tu banda de ruido antes de interpretar una diferencia. ## Paso 6 — la corrección, y confirmarla de dos formas Se actualizó el firmware de los AP de la malla (unidades Deco); el cableado no se tocó. Después: ```text events 9 -> 0.03/s (baseline 1.49/s) ``` y de esos nueve, _ninguno_ era el mensaje del bucle — eran los AP volviendo (`eth1: phy link up`, `eth1: set isolation from 0 to 1`). El mensaje del bucle, que había aparecido cada 2 s (30 veces por minuto, rodeado del doble de registros de blocking/learning), apareció **cero** veces en 300 s. La segunda confirmación, independiente, es la que merece la pena interiorizar: la visión del bridge se volvió _coherente_. | | Antes | Después | | --------------------- | ----- | -------- | | MAC en `ether2` | 0 | **24** | | MAC en `sfp-sfpplus1` | 60 | **36** | | `edge` de `ether2` | false | **true** | Los mismos 60 equipos, repartidos 36/24 en lugar de 60/0. Mientras existió el bucle, el router aprendía casi todos los equipos en el puerto equivocado; el coordinador Zigbee `00:4B:12:96:80:33`, por ejemplo, pasó del SFP+ a `ether2`, donde de verdad está. Una corrección que hace encajar una _segunda_ medida no relacionada es una corrección de la que te puedes fiar. > **No establecido** > > La actualización de firmware eliminó el bucle, y las dos medidas de arriba lo confirman. Por qué > hacía bucle el firmware anterior no está establecido: la corrección se observó, no se explicó. ## La firma **Un fallo real, no provocado** · 2026-09-12 - Un flujo constante y periódico de `events` del kernel en reposo, donde el estado sano es cero. - `received packet on with own address as source address`, con la propia MAC del router dentro. - Tiempos entre llegadas de 2,00–2,01 s: el hello interval de STP. - Un puerto con mucho tráfico en el que el bridge no ha aprendido ningún host, con `edge=false` mientras sus vecinos están a `true`. - `/log/print` y `/interface/bridge/port/monitor`, ambos limpios. ## Qué sacar de esto - Un texto alarmante es una pista; una tasa es evidencia; una tasa antes y después es una conclusión. - Lee los tiempos entre llegadas. A menudo te nombran el protocolo. - Prefiere un cambio que discrimine entre hipótesis a un cambio que tal vez lo arregle. - Desconfía de una corrección que solo tu instrumento principal puede confirmar. ## Véase también - [Puertos de RouterOS y nombres del kernel](/mikroscope/es/reference/port-names/): cómo `eth1` resultó ser `ether2`, y lo que el agente incluye para ahorrarte esa hora. - [La forma de un router en reposo](/mikroscope/es/playbooks/idle/): por qué cero sucesos es la línea base contra la que destacó este flujo. - [La CPU del router, la red del contenedor](/mikroscope/es/limits/namespaces/): por qué el log del kernel es visible desde el contenedor y los contadores de las interfaces no. --- # Una carga limitada por CPU Un bucle de consola que satura un núcleo del RB5009 de referencia durante un minuto — por qué el total del equipo marca un 29 %, cómo se ve la migración del planificador a 10 Hz y qué hicieron la temperatura y la frecuencia. Source: https://jmrplens.github.io/mikroscope/es/playbooks/cpu/ Esta página responde a qué aspecto tiene un cuello de botella de un solo hilo en los datos por núcleo, y por qué el total del equipo lo esconde. La carga se provocó a propósito. Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · Linux 5.6.3 · 2026-09-12 · agente a 10 Hz en un contenedor privilegiado efímero ## Cómo se provocó Sin ningún cambio de configuración y sin dependencias externas — un bucle de consola que quema un núcleo y termina por sí solo: ```text :local i 0; :while ($i < 4000000) do={ :set i ($i + 1) } ``` Duró 58,8 s. ## Lo que registró el agente En tramos de 5 segundos. El agente envía deltas de ticks en bruto; los porcentajes de abajo se calcularon a partir de ellos después. ```text t busy% per-core busy% ctxt/s temp MHz 0 22.3 31.8 7.4 40.4 9.6 9192 36.5 1400 1 30.9 11.7 11.4 89.7 10.6 11325 37.0 1400 2 29.9 14.3 9.4 19.6 76.0 9476 37.0 1400 3 29.1 7.4 9.4 17.6 81.6 10805 37.0 1400 4 29.3 3.1 9.4 4.9 99.8 9257 37.2 1400 8 28.4 2.2 7.0 4.6 99.8 11947 37.2 1400 11 33.0 11.8 11.2 9.0 99.7 12222 37.3 1400 ``` ## Qué leer - **Un total de ~29 % es un núcleo de cuatro.** El total del equipo es una trampa; mira siempre la fila por núcleo. "29 % de CPU" significa aquí "un núcleo está saturado y tres están ociosos", lo que para un cuello de botella de un solo hilo es toda la historia. - **El planificador tardó ~20 s en asentarse.** Los tramos 0–3 muestran el trabajo moviéndose entre núcleos (40 % → 90 % → 76 % → 82 %) antes de fijarse en el núcleo 3 al 99,8 %. Si muestreas cada 1 s o más despacio ves una meseta difusa; a 10 Hz ves la migración. Aquí es donde la resolución se gana lo que cuesta. - **La temperatura lo siguió: de 36,5 a 37,3 °C.** Un núcleo saturado vale ~0,8 °C en esta placa con refrigeración pasiva. Poco, pero sigue la carga, y se lee de `/sys/class/thermal` sin ninguna llamada a la API. - **La frecuencia se quedó plana en 1400 MHz.** En este equipo eso es una _configuración_, no una observación: el propietario ha fijado el reloj al máximo, lo que `/system/routerboard/settings` indica como `Warning: cpu not running at default frequency`. En un equipo que escala, el campo `freq_khz` es donde verías un tick ocupado que se ganó despacio. El agente lee la frecuencia en cada tick y guarda `freq_khz` solo cuando cambia, más un latido cada 60 s, así que con un reloj fijo tienes una fila por minuto, y con un reloj que escala tienes cada escalón que dure al menos un tick. ## Comprueba el muestreador antes que la CPU Antes de concluir nada de un número de CPU, confirma que `mikroscope_slipped_total` es 0. Un tick perdido es uno cuya lectura terminó después de que tocara el siguiente tick, y entonces la propia contabilidad del muestreador es lo primero de lo que deberías desconfiar. ## La firma **Provocado a propósito** · 2026-09-12 - Un total del equipo cercano al 100 % dividido entre el número de núcleos — aquí ~29 % con cuatro núcleos — con una columna por núcleo al 99,7–99,8 %. - Antes de asentarse, la carga saltando visiblemente entre núcleos durante decenas de segundos. - Una subida de temperatura de menos de un grado que sigue a la carga. > **No medido, luego no afirmado** > > Una carga multihilo, una carga lo bastante larga como para llegar al equilibrio térmico y > cualquier cambio de frecuencia del reloj: el reloj del equipo de referencia está fijado, así que > no se observó qué hace un governor que escala bajo este bucle. ## Véase también - [El suelo de resolución es del kernel](/mikroscope/es/limits/): por qué una muestra de 100 ms resuelve un núcleo en escalones del 10 %, y los contadores de la PMU que leen por debajo. - [Cinco minutos con un router](/mikroscope/es/start/walkthrough/): un bucle de script grabado que fija un núcleo, con marcadores que dicen qué más estaba haciendo el router. - [Cada fuente a su propio suelo](/mikroscope/es/limits/source-floors/): por qué la temperatura se lee al 1 Hz que declara la zona y la frecuencia solo se guarda cuando cambia. --- # Una inundación de paquetes Diez segundos de inundación ICMP contra la propia dirección del RB5009 de referencia — la interrupción del switch que hace las veces de contador de interfaz, el único núcleo que pagó y el contador time_squeeze que merece la pena vigilar. Source: https://jmrplens.github.io/mikroscope/es/playbooks/packet-flood/ Esta página responde a qué aspecto tiene el tráfico que la CPU del router tiene que atender, visto desde dentro de un contenedor que no puede ver las interfaces del router. La inundación se provocó a propósito. Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · Linux 5.6.3 · 2026-09-12 · agente a 10 Hz en un contenedor privilegiado efímero ## Cómo se provocó ICMP contra la propia dirección LAN del router, desde un host de la LAN, limitado a 10 s. Es tráfico que la CPU del router tiene que atender, a diferencia del tráfico de LAN a LAN, que el chip del switch reenvía sin que la CPU llegue a verlo: ```sh ping -f -w 10 192.168.0.1 # 67 793 paquetes, ~6.8 kpps, 0 % de pérdida ``` `192.168.0.1` es la dirección LAN del router de referencia; usa la de tu router. ## Lo que registró el agente En tramos de 5 segundos, antes y durante: ```text t busy% per-core busy% sirq squeeze top IRQ 2 4.8 3.8 8.0 3.0 4.2 51 50 switch0=5337 3 5.5 3.9 10.4 3.9 3.9 50 45 switch0=5567 4 9.6 23.3 2.8 3.3 8.8 149 138 switch0=34922 5 14.3 24.9 19.6 3.5 9.2 153 136 switch0=33832 ``` ## Qué leer - **Las interrupciones de `switch0` pasaron de 5,5 k a 34 k por tramo**, una subida de 6×. Es lo más parecido a un contador por interfaz que existe dentro del contenedor: el namespace de red oculta `/proc/net/dev`, pero la interrupción que levanta la NIC es global. No te dará bytes; te dará el inicio, el final y qué núcleo pagó. - **El coste cayó en un solo núcleo** (el núcleo 0, al 23–25 %) porque esa IRQ está fijada. Una inundación que satura un núcleo mientras tres están ociosos es una forma habitual y confusa — el total dice 14 %, y el router parece bloqueado. - **Los softirqs se triplicaron (de 50 a 150)**, y `time_squeeze` subió con ellos. `time_squeeze` es el que hay que vigilar: cuenta las veces que el manejador de softirq agotó su presupuesto con trabajo aún en cola. Un squeeze que sube con un caudal plano es la firma de un router en su techo de paquetes por segundo. - **El tiempo de IRQ hardware no aparece en la columna `irq` de `/proc/stat`** en este kernel — siempre es 0 (sin `IRQ_TIME_ACCOUNTING`). Ese trabajo está dentro de `system`. No leas el campo `irq` para concluir que el router no tiene carga de interrupciones. ## Dónde viven estas señales - Las líneas de interrupción son `mikroscope_irq_total{irq,name,cpu}` en `/metrics`, por núcleo, para las fuentes que aparecieron en el top-K de alguna muestra; `mikroscope_irq_delivered_total` es la suma de todas las fuentes, el denominador para saber qué parte cubre el top-K. En InfluxDB, el reparto por núcleo es `mikroscope_irq_cpu`, con una etiqueta `cpu`. Qué línea levanta una NIC y cómo se llama dependen de la placa y de su driver: compara con la etiqueta `name` o con la tasa, no con `switch0` escrito en una consulta. - El squeeze es `mikroscope_softnet_total{cpu,kind="time_squeeze"}`, junto a `kind="processed"` y `kind="dropped"`; los softirqs son `mikroscope_softirq_total{cpu,kind}`. - El squeeze no tiene un presupuesto entre el que dividirlo: `/proc/sys/net/core/*` (`netdev_budget`, `netdev_max_backlog`) no existe en el namespace del contenedor. El colector marca una muestra como `burst` cuando una cola softnet descartó un paquete o hizo más squeezes de los que esa CPU suele hacer (por encima de su percentil 90 móvil y al menos 3), en una muestra cuyo número de paquetes estuvo en su mediana móvil o por debajo — la evidencia del kernel de una ráfaga más corta que el intervalo de muestreo. Lee [la forma en reposo](/mikroscope/es/playbooks/idle/) para saber por qué la regla no es "cualquier squeeze". ## Tu instrumento forma parte del sistema Un inciso que lo demuestra: ejecutar `/system/routerboard/settings/print` por SSH produjo sucesos del kernel propios — ```text [4] rb_ioctl, cmd: 0x5212, arg: 0x0 [4] rb: RB_GET_CF_INFO ``` Leer la configuración de RouterOS deja rastro en el log del kernel. Cuando estés correlacionando sucesos con tus propias acciones, recuerda que tu instrumento forma parte del sistema. En este equipo, cada conexión SSH cuesta además un 20–27 % de CPU mientras dura. ## La firma **Provocado a propósito** · 2026-09-12 - Una línea de interrupción que se multiplica varias veces, con un inicio y un final bruscos. - El coste en el único núcleo al que está fijada esa línea, mientras el total del equipo se mantiene modesto. - Los softirqs y `time_squeeze` subiendo juntos, frente a un squeeze que nunca es cero en reposo. > **Deliberadamente no provocado** > > Una inundación que llegara al techo de paquetes por segundo del router: esta entregó ~6,8 kpps con > un 0 % de pérdida. "Un squeeze que sube con un caudal plano" es como se espera que se lea el > techo, y aquí no se observó. Tampoco el tráfico que el chip del switch reenvía entre puertos LAN, > que la CPU nunca ve. ## Véase también - [La forma de un router en reposo](/mikroscope/es/playbooks/idle/): la línea base de squeeze e interrupciones desde la que subió esta inundación. - [La CPU del router, la red del contenedor](/mikroscope/es/limits/namespaces/): por qué la interrupción es visible y los contadores de las interfaces no. - [Lo que deriva el colector](/mikroscope/es/sinks/derive/): la marca `burst` y los costes por paquete. --- # Desgaste de la flash El RB5009 de referencia escribe en su NAND en reposo sin que nadie se lo pida — cómo verlo en los contadores de YAFFS, rastrearlo hasta una regla de logging y leer los contadores ECC que avisan antes de que se pierda un bloque. Source: https://jmrplens.github.io/mikroscope/es/playbooks/flash-wear/ Esta página responde a qué está escribiendo en la flash del router, y a si la flash se está desgastando. No hubo que provocar nada: el RB5009 escribe en la NAND por su cuenta, y la fuente `yaffs` del agente lo muestra. Las cifras de MTD son del 2026-09-14. Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · Linux 5.6.3 · 2026-09-12 · agente a 10 Hz en un contenedor privilegiado efímero ## Lo que escribe el router en reposo En una ventana de 30 segundos en reposo, la partición Main recibió **2 escrituras de página y 14 lecturas de página**; en otra, cero. La causa se puede encontrar: ```text /system/logging/print where action="disk" # TOPICS ACTION 30 dns disk ``` El tema `dns` registra en disco, que es también la razón de que el log de este equipo guarde decenas de miles de filas. Si los deltas de `pw` (escrituras de página) o `er` (borrados) suben, pregúntate qué empezó a escribir. ## Qué significan los campos | Campo | Significado | Léelo como | | ----------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `er` | borrados | el contador que corresponde a la _vida útil_ de la flash | | `pw` / `pr` | escrituras / lecturas de página | la carga de trabajo | | `gcc` | copias del GC | amplificación de escritura: `gcc` ≫ `pw` significa que el sistema de ficheros trabaja mucho por cada byte que guardas | | `gc` | recolecciones de basura | con qué frecuencia recolectó el sistema de ficheros | | `bad` | bloques defectuosos | un nivel, y tiene que seguir en 0 | | `free` | chunks libres | margen | En `/metrics` los contadores son `mikroscope_flash_operations_total{device,kind}` con `kind` uno de `page_writes`, `page_reads`, `erasures`, `gc_copies` y `gcs`; los niveles son `mikroscope_flash_bad_blocks` y `mikroscope_flash_free_chunks`. Con los suelos por fuente por defecto, los contadores se leen en cada tick y solo se guardan cuando un contador se movió o cambió el nivel de chunks libres, lo que en el equipo de referencia ocurre unas 0,04 veces por segundo (unas pocas veces por minuto). Que falte una fila `flash` en una muestra significa que no pasó ninguna de las dos cosas, no que la fuente no exista. ## Boot y Main Se informa de los dos dispositivos YAFFS. El reparto es instructivo: tras dos semanas, la partición **Main** mostraba 83 812 escrituras de página y 1 579 borrados, mientras que la partición **Boot** mostraba **6** escrituras de página y 16 borrados en toda la vida del equipo — Boot solo se escribe en una actualización de firmware. ## Los contadores ECC: el aviso antes de la pérdida Con `privileged=yes` también se pueden leer los contadores ECC de MTD, desde `/sys/class/mtd`: `corrected_bits`, `ecc_failures` y `bad_blocks`. Todos están a cero en un equipo sano. Que `corrected_bits` suba es NAND que envejece; `ecc_failures` es pérdida de datos. El recuento de bloques defectuosos de YAFFS es la autopsia — un bloque solo se retira después de que el ECC haya fallado en él. El recuento de bits corregidos es el indicador adelantado, porque sube a medida que las celdas se debilitan. El kernel publica también el techo: `bitflip_threshold` es el número de bits corregidos por paso de ECC a partir del cual mueve los datos fuera de un bloque, y `ecc_strength` es el máximo de bits por paso que el código puede corregir. En el RB5009 de referencia, el 2026-09-14, había tres particiones — `RouterBoard NAND 1 Boot` (8 MiB), `RouterBoard NAND 1 Main` (1 GiB) y `RouterBoot` (1 MiB SPI) — con `corrected_bits`, `ecc_failures`, `bad_blocks` y `bbt_blocks` todos a 0, y `bitflip_threshold` 12 y `ecc_strength` 16 en la NAND. En una muestra son filas `mtd` (`corr`, `fail`, `bad`, `bbt`, `bitflip_threshold`, `ecc_strength`); en `/metrics`, `mikroscope_mtd_ecc_corrected_bits_total{device,partition}`, `mikroscope_mtd_ecc_failures_total`, `mikroscope_mtd_blocks{kind="bad"|"bbt"}`, `mikroscope_mtd_bitflip_threshold` y `mikroscope_mtd_ecc_strength`. Son acumulados desde el arranque y se envían tal como se leen, nunca como diferencias, porque se mueven a la escala de la vida de un equipo. El agente los lee cada 10 s — una cadencia arbitraria y holgada, no un suelo medido. ## Por qué existe `--ephemeral` Esta es la fuente que justifica `--ephemeral`: un despliegue con la raíz y la imagen en tmpfs no añade absolutamente nada a estos contadores. ## La firma **No hizo falta provocarlo** · 2026-09-12 - Deltas de `pw` y `er` en reposo que no has causado tú: algo está configurado para escribir. Mira primero las acciones de `/system/logging` puestas a `disk`. - `gcc` muy por encima de `pw`: el sistema de ficheros está pagando amplificación de escritura. - `corrected_bits` subiendo, o cualquier `ecc_failures` o bloques `bad` nuevos: la propia flash, no la carga de trabajo. > **No medido, luego no afirmado** > > Una flash que se desgasta. Todos los contadores ECC de MTD del equipo de referencia marcaban 0 el > 2026-09-14, así que en el hardware de este proyecto no se observó cómo se ve un `corrected_bits` > que sube con el tiempo, ni con cuánta antelación avisa de un bloque retirado. ## Véase también - [Lo que aporta privileged](/mikroscope/es/limits/privileged/): por qué los contadores ECC necesitan `privileged=yes`. - [Dónde va cada cosa](/mikroscope/es/install/layout/): qué pone `--ephemeral` en tmpfs y a qué renuncia. - [Reglas de alerta](/mikroscope/es/dashboards/alerts/): la alerta de fallos de ECC no corregibles. --- # Conntrack sin la API El namespace del propio contenedor informa de cero conexiones rastreadas, pero el asignador slab global no — cómo leer desde ficheros la población real de conntrack del router y su techo, y por qué no se provocó ninguna tormenta para mostrarlo. Source: https://jmrplens.github.io/mikroscope/es/playbooks/conntrack/ Esta página responde a cuántas conexiones está rastreando el router, sin recorrer una tabla por una sesión de la API. El techo y los timeouts se leyeron los dos el 2026-09-14. Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · Linux 5.6.3 · 2026-09-12 · agente a 10 Hz en un contenedor privilegiado efímero ## El namespace informa de cero El namespace de red del propio contenedor informa de `nf_conntrack_count` = 0 por muy ocupado que esté el router. Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · 2026-09-12 · `privileged=yes` no cambia el espacio de nombres de red ## El asignador slab no El asignador slab es global. Con `privileged=yes` el agente lee `/proc/slabinfo` e informa del número de objetos activos de la caché `nf_conntrack`, que **es** la población real de conntrack del router: ```text slab: {'nf_conntrack': 6240, 'skbuff_head_cache': 1008, 'skbuff_fclone_cache': 400, 'TCP': 327, 'UDP': 180, 'TCPv6': 117, 'UDPv6': 125, 'sock_inode_cache': 645, 'kmalloc-1k': 1152, 'kmalloc-2k': 905} ``` En una muestra es el mapa `slab`; en `/metrics` es `mikroscope_slab_active_objects{cache="nf_conntrack"}`. La familia no aparece sin `privileged=yes`, porque `/proc/slabinfo` solo lo puede leer root. Una caché que el kernel no tiene se omite, no se informa como un 0 permanente — el agente también pide `dst_cache` e `ip_dst_cache`, que no aparecen en la salida de arriba. `/proc/slabinfo` es el fichero más caro que analiza el agente, así que se lee con un suelo de presupuesto de 6 Hz, redondeado a ticks enteros: uno de cada 2 ticks (5 Hz) a los 10 Hz por defecto, uno de cada 17 a 100 Hz. 6 Hz es también más o menos la tasa a la que se midió que cambia `nf_conntrack`. Se guarda cuando cambia, más un latido cada 60 s. ## Compruébalo una vez Compruébalo contra la API una vez, para fiarte de él a partir de entonces: ```text /ip/firewall/connection/print count-only # 6 212 el día anterior; slab decía 6 287 ``` Ese día se registraron dos lecturas del slab frente a él, 6 582 y 6 287; el fragmento de arriba es un tercer momento (6 240). Los dos no coincidirán exactamente — se muestrean en instantes distintos, y el slab cuenta objetos que el asignador aún retiene —, pero se siguen. La diferencia de coste es lo que importa: la lectura de un fichero varias veces por segundo frente a recorrer una tabla por una sesión de la API. ## El techo también se puede leer Junto a `nf_conntrack_count` en `/proc/sys/net/netfilter`, `nf_conntrack_max` no va por namespace. Marca **966 656** desde dentro del contenedor — el mismo número que un `/ip/firewall/connection/tracking/print` de solo lectura da como `max-entries` en el router de referencia (2026-09-14). Así que "cómo de llena está la tabla de conexiones" se puede responder sin la API: la población del slab sobre ese techo. El agente lee el techo una vez al arrancar, porque es un sysctl que edita una persona y no un contador, y lo envía junto a la población que acota: `mikroscope_slab_limit_objects` en `/metrics`, `limit` en la fila slab de InfluxDB. El colector ejecuta dos detecciones sobre la población y su techo: `conntrack-cliff`, cuando `nf_conntrack` cae por debajo de la mitad de su valor guardado anterior, y `conntrack-high`, cuando la ocupación supera el 80 % de `nf_conntrack_max` **y** sube en los últimos 60 s, sin estimación de tiempo hasta llenarse. El mismo subárbol tiene una trampa. Los _timeouts_ de conntrack que hay ahí marcan los valores por defecto de Linux (`tcp_timeout_established` 432 000 s, frente al `1d` de RouterOS; leído el 2026-09-14), así que nunca deben presentarse como la configuración del router. ## Las cachés vecinas Merece la pena vigilarlas por sí mismas: - `skbuff_head_cache` / `skbuff_fclone_cache` — búferes de paquetes en tránsito. Un pico aquí durante un suceso de tráfico es presión de memoria que viene del camino de red, no de algo que hayas instalado. - `TCP` / `UDP` / `sock_inode_cache` — sockets que mantiene el propio router. - `kmalloc-1k` / `kmalloc-2k` — donde se ven las tormentas de asignaciones grandes. ## La firma **No hizo falta provocarlo** · 2026-09-12 - `nf_conntrack` subiendo hacia `nf_conntrack_max` mientras el recuento del propio namespace se queda en 0. - `nf_conntrack` cayendo por debajo de la mitad de su valor guardado anterior, que es lo que marca `conntrack-cliff`; la detección dice dónde mirar, no por qué cayó. - `skbuff_*` subiendo con un suceso de tráfico, lo que sitúa la presión de memoria en el camino de red. > **Deliberadamente no provocado** > > Una tormenta de conntrack. Generar miles de conexiones contra un router de producción arriesga > disparar su propio cortafuegos o un bouncer al estilo de CrowdSec y dejarte fuera del mismo camino > por el que estás trabajando. La comprobación cruzada de arriba da la misma confianza en el > contador sin ese riesgo, pero no se observó qué aspecto tiene una tormenta en estas cachés. ## Véase también - [La CPU del router, la red del contenedor](/mikroscope/es/limits/namespaces/): qué contadores oculta el namespace de red, y la excepción del slab. - [Lo que aporta privileged](/mikroscope/es/limits/privileged/): los ficheros que solo lee root, `/proc/slabinfo` entre ellos. - [Detecciones](/mikroscope/es/sinks/detections/): `conntrack-cliff` y `conntrack-high` completas. --- # El suelo de resolución es del kernel Por qué una lectura de CPU no puede ser más fina que el tick de 10 ms del kernel, la única fuente que lee por debajo de él y hasta dónde puede recordar el agente. Source: https://jmrplens.github.io/mikroscope/es/limits/ Esta página responde a la pregunta que decide cómo leer cada número de CPU que produce mikroscope: cuál es el cambio más pequeño que puede mostrar, y por qué ese límite lo pone el kernel y no el agente. También dice qué fuente sí ve por debajo de ese límite, qué no tiene el kernel de referencia y cuánto sobrevive una muestra en el agente antes de desaparecer. ## Ticks, no tiempo Un tick dura 10 ms. `/proc/stat` no cuenta tiempo, cuenta ticks de `USER_HZ`, 100 por segundo. Una muestra de 100 ms puede contener por tanto 10 ticks por núcleo, así que la fracción de ocupación de un núcleo se resuelve en escalones del 10 %, y la media de cuatro núcleos en escalones del 2,5 %. Sobre 1 s, la resolución de un núcleo es del 1 %. Eso es aritmética del tick, no una propiedad del agente, y ninguna cadencia ni ajuste lo cambia. Lo que el agente hace al respecto es negarse a esconderlo: envía los ticks en bruto y el intervalo real de cada muestra (`dt_ns`), nunca un porcentaje, de modo que la ventana sobre la que divides la eliges tú. La contabilidad de ticks también está cuantizada en los bordes. Un intervalo de 100,3 ms puede llevar 11 ticks (visto en el equipo de desarrollo amd64, 2026-09-11/12), lo que daría una fracción de ocupación por encima de 1. La fracción que deriva el agente está limitada a 1; los ticks en sí siguen en bruto. Muestrear más rápido no afina esto. A 100 Hz una muestra contiene 0 o 1 tick ocupado, así que la fracción de ocupación por muestra tiene dos valores posibles; por encima de unos 20 Hz los contadores de ticks son un indicador de ocupación más que un porcentaje. Lo que sí compra una cadencia mayor está en [el techo de muestreo](/mikroscope/es/cost/rate-ceiling/). ## La única fuente por debajo: los contadores de la propia CPU Una fuente lee por debajo del tick: la unidad de monitorización del rendimiento de la CPU (PMU), a través de `perf_event_open`, que el agente recoge como la fuente `perf` cuando el contenedor es privilegiado. Es la única fuente de mikroscope que no viene de un fichero. Medido en el RB5009 y registrado el 2026-09-12: en una muestra de 100,4 ms en la que `/proc/stat` informó de **cero ticks ocupados en los cuatro núcleos**, la PMU contó entre 2,2 y 4,6 millones de ciclos y entre 0,8 y 2,1 millones de instrucciones ejecutadas, con una tasa de fallos de caché del 3,8–5,7 %. El jiffie redondea ese trabajo hasta hacerlo desaparecer; el contador no. El número que merece la pena vigilar es el de instrucciones por ciclo, y el agente no lo calcula: envía las cuentas en bruto y la división la haces tú. La división compensa porque distingue un núcleo que trabaja de un núcleo parado esperando a la memoria, algo que ningún contador de ticks puede expresar. Medido durante 2 s en el mismo equipo y el mismo día, fue de 0,381 en cpu0 a 0,992 en cpu1. Qué es la fuente en el equipo de referencia (RB5009, RouterOS 7.24.2, kernel 5.6.3, Cortex-A72 r0p1, 2026-09-12, desde dentro de un contenedor privilegiado): - El agente pide siete contadores: `cycles`, `instructions`, `cache-references`, `cache-misses`, `branch-instructions`, `branch-misses` y `bus-cycles`. La prueba del 2026-09-12 abrió `cycles`, `instructions`, `cache-misses`, `branch-misses` y `bus-cycles` a nivel de sistema en 4 de 4 CPU. En los datos del propio agente de las 24 h que terminaron el 2026-09-12, seis informaron en 4 de 4 núcleos, `cache-references` entre ellos, y `branch-instructions` no produjo ninguna fila. - Los eventos genéricos `stalled-frontend` y `stalled-backend` devuelven `ENOENT` en el A72. Harían falta códigos de evento de PMU en bruto, así que el agente no los pide. - Un contador que no se puede abrir está ausente, nunca a cero. Qué contadores se abren depende de la CPU, así que lee la etiqueta `counter` de `mikroscope_perf_events_total{counter,cpu}` en vez de dar por hecho un conjunto. - Sin `privileged=yes` falta la familia entera: los contadores se abren a nivel de sistema, y eso un contenedor sin privilegios no puede hacerlo. Consulta [lo que aporta privileged](/mikroscope/es/limits/privileged/). El colector usa esos mismos dos contadores para una de sus detecciones, `ipc-collapse`: por núcleo, una vez que hay al menos 20 s de historia, que las instrucciones por ciclo de un segundo caigan por debajo de la mitad de su mediana de los 60 s anteriores mientras la tasa de ciclos está por encima de su propia mediana. Se describe junto con las demás reglas en [detecciones](/mikroscope/es/sinks/detections/). ## Ningún reloj más fino desde el kernel No esperes un reloj más fino de PSI ni de `schedstat`. El kernel de RouterOS 7.24.2 del RB5009 (Linux 5.6.3) no tiene ni `/proc/pressure` ni `/proc/schedstat`, y la columna `irq` de su `/proc/stat` vale siempre 0, así que el tiempo de IRQ hardware se cuenta dentro de `system`: Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · Linux 5.6.3 · 2026-09-11 · `/proc/pressure` y `/proc/schedstat` ausentes Tampoco hay un camino de trazado. Un contenedor privilegiado de descubrimiento en el mismo equipo, el 2026-09-12, no encontró ni eBPF, ni kprobes, ni ftrace: ni BTF, ni debugfs, ni tracefs, y los puntos de montaje no existen. Una nueva comprobación, el 2026-09-14, encontró `/sys/fs/bpf` presente y vacío, y `/proc/modules` con 238 módulos cargados, pero ni `/lib/modules`, ni cabeceras del kernel, ni compilador en el equipo: una función del kernel que falta no se puede añadir desde un contenedor. El agente detecta al arrancar lo que tiene el kernel y lo publica en `/capabilities`: el mapa `sources` dice qué fuentes lee realmente este despliegue. Los campos de una fuente ausente faltan en cada muestra y en cada destino, nunca valen cero. El agente sí lee PSI y `schedstat` donde el kernel los tiene. > **Sin probar** > > Los caminos de PSI y `schedstat` nunca se han ejecutado en el RB5009, porque su kernel carece de > ambos. El camino de `schedstat` solo se ejecutó en el equipo de desarrollo amd64 (kernel 6.12.107, > 2026-09-12); el analizador de PSI solo se ha probado con entradas sintéticas. La PMU se abrió en > ese equipo de desarrollo (2026-09-12), pero no se registró qué contadores abrió; en ninguna CPU > salvo el Cortex-A72 del RB5009 se conoce el conjunto de contadores. El hEX S, una compilación de > RouterOS de 32 bits sobre un chip ARM64, no se ha medido en absoluto. ## Hasta dónde recuerda el agente El otro límite duro es la profundidad, no la resolución. El agente guarda sus muestras en un anillo de `--buffer` segundos, 300 s por defecto y entre 10 y 3600 s permitidos, que contiene cadencia × búfer muestras. Nada más antiguo existe en ningún sitio del router. Un corte del colector o del grabador más corto que el anillo se rellena al reconectar: pide `since=` y recibe todas las muestras que se perdió. Un corte más largo que el anillo se informa como un hueco de longitud conocida, nunca se tapa. El agente responde con una línea `{"gap":{"from":…,"to":…}}` que nombra los números de secuencia perdidos, antes de las muestras que aún conserva; `record` lo escribe como un marcador que dice `samples N..M lost`, y `forward` lo cuenta en `mikroscope_collector_gaps_total` y se lo pasa a cada destino. Un anillo más largo cuesta memoria en el agente, y el agente rechaza uno que no quepa. Al arrancar estima el anillo a 2 560 bytes por línea (la línea media se midió en 2 439 B en el RB5009 el 2026-09-12, sin las fuentes de PMU, buddyinfo y MTD; una placa con más núcleos o líneas de interrupción, o más fuentes, cuesta más), suma el presupuesto de la captura por disparo, y sale con un error si el total supera el `memory.max` del contenedor. Si el total es más de la mitad del límite blando de memoria de Go, arranca pero registra un aviso, porque un heap tan justo mantiene al recolector de basura trabajando sin parar. Cómo dimensionar ambos límites está en [el coste del observador](/mikroscope/es/cost/). ## Véase también - [Cada fuente a su propio suelo](/mikroscope/es/limits/source-floors/): las fuentes que no se leen en cada tick, y el motivo con nombre de cada una. - [Lo que aporta privileged](/mikroscope/es/limits/privileged/): la PMU, el log del kernel y las cachés slab, y lo que el contenedor sigue sin ver. - [El techo de muestreo](/mikroscope/es/cost/rate-ceiling/): lo que compra muestrear más rápido cuando el tick ya ha dejado de ser un porcentaje. - [Lo que los números no dicen](/mikroscope/es/cost/limits/): lo que `/metrics` puede y no puede recuperar de estas muestras. --- # La CPU del router, la red del contenedor Qué ficheros del kernel ve un contenedor de RouterOS como los del router y cuáles como propios, por qué privileged no cambia eso, y la cuenta de conntrack que aun así se cuela. Source: https://jmrplens.github.io/mikroscope/es/limits/namespaces/ Un contenedor de RouterOS comparte el kernel del router, pero no toda la visión del kernel. Esta página responde qué ficheros dentro del contenedor describen el router y cuáles describen solo el propio contenedor, qué significa eso para el tráfico por interfaz y el número de conexiones, y por qué ningún ajuste del contenedor que mikroscope pudiera elegir mueve esa línea. ## La CPU y la memoria son del router Los ficheros de CPU, interrupciones, memoria y dispositivos de bloque son globales: dentro del contenedor son los del propio router, y se leen en cada tick (la fila de un dispositivo de bloque solo se guarda cuando hizo algo). Establecido en el RB5009 (RouterOS 7.24.2, kernel 5.6.3, 2026-09-11): - `/proc/stat`, por núcleo - `/proc/interrupts` y `/proc/softirqs` - `/proc/net/softnet_stat`: descartes y _time squeezes_ en la ruta de recepción del kernel. Vive bajo `/proc/net` pero cuenta por CPU, no por espacio de nombres. - `/proc/meminfo`, `/proc/vmstat` y `/proc/loadavg` - `/proc/diskstats` Un contenedor normal también lee como los del router estos ficheros, leídos el 2026-09-12: las dos zonas térmicas bajo `/sys/class/thermal`, `scaling_cur_freq` por núcleo, los contadores de desgaste de la NAND en `/proc/yaffs` y `/proc/buddyinfo`. La cadena `model` del árbol de dispositivos (`RB5009`) tampoco está en un espacio de nombres, y es como el agente identifica la placa sin la API de RouterOS. ## La red es la del contenedor `/proc/net/dev`, `/proc/net/snmp`, `/proc/net/netstat` y `nf_conntrack_count` son por espacio de nombres de red. Dentro del contenedor describen la veth del propio contenedor: 4 paquetes mientras el router reenviaba millones. El agente no los lee como datos del router, y `/capabilities` los lista bajo `namespaced` para que un consumidor vea que se dejaron fuera a propósito y no por descuido. **`privileged=yes` no cambia esto.** Elimina el espacio de nombres de _usuario_ del contenedor, no su espacio de nombres de red: Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · 2026-09-12 · `privileged=yes` no cambia el espacio de nombres de red Así que los bytes y paquetes por interfaz vienen en su lugar de la API de RouterOS, y el colector los fusiona con la capa del kernel sobre el reloj del agente. No se interpolan a partir de nada que el contenedor pueda ver. Cómo se consulta esa capa, y lo poco que queda de ella, está en [la capa de la API de RouterOS](/mikroscope/es/sinks/api-tier/). La misma frontera aparece en otros sitios, cada uno medido en el RB5009: | Qué | Lo que ve el contenedor | Medido | | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ---------- | | `/sys/class/net` | solo `lo` y la veth; ningún dispositivo para ningún puerto frontal, privilegiado o no | 2026-09-14 | | `/sys/class/mdio_bus`, `/sys/class/phy` | `mdio_bus` solo contiene `fixed-0`; `phy` está vacío | 2026-09-14 | | `netdev_budget`, `netdev_max_backlog` y las demás entradas globales de `net.core` | ausentes; las entradas por espacio de nombres, como `somaxconn`, están presentes | 2026-09-14 | | ficheros de `/proc/net/*` creados por módulos | los del propio contenedor: `fib_trie` muestra solo el `/30` de la veth, `snmp6` cuenta sus paquetes | 2026-09-15 | | `nf_conntrack_max` | el techo real del router, 966 656, el mismo que RouterOS informa como `max-entries` | 2026-09-14 | | tiempos de expiración de conntrack | los valores por defecto de Linux (`tcp_timeout_established` 432 000 s frente al `1d` de RouterOS) | 2026-09-14 | Las dos últimas filas están en el mismo directorio y se separan en direcciones opuestas. `nf_conntrack_max` se lee una vez al arrancar y se envía como el techo de la tabla de conexiones. Los tiempos de expiración nunca se presentan como la configuración del router, porque no lo son. La ausencia de `netdev_budget` es también la razón por la que los paneles de _squeeze_ no tienen un denominador de presupuesto: es cosa del espacio de nombres, no del agente. ## Conntrack: la cuenta que se cuela El número de conexiones es la excepción. Con `privileged=yes` el agente lee `/proc/slabinfo`, y el asignador slab es global: la cuenta de objetos activos de la caché `nf_conntrack` es la población real de conntrack del router. En el RB5009, el 2026-09-12, marcaba 6 582 en el contenedor de descubrimiento y 6 287 desde el agente ese mismo día, mientras el espacio de nombres propio del contenedor informaba de 0; la API de RouterOS había contado 6 212 el día anterior. Eso sustituye un recorrido de tabla por la API con la lectura de un fichero, y es la razón por la que el colector nunca consulta la cuenta de conntrack salvo que se indique `--conntrack-every` (desactivado por defecto en todos los `--api-mode`). Con el techo de más arriba, «cuánto de llena está la tabla de conexiones» se puede responder solo desde el contenedor. Lo que no puede decirte es qué son esas conexiones. `/proc/slabinfo` cuenta objetos en una caché y nada más: ni protocolo, ni dirección, ni estado. Una inundación de conntrack y una ráfaga legítima de muchas conexiones (un torrent) se ven igual en él. Sin `privileged=yes` el fichero no se puede leer y la cuenta desaparece; consulta [lo que aporta privileged](/mikroscope/es/limits/privileged/). Cómo contrastarla una vez con la API está en [conntrack sin la API](/mikroscope/es/playbooks/conntrack/). ## Montar las rutas del host no cruza la frontera Si montar las rutas propias del router dentro del contenedor atraviesa los espacios de nombres se probó directamente en el RB5009 el 2026-09-15, con el consentimiento del propietario: un contenedor privilegiado con `/proc`, `/sys` y `/` del host montados. - `/proc` del host se monta pero muestra **cero PID**. RouterOS genera un procfs nuevo en el punto de montaje, así que el espacio de nombres de PID se mantiene y la CPU por proceso de los procesos propios de RouterOS sigue fuera de alcance. - `/sys` del host se monta pero no tiene `class/net`. La parte de red de sysfs es por espacio de nombres de red y el montaje no lleva la del host, así que los contadores por interfaz siguen siendo solo de la API. - `/` del host funciona, y expone el sistema de ficheros de la flash de RouterOS: configuración y ficheros, no telemetría en vivo. Un contenedor privilegiado con `/` montado lee la configuración entera, secretos incluidos. El instalador de mikroscope no monta nada en el contenedor y nunca debe hacer esto. Los espacios de nombres son fronteras del kernel, y un montaje de sistema de ficheros no las cruza. > **Cierto en este equipo, no en el tuyo** > > Cada fila de esta página se leyó en un único RB5009 con RouterOS 7.24.2. Qué ficheros están en un > espacio de nombres es una propiedad del kernel y de cómo construye RouterOS sus contenedores, y no > se ha comprobado en otra versión de RouterOS ni en otra placa. ## Véase también - [La capa de la API de RouterOS](/mikroscope/es/sinks/api-tier/): de dónde viene el tráfico por interfaz, ya que el contenedor no puede verlo. - [Lo que aporta privileged](/mikroscope/es/limits/privileged/): el espacio de nombres de usuario que elimina, y los dos que deja en su sitio. - [Conntrack sin la API](/mikroscope/es/playbooks/conntrack/): leer la cuenta del slab y contrastarla una vez con RouterOS. - [Puertos de RouterOS y nombres del kernel](/mikroscope/es/reference/port-names/): poner nombre a un puerto que el contenedor no puede ver como dispositivo. --- # Lo que aporta privileged Por qué el contenedor del agente se ejecuta con privileged=yes por defecto, las fuentes que ese ajuste hace legibles, y la red, los procesos y los sensores a los que sigue sin llegar. Source: https://jmrplens.github.io/mikroscope/es/limits/privileged/ `install` crea el contenedor del agente con `privileged=yes`. Esta página responde qué cambia ese ajuste en el router, qué fuentes del agente dependen de él, qué no abre por mucho que se configure y a qué renuncias si lo desactivas. ## El valor por defecto, y por qué El contenedor se ejecuta con `privileged=yes` salvo que pases `--privileged=false` a `install` o a `upgrade`, que recrea el contenedor. Es una opción booleana, así que se escribe `=false`; un argumento `false` separado no se lee como su valor. El ajuste existe en RouterOS 7.24 y posteriores. Leer el estado interno del equipo es para lo que existe mikroscope, y privileged es lo que hace legibles los ficheros del kernel reservados a root. Un contenedor normal de RouterOS se coloca en un espacio de nombres de usuario en el que su root corresponde al uid 32768 del host, así que `/proc/slabinfo` y `/dev/kmsg` devuelven `EACCES`. `privileged=yes` elimina ese espacio de nombres de usuario. Sigue siendo una concesión real de privilegios, y por eso `plan` e `install --dry-run` lo muestran junto con el resto de ajustes del contenedor antes de escribir nada. ## Lo que añade Encontrado legible en el RB5009 (RouterOS 7.24.2, kernel 5.6.3) el 2026-09-12, y leído por el agente salvo donde la tabla indica otra cosa: | Fuente | Lo que da | Dónde aparece | | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | `/dev/kmsg` | el búfer circular del kernel como eventos con marca de tiempo, con los nombres reales de las interfaces del router | `mikroscope_kmsg_records_total{level}`, y Loki | | `/proc/slabinfo` | las cachés slab globales: `nf_conntrack` (el número real de conexiones del router), `skbuff_*`, sockets `TCP`/`UDP`, `kmalloc-1k`/`-2k` | `mikroscope_slab_active_objects{cache}` | | contadores ECC de `/sys/class/mtd` | `corrected_bits` y `ecc_failures` por partición NAND | `mikroscope_mtd_ecc_corrected_bits_total`, `mikroscope_mtd_ecc_failures_total` | | la PMU, vía `perf_event_open` | ciclos, instrucciones, referencias y fallos de caché, fallos de predicción de saltos, ciclos de bus, por núcleo, a nivel de sistema | `mikroscope_perf_events_total{counter,cpu}` | | `/proc/pagetypeinfo` | legible, pero el agente no lo lee | en ningún sitio | La PMU necesita privileged por una razón distinta a la de los ficheros: sus contadores se abren a nivel de sistema (`pid = -1`), lo que requiere `CAP_PERFMON` o `CAP_SYS_ADMIN` frente al host. `perf_event_paranoid` vale 2 en el equipo de referencia y no bloquea a un contenedor privilegiado. Lo que la PMU muestra y ningún contador de ticks puede mostrar está en [el suelo de resolución](/mikroscope/es/limits/). El log del kernel se lee sin bloquear y con un tope de 64 registros por tick. Se abre al final del búfer, así que un atasco de registros del arranque nunca se reproduce como si acabara de ocurrir. `mikroscope_kmsg_dropped_total` cuenta episodios de pérdida, no registros: uno por cada tick que llegó al tope de 64 registros (la lectura de más de ese tick se descarta, y el resto de la cola se lee en ticks posteriores), y uno por cada desbordamiento del búfer circular del kernel, que puede suponer muchos registros. Mientras no sea cero, la cuenta por severidad se queda corta. Si este despliegue las obtuvo no se deja a la deducción. `/capabilities` lleva `"privileged": true` solo cuando se abrieron tanto `/proc/slabinfo` como `/dev/kmsg`, y `mikroscope_device_info{privileged="true"}` dice lo mismo en `/metrics` y en el flujo de datos del equipo de cada destino. Sin privileged, las familias de slab, PMU y MTD están ausentes, no a cero. La familia del log del kernel solo aparece cuando se ha visto un registro, así que su ausencia por sí sola no distingue un despliegue sin privilegios de un kernel tranquilo; el indicador `privileged` sí. ## Lo que no añade No añade **ningún** acceso a la red ni **ninguna** vista de los procesos de RouterOS. Privileged elimina el espacio de nombres de usuario y deja en su sitio los de red y de PID: Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · 2026-09-12 · `privileged=yes` no cambia el espacio de nombres de red Así que los contadores por interfaz siguen viniendo de la API de RouterOS, y la CPU por proceso queda fuera de alcance incluso con el `/proc` del host montado en el contenedor. Ambas cosas están medidas en [la CPU del router, la red del contenedor](/mikroscope/es/limits/namespaces/). Tampoco añade capacidades: un contenedor sin privilegios ya tiene las 38. Lo que cambia es el espacio de nombres de usuario al que esas capacidades quedan confinadas, no el conjunto. Y no puede darte más sensores de los que tiene la placa. En el RB5009, `/sys/class/hwmon` está vacío incluso con privilegios. Las dos zonas térmicas, `cpu-thermal` y `soc-thermal`, son todo el conjunto de sensores del modelo base, y ambas se leen sin privileged: no hay lectura de tensión, corriente ni ventilador que obtener. El propio `/system/health` de RouterOS en esa placa informa de exactamente un sensor, `cpu-temperature`, que es la zona `soc-thermal` truncada a grados enteros: un desfase medio de +0,55 °C durante los 8 minutos en que existieron ambas series (2026-09-14). En una placa que sí tenga sensores de tensión, corriente o ventilador, la API los sigue añadiendo. > **Cierto en este equipo, no en el tuyo** > > El conjunto de sensores, el número de capacidades y la correspondencia de uid se leyeron en un > único RB5009 con RouterOS 7.24.2. No se ha comprobado una placa con sensores hwmon, ni un RouterOS > posterior que construya los contenedores de otra manera. ## Funcionar sin él `--privileged=false` conserva todo lo que se puede leer desde un contenedor normal: todos los ficheros globales de `/proc` en las muestras, las zonas térmicas, `scaling_cur_freq`, `/proc/yaffs`, `/proc/buddyinfo`, `/proc/diskstats` y el coste del propio agente. Lo que se pierde: - El log del kernel. El [bucle de capa 2](/mikroscope/es/playbooks/loop/) que este proyecto encontró en su propio router de referencia solo era visible ahí. - El número de conexiones del router desde la caché slab. El recorrido de tabla `count-only` de la API de RouterOS es la fuente que queda, y el colector solo lo ejecuta cuando se indica `--conntrack-every`. - Los contadores ECC de la NAND. Los contadores de desgaste de YAFFS se mantienen. - La PMU, y con ella todo lo que queda por debajo del tick de 10 ms. > **Privileged no es un montaje** > > Un contenedor privilegiado con el `/` del host montado lee la configuración entera de RouterOS, > secretos incluidos (probado en el RB5009 el 2026-09-15). El instalador no monta ninguna ruta del > host en el contenedor del agente, y nada de lo que hay aquí la necesita. ## Véase también - [La CPU del router, la red del contenedor](/mikroscope/es/limits/namespaces/): los dos espacios de nombres que privileged deja en su sitio, fichero a fichero. - [Qué se ejecuta dónde](/mikroscope/es/security/): el contenedor del agente y lo que puede alcanzar, desde el lado de la seguridad. - [Conntrack sin la API](/mikroscope/es/playbooks/conntrack/): la cuenta del slab que privileged hace legible, y cómo comprobarla. - [Desgaste de la flash](/mikroscope/es/playbooks/flash-wear/): los contadores de YAFFS que no necesitan privilegios y los contadores ECC que sí. --- # Cada fuente a su propio suelo Qué fuentes lee el agente en cada tick y cuáles lee o guarda con menos frecuencia, el motivo con nombre que publica cada una, y FLOOR_HZ, el único ajuste que desactiva todos los suelos. Source: https://jmrplens.github.io/mikroscope/es/limits/source-floors/ No todas las fuentes cambian tan rápido como hace tick el muestreador, y registrar un valor más a menudo de lo que el hardware lo refresca es almacenamiento sin información. Esta página responde qué fuentes lee y guarda el agente a la cadencia del muestreador y cuáles no, el motivo que da cada una para la diferencia, de dónde salieron esas cadencias y cómo desactivarlas todas para medir tu propio equipo. ## Los contadores nunca tienen suelo Los ticks de CPU, las interrupciones, las softirqs, los contadores de vmstat, la PMU y los niveles de memoria (que se movían unas 24 veces por segundo en el equipo de referencia) se leen y se envían en cada tick. En un contador, un delta de cero es información real, el núcleo estaba ocioso, así que no hay nada que saltarse. Otras tres fuentes se leen en cada tick con su propia regla: - **El desgaste de la NAND (`/proc/yaffs`) y la E/S de bloque (`/proc/diskstats`)** son contadores y baratos de leer. La fila de un dispositivo solo se guarda cuando su delta no es cero, lo que en el equipo de referencia ocurre unas pocas veces por minuto. El RB5009 lista además dieciséis dispositivos `nbd` ociosos, y una fila por tick para cada uno sería carga útil y nada más. - **El log del kernel (`/dev/kmsg`)** se vacía en cada tick y nunca se ralentiza. El valor de un evento es su marca de tiempo, y un marcador retrasado un segundo ya no cuadra con el pico que explica. ## Una fuente de nivel solo se ralentiza por un motivo con nombre Una fuente de nivel (una temperatura, una frecuencia de reloj, la población de una caché) puede leerse o guardarse con menos frecuencia que en cada tick, pero solo por un motivo que el agente nombra. Nunca se ralentiza porque se la haya visto cambiar despacio, ya que eso mediría una noche en un equipo. Cada fuente de nivel publica su cadencia y su motivo en `/capabilities` bajo `cadences`, en `/metrics` como `mikroscope_source_cadence_hz{source,reason}`, y a cada destino en el [flujo de datos del equipo](/mikroscope/es/sinks/device-info/), de modo que un consumidor lee la cadencia verdadera de un campo en vez de deducirla de los datos. Los motivos de cadencia que puede publicar una fuente: | `reason` | Qué significa | | --- | --- | | `rate` | se lee a la cadencia completa del muestreador; nada de lo que declara el equipo justifica menos | | `declared` | el equipo publica su propia cadencia de refresco, y leer más rápido devuelve el mismo valor con un temblor nuevo | | `policy` | un ajuste dice que el valor no puede moverse por sí solo: un gobernador cpufreq `userspace` | | `budget` | un coste de análisis medido | | `change` | se lee en cada tick y se guarda solo cuando se mueve | | `override` | `FLOOR_HZ` está fijado, y todas las fuentes de nivel van a su única cadencia | `rate` describe solo la cadencia de lectura, así que `/proc/buddyinfo` publica `rate` aunque se guarda al cambiar. Los contadores MTD publican `budget` aunque no se midió ningún coste de análisis para ellos. Así los etiqueta el código, y ninguno de los dos encaja del todo con la tabla de arriba. ## Fuente a fuente | Fuente | Lectura | Almacenamiento | `reason` | | ------------------ | ------------------------------------------------------------ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------- | | zonas térmicas | al `polling_delay` declarado por la zona, si no en cada tick | cada lectura | `declared`, o `rate` donde la placa no declara ninguno o la cadencia del muestreador no es más rápida que la declarada | | `scaling_cur_freq` | en cada tick | al cambiar, o por latido | `change`, o `policy` con un gobernador `userspace` | | `/proc/slabinfo` | a unos 6 Hz | al cambiar, o por latido | `budget` | | `/proc/buddyinfo` | en cada tick | al cambiar, o por latido | `rate` | | contadores ECC MTD | cada 10 s | al cambiar, o por latido | `budget` | Una cadencia más lenta es un número entero de ticks: la cadencia del muestreador dividida entre el suelo, redondeada al entero más cercano y nunca menor que uno. Así que la cadencia publicada es lo que ocurre de verdad, no el suelo nominal. **Temperatura.** En el RB5009 de referencia ambas zonas declaran un `polling_delay` de 1 000 ms (`polling-delay-passive` 250 ms, leído el 2026-09-14), así que el propio kernel vuelve a leer el sensor a 1 Hz; a 10 Hz el agente lee uno de cada 10 ticks. El sensor cuantiza en escalones de unos 0,42 °C y la lectura en bruto tiembla a ambos lados de un escalón decenas de veces por segundo. Guardar al cambiar guardaría ese temblor como señal; muestrear y mantener a la cadencia declarada captura la curva real y descarta el temblor. En una placa que no declara cadencia, las zonas se leen en cada tick. **Frecuencia de CPU.** Un salto de frecuencia es una transición DVFS real, no ruido, así que la frecuencia se lee en cada tick y se guarda cuando se mueve cualquier núcleo. En un equipo con el reloj fijado eso no guarda nada tras la primera lectura salvo el latido; en un equipo que escala recoge cada movimiento. El gobernador del RB5009 de referencia marcaba `userspace` el 2026-09-14, así que por la regla de arriba su motivo ahí es `policy`. **`/proc/slabinfo`** es la cara: 13 833 bytes y 129 líneas por lectura en el equipo de referencia (2026-09-14), el mayor análisis por tick, un orden de magnitud por encima de cualquier otro. Ese coste es la razón de que se ralentice; los 6 Hz a los que se ralentiza son la frecuencia a la que se midió que cambiaba su caché más rápida, `nf_conntrack`. Se guarda al cambiar. A 10 Hz eso es uno de cada 2 ticks (5 Hz); a 100 Hz, uno de cada 17 (unos 5,9 Hz). Cómo se nota ese reparto en el coste por muestra está en [el techo de muestreo](/mikroscope/es/cost/rate-ceiling/). **`/proc/buddyinfo`** ocupa unos 100 bytes, uno de los ficheros más baratos que lee el agente, y no tiene suelo porque no se ha medido ninguno. Las listas libres se agitan con cada asignación, así que en un router ocupado se guardará en la mayoría de los ticks, y eso es la medida, no ruido. **Los contadores ECC de MTD** se leen cada 10 segundos. Eso no es un suelo medido: los contadores se mueven a la escala de la vida de un equipo (todos a cero en la placa de referencia tras años), y cada lectura son seis pequeños ficheros de sysfs por partición, así que diez segundos es una elección arbitraria pero holgada. El código sigue etiquetando esa cadencia como `budget`, aunque no se midió ningún coste de análisis para ella. Necesitan `privileged=yes`. ## El latido, y por qué un medidor no desaparece Toda fuente que se guarda al cambiar se vuelve a emitir además aproximadamente una vez cada 60 segundos (en la primera lectura que toque pasados 60 s), de modo que un valor que se queda quieto una hora sigue teniendo una fila reciente en cualquier almacén. El propio `memory.max` del contenedor, una constante, se vuelve a emitir en las muestras una vez por latido (en cada tick con `FLOOR_HZ`); los `limits` de `/capabilities` y el [flujo de datos del equipo](/mikroscope/es/sinks/device-info/) lo llevan desde el arranque. En `/metrics`, un medidor con suelo mantiene entre emisiones la última lectura, porque el valor de un nivel entre lecturas es el último leído, no nada. `mikroscope_source_age_seconds{source}` dice lo antigua que es esa lectura mantenida. El filtro de cambios se rearma una vez que el muestreador ha tomado su lectura de referencia. Medido contra el árbol de fixtures el 2026-09-15, las tres familias con suelo y `mikroscope_slab_limit_objects` estaban presentes en 6 de 6 scrapes desde 5 s después del arranque. ## De dónde salen los suelos, y lo que no son Un suelo sale de una captura de 10,5 h a 50 Hz en el RB5009 de referencia que midió con qué frecuencia cambia de verdad cada fuente: los 6 Hz a los que se ralentiza `/proc/slabinfo`. Esa captura es una placa, una noche en reposo, con el reloj fijado en el ajuste del propietario. «cpufreq nunca cambió» significa que no cambió _esa noche_. Un router con un reloj que escala, una carga con muchas páginas sucias o una placa distinta tienen suelos distintos. Los suelos son constantes con nombre en `internal/agent/source.go` y no números enterrados en la lógica. > **Cierto en este equipo, no en el tuyo** > > Todas las cadencias de esta página son las del RB5009 de referencia, con RouterOS 7.24.2. Antes de > fiarte de cualquiera de ellas en otro equipo o con otra carga, vuelve a medir con `FLOOR_HZ` igual > a la cadencia del muestreador. ## `FLOOR_HZ`: todos los suelos desactivados a la vez Todos los suelos se pueden anular a la vez, sin recompilar: ```sh mikroscope install --rate 10 --floor-hz 10 # escribe FLOOR_HZ=10 en el envlist del agente ``` `FLOOR_HZ` es la variable de entorno del agente; `--floor-hz` es la opción de despliegue (`install`, `upgrade`, y `plan` para el listado) que la escribe en el envlist del contenedor, solo cuando es mayor que cero. Es un único ajuste global en hercios, de 0 a 1000. - `0`, el valor por defecto, mantiene los suelos por fuente descritos arriba. - Cualquier `N > 0` pone las zonas térmicas, `/proc/slabinfo` y los contadores MTD en una única cadencia de N Hz, y desactiva el filtro de guardar al cambiar para todas las fuentes de nivel, de modo que no se retiene nada y una captura ve cada lectura. Todas las fuentes de nivel publican entonces el motivo `override`. - Un `N` igual o superior a `--rate` lee y emite todo en cada tick. Es la configuración desde la que se midieron los suelos, y la que hay que volver a ejecutar antes de fiarse en tu equipo de cualquier número de esta página. Hay dos cosas que `FLOOR_HZ` no cambia. El filtro de filas por dispositivo de `/proc/yaffs` y `/proc/diskstats` se mantiene: un dispositivo cuyos contadores no se movieron sigue sin fila, lo que no pierde nada porque el delta era cero. Y `scaling_cur_freq` y `/proc/buddyinfo` se leen en cada tick con o sin él; con `N` por debajo de `--rate` se emiten en cada tick, pero su cadencia publicada es la del override, no la del muestreador. Lo que cuesta leerlo todo en cada tick en el RB5009, a 50 y a 100 Hz, son dos de las ejecuciones de [el techo de muestreo](/mikroscope/es/cost/rate-ceiling/). ## Véase también - [El techo de muestreo](/mikroscope/es/cost/rate-ceiling/): las ejecuciones con y sin `FLOOR_HZ`, y por qué el coste por muestra baja al subir la cadencia. - [El flujo de datos del equipo](/mikroscope/es/sinks/device-info/): por dónde llegan a un almacén la cadencia y el motivo de cada fuente. - [Variables de entorno](/mikroscope/es/reference/environment/): `FLOOR_HZ` junto a los demás ajustes del agente. - [El suelo de resolución es del kernel](/mikroscope/es/limits/): el suelo que ningún ajuste puede mover. --- # Qué se ejecuta dónde Qué pieza de mikroscope corre en el router y cuál en tu máquina, a qué llega cada una y dónde vive cada credencial. Source: https://jmrplens.github.io/mikroscope/es/security/ Esta página responde a la pregunta que hay que hacerse antes de poner nada en un router de producción: qué ejecuta mikroscope en él, qué ejecuta en tu propia máquina, a qué puede llegar cada pieza y qué credencial está en cada sitio. En corto: el router aloja un agente que escucha y nunca abre conexiones hacia fuera, y toda credencial que abre algo distinto del agente se queda en tu máquina. ## Qué se ejecuta dónde | Pieza | Corre | Llega a | Credenciales | | ---------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | `mikroscope-agent` | en un contenedor scratch dentro del router | sirve HTTP solo en la dirección de la veth; **ninguna conexión saliente** | no presenta ninguna; un token bearer opcional que _exige_ | | `mikroscope doctor`, `install`, `upgrade`, `uninstall`, `status` | tu máquina, cuando los ejecutas | el router por tu propio ssh de administrador; `install`, `upgrade` y `status` además sondean el `/healthz` del agente | tu clave ssh | | `mikroscope plan`, `install --dry-run` | tu máquina | nada: construyen la imagen e imprimen el listado sin conectarse al router | ninguna, salvo que `plan --rsc` reciba un token (abajo) | | `mikroscope record`, `forward` | tu máquina | el agente por HTTP; la API binaria del router para la capa de la API de `forward`, el transporte relay y `record --log-markers` | el token del agente si hay uno; un usuario de la API dedicado y de solo lectura | | `mikroscope mark` | tu máquina | los ficheros locales de la grabación; la API binaria del router solo con `--log-markers`; nunca el agente | el usuario de la API, solo con `--log-markers` | | destinos | tu máquina, dentro de `forward` | sirven `/metrics` para tu Prometheus; envían a InfluxDB y a los demás destinos que indiques | sus tokens, en tu entorno | | `mikroscope dashboards import`, `check` | tu máquina | tu Grafana | `GRAFANA_TOKEN`, en tu entorno | `plan` e `install --dry-run` nunca llegan al router, así que tampoco hacen las comprobaciones de propiedad; esas solo se hacen en un `install` real. La herramienta no informa de nada a ningún sitio. No hay comprobación de actualizaciones. En el código, el único código de red del agente es su servidor HTTP. Las conexiones salientes viven todas en la CLI — los destinos (HTTP, TCP o UDP), el cliente de Grafana, el cliente de la API de RouterOS, el transporte directo y la sonda de `/healthz` — y cada una va solo a una dirección que tú le diste o, para el agente, a la `.2` de `--subnet` (`172.30.10.2` salvo que la cambies). La exposición Prometheus del propio colector, `forward --prom `, escucha en la dirección que le pases y sirve `/metrics` sin autenticación. Enlázala a una dirección a la que solo llegue tu Prometheus. ## Las credenciales se quedan fuera del router **Por la API binaria, `/container/print` devolvió todas las propiedades de todos los contenedores, `cmd` y `envlist` incluidas, a un usuario con solo `read,api`** (RB5009UG+S+, RouterOS 7.24.2, 2026-09-11; solo se imprimieron los nombres de las propiedades). Leer los valores de las entradas de la envlist, en `/container/envs`, con un usuario así no se comprobó por separado. El diseño supone que también se pueden leer: lo que haya en una envlist se trata como legible por cualquier usuario `read` de ese router, no solo por los administradores. Por eso el agente no tiene destino push: un token de destino en el router lo podría leer cualquier usuario `read`. Si algún día llega el push, será agente → colector con el mismo NDJSON, nunca agente → InfluxDB. Lo que `install` sí pone en la envlist `-env` es configuración, y nada que abra otra cosa: Las entradas que install escribe en la envlist del agente: | Clave | Se escribe | Viene de | Contiene | | --- | --- | --- | --- | | `MIKROSCOPE_TAG` | siempre | `--name` | la marca de propiedad `mikroscope: (managed by mikroscope)`, que se escribe la primera y se borra la última; el agente la ignora | | `RATE_HZ` | siempre | `--rate`, por defecto `10`, 1–100 | la cadencia del muestreador, en Hz | | `BUFFER_S` | siempre | `--buffer`, por defecto `300`, 10–3600 | la longitud del anillo, en segundos | | `PORT` | siempre | `--port`, por defecto `9123`, 1–65535 | el puerto HTTP del agente | | `ADDR` | siempre | `--subnet` | la dirección del agente, la `.2` de la /30; el agente solo escucha ahí | | `MEM_LIMIT_MB` | siempre | `--mem-limit-mb`, por defecto `40`, 8–1024 | el límite blando de memoria de Go del agente, en MiB | | `FLOOR_HZ` | solo cuando es mayor que 0 | `--floor-hz`, por defecto `0`, 0–1000 | una sola cadencia para todas las fuentes de nivel, en Hz | | `CAPTURE_MB` | siempre | `--capture-mb`, por defecto `4`, 0–256 | el presupuesto de capturas por disparo, en MiB; `0` las desactiva | | `TRIGGERS` | solo cuando se da | `--triggers` | las condiciones de disparo; sin ella, el agente usa su conjunto por defecto | | `TOKEN` | solo cuando se da | `--token` | el token bearer que exige el agente, de `--token` o `MIKROSCOPE_TOKEN`, con o sin `--expose` | La última fila es el único secreto que sí vive en el router. Se escribe siempre que se dé `--token` o `MIKROSCOPE_TOKEN`, con o sin `--expose`, y como el resto de la envlist se trata como legible por cualquier usuario `read`. Abre las rutas HTTP del propio agente y nada más. `plan` y `--dry-run` lo imprimen como `value="(token)"`, la línea de opciones como `token=(set)`, y la línea de arranque del agente en el log del router como `token=true`. `plan --rsc` es la excepción a ese enmascarado. Escribe la instalación como un script de RouterOS para ejecutarlo en el propio router, así que la línea de la envlist tiene que llevar el token real; el propio script lo dice en su cabecera. Un `.rsc` generado con un token dentro es una credencial: [lo que el instalador rechaza](/mikroscope/security/installer/) explica cómo tratarlo. ## Dónde viven las credenciales del colector En tu máquina, las credenciales que abren algo distinto del agente se leen solo del entorno, nunca de una opción. El código da el motivo: una opción se ve en `ps` y en el historial de la shell. - La contraseña del usuario de la API: `MIKROSCOPE_API_PASSWORD`. La dirección y el usuario vienen de `--api` y `--api-user`, o de `MIKROSCOPE_API_ADDR` y `MIKROSCOPE_API_USER`. - Credenciales de los destinos: `MIKROSCOPE_INFLUX_TOKEN`, `MIKROSCOPE_LOKI_TOKEN`, `MIKROSCOPE_OTLP_TOKEN`, `MIKROSCOPE_ELASTIC_AUTH`, `MIKROSCOPE_TELEGRAF_TOKEN`. - Grafana: `GRAFANA_TOKEN`. El token del agente es la excepción: tiene una opción `--token` además de `MIKROSCOPE_TOKEN`. El mismo razonamiento vale para él, así que prefiere la variable. La CLI no lee `.env` por su cuenta. Exporta las variables a la shell que la ejecuta, por ejemplo con `set -a; . ./.env; set +a`. ## De dónde sale la imagen del agente El contenedor ejecuta una imagen, y la vía que la pone ahí decide en qué estás confiando. - `install` con una cadena de herramientas de Go y un checkout construye la imagen en tu máquina a partir del código que tienes delante y la sube por tu propia sesión ssh. Confías en tu propio árbol. - `install --agent-tar ` sube el tar que publica la release, por esa misma sesión ssh. La CLI comprueba que el tar es una imagen del agente de mikroscope de la arquitectura que nombra `--arch` antes de enviarlo; comprobar que es el fichero que publicó la release te toca a ti, contra `checksums.txt`. - `install --remote-image ` no sube nada: es el propio router quien descarga la imagen del registro que nombra su ajuste global `/container/config registry-url`. Confías en ese registro y en el camino del router hasta él, y mikroscope no verifica nada de lo que llega. - `plan --rsc` escribe esas mismas órdenes como un script de RouterOS para pegarlo o hacerle `/import`; la imagen sigue teniendo que llegar por una de las dos vías anteriores que no necesitan una subida desde la CLI. ## El contenedor `install` crea un contenedor con estos ajustes, todos impresos por `plan` antes de escribir nada: - **`privileged=yes` por defecto**, `--privileged=false` para renunciar a ello. El ajuste necesita RouterOS 7.24 o posterior. Quita el espacio de nombres de usuario del contenedor (verificado en RB5009UG+S+, RouterOS 7.24.2, 2026-09-12), que es lo que hace legibles el log del kernel, `/proc/slabinfo`, `/proc/pagetypeinfo` y los contadores ECC de la MTD. **No** quita el espacio de nombres de red ni el de PID: ni contadores de interfaz, ni la tabla conntrack del router, ni vista de los procesos de RouterOS. [Lo que aporta privileged](/mikroscope/es/limits/privileged/) tiene las medidas. - **`memory-max=64M`** (`--memory-max`), aplicado como límite cgroup del contenedor, con el límite blando de Go del agente en 40 MiB (`--mem-limit-mb`) dentro de él. - **`restart-policy=on-failure`**, limitado a cinco reintentos separados diez segundos, para que una imagen rota no pueda entrar en bucle al arrancar. - **`start-on-boot=yes`**, o `no` con `--ephemeral`, cuya raíz vive en el disco tmpfs y no sobrevive a un reinicio. - **`logging=yes`**, para que las líneas de ciclo de vida del agente lleguen al log del router. - **`ignore-remote-image-change=yes`**: con el valor por defecto, RouterOS vigila la imagen y, en cuanto se borra el tar, detiene y elimina el contenedor y lo vuelve a extraer minutos después (RB5009UG+S+, RouterOS 7.24.2, 2026-09-11). `install` borra el tar justo después de la extracción, y por eso fija este ajuste. - Raíz e imagen en el disco que elegiste con `--disk`: la flash interna por defecto. - **Ningún bind mount.** `install` no monta ninguna ruta del anfitrión dentro del contenedor. El agente lee `/proc`, `/sys` (`/sys/fs/cgroup` para su propia contabilidad, `/sys/class/thermal` y `/sys/class/mtd` para el equipo) y — con privileged — `/dev/kmsg` y los contadores de rendimiento del hardware. No escribe nada en su raíz durante la ejecución: el anillo y las capturas por disparo se guardan en memoria. Atrapa SIGTERM, porque RouterOS mata en el acto a un contenedor que no lo hace. El último ajuste de la lista es deliberado. El 2026-09-15 se le dieron a un contenedor privilegiado en el RB5009 de referencia `/proc`, `/sys` y `/` del anfitrión como bind mounts, para ver si un montaje da más acceso. El `/proc` del anfitrión se montó pero leyó cero PID, y el `/sys` del anfitrión no tenía `class/net`: los espacios de nombres aguantaron. El `/` del anfitrión sí funcionó, y expone el sistema de ficheros de la flash de RouterOS — configuración y ficheros, secretos incluidos. mikroscope no hace esto, y un contenedor que construyas tú tampoco debería. > **Sin probar** > > Que la instalación persistente sobreviva a un reinicio no está probado: el router de referencia es > de producción y no se reinicia para pruebas. Los ajustes del contenedor de arriba se verificaron > en un RB5009 con RouterOS 7.24.2; no se probó ninguna otra placa ni versión de RouterOS. ## Véase también - [El usuario de la API](/mikroscope/es/security/api-user/): el usuario de RouterOS que necesita el colector, y la política que recibe. - [Lo que abre --expose](/mikroscope/es/security/expose/): las dos reglas de cortafuegos, y por qué el token pasa a ser obligatorio. - [Lo que el instalador rechaza](/mikroscope/es/security/installer/): los objetos sobre los que no construye, y los valores que no mete en una orden. - [Lo que aporta privileged](/mikroscope/es/limits/privileged/): lo que lee la concesión de privilegio por defecto, y lo que no. --- # El usuario de la API El usuario dedicado de RouterOS con el que entra el colector, la política que necesita cada orden y lo que esa política le deja leer. Source: https://jmrplens.github.io/mikroscope/es/security/api-user/ El agente no necesita cuenta en RouterOS. Tres cosas de tu máquina sí: la capa de la API del colector, el transporte relay y los marcadores en el log del router. Esta página responde qué política necesita cada una, cómo crear un usuario que tenga eso y nada más, y qué puede seguir leyendo un usuario así. ## Qué órdenes lo necesitan | Lo usa | Ejecuta por la API binaria | Política | | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | | la capa de la API de `forward` | `/interface/monitor-traffic`, `/interface/print` (`name`, `default-name`, `type`, `comment`, `actual-mtu`), `/interface/list/member/print` (`list`, `interface`), `/interface/bridge/port/print` (`interface`, `bridge`), `/interface/print stats-detail`, `/interface/ethernet/print stats`, `/system/resource/print`, `/system/resource/cpu/print`, `/system/health/print`, `/ip/firewall/connection/print count-only` | `read,api` | | `record --log-markers`, `mark --log-markers` | `/log/print` (`time`, `topics`, `message`) | `read,api` | | el transporte relay (`--transport relay`, o `auto` cuando el directo no responde) | `/tool/fetch output=user` contra la dirección del agente | `read,api,test` | `/tool fetch` y `/tool profile` exigen ambos la política `test` (verificado en RB5009UG+S+, RouterOS 7.24.2, 2026-09-11). mikroscope usa `/tool fetch` para el relay y no usa `/tool profile`. `read,api` basta para la capa de la API y para `--log-markers`. `test` solo hace falta cuando se usa el relay — `--transport relay`, o `auto` cuando el transporte directo no responde — sea cual sea `--api-mode`. Con `--api-mode off` la capa no necesita ningún usuario. No todas las llamadas de la primera fila se ejecutan siempre. `/system/health/print` se omite en `slow` y con `--no-health`; el `count-only` de conntrack solo se ejecuta cuando `--conntrack-every` es mayor que 0, y su valor por defecto es 0 incluso en `full`; `monitor-traffic` solo se ejecuta cuando se da `--interfaces`, mientras que las tres lecturas de configuración del inventario de interfaces se ejecutan siempre que la capa esté encendida, una vez antes de la primera extracción del kernel y de nuevo cada `--labels-every`; las dos lecturas de stats siguen a `--counters-every`, 10 s por defecto. Qué llamadas se ejecutan y con qué cadencia está en [la capa de la API de RouterOS](/mikroscope/es/sinks/api-tier/). Sin dirección y usuario, cada orden lo dice de una manera distinta: - `forward` funciona solo con la capa del kernel y registra `api tier disabled: …`. - `mark --log-markers` y `--transport relay` fallan con `the RouterOS API needs --api, --api-user and MIKROSCOPE_API_PASSWORD`. - `record --log-markers` conserva la grabación, imprime `log markers: the RouterOS API needs …` en stderr y sale con 0. - `--transport auto`, cuando el transporte directo no responde, falla con ``direct transport did not answer and the relay is not configured: the RouterOS API needs … (or `install --expose`)``. Esa comprobación solo mira `--api` y `--api-user`. Un `MIKROSCOPE_API_PASSWORD` vacío no se detecta ahí; aparece como un inicio de sesión fallido, `api : …`. ## El grupo y el usuario Un grupo que concede `read`, `api` y `test` y niega por su nombre todas las demás políticas, y un usuario en él restringido a la dirección del colector: ```text /user/group/add name=mikroscope policy=read,api,test,!write,!ftp,!local,!telnet,!ssh,!reboot,!policy,!winbox,!password,!web,!sniff,!sensitive,!romon,!rest-api /user/add name=mikroscope group=mikroscope password= address=/32 ``` Quita `test` del grupo si nunca vas a usar el relay. Restringe `address=` a la máquina del colector, y restringe `/ip/service` para `api` a tu LAN. Las dos son limitaciones del lado de RouterOS sobre desde dónde se acepta la contraseña, y ninguna depende de mikroscope. Crea este usuario para mikroscope en vez de reutilizar uno hecho para otra herramienta. Un usuario de la API de mínimo privilegio creado para otra cosa suele pertenecer a un grupo que niega `test`, así que no puede ejecutar el relay, y ampliar ese grupo lo amplía también para la otra herramienta. ## Lo que `read` sigue leyendo `read` no es estrecha. **Por la API binaria, `/container/print` devolvió todas las propiedades de todos los contenedores, `cmd` y `envlist` incluidas, a un usuario con solo `read,api`** (el usuario `mikroscope`, RB5009UG+S+, RouterOS 7.24.2, 2026-09-11; solo se imprimieron los nombres de las propiedades). Leer los valores de las entradas de la envlist en `/container/envs` no se comprobó por separado; el diseño supone que este usuario puede, y por eso trata la entrada `TOKEN` del agente, cuando hay una, como legible por él. El colector acota lo que pide allí donde lee configuración. Las tres lecturas del inventario de interfaces nombran sus propiedades — `name`, `default-name`, `type`, `comment` y `actual-mtu`; `list` e `interface`; `interface` y `bridge` — y ningún otro campo, así que ninguna se trae un campo que pudiera llevar un secreto; `/system/resource`, `/system/resource/cpu`, `/system/health` y `/log/print` llevan también un `.proplist`. Las lecturas de contadores — `monitor-traffic`, `stats`, `stats-detail` — y el `count-only` de conntrack no. Nada de esto limita lo que el usuario tiene permitido pedir: sigue pudiendo pedir todas las propiedades de todos los contenedores. ## Cómo se lo pasa la CLI | Ajuste | Opción | Variable de entorno | | ---------- | ------------ | ------------------------- | | dirección | `--api` | `MIKROSCOPE_API_ADDR` | | usuario | `--api-user` | `MIKROSCOPE_API_USER` | | contraseña | ninguna | `MIKROSCOPE_API_PASSWORD` | La contraseña no tiene opción a propósito: una opción se ve en `ps` y en el historial de la shell. `.env.example` da la dirección como `192.168.88.1:8728`. > **La conexión con la API no va cifrada** > > `record`, `mark` y `forward` se conectan a la API binaria sin cifrar. El cliente incluido en el > repositorio puede usar TLS, pero ninguna opción lo activa, así que el inicio de sesión y cada > respuesta cruzan sin cifrar la red entre el colector y el router. `address=` limita desde dónde se > acepta el inicio de sesión; eso no lo cambia. ## Véase también - [Qué se ejecuta dónde](/mikroscope/es/security/): cada pieza, a qué llega y qué credencial guarda. - [La capa de la API de RouterOS](/mikroscope/es/sinks/api-tier/): lo que lee el colector con este usuario, y los perfiles `off`, `slow` y `full`. - [Llegar al agente](/mikroscope/es/install/reaching-the-agent/): los transportes directo y relay, y cuándo necesita `test` el relay. --- # Lo que abre --expose Las dos reglas de cortafuegos etiquetadas que añade install --expose, quién llega al agente a través de ellas y por qué el token pasa a ser obligatorio. Source: https://jmrplens.github.io/mikroscope/es/security/expose/ `--expose` es la única opción de instalación que cambia el cortafuegos del router más allá de las dos pertenencias a listas que añade toda instalación. Esta página responde qué escribe exactamente, quién puede llegar después al agente, qué protege el token y qué no, y cómo se quitan las reglas. **Lo que añade `install --expose`** - dos reglas de cortafuegos, etiquetadas - el token pasa a ser obligatorio - `uninstall` y `status` solo ven las dos reglas si se les vuelve a dar `--expose` Cada objeto lleva el comentario `mikroscope: (managed by mikroscope)` `mikroscope plan` imprime cada orden antes de escribir nada. ## Sin ella El agente escucha en la dirección del contenedor, la `.2` de `--subnet` (`172.30.10.2` por defecto), en `--port` (`9123`). Sin `--expose` solo se llega a él desde las máquinas que el router encamina hacia esa /30 de la veth. En el RB5009 de referencia, las pertenencias a la lista de interfaces y a la lista de direcciones que añade toda instalación bastaron para que una máquina de la LAN llegara a él directamente; [las dos trampas del cortafuegos](/mikroscope/es/install/firewall/) explica por qué hacen falta esas dos. ## Las dos reglas `install --expose --lan-address --token …` añade, después de las pertenencias a listas y antes del contenedor, un dst-nat desde la dirección LAN del router en el puerto del agente hacia la veth: ```text /ip/firewall/nat/add chain=dstnat dst-address= protocol=tcp dst-port=9123 action=dst-nat to-addresses=172.30.10.2 to-ports=9123 comment="mikroscope:mikroscope (managed by mikroscope)" ``` y un accept en forward para ese flujo, colocado antes de la primera regla `chain=forward action=drop`, o añadido al final cuando la cadena forward no tiene ningún drop: ```text /ip/firewall/filter/add chain=forward dst-address=172.30.10.2 protocol=tcp dst-port=9123 connection-nat-state=dstnat action=accept comment="mikroscope:mikroscope (managed by mikroscope)" place-before= ``` Las direcciones y el puerto de arriba son los valores por defecto, y `` representa la búsqueda que la orden real hace en el router antes de añadir la regla; `plan` imprime las dos órdenes exactamente como se ejecutarán, con tus valores. Las dos llevan la etiqueta, y las dos se verificaron en RB5009UG+S+, RouterOS 7.24.2, 2026-09-11: la LAN llegó al agente a través de la dirección del propio router, y las dos reglas se pudieron quitar por etiqueta. `--lan-address` tiene que ser una dirección IPv4; `install` rechaza `--expose` sin ella (`--expose needs the router's IPv4 LAN address`). Una regla existente con la misma cadena, dirección de destino, puerto y protocolo que no lleve la etiqueta detiene `install`, como cualquier objeto ajeno — mira [lo que el instalador rechaza](/mikroscope/es/security/installer/). ## Quién llega al agente a través de ellas A partir de ahí, cualquier máquina de la LAN llega al agente en `:9123`. Ninguna de las dos reglas restringe el origen: el dst-nat no tiene `in-interface` ni `src-address`, y el accept solo compara el destino, el puerto y `connection-nat-state=dstnat`. Qué máquinas pasan lo decide qué máquinas pueden enviar un paquete a la dirección LAN del router, y lo que hagan tus otras reglas antes que estas. > **Sin probar** > > Solo se probó una máquina de la LAN llegando a la dirección LAN del router. Si algo de fuera de la > LAN puede llegar a esa dirección en tu router depende del resto de tu cortafuegos, que mikroscope > ni lee ni cambia, y no se probó ningún camino así. ## El token Como al agente ya no se llega solo a través de la veth, el token es obligatorio: `install` rechaza `--expose` sin uno (`--expose makes the agent reachable from the LAN: a token is mandatory`). Con un token puesto, todos los endpoints que sirve el agente salvo `/healthz` devuelven `401 token required`, con `WWW-Authenticate: Bearer`, a menos que la petición lleve `Authorization: Bearer `: `/capabilities`, `/snapshot`, `/stream`, `/metrics`, `/captures`, `/captures/{id}` (incluido `DELETE`) y `POST /capture`. Una ruta que el agente no sirve recibe `404`, y un método equivocado `405`, haya token o no. El agente quita un prefijo `"Bearer "` opcional antes de comparar, así que también se acepta una cabecera que lleve el token a secas. `/healthz` sigue abierto. Devuelve la versión del agente, la cadencia, los números de secuencia, el tiempo en marcha, la cuenta de retrasos, el hash de capacidades, sus relojes de pared y monótono, y el modelo del device-tree de la placa — el modelo está ahí a propósito, porque es lo que se le pide a un operador que envíe cuando su placa aún no tiene mapa de puertos del kernel a RouterOS. Lo que el token es, y lo que no: - Puede contener letras, dígitos, `_`, `.` y `-`, hasta 128 caracteres; cualquier otra cosa se rechaza antes de la primera orden. - Se guarda en la envlist como `TOKEN`. `/container/print` devolvió la propiedad `envlist` a un usuario `read,api` (RB5009UG+S+, RouterOS 7.24.2, 2026-09-11); leer los valores de las entradas no se comprobó por separado, y el diseño supone que un usuario `read` puede. Tómalo como protección de las rutas HTTP del agente frente a la LAN, no frente a los propios usuarios `read` del router. - Un token puesto sin `--expose` se escribe igualmente y se exige igualmente. - El agente lo compara como una cadena normal, sobre HTTP sin cifrar: el dst-nat no lleva TLS, así que la cabecera cruza la LAN sin cifrar. ## Qué usa el camino expuesto, y qué no Las reglas sirven a un cliente que se dirige a `:` — un job de Prometheus, un navegador, un `curl` con la cabecera. Las órdenes de mikroscope no usan esa dirección: - `record` y `forward` construyen la URL del agente a partir de `--subnet` y `--port`, así que siempre marcan la dirección del contenedor. No hay ninguna opción que las apunte a la dirección LAN. - `install`, `upgrade` y `status` también sondean `/healthz` en la dirección del contenedor. - El transporte relay no puede llevar el token: `/tool fetch` en el router no envía cabecera `Authorization`. Contra un agente con token, el `/healthz` del relay responde y toda petición de muestras se rechaza. Un token necesita el transporte directo, o un despliegue sin token. ## Quitarlas `uninstall` quita las dos reglas, seleccionando cada una por la etiqueta junto con la cadena, la dirección de destino, el puerto y el protocolo; luego pregunta al router si queda algo etiquetado y falla nombrando el paso si es así. Esos selectores `find` ponen entre comillas la dirección y el puerto: sin comillas, RouterOS los interpreta como valores tipados y no encuentra nada — un `dst-port=9123` sin comillas no encontró ninguna regla en RB5009UG+S+, RouterOS 7.24.2, 2026-09-11, y un uninstall construido así habría informado de éxito con la regla todavía en su sitio. `upgrade` no toca ninguna de las dos reglas. Quita el paso del contenedor — el contenedor, la envlist `-env` y la imagen — y lo vuelve a crear, escribiendo la envlist a partir de las opciones dadas a `upgrade`, no de las que usó la instalación. Su única comprobación es que exista cada paso del plan construido con sus propias opciones, y un plan construido sin `--expose` no tiene pasos de reglas que echar en falta. > **Dale a upgrade las opciones que tuvo install** > > Pasa a `upgrade` el mismo `--token` (o `MIKROSCOPE_TOKEN`), `--expose` y `--lan-address`, y las > mismas opciones de ajuste (`--rate`, `--mem-limit-mb`, `--privileged` y las demás), que a la > instalación. Un upgrade sin el token pasa su comprobación, deja las dos reglas en su sitio y > escribe una envlist sin `TOKEN`: al agente se llega entonces desde la LAN sin token. Una opción de > ajuste que falte vuelve a su valor por defecto. ## Véase también - [Llegar al agente](/mikroscope/es/install/reaching-the-agent/): directo, relay y `--expose` comparados. - [Qué se ejecuta dónde](/mikroscope/es/security/): dónde está el token entre las demás credenciales. - [Lo que el instalador rechaza](/mikroscope/es/security/installer/): las comprobaciones de propiedad por las que pasa cada regla. - [Los endpoints HTTP del agente](/mikroscope/es/reference/http/): cada ruta que protege el token. --- # Lo que el instalador rechaza Los objetos sobre los que install no construye, los valores que no mete en una orden de RouterOS y cómo demuestra uninstall que no dejó nada atrás. Source: https://jmrplens.github.io/mikroscope/es/security/installer/ El instalador escribe en un router que no configuró él, por la propia sesión ssh de administrador del operador, así que no hay ninguna frontera de privilegios entre un error y el router. Lo que hace sus veces es un conjunto de rechazos. Esta página los enumera: dónde se detiene `install` antes de escribir, qué no toca, qué trata como un fallo, y cómo `uninstall` demuestra que ha terminado en vez de limitarse a decirlo. ## Nada se escribe antes de listarse `install` construye la imagen, imprime cada orden que ejecutaría con su texto exacto de RouterOS y después, en este orden: 1. ejecuta `doctor`, la comprobación previa de solo lectura, salvo que se dé `--no-doctor`; un requisito que falte lo detiene con `N prerequisite(s) missing; nothing was written`; 2. pregunta `write the objects above to the router? [y/N]`, salvo que se dé `--yes`; cualquier cosa que no sea `y` o `Y` lo detiene con `not confirmed; nothing written`; 3. solo entonces escribe. `plan`, e `install --dry-run`, se detienen después del listado. El listado enmascara el token como `value="(token)"`. `upgrade` no tiene esta garantía. Construye la imagen, rechaza un router en el que falte cualquier paso del plan construido con sus propias opciones (`nothing to upgrade: run install first`) y hace la misma pregunta `write the objects above to the router? [y/N]` — pero antes no imprime ningún listado ni ejecuta `doctor`, así que no hay objetos arriba. Con `y` quita el contenedor, la envlist y la imagen y los vuelve a escribir, la envlist a partir de las opciones dadas a `upgrade`; [lo que abre --expose](/mikroscope/es/security/expose/) explica lo que eso supone para el token. **Lo que `install` escribe en tu router** - una veth - una dirección - una pertenencia a lista de interfaces - una entrada de address-list - una envlist - el tar de la imagen, salvo que `--remote-image` haga que el router se la baje - el contenedor Cada objeto lleva el comentario `mikroscope: (managed by mikroscope)` `mikroscope plan` imprime cada orden antes de escribir nada. `uninstall` elimina por etiqueta exacta más identidad, nunca por patrón, y falla nombrando el paso si queda algo. ## Objetos que no son suyos Antes de escribir, `install` hace al router tres preguntas sobre cada paso en una sola conexión ssh: ¿está nuestro objeto?, ¿existe algo con el mismo efecto?, ¿lleva nuestra etiqueta? Un objeto que existe pero no lleva la etiqueta de mikroscope detiene `install`, nombrando el paso: ```text veth interface veth-mikroscope exists on the router and was not created by mikroscope (no ownership tag); pick another --name/--veth/--subnet, or remove it by hand if it is yours ``` Lo que cuenta como «el mismo efecto» es la identidad del objeto, no solo su nombre: | Paso | Choca con cualquier existente | Es nuestro cuando lleva | | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | | veth | `/interface/veth` con el mismo nombre | la etiqueta en `comment` | | dirección del router | `/ip/address` en esa veth | la etiqueta en `comment` | | pertenencia a la lista de interfaces | miembro con esa interfaz en esa lista | la etiqueta en `comment` | | pertenencia a la lista de direcciones | entrada con la /30 en esa lista | la etiqueta en `comment` | | dst-nat de expose | regla `dstnat` con esa dirección de destino, puerto y protocolo | la etiqueta en `comment` | | accept en forward de expose | regla `forward` con la dirección del contenedor, ese puerto y ese protocolo | la etiqueta en `comment` | | contenedor | un contenedor que use el mismo fichero de imagen, una envlist llamada `-env` o un fichero en la ruta de la imagen | un contenedor con la etiqueta; una envlist con la marca | Un paso que ya es nuestro se salta, así que ejecutar `install` dos veces no crea nada la segunda vez. El rechazo ocurre cuando `install` llega al paso en conflicto. Los pasos anteriores que faltaban ya se han creado; llevan la etiqueta, y `uninstall` los quita. `uninstall` nunca toca el objeto ajeno. ### La envlist y la imagen no llevan comentario Ni `/container/envs` ni `/file` tienen campo de comentario, así que el paso del contenedor los firma de otra manera: la primera entrada que se escribe en la envlist es `MIKROSCOPE_TAG` con la etiqueta exacta, y el fichero de imagen cuenta como nuestro solo mientras exista esa marca. Una envlist ajena con el mismo nombre, un fichero ajeno en la ruta de la imagen o la envlist de otro contenedor son, por tanto, ajenos, y detienen `install`. Los restos de una instalación anterior de mikroscope — una envlist bajo nuestra marca sin contenedor — son nuestros para sustituirlos, e `install` los limpia antes de escribir los nuevos. ## Los selectores son exactos Todo objeto que crea `install` lleva el comentario `mikroscope: (managed by mikroscope)`, al pie de la letra. Todo objeto de red se elimina por ese comentario exacto junto con la identidad que usó su comprobación — `comment="…"`, nunca una coincidencia por patrón. El contenedor se elimina solo por el comentario, y la envlist y la imagen, que no llevan comentario, por `list="-env"` y el nombre exacto del fichero de imagen, solo mientras exista la entrada de marca con la etiqueta exacta. Así que `uninstall` no puede alcanzar una instalación hecha a mano, ni nada cuyo comentario o nombre comparta una subcadena con el nombre del contenedor. Todo `find` pone entre comillas los atributos de dirección y de puerto. Sin comillas, RouterOS los interpreta como valores tipados y la comparación con el valor guardado sale vacía; verificado para los dos en RB5009UG+S+, RouterOS 7.24.2, 2026-09-11. ## Una escritura que imprime es un fallo Una escritura de RouterOS no imprime nada cuando tiene éxito, y por ssh informa de los errores como texto con estado de salida 0, abandonando el resto de una línea unida con `;`. Así que `install` trata cualquier salida de una escritura como un fallo (`create : router said "…"`) y se detiene ahí. El tar de la imagen se sube con scp antes de que se ejecute el paso del contenedor, y antes de que exista la marca. Si ese paso falla después, `install` borra el fichero subido (`undo removed the uploaded …`); de lo contrario contaría como fichero ajeno en cada intento posterior. `uninstall` lee la salida de la misma manera en sentido contrario: una eliminación que imprimió algo se informa como `skip`, no como `gone`. ## Valores que no mete en una orden Toda opción que llega a una orden de RouterOS se interpola en ella tal cual. No hay privilegio que escalar — la orden se ejecuta como tu administrador — pero unas comillas o un punto y coma convertirían un error claro en un confuso error de sintaxis de RouterOS, o un selector en algo más amplio de lo pretendido. Por eso cada valor se comprueba antes de la primera conexión, y uno fuera de estos límites detiene la orden con la regla que ha incumplido: | Opción | Se acepta | | --------------------------------------- | ----------------------------------------------------------------------------------------------- | | `--name` | `^[A-Za-z0-9][A-Za-z0-9_.-]{0,31}$` | | `--veth`, `--iface-list`, `--addr-list` | `^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$` | | `--disk` | `^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$`, o vacío para la flash interna; `--ephemeral` fuerza `tmpfs` | | `--arch` | `^[a-z0-9]{1,16}$` | | `--token` | `^[A-Za-z0-9_.-]{0,128}$` | | `--subnet` | una /30 IPv4, escrita como su dirección de red | | `--port` | 1–65535 | | `--rate` | 1–100 Hz | | `--buffer` | 10–3600 s | | `--memory-max` | `^\d{1,6}[KMG]?$` | | `--mem-limit-mb` | 8–1024 | | `--floor-hz` | 0–1000 | | `--capture-mb` | 0–256 | | `--expose` | necesita `--lan-address` como dirección IPv4, y un token no vacío | | `--triggers` | la propia lista de condiciones del agente, interpretada por `agent.ParseTriggers` | | `--remote-image` | una referencia de registro: `owner/name:1.0.0`, con host o sin él, sin comillas, espacios ni punto y coma | No hay excepción. `--triggers` la comprueba la misma puerta que el resto: `Finish` se la pasa a `agent.ParseTriggers`, el propio intérprete del agente, que es la autoridad sobre lo que significa una condición. Una condición desconocida, un umbral mal formado, unas comillas o un punto y coma hacen fallar el verbo con estado de salida 2 antes de la primera conexión, y no se escribe nada. El agente vuelve a interpretar `TRIGGERS` al arrancar, porque la envlist se puede editar a mano en el router. Ante un valor que no puede interpretar sale con un estado distinto de cero y una línea `mikroscope-agent: bad configuration: …` en el log del router; con la política on-failure, RouterOS puede reintentarlo hasta cinco veces. No hay registrada ninguna ejecución con un `TRIGGERS` erróneo en el router. ## Lo que se niega a enviar al router Dos de las cuatro vías de instalación entregan al router algo que la CLI no ha construido, y cada una tiene su propia comprobación antes de que se escriba nada. - **`--agent-tar `**, el tar de imagen que publica la release, se lee e inspecciona primero en tu máquina. Tiene que ser un tar de tipo docker-save con exactamente una imagen de una sola capa cuyo entrypoint sea `/mikroscope-agent`, y su arquitectura tiene que coincidir con `--arch`; si no, el verbo se detiene, y ante una discrepancia nombra el recurso que hay que descargar (`--agent-tar … is a linux/arm64 image and --arch says arm: download the mikroscope-agent-arm.tar asset instead`). Esa comprobación dice que el tar es una imagen del agente de mikroscope de la arquitectura correcta. No dice que sea el tar que publicó la release: verifícalo contra `checksums.txt` de la release, y su firma cosign si la usas, antes de pasarlo. - **`--remote-image `** hace que el router se descargue la imagen él mismo, así que no se sube nada y ningún tar acaba en el dispositivo. La referencia se contrasta con un patrón de referencia de registro antes de llegar a la línea de órdenes, porque RouterOS la recibe dentro de una cadena entrecomillada en una línea unida por `;`. Después el router necesita alcanzar ese registro por su propia red, y toma el host del registro de `/container/config registry-url`, un ajuste global del dispositivo, compartido con todos los demás contenedores que haya en él y que viene puesto en `https://registry-1.docker.io`. **mikroscope nunca escribe ese ajuste.** `doctor` lo lee y, cuando la referencia nombra un host que no coincide con el ajuste, imprime la única orden que hay que ejecutar (`/container/config/set registry-url=https://ghcr.io`, para la copia de la imagen en GHCR) o dice que uses `--agent-tar` en su lugar. La referencia de Docker Hub `jmrplens/mikroscope-agent:1.0.0` no lleva host y deja el ajuste como lo tenga el router. Confiar en la imagen es confiar en ese registro: nada en la CLI verifica lo que el router se descarga. ## Un script .rsc generado es una credencial `plan --rsc` escribe la instalación como un script de RouterOS para un router al que solo llegas por WinBox o WebFig. Lleva las mismas órdenes que ejecuta `install`, en el mismo orden y con las mismas etiquetas — y, cuando `--token` o `MIKROSCOPE_TOKEN` está fijado, la línea de la envlist lleva el token en claro, porque el router lo necesita. El propio script lo dice en su cabecera. Trata el fichero como tratas el token: no lo subas a un repositorio, no lo pegues donde quede registrado y bórralo de los Files del router después del `/import`. Sin token no guarda ningún secreto, solo el plan. `plan` e `install --dry-run` enmascaran el token en lo que imprimen por el terminal (`value="(token)"`); `--rsc` no puede, porque el script tiene que ejecutarse. ## Cómo demuestra uninstall que ha terminado `uninstall` ejecuta cada eliminación de la más reciente a la más antigua, ignorando lo que ya no está. Después pregunta al router, en una sola conexión, cuántos de los objetos que creó cada paso siguen ahí, imprime una línea por paso con la cuenta y falla nombrando cada paso cuya cuenta no sea cero (`uninstall left objects behind: …`). Una eliminación que no imprimió nada no es una prueba; la cuenta sí. `status` hace esa misma cuenta de forma independiente. El paso del contenedor es el lento, y el orden dentro de él es lo que mantiene honesta la cuenta: - el contenedor se detiene (con guarda, porque detener un contenedor detenido es un error) y se elimina; - `/container/remove` vuelve antes de que el contenedor haya desaparecido, y un `/file/remove` de la imagen lanzado entretanto no hizo nada, en silencio (RB5009UG+S+, RouterOS 7.24.2, 2026-09-11) — así que espera hasta 20 s a que el contenedor desaparezca, y luego reintenta la eliminación del fichero durante hasta 15 s; - se va el resto de la envlist, y la marca se va la última, solo cuando el fichero ya no está. Si la eliminación del fichero no surte efecto, la marca se queda, la cuenta sigue incluyendo la envlist y el fichero, y `uninstall` lo dice en vez de informar de que está limpio. Un recorrido doctor → install → status → upgrade → uninstall (`make roundtrip`) dejó el `/export` del router idéntico byte a byte, comparado por hash (verificado en RB5009UG+S+, RouterOS 7.24.2, 2026-09-12). > **Cierto en este equipo, no en el tuyo** > > El `/export` idéntico byte a byte tras ese único recorrido, y cada comportamiento de RouterOS > citado en esta página — el `/file/remove` silencioso, los selectores `find` entre comillas, los > errores impresos con estado de salida 0 — se observaron en un RB5009 con RouterOS 7.24.2. La > lógica de rechazo en sí está cubierta por pruebas contra un router simulado, no por una ejecución > en otra placa u otra versión de RouterOS. ## Véase también - [Instalar el agente](/mikroscope/es/install/): las órdenes, y lo que comprueba `doctor` antes de todo esto. - [Dónde va cada cosa](/mikroscope/es/install/layout/): cada objeto que crea `install`, y la opción que lo mueve. - [Lo que abre --expose](/mikroscope/es/security/expose/): las dos reglas de cortafuegos opcionales y sus selectores. - [Órdenes y opciones](/mikroscope/es/reference/cli/): cada opción con su valor por defecto y su variable. --- # Órdenes y opciones Cada verbo de la CLI de mikroscope y cada opción con su valor por defecto, su rango y la variable MIKROSCOPE_* que la fija, leídos de cmd/mikroscope. Source: https://jmrplens.github.io/mikroscope/es/reference/cli/ Esta página responde a una pregunta por cada opción de `mikroscope`: qué hace, qué valor toma por defecto, qué rango acepta y si una variable de entorno puede fijarla. Está leída de `cmd/mikroscope/*.go` y de `internal/router/options.go`, no del texto de ayuda, y donde los dos difieren la página lo dice. ## Uso y estado de salida ```sh mikroscope [flags] ``` Los verbos se reparten en cinco grupos, cada uno con su propio juego de opciones: los verbos de despliegue (`doctor`, `plan`, `install`, `upgrade`, `uninstall`, `status`, `image`), los de grabación (`record`, `mark`, `plot`), el colector (`forward`), `dashboards` y `version`. Las opciones son opciones del paquete `flag` de Go: `-rate 50` y `--rate 50` son lo mismo, y un booleano se desactiva con `-privileged=false`. | Estado | Cuándo | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `0` | el verbo terminó | | `1` | el verbo se ejecutó y falló (un requisito que falta, un error del router, un destino que no se pudo construir, un panel sin datos), el verbo es desconocido, o una opción de `record`, `mark`, `plot`, `forward` o `dashboards` no se pudo analizar o validar | | `2` | no se dio ningún verbo, o una opción de un verbo de despliegue no se pudo analizar o validar; no se envió nada al router | Todas las opciones se comprueban antes de la primera conexión, en `Finish` de `internal/router/options.go`, que se ejecuta igual para los verbos de despliegue que para `record`, `mark`, `plot` y `forward`. `--triggers` pasa por el `ParseTriggers` del propio agente, así que una condición desconocida, un umbral mal escrito, una comilla o un punto y coma hacen fallar el verbo con estado 2 y no se envía nada. El agente vuelve a analizar `TRIGGERS` al arrancar, que es lo que detecta una envlist editada a mano en el router: un valor malo ahí aparece como un contenedor que sale con estado `2` tras una línea `mikroscope-agent: bad configuration:` en el log del router, y que `restart-policy=on-failure` vuelve a arrancar. ### De dónde sale un valor por defecto Una opción que nombra una variable en las tablas de abajo lee su valor por defecto de `MIKROSCOPE_`; una variable con la cadena vacía cuenta como no definida, y una opción en la línea de órdenes gana siempre. **Solo tienen variable las opciones que la nombran.** `--rate`, `--buffer`, `--port`, `--memory-max`, `--mem-limit-mb`, `--capture-mb`, `--triggers`, `--floor-hz`, `--privileged`, `--ephemeral` y `--expose` son solo opciones. La CLI no lee `.env` por sí misma; expórtalo antes con `set -a; . ./.env; set +a`. [Variables de entorno](/mikroscope/es/reference/environment/) enumera todas las variables, incluidas las credenciales que no tienen ninguna opción. ## Los verbos de despliegue | Verbo | Qué hace | Escribe en el router | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | | `doctor` | Comprobación previa de solo lectura en una conexión ssh; cada comprobación que falla nombra su arreglo. Sale con 1 si falta algún requisito. | no | | `plan` | Imprime cada objeto que crearía `install` y se detiene. Igual que `install --dry-run`; con `--rsc` escribe en su lugar un script de RouterOS. | no | | `install` | Consigue la imagen, imprime el listado, ejecuta `doctor`, pide confirmación, escribe y después sondea el agente desde este equipo. | sí | | `upgrade` | Consigue una imagen nueva, comprueba que están todos los pasos de la instalación, pregunta, borra y vuelve a crear el paso del contenedor, y sondea. | sí | | `uninstall` | Borra cada paso del más reciente al más antiguo, verifica después por recuento de propiedad y falla nombrando lo que quede. | sí | | `status` | Imprime el recuento de propiedad de cada paso; si hay algo instalado, sondea el agente e imprime su salud y su placa. | no | | `image` | Construye el tar de la imagen del agente y lo escribe en `--out`, para cargarlo a mano. | no | `doctor` ejecuta estas comprobaciones, en este orden: Las comprobaciones que ejecuta doctor: | Comprobación, tal como se imprime | Pasa cuando | La solución que nombra | | --- | --- | --- | | registry-url is https:// | con `--remote-image`, `/container/config registry-url` nombra el host de registro de la referencia. Sin `--remote-image` doctor no lo pregunta: el ajuste es global del equipo y mikroscope nunca lo escribe | `/container/config/set registry-url=https://` en el router, que afecta a todos sus contenedores, o instalar desde un tar con `--agent-tar` | | container package installed and enabled | existe un paquete `container` con `disabled=no` | descargar, subir, reiniciar; después `/system/package/enable container` | | device-mode container=yes | `/system/device-mode` informa `container=yes` | `/system/device-mode/update container=yes` y, en menos de 5 minutos, el botón reset o mode, o un ciclo de alimentación | | architecture matches --arch | el `architecture-name` del router es el que corresponde a `--arch` (`arm64`, `arm`, `x86_64`) | volver a ejecutar con el `--arch` que nombra | | free memory ≥ <--memory-max> | `free-memory` es al menos lo que pide `--memory-max`, 64 MiB por defecto | liberar memoria en el router, o pedir menos con `--memory-max` | | free flash ≥ (image tar + extracted root) | sin `--disk`: `free-hdd-space` es al menos el doble de la imagen más 4 MiB | liberar flash, o instalar con `--disk tmpfs` o `--ephemeral` donde exista un disco tmpfs | | disk exists | con `--disk` o `--ephemeral`: existe un disco con ese slot; su espacio libre no se comprueba | `/disk/add type=tmpfs tmpfs-max-size=64M slot=tmpfs` para un disco en RAM, o nombrar un disco existente con `--disk` | | interface list exists (raw rule trap) | existe la lista `--iface-list` (por defecto `LAN`) | `/interface/list/add name=…`, o pasar la lista que usa tu regla de descarte `in-interface-list=!…` | | address list has entries (raw rule trap) | la lista `--addr-list` (por defecto `LANs`) tiene al menos una entrada | pasar la lista que usa tu regla `drop local if not from default IP range`; una lista vacía vale solo si no hay tal regla | | veth name is free or ours | siempre se informa `ok`, con el recuento encontrado | ninguna: una colisión la detecta el propio `install` | > **Tres maneras de conseguir la imagen del agente, a una opción de distancia** > > `plan`, `install`, `upgrade` e `image` necesitan una imagen del agente, y `buildImage` en > `cmd/mikroscope/main.go` la consigue por la vía que pidan las opciones. Sin ninguna de las dos > ejecutan `go build -trimpath ./cmd/mikroscope-agent` con `CGO_ENABLED=0`, `GOOS=linux` y el > `--arch` como `GOARCH` (`internal/image/image.go`), que necesita una copia del repositorio y una > cadena de herramientas de Go en el `PATH`. Con `--agent-tar` leen el tar publicado, comprueban > que es una imagen del agente mikroscope para `--arch` y suben ese. Con `--remote-image` no se > compila ni se sube nada: el router se descarga la imagen él mismo, e `image` se niega, porque no > hay ningún tar que escribir. Sin cadena de herramientas de Go y sin ninguna de las dos opciones > la compilación falla nombrando ambas opciones y la versión de Go, antes de que se liste nada. La > cuarta vía de instalación no consigue ninguna imagen en esta máquina: `plan --rsc` escribe un > script que corre en el router, así que necesita `--remote-image` o un tar que ya esté en el > equipo. [Cuatro formas de instalar](/mikroscope/es/install/routes/) pone las cuatro una al lado > de otra, y [Instalar el agente](/mikroscope/es/install/) recorre lo que hace `install`. ### Opciones de los verbos de despliegue | Opción | Por defecto | Variable | Acepta | Significado | | ---------------- | ------------------------------------------ | ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--router` | ninguno, obligatoria | `MIKROSCOPE_ROUTER` | `user@host` o un alias de la configuración de ssh | destino de ssh; todos los verbos salvo `plan`, `install --dry-run` e `image` fallan sin ella | | `--ssh-port` | vacío (configuración de ssh) | `MIKROSCOPE_SSH_PORT` | | puerto de ssh | | `--ssh-key` | vacío (agente o configuración de ssh) | `MIKROSCOPE_SSH_KEY` | | fichero de identidad de ssh | | `--name` | `mikroscope` | `MIKROSCOPE_NAME` | `^[A-Za-z0-9][A-Za-z0-9_.-]{0,31}$` | nombre del contenedor; etiqueta cada objeto como `mikroscope: (managed by mikroscope)` | | `--veth` | `veth-mikroscope` | `MIKROSCOPE_VETH` | `^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$` | nombre de la interfaz veth en el router | | `--subnet` | `172.30.10.0/30` | `MIKROSCOPE_SUBNET` | una /30 IPv4 en su dirección de red | el router toma `.1`, el agente `.2` | | `--iface-list` | `LAN` | `MIKROSCOPE_IFACE_LIST` | el mismo patrón que `--veth` | lista de interfaces a la que se une la veth | | `--addr-list` | `LANs` | `MIKROSCOPE_ADDR_LIST` | el mismo patrón que `--veth` | lista de direcciones a la que se une la /30 | | `--disk` | vacío (flash interna) | `MIKROSCOPE_DISK` | `^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$` | disco de RouterOS para el tar de la imagen y la raíz: `tmpfs`, `disk1`, `usb1` … | | `--ephemeral` | `false` | ninguna | | fuerza `--disk tmpfs` y `start-on-boot=no`: nada se escribe en la flash, nada sobrevive a un reinicio | | `--arch` | `arm64` | `MIKROSCOPE_ARCH` | `^[a-z0-9]{1,16}$`; `doctor` conoce `arm64`, `arm`, `amd64` | arquitectura del equipo, usada como `GOARCH` y en el manifiesto de la imagen | | `--goarm` | `5` | ninguna | | nivel de `GOARM`, usado solo con `--arch arm`: 5 arranca en todos los ARM de 32 bits que vende MikroTik, 7 no arranca en las placas EN7562CT (hEX Refresh) | | `--agent-tar` | vacío (compilar el agente aquí) | `MIKROSCOPE_AGENT_TAR` | una ruta a un tar de imagen del agente | `plan`, `install`, `upgrade`, `image`: sube este tar en vez de compilar uno, así no hace falta ni cadena de herramientas de Go ni una copia del repositorio. El tar se comprueba antes: uno que no sea una imagen del agente mikroscope, o que esté compilado para una arquitectura distinta de `--arch`, hace fallar el verbo nombrando el recurso que hay que descargar | | `--remote-image` | vacío (subir un tar) | `MIKROSCOPE_REMOTE_IMAGE` | una referencia de registro, `owner/name:tag`, o con host, `host/owner/name:tag` | `plan`, `install`, `upgrade`: el router se descarga la imagen él mismo, así que no se compila ni se sube nada, y no queda ningún tar en el equipo. RouterOS toma el host del registro del ajuste global `/container/config registry-url`, que viene puesto en `https://registry-1.docker.io` y que mikroscope nunca escribe; `doctor` compara ese ajuste con las referencias que nombran un host propio, como la de GHCR, y nombra la orden que hay que ejecutar | | `--rsc` | `false` | ninguna | | `plan`: escribe un script de RouterOS que instala desde el propio router, en vez del listado | | `--port` | `9123` | ninguna | 1–65535 | puerto HTTP del agente en la veth | | `--rate` | `10` | ninguna | 1–100 | cadencia del muestreador en Hz (envlist `RATE_HZ`); 10, 50 y 100 Hz medidos sin pérdidas en la RB5009 ([techo de muestreo](/mikroscope/es/cost/rate-ceiling/)) | | `--buffer` | `300` | ninguna | 10–3600 | búfer circular en segundos (envlist `BUFFER_S`) | | `--memory-max` | `64M` | ninguna | `^\d{1,6}[KMG]?$` | `memory-max` del cgroup del contenedor, en sintaxis de RouterOS | | `--mem-limit-mb` | `40` | ninguna | 8–1024 | límite blando de memoria de Go del agente en MiB (envlist `MEM_LIMIT_MB`); tiene que caberle el anillo, cadencia × búfer × unos 2,4 kB, con sitio para el recolector de basura | | `--capture-mb` | `4` | ninguna | 0–256 | presupuesto de captura por disparo en MiB (envlist `CAPTURE_MB`); `0` desactiva las capturas | | `--triggers` | vacío (el conjunto por defecto del agente) | ninguna | véase [captura por disparo](/mikroscope/es/record/triggers/) | condiciones de disparo, separadas por comas (envlist `TRIGGERS`) | | `--floor-hz` | `0` | ninguna | 0–1000 | una cadencia única para todas las fuentes de nivel, en Hz (envlist `FLOOR_HZ`); `0` mantiene los suelos por fuente; igual a `--rate` lee y emite cada fuente en cada tick | | `--privileged` | `true` | ninguna | | ejecuta el contenedor con `privileged=yes`; `-privileged=false` lo desactiva | | `--token` | vacío | `MIKROSCOPE_TOKEN` | `^[A-Za-z0-9_.-]{0,128}$` | token bearer que exige el agente (envlist `TOKEN`); obligatorio con `--expose` | | `--expose` | `false` | ninguna | necesita `--lan-address` y `--token` | hace dst-nat del puerto del agente en la dirección LAN del router; añade dos reglas de cortafuegos etiquetadas | | `--lan-address` | vacío | `MIKROSCOPE_LAN_ADDRESS` | una dirección IPv4 | la dirección LAN del router para `--expose` | | `--dry-run` | `false` | ninguna | | `install`: imprime el listado y no escribe nada | | `--yes` | `false` | ninguna | | `install`, `upgrade`: no pregunta antes de escribir | | `--no-doctor` | `false` | ninguna | | `install`: se salta las comprobaciones previas | | `--out` | `mikroscope-agent-.tar` | ninguna | | `image`: ruta de salida del tar. `plan --rsc`: dónde se escribe el script; vacío lo escribe en la salida estándar | Tres ajustes del contenedor no son opciones: `logging=yes`, `restart-policy=on-failure` con `restart-max-count=5` y `restart-interval=10s`, e `ignore-remote-image-change=yes`. Los valores de reinicio son los que fija `Defaults()`; los tres son lo que escribe `install` (`internal/router/steps.go`). `start-on-boot` tampoco tiene opción propia: es `no` con `--ephemeral` y `yes` sin ella. ### Pasa la misma forma a status, upgrade y uninstall `status`, `upgrade` y `uninstall` no leen lo que hay en el router para saber cómo se instaló. Cada uno reconstruye el plan de instalación a partir de las opciones de su propia invocación y selecciona los objetos por los nombres, las rutas y la etiqueta de ese plan. Así que: - Una instalación hecha con `--expose` solo la borra, y solo la verifica, un `uninstall` al que también se le da `--expose --lan-address … --token …`. Sin ellas las dos reglas de cortafuegos no están en el plan, y la verificación no las busca. - `upgrade` borra la envlist junto con el contenedor y la vuelve a escribir a partir de sus propias opciones. Un `upgrade` sin el `--rate`, `--buffer`, `--mem-limit-mb`, `--capture-mb`, `--triggers`, `--floor-hz` o `--token` con los que instalaste escribe en su lugar los valores por defecto, y `--memory-max` y `--privileged` vuelven igualmente a los suyos. - `--name`, `--veth`, `--subnet`, `--iface-list`, `--addr-list`, `--port` y `--disk` o `--ephemeral` deciden qué casan los selectores; cambia uno y el verbo busca otros objetos. > **Sin probar** > > Las tres consecuencias de arriba están leídas de `internal/router/deploy.go`, `probe.go` y > `steps.go`. Ninguna se ha ejercitado contra el router: en particular, no se ha ejecutado un > `upgrade` que pierda el token de una instalación expuesta. ### Lo que escriben los verbos que escriben **Lo que `install` escribe en tu router** - una veth - una dirección - una pertenencia a lista de interfaces - una entrada de address-list - una envlist - el tar de la imagen, salvo que `--remote-image` haga que el router se la baje - el contenedor Cada objeto lleva el comentario `mikroscope: (managed by mikroscope)` `mikroscope plan` imprime cada orden antes de escribir nada. `uninstall` elimina por etiqueta exacta más identidad, nunca por patrón, y falla nombrando el paso si queda algo. **Lo que añade `install --expose`** - dos reglas de cortafuegos, etiquetadas - el token pasa a ser obligatorio - `uninstall` y `status` solo ven las dos reglas si se les vuelve a dar `--expose` Cada objeto lleva el comentario `mikroscope: (managed by mikroscope)` `mikroscope plan` imprime cada orden antes de escribir nada. **Lo que sustituye `upgrade`** - una imagen nueva y el contenedor - la envlist, reescrita con las opciones que recibe `upgrade` - los objetos de red se quedan Cada objeto lleva el comentario `mikroscope: (managed by mikroscope)` `mikroscope plan` imprime cada orden antes de escribir nada. **Lo que elimina `uninstall`** - una veth - una dirección - una pertenencia a lista de interfaces - una entrada de address-list - una envlist - el tar de la imagen, salvo que `--remote-image` haga que el router se la baje - el contenedor Cada objeto lleva el comentario `mikroscope: (managed by mikroscope)` `mikroscope plan` imprime cada orden antes de escribir nada. `uninstall` elimina por etiqueta exacta más identidad, nunca por patrón, y falla nombrando el paso si queda algo. ## record, mark y plot Los tres verbos comparten un juego de opciones con `forward`, así que una opción que significa algo para un hermano se acepta y se ignora: `plot --for 5m` se analiza y no hace nada. La tabla marca qué verbo lee cada opción. | Opción | Por defecto | Variable | La lee | Significado | | --------------- | ---------------------------- | --------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `--out` | `capture-` | ninguna | `record`, `mark` | prefijo de salida: `.jsonl`, `.csv`, `.markers.csv`, `.meta.json`. `mark` necesita el prefijo de una grabación existente | | `--for` | `0` (hasta Ctrl-C) | ninguna | `record`, `forward` | funciona este tiempo y se detiene | | `--from-start` | `false` | ninguna | `record` | rellena con todo lo que guarda el anillo del agente antes de pasar a directo | | `--poll` | `500ms` | ninguna | `record`, `forward` | cada cuánto se tira del anillo del agente | | `--batch` | `0` | ninguna | `record`, `forward` | muestras por petición; `0` es el doble de lo que produce un `--poll` a la cadencia del agente, nunca menos de 20. El relay limita una petición a 18. Una petición se repite hasta que vuelve corta | | `--transport` | `auto` | ninguna | `record`, `forward` | `auto` (directo, luego relay), `direct` (HTTP a la veth) o `relay` (`/tool fetch` sobre la API de RouterOS; cada llamada devuelve como mucho 64 512 B y tardó unos 3 ms o 1 s en el RB5009, RouterOS 7.24.2, 2026-09-11; consulta [llegar al agente](/mikroscope/es/install/reaching-the-agent/)) | | `--log-markers` | `false` | ninguna | `record`, `mark` | `record`: tras grabar, trae el log del router por la API y añade las líneas que casan a `.markers.csv`. `mark`: añade las líneas del log de la ventana de la grabación | | `--topics` | `system,interface,container` | ninguna | `record`, `mark` | temas del log que se conservan como marcadores con `--log-markers` | | `--router-tz` | `Local` | ninguna | `record`, `mark` | zona IANA que muestra el reloj del router; las horas del log de RouterOS no llevan zona | | `--in` | ninguno, obligatoria | ninguna | `plot` | prefijo de la grabación, o la ruta de su `.jsonl` | | `--svg` | `.svg` | ninguna | `plot` | SVG de salida | | `--title` | el prefijo | ninguna | `plot` | título del gráfico | | `--api` | vacío | `MIKROSCOPE_API_ADDR` | `record`, `mark`, `forward` | `host:port` de la API de RouterOS, para el relay, `--log-markers` y la capa de la API | | `--api-user` | vacío | `MIKROSCOPE_API_USER` | `record`, `mark`, `forward` | usuario de la API; su contraseña sale solo de `MIKROSCOPE_API_PASSWORD` | | `--token` | vacío | `MIKROSCOPE_TOKEN` | `record`, `forward` | token bearer que el transporte directo envía al agente | | `--port` | `9123` | ninguna | `record`, `forward` | puerto HTTP del agente | | `--subnet` | `172.30.10.0/30` | `MIKROSCOPE_SUBNET` | `record`, `forward` | la /30 del agente; su dirección es `.2` | `mark` toma el texto del marcador de los argumentos restantes, `mikroscope mark --out cap "queue tree applied"`, o `--log-markers` en lugar de texto. `record` convierte en marcador cada línea que se escribe en un terminal; cuando la entrada estándar no es un terminal, no la lee. `auto` prueba primero `/healthz` por el transporte directo. Si no responde, necesita `--api`, `--api-user` y `MIKROSCOPE_API_PASSWORD` para probar el relay, y falla nombrando `install --expose` cuando el transporte directo no responde y el relay no está configurado; un relay configurado que falla informa `relay transport:` y su error. El relay no lleva el token: `/tool fetch` en el router no envía cabecera `Authorization`, así que de un agente con token solo se puede tirar por el transporte directo. ## forward `forward` ejecuta el colector: tira de la capa del kernel desde el agente, consulta la capa de la API de RouterOS y escribe ambas en cada destino nombrado. Lee `--for`, `--poll`, `--batch`, `--transport`, `--api`, `--api-user`, `--token`, `--port` y `--subnet` de la tabla de arriba, además de sus propias opciones de abajo. Se niega a arrancar sin al menos un destino. ### Capa de la API | Opción | Por defecto | Variable | Significado | | ------------------- | ----------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `--api-mode` | `full` | ninguna | preajuste. `off` pone `--api-every 0` salvo que la fijes tú, así que el colector no abre ninguna sesión de la capa de la API; el transporte de relay sigue usando la API. `slow` ejecuta la capa cada 10 s sin `/system/health` y sin recuento de conntrack; se siguen leyendo `/system/resource`, `/system/resource/cpu`, `monitor-traffic` sobre `--interfaces` y los contadores de puerto de `--counters-every`. `full` (los valores por defecto de las opciones) lo lee todo. Una opción explícita de las de abajo gana sobre él | | `--api-every` | `1s` | ninguna | cadencia de la capa de la API; `0` desactiva la capa. `off` la pone a `0`, `slow` a `10s` | | `--interfaces` | vacío | `MIKROSCOPE_INTERFACES` | interfaces separadas por comas para `monitor-traffic`, una sola llamada para todas | | `--conntrack-every` | `0` | ninguna | pregunta el recuento de conntrack con esta frecuencia; `0` nunca, porque es un recorrido de tabla. Un recorrido tardó 1,3 ms con 6 212 entradas en el RB5009 (fecha no registrada). `slow` la pone a `0` salvo que se dé explícitamente | | `--counters-every` | `10s` | ninguna | lee los contadores acumulados de cada puerto (errores por tipo, reparto de fast-path, caídas de enlace, tamaños de trama) con esta frecuencia; `0` nunca | | `--labels-every` | `5m` | ninguna | vuelve a leer qué es cada interfaz (etiqueta a partir de su comentario, tipo, listas de interfaces, bridge, MTU); se lee una vez antes del primer tirón del kernel y luego con esta frecuencia; `0` toma el valor por defecto de 5 min | | `--no-health` | `false` | ninguna | se salta `/system/health`. `slow` la activa | Sin `--api` ni `--api-user`, `forward` registra `api tier disabled` y ejecuta solo la capa del kernel; es un aviso, no un fallo. ### Destinos | Opción | Por defecto | Variable | Significado | | ------------------- | --------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `--file` | vacío | ninguna | escribe la línea de tiempo combinada como JSONL en esta ruta (truncada al arrancar) | | `--prom` | vacío | ninguna | sirve el `/metrics` de Prometheus en esta dirección, por ejemplo `:9124` | | `--influx` | vacío | `MIKROSCOPE_INFLUX_URL` | URL de escritura de InfluxDB 3, por ejemplo `http://host:8181/api/v3/write_lp?db=mikroscope&precision=nanosecond` | | `--loki` | vacío | `MIKROSCOPE_LOKI_URL` | URL de push de Loki; lleva eventos (log del kernel, huecos, errores de la API, detecciones, disparadores, el registro del equipo), no muestras | | `--loki-tenant` | vacío | `MIKROSCOPE_LOKI_TENANT` | `X-Scope-OrgID` para un Loki multiinquilino | | `--otlp` | vacío | `MIKROSCOPE_OTLP_URL` | endpoint de métricas OTLP/HTTP, por ejemplo `http://host:4318/v1/metrics` | | `--graphite` | vacío | `MIKROSCOPE_GRAPHITE_ADDR` | receptor de texto plano de carbon de Graphite, `host:port` | | `--graphite-prefix` | `mikroscope` | ninguna | primer nodo de cada ruta de métrica de Graphite | | `--elastic` | vacío | `MIKROSCOPE_ELASTIC_URL` | URL base de Elasticsearch u OpenSearch para `_bulk` | | `--elastic-index` | `mikroscope-%Y.%m.%d` | ninguna | nombre del índice; `%Y`, `%m`, `%d` se expanden a la fecha del evento | | `--sql` | vacío | ninguna | escribe sentencias de PostgreSQL/TimescaleDB en esta ruta, o en la salida estándar con `-`; no hay driver de base de datos | | `--sql-hypertable` | `false` | ninguna | emite también llamadas `create_hypertable` de TimescaleDB en la cabecera SQL | | `--telegraf` | vacío | `MIKROSCOPE_TELEGRAF_URL` | receptor de Telegraf: `http://host:8186/telegraf`, `tcp://host:8094` o `udp://host:8094` | | `--stdout` | vacío | ninguna | escribe en la salida estándar como `lp` (line protocol de InfluxDB) o `json` (NDJSON); cualquier otro valor se rechaza | | `--host-tag` | `router` | `MIKROSCOPE_HOST_TAG` | etiqueta o label de host en cada punto, en todos los destinos | | `--queue-seconds` | `60` | ninguna | segundos de datos que puede retener cada destino con cola antes de descartar el lote más antiguo; `0` o menos toma 60 | Ninguna credencial de destino es una opción, porque una opción se ve en `ps` y en el historial de la shell: los tokens salen de `MIKROSCOPE_INFLUX_TOKEN`, `MIKROSCOPE_LOKI_TOKEN`, `MIKROSCOPE_OTLP_TOKEN`, `MIKROSCOPE_ELASTIC_AUTH` y `MIKROSCOPE_TELEGRAF_TOKEN`. Un destino pedido que no se puede construir detiene `forward` antes de que tire de nada. Al salir, `forward` imprime el número de muestras del kernel, muestras de la API, huecos y saltos de desfase, y para cada destino cuántos eventos escribió, descartó y en cuántos falló. Mientras funciona, registra en la salida de error una línea por minuto con los recuentos de kernel, API, huecos, disparos y detecciones, el último número de secuencia, y los escritos, descartados y errores de cada destino. El destino Prometheus del colector dimensiona su histograma de ticks ocupados para 10 Hz sea cual sea la cadencia del agente (`promHistogramRateHz` en `cmd/mikroscope/sinkflags.go`), así que la disposición de los buckets no cambia cuando se reconecta a un agente configurado de otra forma. [Familias de métricas de Prometheus](/mikroscope/es/reference/metrics/) cuenta qué más se sigue de esa constante. ## dashboards ```sh mikroscope dashboards gen mikroscope dashboards import --store influxdb --datasource-uid mikroscope dashboards check --store influxdb --datasource-uid --window 1h --end 2026-09-13T08:30:00Z ``` | Opción | Por defecto | Variable | La lee | Significado | | ------------------ | ------------------ | ------------- | ----------------- | ------------------------------------------------------------------------------------------------------------ | | `--out` | `dashboards` | ninguna | `gen` | directorio de salida para `mikroscope-.json` y `mikroscope-alerts-.yaml`, de los dos almacenes | | `--store` | `influxdb` | ninguna | `import`, `check` | `influxdb` o `prometheus` | | `--grafana` | vacío | `GRAFANA_URL` | `import`, `check` | URL base de Grafana; el token sale solo de `GRAFANA_TOKEN` | | `--datasource-uid` | vacío, obligatoria | ninguna | `import`, `check` | el UID de la fuente de datos asociada a `DS_MIKROSCOPE` | | `--no-probe` | `false` | ninguna | `import`, `check` | no pregunta a la fuente de datos qué medidas contiene; usa los valores compilados por defecto | | `--window` | `15m` | ninguna | `check` | longitud de la ventana de consulta | | `--end` | ahora | ninguna | `check` | instante RFC 3339 en el que termina la ventana, para comprobar contra una captura que ya ha terminado | `import` y `check` se niegan a ejecutarse sin `--grafana` (o `GRAFANA_URL`), `GRAFANA_TOKEN` y `--datasource-uid`. Salvo que se dé `--no-probe`, primero preguntan a la fuente de datos qué medidas contiene; si esa pregunta falla, avisan y siguen con los valores compilados por defecto. `check` imprime una línea por panel (`ok`, `none` para un panel que se sabe vacío, `FAIL`) y sale con 1 si falla algún panel. Las variables son `GRAFANA_URL` y `GRAFANA_TOKEN`, sin el prefijo `MIKROSCOPE_`. ## version `mikroscope version` imprime `mikroscope` y la identidad de compilación de `internal/version`. No admite opciones. ## Véase también - [Variables de entorno](/mikroscope/es/reference/environment/): cada variable `MIKROSCOPE_*`, las credenciales sin opción y la envlist propia del agente. - [Instalar el agente](/mikroscope/es/install/): los verbos de despliegue en el orden en que se usan. - [Grabar, marcar, dibujar](/mikroscope/es/record/): qué es una grabación y cómo le llegan los marcadores. - [El colector](/mikroscope/es/sinks/): qué hace `forward` con las opciones de arriba. --- # Variables de entorno Las variables MIKROSCOPE_* que lee la CLI, las credenciales que solo existen como variables y las variables de la envlist que lee el agente en el router. Source: https://jmrplens.github.io/mikroscope/es/reference/environment/ Dos programas leen variables de entorno, y leen variables distintas. La **CLI** en tu máquina lee variables `MIKROSCOPE_*` como valores por defecto de las opciones, además de un puñado de credenciales que no tienen opción, además de `GRAFANA_URL` y `GRAFANA_TOKEN`. El **agente** en el router lee variables sin prefijo (`RATE_HZ`, `TOKEN` …) de la envlist de su contenedor, que escribe `install`. Esta página enumera ambas, a partir de `cmd/mikroscope/*.go` y `internal/agent/config.go`. ## Cómo las lee la CLI - Una variable fija el **valor por defecto** de una opción; la opción en la línea de órdenes gana. - Una variable con la cadena vacía se trata como no definida. - La CLI nunca lee un fichero `.env`. Expórtalo antes a la shell: ```sh set -a; . ./.env; set +a ``` - Entrecomilla una URL que contenga `&` cuando vive en un fichero que cargas con `source`: sin comillas, `&` es un operador de la shell. `.env.example` entrecomilla `MIKROSCOPE_INFLUX_URL` por eso. La mayoría de las opciones de despliegue **no** tienen variable. `--rate`, `--buffer`, `--port`, `--memory-max`, `--mem-limit-mb`, `--capture-mb`, `--triggers`, `--floor-hz`, `--privileged`, `--ephemeral` y `--expose` se fijan en la línea de órdenes o no se fijan. [Órdenes y opciones](/mikroscope/es/reference/cli/) tiene todas las opciones. ## Router y despliegue | Variable | Opción | La leen | Por defecto en el código | Valor en `.env.example` | | ------------------------- | ---------------- | ----------------------------------------------------- | ------------------------------- | ----------------------- | | `MIKROSCOPE_ROUTER` | `--router` | `doctor`, `install`, `upgrade`, `uninstall`, `status` | ninguno (obligatoria) | vacío | | `MIKROSCOPE_SSH_PORT` | `--ssh-port` | los mismos | configuración de ssh | `22` | | `MIKROSCOPE_SSH_KEY` | `--ssh-key` | los mismos | agente o configuración de ssh | vacío | | `MIKROSCOPE_ARCH` | `--arch` | los verbos de despliegue | `arm64` | `arm64` | | `MIKROSCOPE_AGENT_TAR` | `--agent-tar` | `plan`, `install`, `upgrade`, `image` | vacío (compilar el agente aquí) | no está en el fichero | | `MIKROSCOPE_REMOTE_IMAGE` | `--remote-image` | `plan`, `install`, `upgrade` | vacío (subir un tar) | no está en el fichero | | `MIKROSCOPE_NAME` | `--name` | los verbos de despliegue | `mikroscope` | comentada | | `MIKROSCOPE_VETH` | `--veth` | los verbos de despliegue | `veth-mikroscope` | `veth-mikroscope` | | `MIKROSCOPE_SUBNET` | `--subnet` | los verbos de despliegue, `record`, `forward` | `172.30.10.0/30` | `172.30.10.0/30` | | `MIKROSCOPE_IFACE_LIST` | `--iface-list` | los verbos de despliegue | `LAN` | `LAN` | | `MIKROSCOPE_ADDR_LIST` | `--addr-list` | los verbos de despliegue | `LANs` | `LANs` | | `MIKROSCOPE_DISK` | `--disk` | los verbos de despliegue | vacío (flash interna) | comentada, `tmpfs` | | `MIKROSCOPE_LAN_ADDRESS` | `--lan-address` | los verbos de despliegue | vacío | comentada | | `MIKROSCOPE_TOKEN` | `--token` | los verbos de despliegue, `record`, `forward` | vacío | comentada | `MIKROSCOPE_TOKEN` es dos cosas a la vez. Para `install` es el token que se escribe en la envlist del agente, y que el agente pasa entonces a exigir. Para `record` y `forward` es el token que envía el transporte directo. La envlist no es un almacén de secretos: cualquier usuario de RouterOS con la política `read` puede listar la envlist de todos los contenedores por la API (verificado en RB5009UG+S+, RouterOS 7.24.2, 2026-09-11), y por eso el token es la única credencial que va ahí. ## Llegar al agente y a la API de RouterOS | Variable | Opción | La leen | Significado | | ------------------------- | -------------- | --------------------------- | ----------------------------------------------------------------------------------------------------- | | `MIKROSCOPE_API_ADDR` | `--api` | `record`, `mark`, `forward` | `host:port` de la API binaria de RouterOS; `.env.example` tiene `192.168.88.1:8728` | | `MIKROSCOPE_API_USER` | `--api-user` | `record`, `mark`, `forward` | el usuario dedicado de la API | | `MIKROSCOPE_API_PASSWORD` | ninguna | `record`, `mark`, `forward` | la contraseña de ese usuario; a propósito no hay opción | | `MIKROSCOPE_INTERFACES` | `--interfaces` | `forward` | interfaces separadas por comas para `monitor-traffic`; `.env.example` tiene `bridge,ether1` comentada | El relay, `--log-markers` y la capa de la API fallan, o en el caso de `forward` se desactivan con un aviso, salvo que estén definidas las tres: `--api`, `--api-user` y `MIKROSCOPE_API_PASSWORD`. [El usuario de la API](/mikroscope/es/security/api-user/) tiene la política que necesita ese usuario. ## Destinos | Variable | Opción | Por defecto en el código | Significado | | -------------------------- | --------------- | ------------------------ | --------------------------------------------------------------------------------------- | | `MIKROSCOPE_INFLUX_URL` | `--influx` | vacío | URL de escritura de InfluxDB 3 | | `MIKROSCOPE_LOKI_URL` | `--loki` | vacío | URL de push de Loki | | `MIKROSCOPE_LOKI_TENANT` | `--loki-tenant` | vacío | `X-Scope-OrgID` | | `MIKROSCOPE_OTLP_URL` | `--otlp` | vacío | endpoint de métricas OTLP/HTTP | | `MIKROSCOPE_GRAPHITE_ADDR` | `--graphite` | vacío | `host:port` de texto plano de carbon | | `MIKROSCOPE_ELASTIC_URL` | `--elastic` | vacío | URL base de Elasticsearch u OpenSearch | | `MIKROSCOPE_TELEGRAF_URL` | `--telegraf` | vacío | URL del receptor de Telegraf (`http://`, `tcp://` o `udp://`) | | `MIKROSCOPE_HOST_TAG` | `--host-tag` | `router` | etiqueta de host en cada punto de cada destino; `.env.example` tiene `rb5009` comentada | Basta con definir la variable de URL de un destino para activarlo: `forward` construye todos los destinos cuya opción acaba no vacía, venga de la línea de órdenes o de la variable. Todos estos nombres llevan el prefijo `MIKROSCOPE_`: un `LOKI_URL` a secas no lo lee nada, y `.env.example` lo dice junto a `MIKROSCOPE_LOKI_URL`. ## Credenciales que no tienen opción Una opción se ve en `ps` y en el historial de la shell, así que todos los secretos que usa la CLI se leen solo del entorno. | Variable | Se usa para | | --------------------------- | ------------------------------------------------------ | | `MIKROSCOPE_API_PASSWORD` | el usuario de la API de RouterOS | | `MIKROSCOPE_INFLUX_TOKEN` | InfluxDB, enviado como `Authorization: Bearer ` | | `MIKROSCOPE_LOKI_TOKEN` | Loki | | `MIKROSCOPE_OTLP_TOKEN` | el endpoint OTLP | | `MIKROSCOPE_ELASTIC_AUTH` | Elasticsearch u OpenSearch | | `MIKROSCOPE_TELEGRAF_TOKEN` | el receptor HTTP de Telegraf | | `GRAFANA_TOKEN` | `dashboards import` y `dashboards check` | ## Grafana `dashboards import` y `dashboards check` leen `GRAFANA_URL` (el valor por defecto de `--grafana`) y `GRAFANA_TOKEN`. Ninguna de las dos lleva el prefijo `MIKROSCOPE_`. `.env.example` define `GRAFANA_URL` dos veces, una vacía en su bloque de servicios de la LAN y otra comentada en el bloque de Grafana, y dice que el token nunca va en el fichero. ## La envlist del agente El agente lee estas variables del entorno de su contenedor al arrancar. Un valor no válido le hace imprimir una línea que empieza por `mikroscope-agent: bad configuration:`, que RouterOS guarda en su log bajo el tema `container`, y salir con estado 2. | Variable | Por defecto en el agente | Acepta | Significado | | ---------------------- | -------------------------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `RATE_HZ` | `10` | 1–100 | cadencia del muestreador | | `BUFFER_S` | `300` | 10–3600 | longitud del anillo en segundos | | `PORT` | `9123` | 1–65535 | puerto HTTP | | `ADDR` | vacío | una dirección | dirección de escucha. Vacía escucha en `:PORT`, todas las direcciones del espacio de nombres de red del contenedor; `install` siempre la fija a la dirección de la veth del agente | | `TOKEN` | vacío | | cuando está definida, todas las rutas salvo `/healthz` exigen `Authorization: Bearer ` | | `IRQ_TOP_K` | `8` | 0–64 | líneas de interrupción que se guardan por muestra, las más ocupadas primero; `0` no envía líneas de interrupción ni `irq_total` | | `MEM_LIMIT_MB` | `14` | 8–1024 | límite blando de memoria de Go en MiB | | `FLOOR_HZ` | `0` | 0–1000 | `0` mantiene los suelos por fuente; N pone todas las fuentes de nivel a N Hz y desactiva la emisión solo al cambiar | | `SOURCES` | vacío (todas las fuentes detectadas) | nombres separados por comas | restringe las fuentes opcionales a esta lista; `/proc/stat` se lee siempre | | `TRIGGERS` | `softnet-drop,oom,kmsg<=3,reset,irq-err,flash-bad` | condiciones, separadas por comas | condiciones de disparo de captura | | `CAPTURE_MB` | `4` | 0–256 | presupuesto de bytes retenidos para capturas; `0` las desactiva | | `CAPTURE_PRE_S` | `5` | 1–60 | segundos que se guardan antes de la muestra en la que salta un disparo | | `CAPTURE_POST_S` | `5` | 1–60 | segundos que se guardan después | | `CAPTURE_POLICY` | `first` | `first` o `last` | con el presupuesto lleno: `first` rechaza la captura nueva, `last` expulsa la más antigua | | `TRIGGER_REFRACTORY_S` | `10` | 0–3600 | tiempo de silencio por condición después de saltar | | `PROC_ROOT` | `/proc` | una ruta | de dónde se lee `/proc`; el log del kernel es `/dev/kmsg` solo cuando esto es `/proc`, y si no `dev/kmsg` junto al directorio dado | | `SYS_ROOT` | `/sys` | una ruta | de dónde se lee `/sys` | Los nombres que acepta `SOURCES` son las fuentes opcionales que informa `/capabilities`: `meminfo`, `loadavg`, `softnet`, `softirqs`, `interrupts`, `vmstat`, `psi`, `schedstat`, `self`, `buddyinfo`, `yaffs`, `diskstats`, `slabinfo`, `kmsg`, `thermal`, `cpufreq`, `mtd` y `perf`. Las condiciones de disparo y qué compara cada una están en [captura por disparo](/mikroscope/es/record/triggers/). Antes de escuchar, el agente compara el anillo con el propio `memory.max` del contenedor: `RATE_HZ × BUFFER_S × 2 560 B` más `CAPTURE_MB` tiene que caber, o se niega a arrancar y nombra las tres variables y `--memory-max`. Si esa cifra supera la mitad de `MEM_LIMIT_MB`, arranca y registra un aviso con el límite al que subir. 2 560 B no es una medida en sí: es la línea media medida en el RB5009 con todas las fuentes de esa fecha (2 439 B, 4 núcleos, `IRQ_TOP_K` 8, RouterOS 7.24.2, 2026-09-12) redondeada hacia arriba. Una placa con más núcleos o más líneas de interrupción escribe líneas más largas, así que la comprobación peca de permisiva. Cuando el agente no puede leer su `memory.max`, la comprobación que lo niega no se ejecuta. ### Lo que escribe install en ella `install` y `upgrade` escriben la envlist `-env` con estas entradas, en este orden, y nada más: Las entradas que install escribe en la envlist del agente: | Clave | Se escribe | Viene de | Contiene | | --- | --- | --- | --- | | `MIKROSCOPE_TAG` | siempre | `--name` | la marca de propiedad `mikroscope: (managed by mikroscope)`, que se escribe la primera y se borra la última; el agente la ignora | | `RATE_HZ` | siempre | `--rate`, por defecto `10`, 1–100 | la cadencia del muestreador, en Hz | | `BUFFER_S` | siempre | `--buffer`, por defecto `300`, 10–3600 | la longitud del anillo, en segundos | | `PORT` | siempre | `--port`, por defecto `9123`, 1–65535 | el puerto HTTP del agente | | `ADDR` | siempre | `--subnet` | la dirección del agente, la `.2` de la /30; el agente solo escucha ahí | | `MEM_LIMIT_MB` | siempre | `--mem-limit-mb`, por defecto `40`, 8–1024 | el límite blando de memoria de Go del agente, en MiB | | `FLOOR_HZ` | solo cuando es mayor que 0 | `--floor-hz`, por defecto `0`, 0–1000 | una sola cadencia para todas las fuentes de nivel, en Hz | | `CAPTURE_MB` | siempre | `--capture-mb`, por defecto `4`, 0–256 | el presupuesto de capturas por disparo, en MiB; `0` las desactiva | | `TRIGGERS` | solo cuando se da | `--triggers` | las condiciones de disparo; sin ella, el agente usa su conjunto por defecto | | `TOKEN` | solo cuando se da | `--token` | el token bearer que exige el agente, de `--token` o `MIKROSCOPE_TOKEN`, con o sin `--expose` | > **Los valores por defecto del agente y los de install no son los mismos** > > Un agente arrancado sin ninguna entrada en la envlist usa un límite blando de memoria de 14 MiB. > `install` siempre escribe `MEM_LIMIT_MB=40`, y el `memory-max` del contenedor es `64M`, porque un > anillo de 300 s a 10 Hz ocupa unos 7,3 MB y un límite de 14 MiB mantenía al recolector de basura > funcionando sin parar en el RB5009: 9,38 % de un núcleo frente a 1,39 % con margen, ambos con el anillo lleno, medidos con RouterOS 7.24.2 el 2026-09-12. `IRQ_TOP_K`, > `CAPTURE_PRE_S`, `CAPTURE_POST_S`, `CAPTURE_POLICY`, `TRIGGER_REFRACTORY_S`, `SOURCES`, `PROC_ROOT` > y `SYS_ROOT` no tienen opción, así que un agente instalado funciona con sus valores por defecto. ## En .env.example pero sin que el código las lea `.env.example` es también la plantilla de desarrollo para los equipos del propio proyecto, así que lleva nombres que ningún binario lee. Definirlos no cambia nada: - `MIKROSCOPE_ROUTEROS`, la versión de RouterOS del equipo de referencia. - `OPERATOR_HOST_IP`, el equipo del operador para las pruebas de destinos push y de sondeo. - El bloque `HEXS_*` (`HEXS_ROUTER`, `HEXS_SSH_PORT`, `HEXS_SSH_KEY`, `HEXS_ARCH`, `HEXS_API_ADDR`, `HEXS_API_USER`, `HEXS_API_PASSWORD`), guardado para un hEX S que todavía no ha llegado. ## Véase también - [Órdenes y opciones](/mikroscope/es/reference/cli/): todas las opciones, incluidas las que no fija ninguna variable. - [El usuario de la API](/mikroscope/es/security/api-user/): el usuario de RouterOS detrás de `MIKROSCOPE_API_USER`. - [Cada fuente a su propio suelo](/mikroscope/es/limits/source-floors/): lo que anula `FLOOR_HZ`. - [El coste del observador](/mikroscope/es/cost/): por qué el límite de memoria se dimensiona según el anillo. --- # Los endpoints HTTP del agente Cada ruta que sirve el agente en la veth, sus parámetros de consulta, lo que devuelve, cómo la protege el token y los formatos de línea de /snapshot y /stream. Source: https://jmrplens.github.io/mikroscope/es/reference/http/ El agente es un servidor HTTP y nada más: no abre ninguna conexión saliente. Esta página responde qué devuelve cada ruta, qué parámetros de consulta admite, con qué códigos de estado responde y qué aspecto tiene una línea de su NDJSON. Está leída de `internal/agent/http.go`, `capture.go`, `source.go` e `internal/sample/sample.go`. ## Dónde escucha El agente escucha en `ADDR:PORT`. `install` escribe la dirección de la propia veth del agente en `ADDR` y `9123` en `PORT`, así que por defecto es `http://172.30.10.2:9123`. Un agente arrancado sin `ADDR` escucha en `:PORT`, todas las direcciones dentro del espacio de nombres de red de su contenedor. Cómo llega un equipo a esa dirección, directamente, por el relay o mediante `--expose`, está en [llegar al agente](/mikroscope/es/install/reaching-the-agent/). El servidor le da a un cliente 5 s para enviar las cabeceras de su petición y 30 s para recibir una respuesta (`ReadHeaderTimeout` y `WriteTimeout` en `internal/agent/agent.go`). ## Autenticación Sin `TOKEN`, todas las rutas están abiertas. Con él, **todas las rutas salvo `/healthz`** necesitan: ```http Authorization: Bearer ``` El valor de la cabecera se compara con el token después de quitarle un prefijo `"Bearer "` opcional, así que también se acepta una cabecera que lleve el token a secas. Una petición sin ella, o con otro token, recibe `401`, el cuerpo `token required` y `WWW-Authenticate: Bearer`. `/healthz` sigue abierta a propósito: lleva la cadena de la placa que se le pide enviar a un operador cuando su placa no tiene mapa de puertos, y para eso no debería hacer falta un token. El transporte por relay no puede presentar un token. `/tool fetch` se ejecuta en el router y no envía cabecera `Authorization` (`internal/transport/transport.go`), así que en un agente con token el relay llega a `/healthz` y a nada más. `--expose` hace obligatorio el token; [lo que abre --expose](/mikroscope/es/security/expose/) cuenta por qué. ## Las rutas | Método | Ruta | Token | Devuelve | | -------- | ---------------- | ----- | --------------------------------------------------------------------------------------------------------- | | `GET` | `/healthz` | no | JSON: si está vivo, números de secuencia, relojes, cadencia, ticks retrasados, hash de capacidades, placa | | `GET` | `/capabilities` | sí | JSON: el kernel, la placa, las fuentes, los techos propios del equipo y la cadencia de cada fuente | | `GET` | `/snapshot` | sí | NDJSON, y cierra: los últimos N segundos, o hasta N muestras tras un número de secuencia | | `GET` | `/stream` | sí | NDJSON, por trozos y abierto: relleno desde un número de secuencia, y luego en directo | | `GET` | `/metrics` | sí | exposición de texto de Prometheus, `text/plain; version=0.0.4` | | `GET` | `/captures` | sí | JSON: el índice de capturas | | `GET` | `/captures/{id}` | sí | NDJSON: una línea de cabecera de la captura y luego las líneas de muestra tal cual | | `DELETE` | `/captures/{id}` | sí | `204`, y los bytes de la captura vuelven al presupuesto | | `POST` | `/capture` | sí | JSON: arma una captura ahora | Cualquier otra ruta es `404`; una ruta conocida con otro método la rechaza el enrutador de Go con `405`. ## `GET /healthz` | Campo | Tipo | Significado | | ------------------- | ------ | ---------------------------------------------------------------------------------------- | | `ok` | bool | siempre `true` cuando el agente responde | | `seq` | entero | número de secuencia de la muestra más reciente del anillo; 0 cuando el anillo está vacío | | `oldest_seq` | entero | número de secuencia más antiguo que aún se guarda; 0 cuando está vacío | | `wall_ns` | entero | el reloj de pared del router en la respuesta, ns desde la época | | `mono_ns` | entero | el reloj monotónico del agente en la respuesta, ns | | `uptime_s` | float | segundos desde que se montó el servidor HTTP | | `rate_hz` | entero | cadencia configurada del muestreador | | `slipped` | entero | ticks cuya lectura terminó después de que tocara el siguiente tick | | `capabilities_hash` | cadena | ocho dígitos hexadecimales sobre el kernel, el número de núcleos y las fuentes activas | | `version` | cadena | la identidad de compilación del agente | | `board` | cadena | el modelo del device tree, por ejemplo `RB5009`; se omite cuando la placa no tiene | La CLI usa `wall_ns` frente a su propio reloj para medir el desfase, `seq` y `oldest_seq` para planificar un relleno, y `capabilities_hash` para darse cuenta de que ha cambiado el kernel o el conjunto de fuentes que tiene debajo; ningún código de la CLI lee `mono_ns`. El sondeo que sigue a `install` imprime esta respuesta como `direct transport ok: agent , Hz, seq , slipped, round trip`, donde `` es la versión de la propia CLI, que esta graba en el agente que compila (`dev` cuando la CLI se compiló sin ella). La prueba de ida y vuelta registrada en el RB5009 el 2026-09-12 tiene dos sondeos, 7 ms tras `install` y 5 ms tras `upgrade`; el primero imprimió `direct transport ok: agent 4857d0a-dirty, 10 Hz, seq 29, 0 slipped, 7ms round trip`. ## `GET /capabilities` Lo que el agente estableció al arrancar sobre el kernel y la placa, sin la API de RouterOS. [El flujo de datos del equipo](/mikroscope/es/sinks/device-info/) es este objeto tal como el colector se lo entrega a cada destino. | Campo | Significado | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `kernel` | la cadena de versión del kernel de `/proc/version` | | `board` | el modelo del device tree; se omite cuando no existe | | `ports` | nombre de interfaz del kernel a nombre por defecto de RouterOS, para una placa de la tabla de puertos; si no, se omite | | `ports_from` | cómo se estableció ese mapa; se omite con él | | `cores` | número de núcleos, de la primera lectura de `/proc/stat` | | `user_hz` | `USER_HZ`, la unidad de los contadores de ticks | | `sources` | nombre de fuente a `true` cuando el fichero se abrió y la fuente está activa: `stat`, `meminfo`, `loadavg`, `softnet`, `softirqs`, `interrupts`, `vmstat`, `psi`, `schedstat`, `self`, `yaffs`, `diskstats`, `slabinfo`, `kmsg`, `thermal`, `cpufreq`, `perf`, `buddyinfo`, `mtd` | | `namespaced` | lo que el agente a propósito no lee como datos del router: `net/dev`, `net/snmp`, `net/netstat`, `sys/net/netfilter/nf_conntrack_count` | | `cgroup` | `true` cuando se puede leer el `cpu.stat` de cgroup2, así que el coste propio es exacto | | `privileged` | `true` cuando se abrieron tanto `/proc/slabinfo` como `/dev/kmsg` | | `limits` | los techos propios del equipo, leídos una vez al arrancar (abajo) | | `cadences` | nombre de fuente a `{"hz", "reason"}` para cada fuente de nivel (abajo) | | `hash` | el mismo valor que el `capabilities_hash` de `/healthz` | `limits` no tiene etiquetas JSON en el código, así que sus claves son los nombres de campo de Go: `ThermalCriticalMilliC` (zona al punto de disparo crítico más bajo, m°C), `ThermalPollingMS` (zona a su `polling_delay`), `CPUFreqMinKHz`, `CPUFreqMaxKHz`, `CPUFreqStepsKHz` (núcleo a su escalera), `CPUFreqGovernor`, `CPUFreqRelated` (núcleo a los núcleos que cambian de frecuencia con él), `CgroupMemoryMaxBytes` y `ConntrackMax`. Un techo que el equipo no publica queda vacío o a 0. El `reason` de una cadencia es uno de estos: `rate` (se lee a la cadencia del muestreador), `declared` (el equipo publica su propia cadencia de refresco), `policy` (un gobernador de cpufreq `userspace` significa que el reloj no puede moverse sin una escritura), `budget` (un coste de análisis medido), `change` (se lee en cada tick y se guarda al cambiar) u `override` (`FLOOR_HZ`). Los contadores nunca aparecen aquí, porque a los contadores nunca se les pone suelo. [Cada fuente a su propio suelo](/mikroscope/es/limits/source-floors/) tiene las medidas que hay detrás de los suelos. ## `GET /snapshot` Escribe NDJSON y cierra. Dos formas, según esté presente `since` o no. | Parámetro | Por defecto | Acepta | Significado | | --------- | ----------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `seconds` | `1` | 1–3600 | sin `since`: las `seconds × rate` muestras más recientes que guarda el anillo, de la más antigua a la más nueva | | `since` | ausente | un número de secuencia | hasta `max` muestras con número de secuencia mayor, de la más antigua a la más nueva; `since=0` empieza por la más antigua guardada | | `max` | `20` | 1–10000 | con `since`: el máximo de muestras que lleva una respuesta | Un valor fuera de su rango es `400` con un motivo de una línea. La forma `since` es de la que tiran `record` y `forward`, por los dos transportes. Añade dos tipos de línea que la forma `seconds` no tiene: primero una línea de hueco cuando `since` es más antiguo que el anillo, y una línea de disparo antes de cada muestra en la que saltó una condición de captura. `max` existe por el relay, porque `/tool fetch output=user` devuelve como mucho 64 512 B en RouterOS 7.24.2 y trunca el resto sin avisar; el relay pide como mucho 18 líneas. > **Un /snapshot grande no es una forma gratuita de leer el coste** > > Un `/snapshot` de 60 s a 10 Hz hace que el agente entregue unas 600 líneas, alrededor de 1,5 MB, y > el `self.cpu_us` de esas muestras incluye el coste de servirlas. Lee el coste del agente en > `/metrics`: [el coste del observador](/mikroscope/es/cost/) tiene el procedimiento. ## `GET /stream` NDJSON por trozos con `Cache-Control: no-store`, abierto hasta que el cliente se va. - `since` ausente o `0`: empieza en directo. La primera muestra que se escribe es la siguiente que se produce después de la petición; la más reciente que ya estaba en el anillo no se escribe. - `since=N`: primero rellena con todas las muestras guardadas posteriores a N y después sigue. Un `since` más antiguo que el anillo escribe primero una línea de hueco. - El anillo se consulta cada medio período y cada muestra nueva se escribe en cuanto llega, con las líneas de disparo antes de las muestras en las que saltaron. - Cada 5 s se escribe una línea de comentario `# heartbeat seq=`. Un consumidor se salta las líneas que empiezan por `#`. Un `since` que no es un número es `400`. > **Sin probar** > > El `WriteTimeout` de 30 s del servidor se aplica a todas las respuestas, y el servidor HTTP de Go > no lo levanta para un manejador que sigue escribiendo, así que cabe esperar que una conexión a > `/stream` termine unos 30 s después de abrirse. Eso está leído de `internal/agent/agent.go` y > `http.go`, no medido. La CLI no depende de `/stream`: `record` y `forward` tiran de > `/snapshot?since=`. ## Los tipos de línea Aparte del comentario de latido de `/stream`, cada línea que escriben `/snapshot`, `/stream` y `/captures/{id}` es un objeto JSON y un salto de línea. Existen cuatro tipos, que se distinguen por su primera clave: | Línea | Dónde | Significado | | ---------------------------------- | --------------------------------- | ------------------------------------------------------ | | una muestra, que empieza `{"seq":` | las tres | un tick, abajo | | `{"gap":{"from":F,"to":T}}` | `/snapshot?since=`, `/stream` | las muestras de F a T ya no están en el anillo | | `{"trigger":{…}}` | `/snapshot?since=`, `/stream` | saltó una condición de captura en la muestra que sigue | | `{"capture":{…}}` | primera línea de `/captures/{id}` | la cabecera de la captura | Un consumidor que no conoce un tipo se lo salta, así que un tipo de línea que no ha visto nunca no le cuesta nada. ### Una línea de muestra Cada campo numérico es un **delta desde la muestra anterior** salvo que la tabla diga que es un nivel. Una fuente que el kernel no tiene, o que el despliegue no puede leer, se **omite**, nunca se escribe como cero. Los ticks ocupados de un núcleo son `u + n + s + q + sq + st`; idle e iowait no cuentan como ocupados. | Campo | Tipo | Contenido | | ------------------ | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `seq` | — | número de secuencia, desde 1 | | `mono_ns` | — | el reloj monotónico del agente en la lectura | | `wall_ns` | — | el reloj de pared del router en la lectura, ns desde la época | | `dt_ns` | — | el intervalo real desde la muestra anterior; todos los deltas son sobre este, no sobre el período nominal | | `cpu` | delta | un objeto por núcleo: `u` user, `n` nice, `s` system, `i` idle, `w` iowait, `q` irq, `sq` softirq, `st` steal, en ticks de `USER_HZ` | | `cpu_total` | delta | las mismas claves, de la propia línea resumen `cpu` de `/proc/stat` | | `ctxt`, `intr` | delta | cambios de contexto e interrupciones de `/proc/stat` | | `forks` | delta | la línea `processes` de `/proc/stat` | | `procs_blocked` | nivel | tareas en espera no interrumpible | | `psi` | delta | `cpu_some`, `mem_some`, `mem_full`, `io_some`, `io_full`, µs; solo en un kernel con PSI | | `sched` | delta | por CPU `run_ns`, `wait_ns`; solo en un kernel con `/proc/schedstat` | | `softnet` | delta | por CPU `p` procesados, `d` descartados, `ts` time squeeze | | `softirq` | delta | tipo de softirq a un array de recuentos por CPU | | `irq` | delta | las `IRQ_TOP_K` líneas de interrupción más ocupadas en este tick (8 por defecto): `id`, `name`, array `cpu` | | `irq_total` | delta | todas las líneas de interrupción sumadas, el denominador del top-K | | `irq_err` | delta | la fila `Err` de `/proc/interrupts`; se omite cuando es cero | | `mem` | nivel | `/proc/meminfo` en kB, con claves por nombre de campo de Go: `MemTotal`, `MemFree`, `MemAvailable`, `Buffers`, `Cached`, `Dirty`, `Shmem`, `Slab`, `SReclaimable`, `CommittedAS`, `Writeback`, `SUnreclaim`, `AnonPages`, `Mapped`, `KernelStack`, `PageTables`, `CommitLimit`, `Active`, `Inactive` | | `load` | nivel | `/proc/loadavg`: `Load1`, `Load5`, `Load15`, `Running`, `Total`, `LastPID` | | `vm` | delta | eventos de `/proc/vmstat`: `pgfault`, `pgmajfault`, y cuando no son cero `pgscan_kswapd`, `pgscan_direct`, `pgsteal_kswapd`, `pgsteal_direct`, `pgalloc`, `pgfree`, `allocstall`, `compact_stall`, `oom_kill`, `pswpin`, `pswpout` | | `vmg` | nivel | `nr_free_pages`, `nr_dirty`, `nr_writeback`, `nr_slab_reclaimable`, `nr_slab_unreclaimable`, en páginas | | `self` | mixto | `cpu_us` (delta, µs), `rss` (nivel, bytes), `cg_mem` (nivel, se omite cuando es cero), `cg` (`true` cuando se leyó cgroup2), y `throttled`, `throttled_us`, `oom_kill` (deltas); los cuatro últimos se omiten cuando son cero o falso | | `thermal` | nivel | por zona `type`, `mc` (m°C) y `Celsius`; a la cadencia declarada de la zona, en cada tick cuando ninguna zona declara una, o a `FLOOR_HZ` | | `freq_khz` | nivel | por núcleo, kHz; al cambiar o en el latido de 60 s | | `thermal_critical` | nivel | zona a punto de disparo crítico en m°C, en las filas que llevan `thermal` | | `freq_max_khz` | nivel | núcleo a techo de cpufreq en kHz, en las filas que llevan `freq_khz` | | `cgroup_mem_max` | nivel | el `memory.max` del contenedor en bytes, en el primer tick y una vez por latido | | `flash` | mixto | por dispositivo YAFFS `dev`, `pw`, `pr`, `er`, `gcc`, `gc` (deltas) y `bad`, `free` (niveles); se omite un dispositivo sin operaciones y con los chunks libres sin cambios | | `disk` | mixto | por dispositivo de bloques `name`, `r`, `rs`, `w`, `ws`, `io_ms` (deltas) e `inflight` (nivel); se omite un dispositivo inactivo | | `slab` | nivel | caché a objetos activos; [requiere `privileged=yes`](/mikroscope/es/limits/privileged/); al cambiar, con un suelo de presupuesto | | `slab_limit` | nivel | caché a su techo publicado, hoy solo `nf_conntrack` | | `perf` | delta | por contador hardware `name`, array `cpu`, y `enabled_ns`, `running_ns`; [requiere `privileged=yes`](/mikroscope/es/limits/privileged/), y solo los contadores que implementa la CPU | | `buddy` | nivel | por zona `node`, `zone`, array `free` indexado por orden; al cambiar o en el latido | | `mtd` | nivel | por partición `dev`, `name`, `corr`, `fail`, `bad`, `bbt`, `bitflip_threshold`, `ecc_strength`; [requiere `privileged=yes`](/mikroscope/es/limits/privileged/); recuentos del kernel desde el arranque | | `events` | — | registros del log del kernel en este tick: `prio`, `lvl` (0 emerg … 7 debug), `fac`, `seq`, `us` (µs desde el arranque), `msg`, y, cuando el texto nombra un puerto, `iface`, `ros_iface` y `kind` (`link-up`, `link-down`, `stp-`, `own-address` u `other`) | | `events_dropped` | — | episodios de pérdida del log del kernel en este tick, no registros: uno si el tick llegó al tope de 64 registros, uno por cada desbordamiento del búfer circular del kernel (que puede suponer muchos registros); mientras no sea cero, `events` es una cota inferior; se omite cuando es cero | | `resets` | — | contadores monotónicos que retrocedieron sin un desbordamiento de 32 bits en este tick; se omite cuando es cero | Los nombres de contadores perf que el agente intenta abrir son `cycles`, `instructions`, `cache-references`, `cache-misses`, `branch-instructions`, `branch-misses` y `bus-cycles`. Las cachés slab que guarda son `nf_conntrack`, `skbuff_head_cache`, `skbuff_fclone_cache`, `TCP`, `UDP`, `TCPv6`, `UDPv6`, `sock_inode_cache`, `dst_cache`, `ip_dst_cache`, `kmalloc-1k` y `kmalloc-2k`, allí donde el kernel las tiene. ## `GET /metrics` La exposición de texto de Prometheus, construida a partir de contadores acumulados que ningún scrape pone a cero, así que `rate()` sobre cualquier rango es correcto y dos scrapers ven los mismos valores. El texto se construye en memoria y se escribe después de liberar el cerrojo de los contadores, así que un scraper lento no puede frenar al muestreador. [Familias de métricas de Prometheus](/mikroscope/es/reference/metrics/) enumera todas las familias. ## Capturas Las cuatro rutas de captura responden `404` con `captures disabled (CAPTURE_MB=0)` cuando el presupuesto es 0. ### `GET /captures` | Campo | Significado | | -------------- | -------------------------------------------------------------------------------------- | | `policy` | `first` o `last` | | `budget_bytes` | el presupuesto de bytes retenidos, `CAPTURE_MB` en bytes | | `bytes` | bytes que retienen las capturas guardadas | | `pending` | la captura que aún está recogiendo su ventana posterior al disparo; se omite si no hay | | `captures` | las cabeceras de las capturas guardadas, de la más antigua a la más nueva | | `triggers` | las condiciones configuradas, cada una con `name` y `threshold` | ### `GET /captures/{id}` Una línea de cabecera `{"capture":{…}}` y luego las líneas de muestra exactamente como las habría escrito `/snapshot`, así que no hace falta ningún analizador nuevo. Los bytes servidos se cuentan en `mikroscope_capture_bytes_served_total`: una descarga se ejecuta en el mismo núcleo que el muestreador. Un `id` que no es un número es `400`; uno desconocido es `404` con `no such capture`. | Campo | Significado | | ------------------------------ | --------------------------------------------------------------------------------------------------------------------- | | `id` | número de captura, desde 1 | | `cause` | el nombre de la condición tal como está configurada, o `manual` | | `condition` | la condición tal como está configurada, por ejemplo `busy>=0.95`; vacía en una captura manual | | `field` | lo que se comparó, por ejemplo `cpu[2].busy_ratio`; en una captura manual, el `reason` | | `value` | el valor que la hizo saltar | | `threshold` | el umbral configurado | | `fire_seq` | la muestra en la que saltó | | `fire_mono_ns`, `fire_wall_ns` | los dos relojes en el disparo | | `first_seq`, `last_seq` | la ventana guardada | | `samples`, `bytes` | su tamaño | | `complete` | `false` cuando guarda menos de `pre + post + 1` muestras: el anillo no llegaba tan atrás, o el agente se detuvo antes | La línea de disparo de `/snapshot` y `/stream` lleva `id`, `cause`, `field` (se omite si está vacío), `value`, `threshold`, `seq` y `wall_ns`. El agente guarda las 64 últimas. ### `DELETE /captures/{id}` Libera los bytes de la captura y responde `204` sin cuerpo. Los ids desconocidos o no numéricos responden igual que con `GET`. ### `POST /capture` Arma una captura en la muestra más reciente, como si hubiera saltado una condición: ```sh curl -X POST "http://172.30.10.2:9123/capture?reason=queue-tree-applied" ``` `reason` vale `operator` por defecto. La respuesta es `{"id":N,"armed":true}`. Si otra captura sigue recogiendo su ventana, o si una captura manual saltó dentro de la ventana refractaria, la respuesta es `409` y no se arma nada. Las condiciones, el presupuesto y lo que una captura no puede mostrar están en [captura por disparo](/mikroscope/es/record/triggers/). ## Véase también - [Llegar al agente](/mikroscope/es/install/reaching-the-agent/): las tres formas en que un equipo llega a estas rutas. - [Familias de métricas de Prometheus](/mikroscope/es/reference/metrics/): lo que lleva `/metrics`. - [Captura por disparo](/mikroscope/es/record/triggers/): las condiciones que hay detrás de `/captures`. - [Lo que abre --expose](/mikroscope/es/security/expose/): cuándo el token pasa a ser obligatorio. --- # Familias de métricas de Prometheus Todas las familias del /metrics del agente y de la exposición --prom del colector, agrupadas por fuente, con su tipo, sus labels y cuándo faltan. Source: https://jmrplens.github.io/mikroscope/es/reference/metrics/ Esta página enumera todas las familias de métricas que mikroscope expone en el formato de texto de Prometheus: su nombre, su tipo, sus labels, qué cuenta y cuándo no está. Está leída de `internal/agent/metrics.go`, `internal/agent/capture.go` e `internal/sinks/prometheus.go`. Cada familia aparece bajo la fuente de la que sale, porque eso es lo que decide si una placa concreta la tiene. ## Dos exposiciones, un solo renderizador El agente sirve `/metrics` en el router, y `forward --prom :9124` sirve otro en el equipo del colector. Los dos los escribe el mismo código `Totals`: el colector le pasa las muestras de las que tiró, así que un despliegue al que solo se llega por el relay sigue teniendo familias independientes del scrape. No son idénticas. | Familias | Agente `:9123/metrics` | Colector `--prom` | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | -------------------------------------------------------- | | todo lo que se recalcula a partir de las muestras: CPU, ventanas, rachas, ruta de recepción, memoria, PMU, sensores, flash, disco, log del kernel, contadores del observador | sí | sí | | hechos del equipo (`mikroscope_device_info`, techos, cadencias) | sí | sí, en cuanto el colector ha leído `/capabilities` | | los histogramas de tiempos del muestreador y `mikroscope_slipped_total` | sí | no | | familias de disparos y capturas | sí, mientras `CAPTURE_MB` sea mayor que 0 | no | | `mikroscope_collector_*`, `mikroscope_derived_*` | no | sí | | `mikroscope_api_*` | no | sí, en cuanto la capa de la API ha entregado una muestra | El colector deja fuera `mikroscope_slipped_total` en vez de escribir 0, porque un 0 ahí sería una afirmación sobre un muestreador que nunca ejecutó. Para tener los dos conjuntos en un mismo Prometheus, haz scrape del colector para todo y del agente solo para lo que el colector no puede producir. Hacer scrape del agente sin la lista de conservación duplica cada contador que el colector también expone: ```yaml - job_name: "mikroscope" scrape_interval: 5s static_configs: [{ targets: [":9124"] }] - job_name: "mikroscope-agent" scrape_interval: 5s static_configs: [{ targets: ["172.30.10.2:9123"] }] metric_relabel_configs: - source_labels: [__name__] regex: "mikroscope_(tick_.*|trigger_.*|capture.*|captures_held|slipped_total)" action: keep ``` ### Lo que hace distinto la copia del colector El destino del colector está construido para 10 Hz nominales sea cual sea la cadencia del agente (`promHistogramRateHz` en `cmd/mikroscope/sinkflags.go`), para que la disposición de sus buckets no cambie cuando se reconecta a un agente configurado de otra forma. De esa constante se siguen cinco cosas, leídas del código: - `mikroscope_info{rate_hz}` en el colector marca `10`, no la cadencia del agente. `/healthz` tiene la del agente. - `mikroscope_cpu_busy_ticks` tiene los buckets de `le="0"` a `le="11"`, dimensionados para una muestra de 100 ms. Un agente por debajo de 10 Hz pone sus muestras más ocupadas en `+Inf`. - El anillo que hay detrás de las ventanas móviles guarda 60 s de muestras del agente conectado: el colector lee su cadencia de la comprobación de salud y dimensiona el anillo con ella, así que `window="60s"` son 60 s a cualquier cadencia. - La media móvil que hay detrás de `mikroscope_softnet_burst_samples_total` tiene un peso de 1/600, que son 60 s de memoria a 10 Hz y menos por encima. - Una línea de interrupción sale de `mikroscope_irq_total` tras 36 000 muestras fuera de todos los top-K, lo que solo es una hora a 10 Hz: 12 min a 50 Hz, 6 min a 100 Hz. La comprobación se hace cada 1 000 muestras, así que una línea puede quedarse hasta eso más. Los contadores del colector cuentan desde que arrancó el colector, y `mikroscope_uptime_seconds` y `mikroscope_info{version}` describen el colector. `mikroscope_self_*` siguen describiendo el agente: salen de las muestras del agente. > **Sin probar** > > Las consecuencias enumeradas arriba para un agente que funciona a una cadencia distinta de 10 Hz > son aritmética a partir de `internal/sinks/prometheus.go`. Ninguna se ha comparado con la propia > exposición del agente a 50 o 100 Hz. ## Convenciones - **Los contadores cuentan desde que arrancó el exportador y nunca se ponen a cero en un scrape.** El anillo lleva deltas; `/metrics` los acumula. Así `rate()` sobre cualquier rango es correcto, y dos scrapers ven los mismos valores. - **Los medidores son la muestra más reciente.** Una fuente con suelo (thermal, cpufreq, slab, buddyinfo, MTD) falta a propósito en la mayoría de las muestras, así que su medidor mantiene la última lectura entre emisiones, y `mikroscope_source_age_seconds` dice lo vieja que es esa lectura. - **Ausente no es cero, salvo cinco excepciones.** La mayoría de las familias de una fuente que el kernel no tiene, o que el despliegue no puede leer, no se renderizan en absoluto. Las excepciones son `mikroscope_meminfo_kbytes`, `mikroscope_load`, `mikroscope_threads`, `mikroscope_self_rss_bytes` y `mikroscope_irq_errors_total`: se escriben desde la primera muestra y marcan 0 cuando su fichero no se puede leer o `SOURCES` lo deja fuera. `mikroscope_irq_errors_total` también marca 0 en un kernel cuyo `/proc/interrupts` no tiene fila `Err`. - **Una dimensión, un nombre.** Un procesador es la label `cpu` en todas partes. Qué núcleos, zonas, líneas de interrupción, cachés y contadores existen es una propiedad de la placa; lee la label, no supongas nunca un conjunto. - **Ninguna proporción en las muestras.** Los medidores de proporción de ocupación son estadísticas de ventana calculadas en el scrape, y los medidores `mikroscope_derived_*` del colector son divisiones que hizo junto a sus entradas. Cualquier otra proporción te toca dividirla a ti. ## Ticks de CPU y `/proc/stat` | Familia | Tipo | Labels | Significado | | -------------------------------------- | --------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_cpu_ticks_total` | counter | `cpu`, `mode`: `user`, `nice`, `system`, `idle`, `iowait`, `irq`, `softirq`, `steal` | ticks de `USER_HZ` por núcleo y modo | | `mikroscope_cpu_aggregate_ticks_total` | counter | `mode` | lo mismo, de la propia línea resumen `cpu` de `/proc/stat`; un nombre aparte para que sumar las series por núcleo no la cuente dos veces | | `mikroscope_cpu_busy_ticks` | histogram | `cpu`, `le` | ticks ocupados por muestra, un bucket por cada entero alcanzable, de 0 a uno más de lo que cabe en un período, más `+Inf`. `_sum` es el total exacto de ocupación. Recupera el tiempo por encima de un umbral con resolución de una muestra, no la continuidad | | `mikroscope_context_switches_total` | counter | ninguna | `ctxt` | | `mikroscope_interrupts_total` | counter | ninguna | `intr`, todas las fuentes | | `mikroscope_forks_total` | counter | ninguna | la línea `processes`: RouterOS lanzando scripts, fetches y contenedores, aunque el espacio de nombres de PID esconda los procesos | | `mikroscope_procs_blocked` | gauge | ninguna | tareas en espera no interrumpible; en un kernel sin PSI, la única señal directa de bloqueo | Un tick está ocupado cuando es `user`, `nice`, `system`, `irq`, `softirq` o `steal`. En el RB5009 con RouterOS 7.24.2 la columna `irq` es siempre 0 (medido el 2026-09-11), así que el tiempo de IRQ hardware está dentro de `system`: lee ahí el modo `irq` como ausente, no como un router sin carga de interrupciones. ## Ventanas móviles y rachas de ocupación Se calculan en el scrape a partir del anillo sobre ventanas fijas de reloj de pared, así que un scraper con cualquier intervalo de hasta 60 s ve el pico de un transitorio, sea quien sea el último que hizo scrape. | Familia | Tipo | Labels | Significado | | -------------------------------------- | --------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `mikroscope_cpu_busy_ratio_window` | gauge | `cpu`, `window`: `1s`, `10s`, `60s`; `stat`: `max`, `min`, `p95` | estadísticas de la proporción de ocupación sobre la ventana móvil | | `mikroscope_sample_interval_seconds` | gauge | `window`, `stat` | el intervalo medido del muestreador sobre la ventana móvil; su `max` dice si se entregó la cadencia | | `mikroscope_cpu_busy_run_seconds` | histogram | `cpu`, `threshold`: `0.5`, `0.9`; `le`: 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, `+Inf` | duración de cada racha de muestras consecutivas en la proporción de ocupación o por encima, en segundos de los propios intervalos de las muestras, observada cuando la racha termina | | `mikroscope_cpu_busy_run_open_seconds` | gauge | `cpu`, `threshold` | cuánto dura ya la racha en curso; 0 por debajo del umbral | Una meseta de 2 s es una observación de 2 en `mikroscope_cpu_busy_run_seconds` y veinte picos sueltos de 100 ms son veinte de 0,1; el histograma de ticks ocupados no puede distinguirlos. ## Los tiempos del propio muestreador Solo el agente. Una muestra se lee a lo largo de un tramo de tiempo, no en un instante, y estas familias miden ese tramo. | Familia | Tipo | Labels | Significado | | -------------------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_tick_interval_seconds` | histogram | `le` | intervalo medido entre muestras; buckets a 0,5, 0,9, 0,95, 0,99, 1,01, 1,05, 1,1, 1,25, 1,5, 2 y 5 veces el período nominal | | `mikroscope_tick_wake_latency_seconds` | histogram | `le` | cuánto tarde se ejecutó el bucle después de que saltara su temporizador; buckets 0,1 ms, 0,25, 0,5, 1, 2, 5, 10, 20, 50, 100 ms | | `mikroscope_tick_read_seconds` | histogram | `le` | cuánto tardó la lectura de todas las fuentes que tocaban; los mismos buckets | | `mikroscope_slipped_total` | counter | ninguna | ticks cuya lectura terminó después de que tocara el siguiente tick | Un tick retrasado no es una muestra perdida: la muestra se produce igual, con su `dt_ns` real. Si `mikroscope_slipped_total` se mueve, desconfía de la contabilidad del propio muestreador antes que de la del router. ## Ruta de recepción e interrupciones `/proc/net/softnet_stat`, `/proc/softirqs` y `/proc/interrupts` son globales dentro del contenedor, a diferencia de `/proc/net/dev`. | Familia | Tipo | Labels | Significado | | ------------------------------------------- | ------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_softnet_total` | counter | `cpu`, `kind`: `processed`, `dropped`, `time_squeeze` | contadores de softnet por CPU | | `mikroscope_softnet_squeezed_samples_total` | counter | `cpu` | muestras en las que esa CPU tuvo algún squeeze o descarte; su tasa es un ciclo de trabajo | | `mikroscope_softnet_burst_samples_total` | counter | `cpu` | las muestras con squeeze cuyo recuento de paquetes estuvo en la media móvil de esa CPU o por debajo (EWMA con memoria de un minuto); indicio de ráfagas más cortas que una muestra, no un recuento de ellas | | `mikroscope_softirq_total` | counter | `cpu`, `kind` | recuentos de softirq; qué tipos existen es la lista del kernel. `NET_RX` lleva la carga de reenvío de un router | | `mikroscope_irq_total` | counter | `irq`, `name`, `cpu` | líneas que aparecieron en el top-K de alguna muestra. Una línea que se queda fuera de todos los top-K durante una hora sale de la familia, y vuelve a empezar desde 0 si regresa; en el colector la hora solo se cumple a 10 Hz (arriba) | | `mikroscope_irq_delivered_total` | counter | ninguna | todas las líneas de interrupción sumadas, incluidas las de fuera del top-K: el denominador de `mikroscope_irq_total` | | `mikroscope_irq_errors_total` | counter | ninguna | la fila `Err` de `/proc/interrupts`, que el top-K nunca muestra mientras se queda en cero | Identifica una interrupción por su label `name` o por su tasa, nunca por un nombre fijado en el código: qué líneas levanta una NIC es una propiedad de la placa y de su driver. ## Planificador, carga y PSI | Familia | Tipo | Labels | Falta cuando | Significado | | ------------------------------------- | ------- | -------------------------------------------------------------------------------------------- | ------------------------------------ | ------------------------------------------------------ | | `mikroscope_load` | gauge | `period`: `1m`, `5m`, `15m` | aún no hay muestra | cargas medias | | `mikroscope_threads` | gauge | `state`: `running`, `total` | aún no hay muestra | de `/proc/loadavg` | | `mikroscope_psi_stall_usec_total` | counter | `resource`, `kind`: `cpu`/`some`, `memory`/`some`, `memory`/`full`, `io`/`some`, `io`/`full` | el kernel no tiene `/proc/pressure` | microsegundos de bloqueo de PSI | | `mikroscope_sched_run_seconds_total` | counter | `cpu` | el kernel no tiene `/proc/schedstat` | tiempo que las tareas pasaron en cada CPU | | `mikroscope_sched_wait_seconds_total` | counter | `cpu` | lo mismo | tiempo que las tareas ejecutables esperaron a cada CPU | Tanto las familias de PSI como las de schedstat faltan en el RB5009, cuyo kernel de RouterOS 7.24.2 no tiene ninguno de los dos ficheros: Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · Linux 5.6.3 · 2026-09-11 · `/proc/pressure` y `/proc/schedstat` ausentes ## Memoria | Familia | Tipo | Labels | Significado | | ------------------------------ | ------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_meminfo_kbytes` | gauge | `field` | `/proc/meminfo` en kB: `MemTotal`, `MemFree`, `MemAvailable`, `Buffers`, `Cached`, `Dirty`, `Writeback`, `Shmem`, `Slab`, `SReclaimable`, `SUnreclaim`, `AnonPages`, `Mapped`, `KernelStack`, `PageTables`, `Active`, `Inactive`, `Committed_AS`, `CommitLimit` | | `mikroscope_vm_events_total` | counter | `event` | eventos de `/proc/vmstat`: `pgfault`, `pgmajfault`, `pgscan_kswapd`, `pgscan_direct`, `pgsteal_kswapd`, `pgsteal_direct`, `pgalloc`, `pgfree`, `allocstall`, `compact_stall`, `oom_kill`, `pswpin`, `pswpout`. `pgalloc` y `allocstall` se suman sobre las zonas | | `mikroscope_vm_pages` | gauge | `field` | niveles de `/proc/vmstat` en páginas: `nr_free_pages`, `nr_dirty`, `nr_writeback`, `nr_slab_reclaimable`, `nr_slab_unreclaimable` | | `mikroscope_buddy_free_blocks` | gauge | `node`, `zone`, `order` | bloques libres de 2^order páginas de `/proc/buddyinfo`: la fragmentación que `MemFree` no puede mostrar | Los eventos y los niveles son familias separadas a propósito: que `nr_dirty` baje son páginas que se escriben a disco, no un recuento negativo de eventos. `MemFree` en kB dividido entre `nr_free_pages` da el tamaño de página a partir de los datos y no de una suposición. Las familias de vmstat faltan hasta que una muestra muestra un fallo de página o un nivel de páginas libres, porque un `/proc/vmstat` ilegible y un tick tranquilo llegan los dos como ceros. ## PMU y frecuencia de CPU | Familia | Tipo | Labels | Falta cuando | Significado | | -------------------------------------------- | ------- | ---------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_perf_events_total` | counter | `counter`, `cpu` | sin privileged, o sin PMU accesible | eventos de contadores hardware de `perf_event_open`; un contador que la CPU no implementa falta | | `mikroscope_perf_time_enabled_seconds_total` | counter | `counter`, `cpu` | lo mismo | segundos que cada contador estuvo activado | | `mikroscope_perf_time_running_seconds_total` | counter | `counter`, `cpu` | lo mismo | segundos que estuvo contando de verdad; por debajo de `enabled` el PMU está multiplexado y los recuentos se escalan a la baja por running sobre enabled | | `mikroscope_cpu_clock_cycles_total` | counter | `cpu` | sin cpufreq | la frecuencia del gobernador integrada sobre el intervalo de cada muestra: ciclos nominales ofrecidos, no ciclos ejecutados. Su `rate()` es la frecuencia media sobre cualquier ventana | | `mikroscope_cpu_frequency_hertz` | gauge | `cpu` | sin cpufreq | la frecuencia del gobernador en la emisión más reciente | Las instrucciones por ciclo son `rate(mikroscope_perf_events_total{counter="instructions"})` entre `rate(…{counter="cycles"})`; el agente envía los recuentos y nunca la proporción. ## Temperatura y cachés slab | Familia | Tipo | Labels | Falta cuando | Significado | | -------------------------------- | ----- | ------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_thermal_celsius` | gauge | `zone` | no hay zona térmica | temperatura bajo el propio nombre de zona del kernel, por ejemplo `cpu-thermal`, `soc-thermal` en el RB5009 | | `mikroscope_slab_active_objects` | gauge | `cache` | sin privileged | objetos activos por caché slab; `nf_conntrack` es el recuento real de conexiones del router, aunque el espacio de nombres del contenedor informe de cero | | `mikroscope_slab_limit_objects` | gauge | `cache` | sin privileged, o sin techo publicado | el techo de las cachés que lo tienen, hoy `nf_conntrack` a partir de `nf_conntrack_max`; se lee una vez al arrancar el agente | La ocupación de la tabla de conexiones es `mikroscope_slab_active_objects{cache="nf_conntrack"} / ignoring(cache) mikroscope_slab_limit_objects{cache="nf_conntrack"}`. Un cambio en `nf_conntrack_max` se ve después de reiniciar el agente. ## Flash y dispositivos de bloques | Familia | Tipo | Labels | Falta cuando | Significado | | ----------------------------------------- | ------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_flash_operations_total` | counter | `device`, `kind`: `page_writes`, `page_reads`, `erasures`, `gc_copies`, `gcs` | no hay `/proc/yaffs` | operaciones NAND de YAFFS; `erasures` se traduce en vida de la flash, `gc_copies` sobre `page_writes` es la amplificación de escritura | | `mikroscope_flash_bad_blocks` | gauge | `device` | no hay `/proc/yaffs`, y en cualquier scrape cuya muestra más reciente no llevaba fila de flash (sin operaciones, chunks libres sin cambios); no se mantiene entre emisiones | bloques que la NAND ha retirado; una subida es la flash desgastándose | | `mikroscope_flash_free_chunks` | gauge | `device` | lo mismo que `mikroscope_flash_bad_blocks` | chunks aún libres | | `mikroscope_mtd_ecc_corrected_bits_total` | counter | `device`, `partition` | sin privileged | bits que corrigió el ECC desde el arranque, el recuento propio del kernel; sube antes de que se retire un bloque | | `mikroscope_mtd_ecc_failures_total` | counter | `device`, `partition` | sin privileged | lecturas que el ECC no pudo corregir desde el arranque: pérdida de datos | | `mikroscope_mtd_blocks` | gauge | `device`, `partition`, `kind`: `bad`, `bbt` | sin privileged | bloques malos, y bloques que ocupa la tabla de bloques malos | | `mikroscope_mtd_bitflip_threshold` | gauge | `device`, `partition` | no se publica | bits corregidos por paso de ECC a partir de los cuales el kernel mueve los datos de un bloque | | `mikroscope_mtd_ecc_strength` | gauge | `device`, `partition` | no se publica | máximo de bits por paso de ECC que el código puede corregir | | `mikroscope_disk_operations_total` | counter | `device`, `op`: `read`, `write` | ningún dispositivo ha hecho E/S | peticiones completadas | | `mikroscope_disk_sectors_total` | counter | `device`, `op` | lo mismo | sectores transferidos; la conversión a bytes queda a cargo del lector | | `mikroscope_disk_io_seconds_total` | counter | `device` | lo mismo | tiempo que el dispositivo tuvo E/S en curso | | `mikroscope_disk_inflight` | gauge | `device` | sin E/S en ese dispositivo en la muestra más reciente; no se mantiene entre emisiones | peticiones en curso | Un dispositivo de bloques que no hizo nada no está en la muestra, y por tanto tampoco aquí; un comentario de `internal/agent/metrics.go`, sin fecha ni versión de RouterOS, dice que el RB5009 lista dieciséis dispositivos `nbd` inactivos. A diferencia de las fuentes con suelo de las convenciones de arriba, los niveles de flash y `mikroscope_disk_inflight` no se arrastran, así que faltan en la mayoría de los scrapes de una placa tranquila. ## Log del kernel `/dev/kmsg` es solo para root, así que estas familias necesitan un contenedor privileged. El texto del registro nunca es una label. | Familia | Tipo | Labels | Significado | | ------------------------------------ | ------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_kmsg_records_total` | counter | `level`: `emerg`, `alert`, `crit`, `err`, `warn`, `notice`, `info`, `debug` | registros por severidad de syslog | | `mikroscope_kmsg_dropped_total` | counter | ninguna | episodios de pérdida, no registros: uno por cada tick que llegó al tope de 64 registros, uno por cada desbordamiento del búfer circular del kernel (que puede suponer muchos registros); mientras se mueve, los recuentos por nivel de arriba son una cota inferior | | `mikroscope_kmsg_port_records_total` | counter | `port`, `kind`, `level` | registros cuyo texto nombraba un puerto: un subconjunto de la familia de arriba. `kind` es `link-up`, `link-down`, `stp-` (`blocking`, `listening`, `learning`, `forwarding`, `disabled`), `own-address` —el bridge recibió una trama con su propia MAC como dirección de origen, la firma de un bucle de capa 2— u `other`; solo se escriben las ternas distintas de cero | `mikroscope_kmsg_records_total` y `mikroscope_kmsg_dropped_total` se renderizan desde el principio, a 0, en las dos exposiciones cuando las capacidades listan `kmsg` como fuente, así que un router tranquilo se lee como silencioso y no como ilegible. Sin eso, aparecen con el primer registro. La familia por puerto aparece con el primer registro que nombra un puerto. En el agente, `port` es el nombre por defecto de RouterOS en una placa de la tabla de puertos, y si no el nombre del kernel. En la copia del colector, el inventario de interfaces de la capa de la API pone ahí el nombre actual del puerto en RouterOS, así que un puerto renombrado de `ether5` a `WAN` se cuenta bajo `WAN`; sin capa de la API, el registro conserva el nombre por defecto de la placa. El colector también clasifica todo registro que le llega sin `kind` propio, así que su exposición lleva la label aunque la del agente no la lleve —que es como está el RB5009 de referencia a 2026-09-16, con un agente cuyo `/metrics` no tiene `kind`—. Un puerto que se levanta escribe cuatro registros en un bridge, no cuatro fallos: `link-up` y después `stp-blocking`, `stp-learning` y `stp-forwarding` en su puerto del bridge. `own-address` es el único tipo que ya es un fallo por sí solo. ## El propio observador | Familia | Tipo | Labels | Significado | | ----------------------------------------- | ------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_self_cpu_usec_total` | counter | ninguna | microsegundos de CPU que usó el contenedor del agente: de cgroup2 cuando está montado, y si no ticks de `/proc/self/stat` | | `mikroscope_self_rss_bytes` | gauge | ninguna | el conjunto residente del agente | | `mikroscope_self_cgroup_memory_bytes` | gauge | ninguna | el `memory.current` del cgroup del contenedor, RSS más la caché de páginas que se le carga; falta sin cgroup2 | | `mikroscope_self_throttled_periods_total` | counter | ninguna | períodos en los que la cuota `cpu.max` del contenedor lo detuvo; falta sin cgroup2 | | `mikroscope_self_throttled_seconds_total` | counter | ninguna | segundos que pasó detenido; falta sin cgroup2 | | `mikroscope_self_oom_kills_total` | counter | ninguna | procesos matados por OOM dentro del propio cgroup del agente; no los del router, que son `mikroscope_vm_events_total{event="oom_kill"}` | | `mikroscope_samples_total` | counter | ninguna | muestras incorporadas a estos contadores | | `mikroscope_sample_seq_total` | counter | ninguna | número de secuencia de la muestra más reciente; su `increase()` frente a `increase(mikroscope_samples_total)` es exactamente los ticks producidos y no incorporados | | `mikroscope_sampled_seconds_total` | counter | ninguna | los intervalos propios de las muestras sumados; su `rate()` es el tiempo de reloj de pared cubierto por segundo, 1 mientras no se retrase ningún tick. El denominador de las proporciones derivadas | | `mikroscope_counter_resets_total` | counter | ninguna | contadores monotónicos que retrocedieron sin un desbordamiento de 32 bits; una tasa a través de un tick así es una cota inferior | | `mikroscope_info` | gauge | `version`, `rate_hz` | siempre 1 | | `mikroscope_uptime_seconds` | gauge | ninguna | segundos desde que arrancó este exportador | El coste del agente son dos lecturas de `mikroscope_self_cpu_usec_total` separadas 60 s en régimen estacionario, divididas entre 60 000 000. [El coste del observador](/mikroscope/es/cost/) tiene el procedimiento y las cifras medidas. ## Hechos del equipo Lo que el agente estableció sobre la placa al arrancar, sin la API de RouterOS. Cada familia falta cuando el equipo no publica ese dato. | Familia | Tipo | Labels | Significado | | ----------------------------------------- | ----- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_device_info` | gauge | `board`, `kernel`, `cores`, `privileged`, `cgroup`, `ports_from`, `hash` | siempre 1; `hash` cambia cuando cambia el conjunto de fuentes | | `mikroscope_thermal_critical_celsius` | gauge | `zone` | el punto de disparo crítico más bajo que declara la zona; no se incluyen los disparos pasivos ni activos | | `mikroscope_thermal_polling_seconds` | gauge | `zone` | el `polling_delay` de la zona: las lecturas más rápidas que esto ven el mismo valor | | `mikroscope_cpu_frequency_limit_hertz` | gauge | `cpu`, `bound`: `min`, `max` | el rango del reloj hardware | | `mikroscope_cpu_frequency_step_hertz` | gauge | `cpu`, `step` | cada frecuencia que usa el driver; `step` cuenta desde la más lenta | | `mikroscope_cpu_frequency_governor_info` | gauge | `cpu`, `governor` | siempre 1; `userspace` es un reloj fijo, `ondemand` o `schedutil` uno que escala | | `mikroscope_cpu_frequency_cluster` | gauge | `cpu` | el núcleo de número más bajo que cambia de frecuencia con este; `{0,1}` y `{2,3}` en el RB5009 | | `mikroscope_self_cgroup_memory_max_bytes` | gauge | ninguna | el propio `memory.max` del contenedor, tal como lo fijó el operador | | `mikroscope_source_cadence_hz` | gauge | `source`, `reason` | la cadencia a la que se lee y guarda cada fuente de nivel, y por qué: `declared`, `policy`, `budget`, `change`, `override` o `rate` | | `mikroscope_source_age_seconds` | gauge | `source` | segundos desde la última lectura real de cada fuente con suelo (`thermal`, `cpufreq`, `slabinfo`, `buddyinfo`, `mtd`) | Todas salvo la última se leen una vez al arrancar el agente; un techo que cambia mientras el agente funciona se ve después de reiniciarlo. ## Disparos y capturas Solo el agente, y solo mientras `CAPTURE_MB` sea mayor que 0. Cada par de condición y motivo se escribe desde el principio a 0. | Familia | Tipo | Labels | Significado | | --------------------------------------- | ------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | `mikroscope_trigger_fired_total` | counter | `condition` | veces que cada condición configurada armó una captura; `condition="manual"` aparece tras el primer `POST /capture` | | `mikroscope_trigger_suppressed_total` | counter | `condition`, `reason`: `refractory`, `pending` | veces que una condición se cumplió y no se armó nada: cuánto de una ráfaga no se vio | | `mikroscope_capture_refused_total` | counter | `reason`: `budget`, `empty` | capturas recogidas y no guardadas: el presupuesto estaba lleno, o el anillo ya no guardaba la ventana | | `mikroscope_captures_held` | gauge | ninguna | capturas retenidas | | `mikroscope_capture_bytes` | gauge | ninguna | bytes del anillo que retienen las capturas guardadas | | `mikroscope_capture_budget_bytes` | gauge | ninguna | el presupuesto | | `mikroscope_capture_bytes_served_total` | counter | ninguna | bytes entregados por `/captures/{id}`, que se ejecuta en el núcleo del muestreador | ## Solo el colector: la etapa de derivación | Familia | Tipo | Labels | Significado | | -------------------------------------------- | ------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `mikroscope_collector_gaps_total` | counter | ninguna | huecos del anillo que vio el colector: muestras perdidas entre peticiones | | `mikroscope_collector_triggers_total` | counter | `cause` | disparos de captura que el colector vio lanzar al agente; falta hasta el primero. Las ventanas se quedan en el agente, bajo `/captures` | | `mikroscope_collector_detections_total` | counter | `rule` | eventos de detección por regla, todas las reglas a 0 desde el primer scrape: `counter-reset`, `agent-restart`, `agent-oom`, `microburst`, `reboot`, `link-flap`, `conntrack-cliff`, `conntrack-high`, `thermal-high`, `thermal-rising`, `ipc-collapse` | | `mikroscope_collector_bursts_total` | counter | ninguna | muestras marcadas como ráfaga más corta que una muestra | | `mikroscope_derived_memory_pressure` | gauge | ninguna | la escalera del asignador en la muestra más reciente: 0 nada, 1 kswapd escaneó, 2 reclamación directa, 3 una asignación se bloqueó o se sacó una página a swap, 4 actuó el OOM killer | | `mikroscope_derived_cycles_per_packet` | gauge | ninguna | ciclos del PMU por paquete, sumados sobre los núcleos; falta sin PMU, en una muestra sin paquetes y en una con un reinicio de contador | | `mikroscope_derived_instructions_per_packet` | gauge | ninguna | lo mismo, instrucciones | | `mikroscope_derived_cache_misses_per_packet` | gauge | ninguna | lo mismo, fallos de caché | | `mikroscope_derived_packets_per_interrupt` | gauge | ninguna | paquetes por interrupción del dispositivo, la profundidad de agrupación de NAPI; falta cuando la fila del temporizador no estaba en el top-K de la muestra | | `mikroscope_derived_fastpath_share` | gauge | `interface`, `direction`: `rx`, `tx` | la fracción de fast path del tráfico que la interfaz entrega a la CPU, entre las dos últimas consultas de contadores: `fp-rx-byte` sobre `driver-rx-byte` en un puerto del switch, y sobre `rx-byte` en una interfaz software. Falta para una dirección que no movió bytes | La fracción de fast path no es una fracción del cable: una trama que el chip del switch reenvía por hardware no está en ninguno de sus dos números. En un puerto del switch los dos van juntos —medido el 2026-09-16 en el RB5009 de referencia, `fp-rx-byte` coincide con `driver-rx-byte` con unos pocos kB de diferencia, así que todos los puertos leen en torno al 100 %—. Donde el número se mueve es en las interfaces software: el bridge pasó por fast path 211,9 GB de los 663,0 GB que llevó a la CPU desde el arranque (32 %), y `PPPoE_DIGI` un 99,97 %. `fp-tx-byte` está a 0 en todas las interfaces de ese router tras cientos de GB transmitidos, así que `direction="tx"` se retiene mientras el `fp-tx-byte` acumulado sea 0, en lugar de publicar un 0 % inventado. Las reglas y lo que cada una no puede afirmar están en [detecciones](/mikroscope/es/sinks/detections/); los valores derivados, en [lo que deriva el colector](/mikroscope/es/sinks/derive/). ## Solo el colector: la capa de la API de RouterOS Faltan hasta que la capa de la API ha entregado una muestra, y faltan del todo con `--api-mode off` salvo que también se dé `--api-every` explícitamente. | Familia | Tipo | Labels | Significado | | ---------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_api_up` | gauge | ninguna | 1 en cuanto la capa de la API ha entregado una muestra. No vuelve a 0 si la capa se detiene después, aunque su línea HELP diga "while" | | `mikroscope_api_cpu_load` | gauge | ninguna | `cpu-load` tal como lo informa `/system/resource`, una media de un segundo | | `mikroscope_api_memory_bytes` | gauge | `kind`: `free`, `total` | de `/system/resource` | | `mikroscope_api_uptime_seconds` | gauge | ninguna | el tiempo en marcha del router | | `mikroscope_api_core_percent` | gauge | `cpu`, `kind`: `load`, `irq`, `disk` | `/system/resource/cpu` | | `mikroscope_api_health` | gauge | `name` | lecturas de `/system/health`, con el nombre de RouterOS | | `mikroscope_api_interface` | gauge | `interface`, `kind`: `rx_bps`, `tx_bps`, `rx_pps`, `tx_pps`, y `rx_drops`, `tx_drops`, `tx_queue_drops`, `rx_errors`, `tx_errors` solo cuando el router los devolvió | tasas instantáneas de `monitor-traffic` | | `mikroscope_api_interface_info` | gauge | `interface`, `label`, `type`, `role`, `bridge`, `default_name` | siempre 1, una serie por interfaz: `label` es su comentario de RouterOS, `type` el tipo de interfaz de RouterOS, `role` sus listas de interfaces, `bridge` el bridge del que es puerto y `default_name` el nombre de fábrica de un puerto físico. Un valor vacío es como la exposición escribe «ninguno» | | `mikroscope_api_interface_counter_total` | counter | `interface`, `counter` | cada contador numérico por puerto que guarda RouterOS, con su propio nombre (`rx-overflow`, `fp-rx-byte`, `link-downs` …), para cada interfaz | | `mikroscope_api_conntrack_entries` | gauge | ninguna | recuento de `/ip/firewall/connection`, mantenido entre consultas; solo con `--conntrack-every` | Lo que es cada interfaz —comentario, tipo, listas de interfaces, bridge— es una familia info y no un juego de labels en cada tasa y cada contador, porque eso lo edita una persona y una label que cambia empieza una serie nueva. El colector lee la configuración una vez al arrancar y de nuevo cada `--labels-every` (5 minutos por defecto), y la familia tiene una serie por interfaz, tenga comentario o no. Únela en una consulta: ```text mikroscope_api_interface_counter_total * on(interface) group_left(label, type, role) mikroscope_api_interface_info ``` Merece la pena unirla porque RouterOS cuenta cosas distintas según el tipo, y `type` es quien dice cuál: un puerto `ether` dentro de un bridge cuenta su cable, incluidas las tramas que el chip del switch reenvió por hardware, mientras que el `bridge` cuenta su lado de CPU. Medido el 2026-09-16 en el RB5009 de referencia, `ether1` había recibido 255,8 GB en el cable y 29,7 GB en el driver. Son dos planos distintos y ninguno es un subconjunto del otro: nunca sumes un puerto y su bridge. Los tamaños y la configuración que resultan analizarse como enteros —`mtu`, `actual-mtu`, `l2mtu`, `max-l2mtu` y `sfp-shutdown-temperature`— no están en `mikroscope_api_interface_counter_total`, porque un `rate()` de una MTU no cuenta nada. La MTU viaja en el inventario de interfaces, que los sinks de filas escriben como campo. En el RB5009 con RouterOS 7.24.2, `monitor-traffic` devolvió `rx-drops`, `tx-drops` y `tx-queue-drops` por segundo y ninguna clave de errores (2026-09-15), así que ahí faltan los tipos `rx_errors` y `tx_errors`; `mikroscope_api_interface_counter_total` lleva en su lugar los errores por tipo del puerto. > **Texto de ayuda que dice otra cosa** > > La línea HELP de `mikroscope_kmsg_records_total` sigue diciendo que la familia solo aparece cuando > se ha visto un registro. El código también la renderiza a 0 desde el principio cuando las > capacidades listan `kmsg`; esta página sigue al código. ## Véase también - [Prometheus](/mikroscope/es/sinks/prometheus/): poner en marcha la exposición del colector y hacerle scrape. - [Medidas de InfluxDB y SQL](/mikroscope/es/reference/measurements/): los mismos datos como filas. - [Los endpoints HTTP del agente](/mikroscope/es/reference/http/): `/metrics` y las rutas que lo acompañan. - [Lo que los números no dicen](/mikroscope/es/cost/limits/): lo que estas familias pueden y no pueden recuperar. --- # Medidas de InfluxDB y SQL Cada medida que escribe el codificador de line protocol de InfluxDB y cada tabla que crea el destino SQL, con etiquetas, campos, columnas, claves, unidades y si cada valor es un delta o un nivel. Source: https://jmrplens.github.io/mikroscope/es/reference/measurements/ Esta página responde qué contiene una fila en el almacén: qué medida o tabla, qué etiquetas o columnas de clave la identifican, qué campos lleva, en qué unidad, y si un valor es un delta sobre el intervalo de la muestra o un nivel. Está leída de `internal/sinks/influx.go`, `device.go` y `sql.go`. Los dos almacenes no guardan el mismo conjunto; [dónde difieren](#dónde-difieren-los-dos-almacenes) es la última sección. ## Qué destinos escriben esto - `forward --influx` envía el line protocol de InfluxDB de abajo. - `forward --stdout lp` y `forward --telegraf` renderizan con el mismo codificador, así que escriben las mismas medidas y líneas. El orden de las líneas de `mikroscope_api_health`, `mikroscope_softirq` y `mikroscope_slab` dentro de un lote no es estable: el codificador recorre mapas de Go para ellas, e `internal/sinks/telegraf.go` registra 12 renderizados de un mapa de 8 nombres que dieron 7 órdenes distintos (2026-09-12). - `forward --sql` escribe sus propias tablas de PostgreSQL, que se enumeran más abajo. Loki, OTLP, Graphite y Elasticsearch dan otra forma a la misma línea de tiempo; están en [el fichero y los demás destinos](/mikroscope/es/sinks/other/). ## Convenciones - **Cada fila lleva `host`**, el valor de `--host-tag` (`router` por defecto), como etiqueta en InfluxDB y como columna `host` en SQL. - **Las marcas de tiempo son el reloj de pared del agente**, en ns para InfluxDB y como `TIMESTAMPTZ` para SQL. Una fila de la capa de la API se sella con el reloj del colector corregido por el desfase medido, así que las dos capas comparten la hora del agente. Los huecos y las filas de datos del equipo no tienen reloj propio y llevan el del colector en el momento en que los procesó. - **Los contadores son deltas desde la muestra anterior**, no totales acumulados, y los niveles son el valor tal como se leyó. Las tablas de abajo dicen cuál es cuál; nunca sumes un nivel. - **Ausente es ausente, en las medidas por fuente.** `psi`, `thermal`, `slab`, `flash`, `mtd`, `disk`, `perf`, `kmsg`, `buddy` e `irq` no escriben ninguna fila para una fuente que el kernel no tiene o que el despliegue no puede leer. Un techo que el equipo no publica es un campo que falta en InfluxDB y `NULL` en SQL, nunca 0. La excepción: en InfluxDB `mikroscope_mem`, `mikroscope_load`, `mikroscope_vm`, `mikroscope_vm_level`, `mikroscope_stat`, `mikroscope_sample` y `mikroscope_self`, y en SQL `mikroscope_mem`, `mikroscope_load`, `mikroscope_stat` y `mikroscope_self`, se escriben en cada muestra y marcan 0 para una fuente que no se pudo leer. - **Una dimensión, un nombre.** Un procesador es `cpu` en todas las etiquetas y columnas. - **La unidad va en el nombre del campo**: `_kb`, `_khz`, `_ns`, `_us`, `_s`, `_ms`, `_bps`, `_pps`. Las temperaturas son `celsius` junto a `critical_celsius`; el tiempo ocupado de un dispositivo de bloques es `io_s`, convertido una sola vez desde los milisegundos del kernel. En line protocol, una `u` final es un entero sin signo, `i` uno con signo, un número a secas un float, `true`/`false` un booleano, y un valor entre comillas una cadena. ## InfluxDB: la capa del kernel Un juego de filas por muestra del kernel, todas en el `wall_ns` de esa muestra. ### CPU, interrupciones y la propia muestra | Medida | Etiquetas | Campos | Tipo | | -------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | `mikroscope_cpu` | `cpu` | `user`, `nice`, `system`, `idle`, `iowait`, `irq`, `softirq`, `steal` (u, ticks de `USER_HZ`); `busy_ratio` (float, 4 decimales); `dt_ns` (i) | los ticks son deltas; `busy_ratio` es ticks ocupados sobre `dt_ns`, con tope en 1 | | `mikroscope_cpufreq` | `cpu` | `khz` (u); `max_khz` (u) donde el núcleo publica un techo | nivel, en las muestras que lo llevan: al cambiar o en el latido de 60 s | | `mikroscope_stat` | ninguna | `ctxt`, `intr`, `forks`, `irq_total`, `irq_err` (u) | deltas | | `mikroscope_softnet` | `cpu` | `processed`, `dropped`, `time_squeeze` (u) | deltas | | `mikroscope_softirq` | `kind`, `cpu` | `count` (u) | delta; solo los pares (kind, cpu) distintos de cero | | `mikroscope_irq` | `irq`, `name` | `count` (u), sumado sobre las CPU | delta; solo las líneas del top-K de la muestra | | `mikroscope_irq_cpu` | `irq`, `name`, `cpu` | `count` (u) | delta; solo las CPU distintas de cero | | `mikroscope_sample` | ninguna | `seq` (u), `dt_ns` (i), `mono_ns` (i) | una fila por muestra | | `mikroscope_psi` | ninguna | `cpu_some_us`, `mem_some_us`, `mem_full_us`, `io_some_us`, `io_full_us` (u) | deltas; solo en un kernel con PSI | `busy_ratio` es la única proporción de la capa del kernel, y la calcula el destino, no la envía el agente. Los ticks que tiene al lado son lo que hay que dividir cuando importa la ventana: suma `user + nice + system + irq + softirq + steal` y divide entre `dt_ns / 1e9 × 100`. ### Memoria y carga | Medida | Etiquetas | Campos | Tipo | | --------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | | `mikroscope_mem` | ninguna | `total_kb`, `free_kb`, `available_kb`, `cached_kb`, `buffers_kb`, `slab_kb`, `sreclaimable_kb`, `sunreclaim_kb`, `anon_kb`, `mapped_kb`, `dirty_kb`, `writeback_kb`, `kernel_stack_kb`, `page_tables_kb`, `committed_kb`, `commit_limit_kb`, `shmem_kb`, `active_kb`, `inactive_kb` (u) | niveles | | `mikroscope_load` | ninguna | `load1`, `load5`, `load15` (float, 2 decimales); `running`, `threads`, `procs_blocked` (u) | niveles | | `mikroscope_vm` | ninguna | `pgfault`, `pgmajfault`, `pgscan_kswapd`, `pgscan_direct`, `pgsteal_kswapd`, `pgsteal_direct`, `pgalloc`, `pgfree`, `allocstall`, `compact_stall`, `oom_kill`, `pswpin`, `pswpout` (u) | deltas | | `mikroscope_vm_level` | ninguna | `nr_free_pages`, `nr_dirty`, `nr_writeback`, `nr_slab_reclaimable`, `nr_slab_unreclaimable` (u, páginas) | niveles | | `mikroscope_buddy` | `node`, `zone` | `free_pages` (u, la suma sobre los órdenes en páginas); `order_0` … `order_N` (u, bloques libres de 2^N páginas) | niveles, al cambiar o en el latido | `mikroscope_vm` y `mikroscope_vm_level` son medidas separadas porque un delta de `pgscan` es una tasa de eventos y `nr_dirty` es una profundidad. ### Sensores, cachés slab y flash | Medida | Etiquetas | Campos | Tipo | | -------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `mikroscope_thermal` | `zone` | `celsius` (float, 3 decimales); `critical_celsius` (float) donde la zona declara un disparo crítico | nivel, a la cadencia declarada de la zona | | `mikroscope_slab` | `cache` | `active` (u, objetos); `limit` (u) para las cachés con un techo publicado, hoy `nf_conntrack` | nivel; [requiere `privileged=yes`](/mikroscope/es/limits/privileged/); se guarda al cambiar con un suelo de presupuesto | | `mikroscope_flash` | `device` | `page_writes`, `page_reads`, `erasures`, `gc_copies`, `gcs` (u); `bad_blocks`, `free_chunks` (u) | los cinco primeros deltas, los dos últimos niveles; solo cuando algo cambió | | `mikroscope_mtd` | `device`, `partition` | `corrected_bits`, `ecc_failures`, `bad_blocks`, `bbt_blocks` (u); `bitflip_threshold`, `ecc_strength` (u) donde se publican | niveles: los recuentos del kernel desde el arranque tal como se leen, nunca diferenciados; [requiere `privileged=yes`](/mikroscope/es/limits/privileged/) | | `mikroscope_disk` | `device` | `reads`, `read_sectors`, `writes`, `write_sectors` (u); `io_s` (float, 3 decimales); `inflight` (u) | `inflight` es un nivel, el resto deltas; un dispositivo inactivo no escribe ninguna fila | `limit` es una palabra reservada de SQL, así que una consulta SQL de InfluxDB 3 la pone entre comillas dobles, como hace el panel de la tabla de conexiones que se distribuye: `max("limit")`. El panel de frecuencia de CPU que se distribuye pone igual entre comillas dobles el campo `cluster` de `mikroscope_device_cpufreq` (`internal/dashboards/panels_p5.go`); ninguna fuente del repositorio dice por qué. > **La alerta generada para InfluxDB pide el nombre de columna de SQL** > > `mikroscope dashboards gen` escribe en `mikroscope-alerts-influxdb.yaml` una alerta de conntrack > cuya consulta lee `limit_objs` de `mikroscope_slab`. Esa es la columna del destino SQL; el campo > de InfluxDB es `limit`. Leído de `internal/dashboards/alerts.go` y de `internal/sinks/influx.go`; > la regla no se ejecutó contra ningún almacén para esta página. ### Observador, PMU y log del kernel | Medida | Etiquetas | Campos | Tipo | | ----------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_self` | ninguna | `cpu_us` (u); `rss`, `cgroup_mem` (u, bytes); `cgroup_mem_max` (u) en las muestras que lo llevan; `throttled`, `throttled_us`, `oom_kill` (u) con cgroup2; `resets`, `kmsg_dropped`, `seq` (u) | `cpu_us`, `throttled`, `throttled_us`, `oom_kill`, `resets`, `kmsg_dropped` son deltas; `rss`, `cgroup_mem`, `cgroup_mem_max` niveles | | `mikroscope_perf` | `counter`, `cpu` | `count` (u); `enabled_ns`, `running_ns` (u) cuando el kernel los informó | deltas; [requiere `privileged=yes`](/mikroscope/es/limits/privileged/); `running_ns` por debajo de `enabled_ns` significa que el recuento está multiplexado | | `mikroscope_kmsg` | `level`, `port`, `kind`, `label`, `role` | `count` (u), registros en esta muestra | por (level, port, kind); un registro que no nombra ningún puerto no lleva ninguna de `port`, `kind`, `label` ni `role`; `label` y `role` solo donde el inventario los conoce; solo las combinaciones distintas de cero | `resets` cuenta los contadores monotónicos que retrocedieron en este tick sin un desbordamiento de 32 bits, y `kmsg_dropped` los episodios de pérdida del log del kernel, no registros: uno por un tick que llegó al tope de 64 registros, uno por cada desbordamiento del búfer circular del kernel, que puede suponer muchos registros. Los dos suelen ser 0; cualquiera de ellos distinto de cero significa que no hay que fiarse del tick como tasa. El texto de los registros del log del kernel no está en InfluxDB; `port` es el nombre actual de la interfaz en RouterOS cuando el colector tiene el inventario de la capa de la API, y si no el nombre de fábrica de la placa, o el nombre del kernel en una placa sin tabla de puertos. `kind` dice qué le pasó a ese puerto: `link-up`, `link-down`, `stp-blocking`, `stp-listening`, `stp-learning`, `stp-forwarding`, `stp-disabled`, `own-address` —el puente recibió una trama con su propia MAC como dirección de origen, la firma del bucle de capa 2— u `other`. Un enlace que sube escribe cuatro registros, no cuatro fallos: el link-up y las tres transiciones de STP de su puerto del puente. `label` y `role` son el comentario de RouterOS del puerto y sus listas de interfaces, y se etiquetan solo donde el inventario los tiene. Como una consulta que nombra una columna que el almacén nunca ha recibido falla al planificarse, `kind` solo se puede consultar después de escribir el primer registro de puerto con él. ## InfluxDB: la capa de la API de RouterOS Un juego por consulta a la API, sellado con la hora del agente. | Medida | Etiquetas | Campos | Tipo | | --------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_api_system` | ninguna | `cpu_load` (u, porcentaje); `free_memory`, `total_memory`, `free_hdd` (u, bytes); `uptime_s` (u) | la vista de un segundo de RouterOS, tal como la informa | | `mikroscope_api_core` | `cpu` | `load`, `irq`, `disk` (u, porcentaje) | tal como lo informa `/system/resource/cpu` | | `mikroscope_api_health` | `name` | `value` (float) | tal como lo informa `/system/health` | | `mikroscope_api_iface` | `interface`, `label`, `type`, `role`, `bridge` | `rx_bps`, `tx_bps`, `rx_pps`, `tx_pps` (u); `rx_drops`, `tx_drops`, `tx_queue_drops`, `rx_errors`, `tx_errors` (u) solo cuando el router los devolvió | tasas instantáneas de `monitor-traffic`; las cuatro etiquetas del inventario se omiten una a una cuando están vacías | | `mikroscope_api_ifcounters` | `interface`, `label`, `type`, `role`, `bridge` | un campo por cada contador numérico que devolvió RouterOS, con `-` convertido en `_` (`rx_overflow`, `fp_rx_byte`, `link_downs` …) (u) | acumulado desde el arranque o desde el último reinicio del puerto; todas las interfaces, en las consultas de `--counters-every` | | `mikroscope_api_ifinfo` | `interface`, `label`, `type`, `role`, `bridge` | `default_name` (string, el nombre de fábrica de un puerto físico, `""` en una interfaz que no lo tiene); `mtu` (u) solo por encima de 0 | qué es cada interfaz; se escribe una vez antes de la primera petición al kernel y otra vez en cada relectura de `--labels-every`, nunca por consulta | | `mikroscope_api_conntrack` | ninguna | `entries` (u) | solo en las consultas que lo pidieron, cada `--conntrack-every` | Los campos de `mikroscope_api_ifcounters` varían por puerto y por placa: un contador que un puerto no informa no es un campo de su fila. `mtu`, `actual-mtu`, `l2mtu`, `max-l2mtu` y `sfp-shutdown-temperature` son enteros que no cuentan nada, así que no son campos ahí; la MTU es el campo `mtu` de `mikroscope_api_ifinfo`. El inventario que hay detrás de `label`, `type`, `role` y `bridge` son tres lecturas de configuración: `/interface/print`, `/interface/list/member/print` e `/interface/bridge/port/print`. `label` es el comentario de RouterOS, `type` el tipo propio de RouterOS (`ether`, `bridge`, `vlan`, `pppoe-out`, `wg`, `veth`, `loopback`), `role` las listas de interfaces a las que pertenece la interfaz, ordenadas y unidas por comas (`WAN`, `LAN,VPN`), donde un miembro de un puente que no está en ninguna lista propia toma las listas de su puente, y `bridge` el puente del que es puerto. Una lectura de `/interface` que falla conserva el inventario que ya se tenía y escribe un registro de error en su lugar; las lecturas de listas y de puentes son de mejor esfuerzo. `type` es lo que impide sumar dos filas, porque RouterOS cuenta cosas distintas en interfaces distintas. Un puerto del conmutador cuenta su cable, incluidas las tramas que el chip conmutó por hardware; un puente cuenta su lado de la CPU; una VLAN o un PPPoE cuentan lo que la CPU envió y recibió. En el router de referencia (RB5009, RouterOS 7.24.2, 2026-09-16) ether1 recibió 255,8 GB por el cable y 29,7 GB de ellos llegaron a la CPU: la fila `ether` y la fila `bridge` son planos distintos, ninguno subconjunto del otro, y sumarlos no cuenta nada que exista. ## InfluxDB: valores derivados, anotaciones y datos del equipo | Medida | Etiquetas | Campos | Sellada con | | --------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | | `mikroscope_derived` | ninguna | `mem_pressure` (i, 0–4); `burst`, `suspect` (bool); `cycles_per_packet`, `instructions_per_packet`, `cache_misses_per_packet`, `packets_per_irq` (float, 3 decimales) cuando se pueden calcular | la hora de la muestra del kernel | | `mikroscope_derived_iface` | `interface` | `rx_bytes`, `fp_rx_bytes`, `tx_bytes`, `fp_tx_bytes` (u, deltas desde la consulta de contadores anterior); `fp_rx_share`, `fp_tx_share` (float, 4 decimales) cuando se movieron bytes | la hora de la muestra de la API | | `mikroscope_detection` | `rule`, `key` | `value`, `threshold` (float); `seq` (u); `message` (string) | la hora de la detección; `key` se omite cuando la regla no tiene | | `mikroscope_trigger` | `cause` | `id`, `seq` (u); `value`, `threshold` (float); `field` (string) | el reloj de pared del agente en el disparo | | `mikroscope_gap` | ninguna | `from`, `to` (u, números de secuencia que ya no están en el anillo) | el reloj del colector | | `mikroscope_device` | `board`, `kernel` | `cores` (i); `privileged`, `cgroup` (bool); `sources`, `hash` (string); `conntrack_max`, `cgroup_mem_max` (u) donde se publican; `ports_from` (string) donde se conoce | el reloj del colector | | `mikroscope_device_thermal` | `zone` | `critical_celsius` (float); `polling_ms` (i) | el reloj del colector | | `mikroscope_device_cpufreq` | `cpu` | `cluster` (i); `min_khz`, `max_khz` (u); `governor` (string); `steps` (string, kHz separados por espacios) | el reloj del colector | | `mikroscope_device_cadence` | `source`, `reason` | `hz` (float) | el reloj del colector | `fp_rx_share` es la fracción del tráfico que una interfaz entrega a la CPU que pasó por el fast path, no una fracción del cable: `fp-rx-byte` sobre `driver-rx-byte` en un puerto del conmutador, cuyo `rx-byte` es el total del cable, y sobre `rx-byte` en una interfaz por software, que no tiene contadores de driver. Las tramas conmutadas por hardware no están en ninguno de los dos números. `rx_bytes` y `tx_bytes` son los denominadores de la fracción, no totales del cable. Medido en el RB5009 de referencia (2026-09-16): los puertos del conmutador dan ~100 % (`fp-rx-byte` iguala a `driver-rx-byte` con unos pocos kB de diferencia), el puente llevó por el fast path 211,9 GB de 663,0 GB desde el arranque, y PPPoE_DIGI el 99,97 %. `fp-tx-byte` se quedó en 0 en todas las interfaces tras cientos de GB transmitidos, así que la fracción de tx y sus deltas se retienen mientras el `fp-tx-byte` acumulado sea 0: `fp_tx_share` no está y `tx_bytes` y `fp_tx_bytes` valen 0. Un valor derivado se escribe junto a sus entradas y nunca en lugar de ellas, así que el almacén puede volver a calcularlo. `suspect` marca una muestra con un reinicio de contador, en la que una cifra por paquete sería una cota inferior, así que los campos por paquete se dejan fuera. Las etiquetas `board` y `kernel` valen `unknown` cuando el agente no pudo establecerlas. Las cuatro medidas `mikroscope_device*` se escriben una vez cuando arranca `forward` y de nuevo antes de un minuto después de que cambie el hash de capacidades del agente, porque el colector vuelve a leer `/healthz` una vez por minuto. Un transporte que no puede traer `/capabilities` no escribe ninguna fila del equipo. Qué significa cada valor y cada regla está en [lo que deriva el colector](/mikroscope/es/sinks/derive/), [detecciones](/mikroscope/es/sinks/detections/) y [el flujo de datos del equipo](/mikroscope/es/sinks/device-info/). ## Lo que exige InfluxDB 3 Core - Un nodo guarda como mucho cinco bases de datos. Una escritura a una sexta falla con `422`; el destino espera y sigue intentándolo. - Toda consulta tiene que estar acotada en el tiempo. - El tipo de una columna es inmutable una vez escrito. - Una consulta que nombra un campo que el almacén nunca ha recibido falla al planificarse, exactamente igual que una tabla que falta: `No field named limit. Valid fields are …` (verificado a través del proxy de la fuente de datos de Grafana, 2026-09-14). - La URL de escritura contiene `&`; entrecomíllala cuando vive en un fichero que cargas con `source`. El destino envía un lote por segundo y encola hasta `--queue-seconds` × 64 KiB de lotes, un presupuesto dimensionado para unos 1,2 KiB por muestra a 10 Hz, y después descarta el más antiguo. Un lote de más de 64 KiB, a 50 o 100 Hz o con las fuentes privileged, hace que quepan menos de `--queue-seconds` lotes; [InfluxDB 3](/mikroscope/es/sinks/influxdb/) cubre la entrega. ## SQL: el fichero `--sql out.sql` escribe texto de PostgreSQL: una cabecera y luego un `INSERT` por registro. No hay driver de base de datos; la conexión es cosa de `psql`. ```sh mikroscope forward --sql out.sql --for 10m && psql -f out.sql ``` - La cabecera es `SET standard_conforming_strings = on;` y un `CREATE TABLE IF NOT EXISTS` por tabla, así que cada fichero vuelve a declarar el esquema sin daño. `--sql-hypertable` añade un `SELECT create_hypertable('', 'time', if_not_exists => TRUE);` por tabla. - La clave primaria de cada tabla empieza por `time, host`, y cada `INSERT` termina en `ON CONFLICT DO NOTHING`: aplicar dos veces el mismo fichero no hace nada, porque una fila es un instante inmutable, nunca un total que una pasada posterior revisa. - `TIMESTAMPTZ` guarda microsegundos, así que los tres últimos dígitos de los nanosegundos del agente se pierden al redondear; dos muestras separadas por menos de 1 µs colisionarían en la clave. - Un float que es NaN o infinito se escribe `NULL`. Un byte NUL en una cadena se descarta, y los bytes que no son UTF-8 válido pasan a ser U+FFFD, porque PostgreSQL rechaza las dos cosas. - `--sql -` escribe en la salida estándar. Como este destino no tiene cola, un `psql` que se queda atrás bloquea el bucle de peticiones del colector en vez de descartar. - El destino cuenta los eventos que escribió en el fichero, no las filas que guardó el servidor. `dt_ns` solo está en `mikroscope_cpu`. Una tasa sobre cualquier otra tabla de deltas se une con `mikroscope_cpu` por `(time, host)` para obtener el intervalo real en vez de suponer el período nominal. ## SQL: tablas de la capa del kernel | Tabla | Clave primaria | Columnas después de `time` | | -------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_cpu` | `time, host, cpu` | `host`, `cpu`, `user_ticks`, `nice_ticks`, `system_ticks`, `idle_ticks`, `iowait_ticks`, `irq_ticks`, `softirq_ticks`, `steal_ticks` (deltas), `busy_ratio`, `dt_ns` | | `mikroscope_softnet` | `time, host, cpu` | `host`, `cpu`, `processed`, `dropped`, `time_squeeze` (deltas) | | `mikroscope_irq` | `time, host, irq` | `host`, `irq`, `name`, `count` (delta, sumado sobre las CPU; líneas del top-K) | | `mikroscope_mem` | `time, host` | `host`, `free_kb`, `available_kb`, `cached_kb`, `slab_kb`, `sunreclaim_kb` (niveles) | | `mikroscope_load` | `time, host` | `host`, `load1`, `load5`, `load15`, `running`, `threads`, `procs_blocked` (niveles) | | `mikroscope_stat` | `time, host` | `host`, `ctxt`, `intr`, `forks`, `irq_total`, `irq_err`, `pgfault`, `pgmajfault` (deltas) | | `mikroscope_self` | `time, host` | `host`, `cpu_us` (delta), `rss`, `cgroup_mem` (niveles), `throttled`, `throttled_us`, `oom_kill` (deltas, `NULL` sin cgroup2), `seq` | | `mikroscope_buddy` | `time, host, node, zone, block_order` | `host`, `node`, `zone`, `block_order`, `free_blocks` (nivel; una fila por zona y orden, y `order` está reservada) | | `mikroscope_mtd` | `time, host, device` | `host`, `device`, `partition`, `corrected_bits`, `ecc_failures`, `bad_blocks`, `bbt_blocks`, `bitflip_threshold`, `ecc_strength` (niveles; umbrales `NULL` si no se publican) | | `mikroscope_psi` | `time, host` | `host`, `cpu_some_us`, `mem_some_us`, `mem_full_us`, `io_some_us`, `io_full_us` (deltas) | | `mikroscope_thermal` | `time, host, zone` | `host`, `zone`, `celsius`, `critical_celsius` (`NULL` si no se publica) | | `mikroscope_slab` | `time, host, cache` | `host`, `cache`, `active_objs`, `limit_objs` (`NULL` para las cachés sin techo publicado) | | `mikroscope_disk` | `time, host, device` | `host`, `device`, `reads`, `read_sectors`, `writes`, `write_sectors`, `io_s` (deltas), `inflight` (nivel) | | `mikroscope_flash` | `time, host, device` | `host`, `device`, `page_writes`, `page_reads`, `erasures`, `gc_copies`, `gcs` (deltas), `bad_blocks`, `free_chunks` (niveles) | | `mikroscope_event` | `time, host, kernel_seq` | `host`, `level`, `facility`, `kernel_seq`, `time_usec` (µs desde el arranque, el reloj monotónico del kernel, no el de `time`), `message`, `port`, `kind` (los dos `NULL` en un registro que no nombra ningún puerto) | Los nombres de columna evitan tener que entrecomillar en PostgreSQL: las columnas de ticks son `user_ticks` y compañía porque `user` está reservada. Las columnas de contadores son `BIGINT`, ya que PostgreSQL no tiene un tipo de 64 bits sin signo y ningún delta que produzca un router en una muestra se acerca a 2^63. ## SQL: tablas de la capa de la API, del colector y del equipo | Tabla | Clave primaria | Columnas después de `time` | | --------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mikroscope_api_system` | `time, host` | `host`, `cpu_load`, `free_memory`, `total_memory`, `free_hdd`, `uptime_s`, `version` | | `mikroscope_api_core` | `time, host, cpu` | `host`, `cpu`, `load`, `irq`, `disk` | | `mikroscope_api_health` | `time, host, name` | `host`, `name`, `value` | | `mikroscope_api_iface` | `time, host, interface` | `host`, `interface`, `label`, `rx_bps`, `tx_bps`, `rx_pps`, `tx_pps`, `rx_drops`, `tx_drops`, `tx_queue_drops`, `rx_errors`, `tx_errors` (columnas de pérdidas `NULL` cuando el router no las devolvió) | | `mikroscope_api_conntrack` | `time, host` | `host`, `entries`: el último recuento, escrito en cada consulta a la API una vez que se ha leído uno | | `mikroscope_api_ifinfo` | `time, host, interface` | `host`, `interface`, `default_name`, `type`, `role`, `bridge`, `label`, `mtu` (`NULL` donde el router no da ninguno): qué es cada interfaz, una fila por interfaz y por lectura del inventario | | `mikroscope_api_ifcounter` | `time, host, interface, counter` | `host`, `interface`, `counter` (el propio nombre de RouterOS, con sus guiones), `value`: una fila por contador | | `mikroscope_api_error` | `time, host, message` | `host`, `message`: qué orden de la API falló en esa consulta y por qué | | `mikroscope_gap` | `time, host, seq_from, seq_to` | `host`, `seq_from`, `seq_to` | | `mikroscope_trigger` | `time, host, id` | `host`, `id`, `cause`, `field`, `value`, `threshold`, `seq` | | `mikroscope_derived` | `time, host` | `host`, `seq`, `mem_pressure`, `burst`, `suspect`, `cycles_per_packet`, `instructions_per_packet`, `cache_misses_per_packet`, `packets_per_irq` (`NULL` donde no se puede calcular) | | `mikroscope_derived_iface` | `time, host, interface` | `host`, `interface`, `rx_bytes`, `fp_rx_bytes`, `tx_bytes`, `fp_tx_bytes`, `fp_rx_share`, `fp_tx_share` | | `mikroscope_detection` | `time, host, rule, key` | `host`, `rule`, `key` (cadena vacía cuando la regla no tiene), `seq`, `value`, `threshold`, `message` | | `mikroscope_device` | `time, host` | `host`, `board`, `kernel`, `cores`, `privileged`, `cgroup`, `sources`, `conntrack_max`, `cgroup_mem_max`, `ports_from`, `hash` | | `mikroscope_device_thermal` | `time, host, zone` | `host`, `zone`, `critical_celsius`, `polling_ms` | | `mikroscope_device_cpufreq` | `time, host, cpu` | `host`, `cpu`, `cluster`, `min_khz`, `max_khz`, `governor`, `steps` | | `mikroscope_device_cadence` | `time, host, source` | `host`, `source`, `reason`, `hz` | ## Dónde difieren los dos almacenes | Dato | InfluxDB | SQL | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | | frecuencia de CPU | `mikroscope_cpufreq` | no se escribe | | interrupciones y softirqs por CPU | `mikroscope_irq_cpu`, `mikroscope_softirq` | no se escriben; `mikroscope_irq` solo tiene la suma | | eventos y niveles de vmstat | `mikroscope_vm`, `mikroscope_vm_level` | solo `pgfault` y `pgmajfault`, en `mikroscope_stat` | | contadores del PMU | `mikroscope_perf` | no se escriben | | secuencia y relojes de la muestra | `mikroscope_sample` | `seq` en `mikroscope_self`; `dt_ns` en `mikroscope_cpu` | | `/proc/meminfo` | diecinueve campos en `mikroscope_mem` | cinco columnas en `mikroscope_mem` | | log del kernel | recuentos por nivel, puerto y kind, `mikroscope_kmsg`; sin texto | cada registro con su texto, `port` y `kind`, `mikroscope_event`; sin recuentos | | extras del observador | `cgroup_mem_max`, `resets`, `kmsg_dropped` en `mikroscope_self` | no se escriben | | techo de slab | campo `limit` | columna `limit_objs`; población `active` frente a `active_objs` | | listas libres | una fila por zona, un campo por orden | una fila por zona y orden | | qué es una interfaz | `label`, `type`, `role` y `bridge` como etiquetas en `mikroscope_api_iface` y `mikroscope_api_ifcounters`, junto a `mikroscope_api_ifinfo` | solo `label` en `mikroscope_api_iface`; el resto, a través de `mikroscope_api_ifinfo` | | contadores de puerto | `mikroscope_api_ifcounters`, una fila por puerto, `_` en los nombres | `mikroscope_api_ifcounter`, una fila por contador, nombres de RouterOS | | recuento de conntrack | solo en las consultas que lo pidieron | el último recuento, repetido en cada consulta después de la primera | | fallos de órdenes de la API | no se escriben | `mikroscope_api_error` | | límites de un hueco | `from`, `to` | `seq_from`, `seq_to` | | `mikroscope_api_system.version` | no se escribe | `version` | > **No medido, luego no afirmado** > > El destino SQL nunca se ha ejecutado contra un TimescaleDB real en este repositorio: las llamadas > a `create_hypertable` siguen la firma documentada de TimescaleDB 2.x y no están verificadas. Su > tamaño se midió el 2026-09-12 sobre el propio fixture de pruebas del paquete (2 núcleos, una cola > de softnet, una línea de interrupción, sin fuentes privileged), no en un router: un evento del > kernel se renderiza en 1 375 B de SQL frente a 716 B de line protocol, un evento de la API en > 1 138 B frente a 608 B, y 10 Hz más la capa de la API a 1 Hz escriben unos 14 KiB/s tras una > cabecera de 5,6 KiB; con las fuentes privileged el mismo evento del kernel crece hasta 2 749 B. > Las cifras no cubren catorce de las treinta y dos tablas que escribe el destino — `load`, `stat`, > `buddy`, `mtd`, `api_ifcounter`, `api_ifinfo`, `trigger`, `derived`, `derived_iface`, `detection` > y las cuatro tablas `device` — y no se han vuelto a medir, así que son anteriores a las columnas > `port` y `kind` de `mikroscope_event`. La cabecera de las treinta y dos, calculada a partir de > las cadenas del esquema de `internal/sinks/sql.go` y no medida, ocupa 7 757 B, unos 7,6 KiB. No > se ha medido cómo se comporta `--sql - | psql` cuando `psql` se queda atrás frente a un agente a > 10 Hz. ## Véase también - [InfluxDB 3](/mikroscope/es/sinks/influxdb/): apuntar `forward` a InfluxDB y qué garantías de entrega tiene. - [Familias de métricas de Prometheus](/mikroscope/es/reference/metrics/): los mismos datos como scrape. - [El fichero y los demás destinos](/mikroscope/es/sinks/other/): JSONL, SQL en la práctica, Loki, OTLP, Graphite, Elasticsearch, Telegraf. - [Importar y comprobar](/mikroscope/es/dashboards/import-and-check/): los paneles que consultan estas medidas. --- # Puertos de RouterOS y nombres del kernel El log del kernel dice eth5 donde RouterOS dice ether6 — cómo medir la correspondencia en un puerto muerto en un solo paso seguro, qué incluye el agente para el RB5009 y cuánto de esa tabla se midió. Source: https://jmrplens.github.io/mikroscope/es/reference/port-names/ El log del kernel nombra netdevs (`eth0`, `eth5`), RouterOS nombra interfaces (`ether1`, `sfp-sfpplus1`) y **no coinciden**. Esta página responde a qué cable se refiere un registro del log del kernel. Durante [el caso del bucle](/mikroscope/es/playbooks/loop/) esto costó tiempo de verdad: `eth1` en el log parece que debería ser `ether1`, y no lo es. Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · Linux 5.6.3 · 2026-09-12 · agente a 10 Hz en un contenedor privilegiado efímero Las observaciones posteriores llevan su fecha donde aparecen. ## Por qué hay que medirlo RouterOS no expone ninguna correspondencia, y el contenedor no puede leer una. Los dispositivos de red están en su namespace, así que `/sys/class/net` dentro del contenedor solo muestra `lo` y la veth; `/sys/class/mdio_bus` solo contiene `fixed-0` y `/sys/class/phy` está vacío. `privileged=yes` no cambia eso. Los nombres de RouterOS viven en la configuración de RouterOS, no en el kernel. ## Mídelo en un puerto muerto Elige un puerto que seguro no lleve nada, apágalo y enciéndelo, y lee el nombre que escribe el kernel. Busca primero un puerto realmente muerto — cero paquetes en ambos sentidos, en toda su vida: ```text /interface/print stats where name="ether6" or name="ether7" ``` Después, con el agente en marcha: ```text /interface/ethernet/disable [find name="ether6"] :delay 4s /interface/ethernet/enable [find name="ether6"] ``` El kernel dijo: ```text [6] br0: port 7(eth5) entered blocking state [4] eth5: set isolation from 0 to 1 [4] eth5: set isolation from 1 to 0 ``` Así que `ether6` de RouterOS es `eth5` del kernel. Repetirlo en `ether7` dio `eth6`: un **desfase de −1**, visto en dos puntos. ## La tabla del RB5009 La correspondencia es un desplazamiento de uno — RouterOS numera los puertos desde 1 y el kernel desde 0, chip del switch incluido (`switch0` es el `switch=switch1` que declaran todos los puertos): | RouterOS | kernel | cómo se sabe | | ------------------- | --------------- | ----------------------------------------------------- | | `ether1` | `eth0` | deducido | | `ether2` | `eth1` | **medido** — el bucle del caso de estudio, 2026-09-13 | | `ether3` … `ether5` | `eth2` … `eth4` | deducido | | `ether6` | `eth5` | **medido** — apagado y encendido el 2026-09-15 | | `ether7` | `eth6` | **medido** — apagado y encendido el 2026-09-15 | | `ether8` | `eth7` | deducido | | `sfp-sfpplus1` | `eth8` | deducido, el último de la enumeración | | `switch1` | `switch0` | deducido | Los pares de `ether6` y `ether7` se midieron el **2026-09-15**. Los dos puertos están comentados como `Unused` y ninguno estaba `RUNNING` — sin cable y sin enlace — así que cada uno se apagó y se volvió a encender por la API mientras el agente leía `/dev/kmsg`. El kernel registró `br0: port 7(eth5) entered disabled state` dentro de la ventana de `ether6` y `port 7(eth6)` dentro de la de `ether7`, con nueve segundos entre ellas, que es lo que descarta leer dos veces el mismo apagado. No se interrumpió ningún tráfico y los dos puertos estaban de vuelta en menos de cuatro segundos. Eso hace tres pares medidos, todos con el mismo desplazamiento de uno, que es en lo que se apoyan las seis filas deducidas. Los pares deducidos se apoyan en dos observaciones de un `/interface/ethernet/print` de solo lectura (2026-09-13, la fecha de la cadena de evidencia de la propia tabla): los nueve puertos llevan direcciones MAC consecutivas, de `…:55` para `ether1` a `…:5D` para `sfp-sfpplus1`, en el orden de enumeración del propio RouterOS; y los nueve declaran `switch=switch1` frente al único `switch0` del kernel. ## Dos advertencias - **El nombre es fiable; el número de puerto del bridge, no.** Los dos apagados dieron `port 7`, porque el kernel reutiliza las posiciones de puerto cuando un puerto sale del bridge y vuelve a entrar. Compara con `eth5`, nunca con `port 7`. - **El desfase es una propiedad del driver de este modelo, no una regla.** Vuelve a medirlo en otro equipo en lugar de suponerlo, y ten cuidado con la intuición de que el SFP+ tiene que ser `eth0` — aquí es el último, no el primero. El device tree tampoco ayuda: muestra el `ethernet@0` del SoC con tres MAC, solo `eth0` habilitada (el enlace de subida de 10 G) y `eth1`/`eth2` deshabilitadas, mientras que los nueve puertos del frontal son netdevs que el driver del switch crea en tiempo de ejecución (el device tree se analizó el 2026-09-14). Los nombres del log del kernel son los de tiempo de ejecución. ## Qué hace el agente con la tabla El agente lee el modelo de placa del device tree al arrancar — `/proc/device-tree/model` dice `RB5009` incluso sin privilegios — y, en una placa que está en su tabla, nombra los puertos sin preguntar a RouterOS: - todo registro del log del kernel cuyo texto nombra un puerto lleva los dos nombres y lo que le ha pasado a ese puerto: `iface`, `ros_iface` y `kind` en el suceso, etiquetas `port` y `kind` en las filas de InfluxDB e `iface=eth1 ros_iface=ether2 port_event=own-address` en una línea de Loki; - `/metrics` añade `mikroscope_kmsg_port_records_total{port,kind,level}`, con el nombre de RouterOS como `port` en una placa que está en la tabla, el nombre del kernel en una placa que no lo está, y ninguna serie de puerto en un equipo cuyo device tree no declara modelo. Es un subconjunto de `mikroscope_kmsg_records_total`, no una partición: los registros que no nombran ningún puerto no aparecen en él; - la detección `link-flap` del colector usa el nombre de puerto como clave y lee esa misma clasificación: un parpadeo son registros `link-up` y `link-down` de un puerto, contados, no el texto analizado por segunda vez; - `mikroscope status` muestra la placa y si tiene correspondencia, por ejemplo `eth1 (ether2)`; `/healthz` y `/capabilities` llevan la placa, y `/capabilities` y `mikroscope_device_info` llevan la cadena de evidencia de la tabla como `ports_from`, para que nadie tenga que fiarse de la correspondencia a ciegas. **Una placa desconocida no recibe nombres de puerto, ni siquiera adivinados.** El desplazamiento de uno no se aplica a una placa que nadie ha medido, porque un nombre de puerto equivocado dicho con seguridad manda a alguien al cable equivocado. En una placa así, `status` lo dice y pide el par: baja un puerto, mira qué `ethN` nombra el log y envía ese par junto con la cadena de la placa. ## Qué clase de suceso fue El nombre del puerto por sí solo no dice qué le ha pasado, así que todo registro que nombra uno se clasifica también, con `procfs.KmsgKind` sobre el texto del registro. Las clases, y las formas de registro que escribe el kernel de RouterOS (RB5009, kernel 5.6.3, registros vistos entre el 2026-09-12 y el 2026-09-15): | `kind` | el registro | | ------------- | ----------------------------------------------------------------------------------- | | `link-up` | `eth8: link up, 1Gbps, full-duplex`, `eth1: Link is Up - 1Gbps/Full`, `eth1: phy link up` | | `link-down` | `eth1: link down` | | `stp-` | `br0: port 2(eth1) entered blocking state` — `blocking`, `listening`, `learning`, `forwarding`, `disabled` | | `own-address` | `br0: received packet on eth1 with own address as source address (addr:…, vlan:0)` — la firma del bucle de nivel 2 | | `other` | cualquier otra cosa que nombre un puerto, incluido `eth1: link becomes ready`, que es la configuración de direcciones IPv6 dándose cuenta de la portadora y no una transición propia | El agente clasifica en el momento de leer y envía `kind` dentro del registro, y un colector que tiene delante un agente más antiguo cuyos registros no lo llevan los clasifica él mismo, con la misma función. Es lo que pasa hoy en el router de referencia: el agente que corre allí no se ha vuelto a desplegar con esta compilación, así que su `/metrics` todavía no lleva la etiqueta `kind` y el trabajo lo hace el colector. **Cuatro registros no son cuatro fallos.** A un `link-up` normal le siguen `stp-blocking`, `stp-learning` y `stp-forwarding` mientras el bridge devuelve el puerto al servicio. ## Del nombre por defecto al nombre actual La tabla corresponde a los nombres **por defecto** de RouterOS. Un puerto renombrado en el router (`ether5` → `WAN`) tiene un nombre que la tabla no puede conocer, así que el colector se lo pregunta a la capa de la API. Su inventario de interfaces —leído una vez antes del primer sondeo del kernel y otra vez cada `--labels-every`, cinco minutos por defecto— guarda de cada interfaz su nombre de fábrica, su nombre actual, su comentario y sus listas de interfaces, y el colector lo usa en cada registro que nombra un puerto para: - sustituir el nombre por defecto por el **actual**, de modo que quien haya renombrado `ether5` como `WAN` lea `WAN` en el suceso, en la etiqueta `port` de InfluxDB, en la línea de Loki y en la clave de `link-flap`; - añadir `label`, el comentario del puerto, y `role`, sus listas de interfaces — así el parpadeo de `eth5` llega como `ether6`, etiqueta `Unused`, y no como un número de netdev que alguien tiene que buscar. Sin capa de la API el registro se queda con el nombre por defecto de la placa y sin etiqueta, que es lo que puede sostener. ## Dónde un mensaje del kernel se convierte en un cable Dos paneles de la fila **Kernel log** de los dashboards son donde se juntan el nombre, la clase y el comentario: - **Port events from the kernel log, per port and kind** — barras por intervalo, una serie por puerto y clase, con `sum by (port, kind) (increase(mikroscope_kmsg_port_records_total[$__interval]))` en Prometheus y con las etiquetas `port`/`kind` en InfluxDB; - **Port events in the window, per port** — una tabla con una fila por puerto que el log haya nombrado: el puerto, su `label` y su `role`, y una columna por clase (link down, link up, own address (loop), STP blocking, STP disabled, STP learning, STP forwarding, other). Los dos son paneles que se sabe que pueden salir vacíos —un conjunto de puertos callado es el estado sano— y la forma de Prometheus de cada uno lee el `/metrics` del colector, cuya copia de la familia lleva un `kind` en todo registro de puerto venga como venga del agente. En un almacén InfluxDB la columna `kind` no existe hasta que se escribe el primer registro de puerto clasificado por clase, así que las dos consultas se validaron el 2026-09-16 contra una tabla sintética en ese mismo InfluxDB 3, porque el almacén en producción todavía no tenía esa columna. Junto a ellos se publican dos reglas de alerta, en los dos ficheros de aprovisionamiento. Las dos leen `/dev/kmsg` a través del agente y no preguntan nada a RouterOS: - `mikroscope-l2-loop`, crítica: cualquier registro `own-address` en cinco minutos. En el RB5009 de referencia esa firma corrió a 1,49 registros/s durante horas el 2026-09-12 mientras todos los contadores de RouterOS parecían sanos. - `mikroscope-port-link-down`, aviso: cualquier registro `link-down` en cinco minutos — el suceso suelto, ya que de las repeticiones se ocupa `link-flap`. La forma de InfluxDB de cada una necesita un almacén que ya haya guardado un registro de puerto clasificado por clase; antes de eso la consulta falla al planificarse. > **Sin probar** > > No se ha visto saltar ninguna de las dos reglas de puerto con un suceso real: se escribieron > contra las formas de registro medidas en el RB5009 de referencia y no se han puesto delante de un > bucle ni de una caída de enlace en vivo. El recorrido de render fila a fila con Chromium sin > interfaz del 2026-09-15 cubrió 168 paneles de InfluxDB y 130 de Prometheus, y no se ha repetido > para estos dos. La tabla de puertos sobre la que se apoya todo esto tiene su propio límite. > **Cierto en este equipo, no en el tuyo** > > El desplazamiento de uno, la posición de la jaula SFP+ y la reutilización de `port 7` se > observaron en un RB5009UG+S+ con RouterOS 7.24.2. No se afirman para ninguna otra placa, y el > agente no los aplica a ninguna. ## Véase también - [Un bucle que solo veía el kernel](/mikroscope/es/playbooks/loop/): el fallo que hizo que esta correspondencia valiera una hora. - [El flujo de datos del equipo](/mikroscope/es/sinks/device-info/): por dónde viajan la placa y `ports_from`. - [Detecciones](/mikroscope/es/sinks/detections/): la regla `link-flap`, que usa estos nombres como clave. - [La capa de la API de RouterOS](/mikroscope/es/sinks/api-tier/): el inventario de interfaces que convierte un nombre por defecto en el actual, con su comentario y sus listas. - [Reglas de alerta](/mikroscope/es/dashboards/alerts/): las reglas de bucle y de caída de enlace que disparan estos registros. --- # Cuando algo no funciona Los síntomas que produce este proyecto, con las palabras que ves de verdad —un error de RouterOS, un contenedor que se cierra, un panel vacío, un destino que descarta—, con lo que significa cada uno y la página que lo explica. Source: https://jmrplens.github.io/mikroscope/es/reference/troubleshooting/ Cada página de aquí explica una cosa a fondo. Esta es el índice al que acudes cuando algo ya está roto: busca la línea que estás mirando y te dice qué significa y dónde está la explicación. ## Instalando | Ves | Significa | | --------------------------------------------------------------------- | ---------------------------------------------------------------------- | | `doctor`: `device-mode container=no` | [El paso que nadie puede hacer en remoto](#device-mode-containeryes) | | `doctor`: `container package installed and enabled … found=0` | [El paquete no está en el router](#sin-paquete-container) | | `doctor`: `architecture matches --arch … router=arm` | Vuelve a ejecutar con el `--arch` que nombra | | RouterOS: `unknown parameter privileged` | [RouterOS anterior a 7.24](#unknown-parameter-privileged) | | Registro del contenedor: `exec format error` | [La imagen equivocada para la placa](#exec-format-error) | | `no Go toolchain on PATH` | Instala con `--remote-image` o `--agent-tar` en su lugar | | `--agent-tar …: this is not a mikroscope agent image` | El artefacto equivocado — mira [qué tar](/mikroscope/es/install/routes/#qué-tar) | | `doctor`: `registry-url is https://ghcr.io … registry-url=…` | [El host del registro es global](#el-host-del-registro) | | `doctor`: falla `free flash ≥ …` | `--disk tmpfs` o `--ephemeral`, o libera espacio en la flash | ### device-mode container=yes MikroTik pone los contenedores detrás de un interruptor que no se puede accionar por la red. `/system/device-mode/update container=yes` lo empieza, y entonces la consola pide una confirmación física —el botón de reset, o un ciclo de alimentación— en cinco minutos. Ninguna opción, ni script, ni versión de esta herramienta puede hacer ese paso por ti. Es lo primero que hay que preparar, porque todo lo demás espera a eso: [Lo que necesita el router](/mikroscope/es/install/prerequisites/). ### Sin paquete container El paquete `container` es una descarga aparte de mikrotik.com, por arquitectura y por versión de RouterOS. Súbelo, reinicia y luego `/system/package/enable container`. `doctor` lo cuenta como presente solo cuando está instalado **y** no deshabilitado. ### unknown parameter privileged RouterOS 7.24 añadió `privileged=`, y el paso del contenedor lo escribe, así que una 7.x anterior falla ahí —después de haber subido el tar, que es la razón de que la instalación se lo lleve de vuelta—. O actualizas RouterOS, o instalas con `--privileged=false` y lees antes [qué te da privileged](/mikroscope/es/limits/privileged/): sin eso el agente no puede leer `/dev/kmsg`, y el registro del kernel es donde empiezan varios de los playbooks de este proyecto. ### exec format error El contenedor arranca y muere al momento, y el registro dice `exec format error`. La imagen es de otra arquitectura que la placa —y en ARM de 32 bits, «arm» no es una sola arquitectura—. La documentación de contenedores de MikroTik dice que los equipos con CPU EN7562CT, la serie hEX Refresh, «solo admiten imágenes de contenedor arm32v5»; sus demás placas ARM de 32 bits ejecutan un espacio de usuario ARMv7. Una imagen ARMv5 funciona en las dos, una ARMv7 no arranca en las primeras. Así que: - Con `--remote-image` esto no puede pasar: el índice publicado lleva las cuatro plataformas y el router reconoce la suya. - Con `--agent-tar`, toma `mikroscope-agent-armv5.tar` cuando la placa sea ARM de 32 bits y no tengas la certeza de cuál de las dos es. - Compilando desde un checkout, `--goarm 5` es el valor por defecto por lo mismo. [Qué tar](/mikroscope/es/install/routes/#qué-tar) es la tabla. ### El host del registro `/container/config registry-url` es un único ajuste global de RouterOS, compartido con todos los demás contenedores del equipo, y mikroscope lo lee y nunca lo escribe. La referencia de Docker Hub funciona tal cual porque es a donde apunta RouterOS de fábrica; la de GHCR necesita cambiar antes ese ajuste, lo que lo cambia también para todos los demás de ese router. ## El agente está instalado y no responde nada ```sh mikroscope status ``` Eso imprime los recuentos de propiedad y, si alcanza al agente, su salud. Si los recuentos están y la salud no, el contenedor está corriendo y lo que falla está entre tú y él: - **El cortafuegos.** Dos reglas se comen este tráfico habitualmente, y ninguna es evidente: [Las dos trampas del cortafuegos](/mikroscope/es/install/firewall/) es esa página, y `doctor` comprueba las dos pertenencias a listas que las evitan. - **La ruta.** El agente responde en su `/30`, del lado LAN del router. Un colector en otra máquina llega de las formas que enumera [Llegar al agente](/mikroscope/es/install/reaching-the-agent/). - **El contenedor nunca arrancó.** `/container/print detail` en el router, y `/log/print where topics~"container"`. ## Llegan datos y algo está vacío | Ves | Significa | | ----------------------------------------------------- | -------------------------------------------------------------------------- | | Ningún `events`, nunca | `/dev/kmsg` necesita `privileged=yes` | | Ningún panel de PMU, ni ciclos ni instrucciones | `perf_event_open` no está disponible en ese kernel o placa | | Un panel dice **No data** y los demás están bien | Esa medida no se produce en este equipo; el dashboard tiene una fila para eso | | Un panel muestra una insignia roja de error | La consulta falló: la fuente de datos, no los datos | | `forward` imprime `… dropped` para un destino | [El destino no pudo seguir el ritmo](#un-destino-está-descartando) | | Loki lo aceptó todo y una consulta no devuelve nada | Un envío no es consultable hasta que se vuelca el trozo | | Los números paran en un momento redondo y siguen | Un hueco: el anillo dio la vuelta antes de que el colector tirara de él | | El tier de kernel se para en seco y el de API sigue | [El agente se reinició](#el-agente-se-reinició-y-el-tier-de-kernel-se-paró) | Los dashboards llevan una fila llamada **«Not available on this device»** justamente para esto: los paneles cuya medida no produce el kernel o la placa se mueven allí en vez de dejarlos dibujando una gráfica vacía entre los demás. `mikroscope dashboards check` le pregunta a la fuente de datos qué medidas tiene de verdad y hace esa separación para tu almacén: [Importar y comprobar](/mikroscope/es/dashboards/import-and-check/). ### El agente se reinició y el tier de kernel se paró El agente numera sus muestras desde 1 en cada arranque, así que un agente que se reinicia —una actualización, un contenedor que se reinicia, un arranque del router— tiene un número de secuencia más alto muy por debajo del cursor del colector. El colector se entera en su siguiente lectura de estado, que es una vez por minuto, registra ```text agent restarted: its newest sample is 571 and the cursor was 1737212; resuming from 1 ``` y sigue desde la muestra más antigua del anillo nuevo, así que lo que el agente tomó mientras nadie recogía se recupera en vez de saltárselo. El minuto de muestras que va del reinicio a la lectura de estado se pierde con el contenedor, no por culpa del colector. Antes de 1.0.4 no se enteraba: el cursor se quedaba donde estaba, el anillo del agente respondía un lote vacío a cada petición, y el tier de kernel se paraba para siempre mientras el de API seguía contando y los destinos seguían escribiéndose — así que la ejecución parecía sana. Si estás en una versión anterior, reinicia el colector después de reiniciar el agente: toma su cursor de la lectura de estado del arranque. ### Un destino está descartando Todos los destinos de red tienen cola, y la cola está acotada —`--queue-seconds`, 60 por defecto—. Un destino que no puede seguir el ritmo pierde el lote más viejo en lugar de frenar el bucle de lectura, y la cuenta se imprime al final de la ejecución y se exporta como métrica. Es una decisión deliberada, y [el colector](/mikroscope/es/sinks/) la explica: el anillo del agente es lo que protege los datos, y un colector esperando a un almacén lento perdería más de lo que pierde el almacén. ## Leer lo que muestra Cuando los datos ya llegan, la pregunta cambia de «por qué está roto esto» a «qué me está diciendo esto». Eso es otro conjunto de páginas: [Cómo leer lo que muestra](/mikroscope/es/playbooks/) —primero la forma de un router en reposo, y después siete fallos leídos contra ella—. > **Pregunta a las herramientas antes de seguir leyendo** > > `mikroscope doctor` nombra el arreglo de cualquier cosa que falte en el router, `mikroscope status` > dice qué está instalado y si responde, y `mikroscope dashboards check` ejecuta la consulta de cada > panel e imprime cuáles volvieron sin nada. Entre los tres responden casi toda esta página para tu > propio equipo en vez de en general. ## Véase también - [Lo que necesita el router](/mikroscope/es/install/prerequisites/): los tres requisitos, y qué comprueba `doctor`. - [Las dos trampas del cortafuegos](/mikroscope/es/install/firewall/): por qué el agente puede estar corriendo y ser inalcanzable. - [Qué te da privileged](/mikroscope/es/limits/privileged/): qué se pierde sin eso, fuente por fuente. - [Cómo se prueba el proyecto](/mikroscope/es/reference/testing/): qué se ha demostrado y cómo, si te estás preguntando si eres tú o el proyecto. --- # Cómo se prueba el proyecto a sí mismo Las tres capas de pruebas — los bytes que cada destino pone en el cable, si un almacén real los acepta y si las consultas de los propios paneles responden —, qué demuestra cada una, qué no demuestra ninguna y la orden de cada una. Source: https://jmrplens.github.io/mikroscope/es/reference/testing/ Un destino puede equivocarse en tres sitios, y cada uno pide una prueba distinta. Puede codificar los bytes equivocados. Puede codificar bytes que un almacén rechaza — algo que un servidor de captura nunca advierte, porque responde 204 a todo. Y puede escribir algo que el almacén guarda pero ningún panel puede volver a leer. El proyecto tiene por eso tres capas, y son tres órdenes. | Capa | Orden | Docker | Qué demuestra | | ------------------- | ---------------------- | ------ | ----------------------------------------------------------------------- | | 1, el contrato | `make test-e2e` | no | los bytes exactos que cada destino pone en el cable | | 2, los almacenes | `make test-e2e-docker` | sí | que un almacén real los acepta y los devuelve sin cambiarlos | | 3, los paneles | la misma orden | sí | que la consulta de cada panel responde contra lo que los destinos escribieron | Ninguna de las tres toca un router. Las capas 1 y 2 ejecutan el colector contra un agente simulado que sirve muestras enlatadas, y la capa 1 ejecuta además el binario real del agente contra `testdata/proc/rb5009`, un árbol `/proc` y `/sys` capturado del equipo de referencia. Eso es lo que hace que una ejecución sea reproducible en cualquier máquina, y lo que deja al router fuera del bucle. ## Capa 1 — el contrato `make test-e2e` compila los dos binarios y apunta cada destino a un receptor dentro del binario de prueba: servidores HTTP de captura para InfluxDB, Loki, OTLP, Elasticsearch y Telegraf, escuchas TCP y UDP para Graphite y los modos de socket de Telegraf, ficheros reales para los destinos de fichero y SQL, la salida estándar del propio proceso, y un scrape del `/metrics` del colector para Prometheus. Cada receptor comprueba lo que llegó, byte a byte. No necesita router, ni Grafana, ni base de datos, ni contenedor, ni red: toda dirección que enlaza o marca está en loopback. `make test-e2e-offline` lo demuestra en vez de afirmarlo, ejecutando la batería entera dentro de un espacio de nombres de red que no tiene más que `lo`. ## Capa 2 — los almacenes `make test-e2e-docker` levanta nueve almacenes con docker compose, ejecuta el mismo colector contra el mismo agente simulado con todos los destinos apuntando a ellos y luego le pregunta a cada almacén por su propia API. El JSONL del destino de fichero es el oráculo contra el que se compara cada almacén, valor a valor y no solo por recuento. | Almacén | La pregunta que se le hace | | ----------------------- | --------------------------------------------------------------------------- | | InfluxDB 3 Core | SQL por HTTP: las tablas, un recuento de filas por tabla, cada valor `ctxt` | | PostgreSQL 18 | el guion del destino SQL por `psql`, luego recuentos y el rango de `ctxt` | | Elasticsearch 9 | `_search` con una agregación por tipo, y un documento entero | | Loki 3 | `query_range` para las etiquetas de esta ejecución, y el texto de cada registro | | Graphite | `metrics/find` para el árbol, `render` para los puntos y su orden | | OpenTelemetry Collector | lo que decodificó, escrito de vuelta como OTLP/JSON | | Telegraf 1.39 | el protocolo de línea que analizó: medidas, etiquetas, tipos de campo, marcas de tiempo | | Prometheus 3 | un scrape del exportador, contra la exposición que sirvió | Cada uno está ahí porque ese producto rechaza algo que un servidor de captura acepta: - **InfluxDB 3** fija una columna como etiqueta o como campo la primera vez que ve la tabla y rechaza una escritura posterior que no coincida. - **Carbon** no responde nada en absoluto: un punto más viejo que su archivo más largo, o un nombre del que whisper no puede hacer una ruta, se descarta en silencio. - **Loki** responde 204 a un envío que no es consultable hasta que el trozo se vuelca, y rechaza entradas desordenadas por flujo, en el cuerpo. - **Elasticsearch** infiere un mapeo del primer documento que ve para un campo y luego rechaza otro posterior que no encaje — por documento, dentro de una petición bulk que aun así responde 200. - **Telegraf** es un analizador real de protocolo de línea: un espacio sin escapar en el valor de una etiqueta, o un campo sin tipo, se descarta y el lote sigue siendo 204. - **PostgreSQL** es lo único que puede decir si el guion del destino SQL es SQL válido, si los tipos que eligió aguantan los valores que emite y si sus claves primarias chocan en una ejecución real. ## Capa 3 — los paneles La misma orden importa después los dos dashboards en un Grafana real, apuntado al almacén que la ejecución acaba de llenar, y pasa la consulta de cada panel por la propia `/api/ds/query` de Grafana. Ahí es donde un panel falla por motivos a los que ninguna prueba unitaria llega: un tipo que devuelve una agregación y el complemento de la fuente de datos no sabe decodificar, una macro que el complemento escapa, una columna que el almacén no tiene. La primera ejecución completa, el 2026-09-16, encontró uno: la tabla de eventos de puerto nombra `label` y `role`, que el destino escribe en una fila del registro del kernel solo desde el inventario de la capa de la API. Una columna que no está en la tabla no es una columna vacía en InfluxDB 3 — es `Schema error: No field named label` y un panel que no puede dibujarse. ## Cómo ejecutarlo ```sh make test-e2e-docker # todo; la pila se levanta y se apaga con la ejecución make e2e-docker-up # deja la pila levantada, para una ejecución concreta go test -v -tags dockere2e -run TestLoki ./test/e2e/docker/ make e2e-docker-down ``` El paquete está tras la etiqueta de compilación `dockere2e`, así que `make test` y todos los trabajos por defecto de la CI no compilan nada de él: la etiqueta es la forma de pedir nueve contenedores. `make lint` lo comprueba de tipos para que no se pudra, y el flujo semanal de E2E y la puerta de publicación lo ejecutan. Medido en la máquina de desarrollo el 2026-09-16: la pila se levanta en 55 a 81 s con las imágenes ya descargadas, y la batería entera tarda de 75 a 100 s desde cero. > **Linux, para dos de los nueve** > > Prometheus y Grafana se ejecutan sobre la pila de red del anfitrión, porque > Prometheus es el único destino que se consulta en vez de recibir envíos y > tiene que alcanzar el exportador del colector en el anfitrión. Un contenedor > en una red puente llega al anfitrión por la pasarela del puente, y ese camino > pasa por la cadena INPUT del anfitrión, que un cortafuegos que deniega por > defecto descarta. Los otros siete servicios son contenedores normales de red > puente. ## Lo que las tres capas siguen dejando a una persona > **Qué no demuestra ninguna de ellas** > > Que un destino funcione desde el router. Las muestras de las tres capas vienen > de un agente simulado o de un árbol `/proc` capturado, nunca de un equipo > vivo, y la pila de contenedores se ejecuta en la máquina de desarrollo y no a > través del veth del router. Fichero, Prometheus e InfluxDB 3 han llevado > muestras reales del RB5009 de extremo a extremo; los otros siete no. ## Véase también - [Dónde está el proyecto](/mikroscope/es/about/status/): qué se ha ejecutado contra el equipo de referencia y qué no. - [El colector y sus destinos](/mikroscope/es/sinks/): qué escribe cada destino. - [Importar y comprobar los dashboards](/mikroscope/es/dashboards/import-and-check/): el mismo `dashboards check` que ejecuta la tercera capa, contra tu almacén. --- # En qué punto está Qué funciona de extremo a extremo, cuánto cuesta en el único equipo donde se ha ejecutado, qué se encontró y no se ha corregido, y qué publica la versión. Source: https://jmrplens.github.io/mikroscope/es/about/status/ Esta página responde a lo que necesita saber primero quien está decidiendo si probar mikroscope: qué partes funcionan de extremo a extremo, en qué hardware se demostró, cuánto cuesta hoy el observador, qué defectos se conocen y siguen abiertos, y si hay algo que descargar. Está contrastada con el código a fecha del 2026-09-16. ## Qué funciona de extremo a extremo Todo lo siguiente, salvo las baterías de pruebas y los siete destinos señalados como tales, se ha ejecutado contra el RB5009 de referencia (RouterOS 7.24.2), y no solo contra simulaciones: - **El agente** lee `/proc`, `/sys`, `/dev/kmsg` y los contadores de `perf_event_open` del kernel compartido con un temporizador fijo de 1 a 100 Hz (10 Hz por defecto; medidos 10, 50 y 100 Hz), guarda las muestras en un anillo y las sirve: `/healthz`, `/capabilities`, `/snapshot`, `/stream`, `/metrics`, y los endpoints de captura por disparo `/captures` y `/capture`. - **La CLI de despliegue** lo instala, lo actualiza y lo retira — `doctor`, `plan`, `install`, `status`, `upgrade`, `uninstall` — listando cada escritura antes de hacerla y verificando cada retirada por recuento de propiedad. El 2026-09-12, `doctor` → `install` → `status` → `upgrade` → `uninstall` dejó el `/export` del router idéntico byte a byte, comparado por hash en memoria y sin escribirlo nunca a disco. El agente respondió 3 s después de la instalación, con un tiempo de ida y vuelta de 5–7 ms. El 2026-09-17 se ejecutaron las cuatro rutas de instalación contra el mismo equipo, una tras otra y cada una retirada antes de la siguiente: la compilación desde el checkout, el tar del agente publicado, la descarga que el propio router hizo desde Docker Hub y el script de `plan --rsc` importado en el router sin CLI ninguna en la instalación misma. El agente respondió en todas, y el `/export` posterior a las cuatro era idéntico byte a byte al anterior a ellas. - **`record`, `mark` y `plot`** funcionan de extremo a extremo. Una grabación de 60 s a 10 Hz el 2026-09-12 dio exactamente 600 muestras, 0 huecos y −7 ms de desfase de reloj. - **`forward`**, el colector, une la capa del kernel con la capa de la API de RouterOS y escribe en diez destinos. Fichero, Prometheus e InfluxDB 3 se han ejecutado desde el RB5009; por Loki, OTLP, Graphite, Elasticsearch, SQL, Telegraf y stdout todavía no han pasado muestras del router, pero todos ellos escriben ya en el producto real — InfluxDB 3, PostgreSQL, Elasticsearch, Graphite, Loki, un OpenTelemetry Collector, Telegraf y Prometheus en contenedores — y la batería vuelve a leer cada uno por su propia API (2026-09-16). Su etapa de derivación añade valores derivados y eventos de detección junto a las filas en bruto, y emite un flujo de datos del equipo una vez por cada hash de capacidades. - **Cinco dashboards de Grafana**, uno por almacén —InfluxDB 3, Prometheus, PostgreSQL, Graphite y Elasticsearch—, generados a partir de una sola lista de paneles, con reglas de alerta para los tres cuyo lenguaje de consulta es el de las reglas. A los dos almacenes SQL se les hace la misma pregunta en dos dialectos: los paneles de PostgreSQL son los de InfluxDB reescritos, y las 209 consultas las planifica un PostgreSQL real en la batería de contenedores. Graphite y Elasticsearch llevan menos paneles a propósito (41 y 30 frente a 171): Graphite no tiene etiquetas y Elasticsearch no tiene documentos anidados, así que lo que no pueden expresar está ausente en vez de equivocado. Los cinco se importan en un Grafana real sobre los almacenes que llenó la batería, y se le pregunta a cada panel: el 2026-09-17 devolvieron datos 57, 98, 136, 35 y 28 paneles, y no falló ninguno. El 2026-09-16, en Grafana 13.2.1, contra el RB5009 con los disparadores por defecto y un `forward` de 30 minutos hacia los dos almacenes, `dashboards check` dio por buenos 171 paneles de InfluxDB (0 fallidos, 10 vacíos conocidos tolerados) y 133 paneles de Prometheus (0 fallidos, 9 vacíos conocidos tolerados). El recorrido sin interfaz, fila a fila, de los dos dashboards — 0 insignias de error y 0 "No data" — es del 2026-09-15 y cubre 168 y 130 paneles; no se ha repetido para los dos paneles de eventos de puerto ni para la tabla de inventario de interfaces, que no cubre. Un recorrido del 2026-09-14, contra una captura de 10,5 h del mismo equipo, renderizó 140 paneles de InfluxDB con el mismo resultado. - **Una batería de extremo a extremo** compila los dos binarios y los ejecuta contra un árbol `/proc` capturado del equipo de referencia y contra un agente simulado, con un receptor por cada protocolo de destino que comprueba los bytes. No necesita router, ni Grafana, ni red, y se ejecuta en la CI. - **Una segunda batería contra los almacenes mismos** (`make test-e2e-docker`) levanta nueve de ellos con docker compose, ejecuta el mismo colector contra el mismo agente simulado con todos los destinos apuntando a ellos y luego le pregunta a cada almacén por su propia API: SQL contra InfluxDB 3, `psql` contra el guion que escribió el destino SQL, una búsqueda contra Elasticsearch, `query_range` contra Loki, `render` contra Graphite, el OTLP que el colector decodificó, el protocolo de línea que Telegraf analizó y un scrape del exportador almacenado en Prometheus. Después importa los dos dashboards en Grafana y pasa la consulta de cada panel por la propia API de Grafana. Necesita Docker y ningún router: las muestras son las mismas enlatadas, así que la ejecución es reproducible en cualquier máquina. La primera ejecución completa, el 2026-09-16, encontró un panel que nombraba dos columnas que el almacén solo tiene cuando se ejecutó la capa de la API. ## Lo que cuesta hoy el agente Con los valores por defecto de la instalación — 10 Hz, suelos por fuente por defecto, un anillo de 300 s — el agente cuesta **2,85 % de un núcleo y 31,3 MiB de RSS**, leídos de su propio cgroup en régimen estacionario con el anillo lleno: Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · 2026-09-15 · ventanas de 60 s en régimen estacionario (con el anillo ya lleno), conjunto completo de fuentes, colector reenviando a la vez a fichero, a una exposición Prometheus y a InfluxDB 3 Las ejecuciones medidas: | cadencia | suelos | CPU de un núcleo | µs/muestra | RSS | ticks retrasados | huecos / descartes | | --- | --- | --- | --- | --- | --- | --- | | 10 Hz (por defecto) | por defecto | **2,85 %** | 2 856 | 31,3 MiB | **0** | 0 / 0 | Eso está por encima del presupuesto de 2 % de un núcleo y 16 MiB de RSS. El presupuesto es orientativo, no un contrato: el coste depende del equipo, del conjunto de fuentes y del tamaño del anillo. El presupuesto de imagen es 8 MiB; el único tamaño de imagen registrado es 6,1 MiB, y no lleva fecha. Dos reglas mantienen honesta la cifra de coste: esperar a que se llene el anillo (`BUFFER_S`) antes de citar una cifra de régimen estacionario — en el RB5009, con un límite blando de memoria de 14 MiB, una lectura tomada en el primer minuto tras la instalación dio un 1,47 % de un núcleo frente a un 9,38 % en régimen estacionario — y leer el coste de `/metrics` en vez de un `/snapshot` grande, cuya respuesta de ~1,5 MB tiene que serializar el agente. ### Coste a 10 Hz, por configuración Todas las filas se midieron en el mismo RB5009 a 10 Hz, y cada una es una ventana sin dispersión registrada. Son los ajustes que un lector puede elegir, así que la tabla dice lo que cuesta cada uno, no lo que valía el número en algún momento anterior. | Configuración | CPU de un núcleo | RSS | Medido | Nota | | ------------------------------------------------------------------------ | ---------------------------------------------------------------------- | ------------------------------ | ---------- | ------------------------------------------------------------------------------------------------------------- | | Cada fuente leída en cada tick | 2,43 % | no registrado | 2026-09-12 | por encima del presupuesto | | Anillo lleno, `MEM_LIMIT_MB` 14 | 9,38 % (9 374 µs/muestra) | no registrado | 2026-09-12 | un anillo de 300 s de líneas de unos 2,4 kB ocupa ~7,3 MB; el GC de Go corre sin pausa | | Anillo lleno, `--mem-limit-mb 40`, `--memory-max 64M` | 1,39 % (1 388 µs/muestra) | 25,13 MiB | 2026-09-12 | 0 ticks retrasados | | Contadores PMU activados, un `forward` en marcha escribiendo en InfluxDB | 1,72 % | no registrado | 2026-09-12 | 0 ticks retrasados; 2 400 muestras reenviadas, 0 huecos, 0 descartes | | Los valores por defecto de la instalación | 2,85 % | 31,3 MiB | 2026-09-15 | la cifra de arriba | ## Un equipo, una versión de RouterOS Todas las cifras tomadas en el equipo que aparecen en esta documentación vienen de un único RB5009UG+S+ con RouterOS 7.24.2, el router de producción del propietario. No hay equipo de laboratorio. > **Sin probar** > > Una segunda placa de cualquier tipo. El hEX S (2025) — RouterOS de 32 bits sobre un chip ARM64, > que es para lo que existe la compilación `linux/arm` del agente — no ha llegado, así que ni la > ruta del desbordamiento de contadores de 32 bits ni la imagen `linux/arm` se han ejecutado en > hardware. Ningún host RouterOS x86_64. Ninguna versión de RouterOS distinta de 7.24.2. Nada que > necesite un reinicio, que espera a una ventana de mantenimiento. La batería de extremo a extremo > solo se ha ejecutado en Linux; no se ha ejecutado en macOS ni en Windows. ## Encontrado y sin corregir Contrastado con el código a fecha de esta página: - **El resumen final de `forward` va a stdout**, el mismo flujo en el que el destino `--stdout` escribe los registros, así que `forward --stdout=lp | telegraf` termina cada ejecución con líneas que el consumidor no puede analizar. - **Un `--token` erróneo no hace fallar la ejecución.** Cada petición queda registrada como `401 Unauthorized`, y la ejecución termina en el plazo de `--for` con código de salida 0 y `forwarded 0 kernel samples`. La comprobación de salud que hace `forward` antes de empezar a pedir datos lee `/healthz`, que no necesita token, así que la supera. El mensaje es correcto; el código de salida no le dice nada a un fichero de unidad. - **Números de secuencia duplicados en la ejecución nocturna del 2026-09-13.** La ejecución a 50 Hz escribió valores de `seq` repetidos en InfluxDB; los paneles llaman a esas filas "duplicate sample (seq repeated)". La explicación más probable, sin verificar, es que el cliente HTTP de Go reenviaba un POST por una conexión reutilizada ya muerta después de que el servidor lo hubiera confirmado. Los destinos InfluxDB, Loki, OTLP, Elasticsearch y Telegraf rechazan ese reenvío, así que una conexión muerta es un error que el destino reintenta y contabiliza. Ninguna ejecución posterior a esa noche muestra si los duplicados han desaparecido. - **Graphite nombra las zonas térmicas por índice.** El índice es deliberado — la cadena `type` de una zona no es única entre zonas — pero la lista de rutas al principio de `internal/sinks/graphite.go` dice `thermal.`. ### Encontrado en el equipo y sin recoger Dos ficheros legibles del equipo de referencia, leídos allí el 2026-09-15, no se recogen: `/proc/cmdline`, que lleva `board=5009 ver=7.24.1` — una segunda identidad del equipo sin la API — y el watchdog hardware en `/sys/class/watchdog/watchdog0`. ## Qué publica la versión La versión actual es la **v1.0.0** — la que hay en `VERSION`, compilada en los dos binarios y que devuelve `mikroscope version`. Una etiqueta `v*` lanza la configuración de GoReleaser, que publica: - **Archivos de la CLI** para linux, darwin, windows y freebsd en amd64, arm64 y arm: `.tar.gz`, y `.zip` en Windows, cada uno con `LICENSE` y `README.md`. - **Archivos del agente** para linux en esas mismas tres arquitecturas, para quien quiera el binario pelado en lugar de una imagen. - **Tar de imagen del agente para cargar a mano**, uno por arquitectura — `mikroscope-agent-arm64.tar`, `mikroscope-agent-arm.tar`, `mikroscope-agent-amd64.tar` —, que es lo que `install --agent-tar` sube al router. - **La imagen del agente en dos registros**, `jmrplens/mikroscope-agent:1.0.0` en Docker Hub y `ghcr.io/jmrplens/mikroscope-agent:1.0.0` en GHCR, cada una un único manifiesto sobre `linux/amd64`, `linux/arm64` y `linux/arm/v7`, que `install --remote-image` hace descargar al router. RouterOS toma el host del registro del ajuste global `/container/config registry-url`, que viene puesto en `https://registry-1.docker.io`, así que la referencia de Docker Hub no necesita fijar nada en el equipo y la de GHCR necesita cambiar antes ese ajuste. - **`checksums.txt`**, que cubre todos los archivos y todos los tar de imagen, una firma cosign sin claves sobre él y un SBOM SPDX por archivo, firmado a su vez. Instalar desde una versión publicada no necesita ni toolchain de Go ni copia del repositorio. Con una copia del repositorio y Go 1.27 se instala en cambio un agente compilado desde tu propio árbol: `make build` para la CLI y `make build-agent` para el agente, que `install` e `image` compilan también por su cuenta. Las cuatro maneras de llevar el agente a un router, y lo que necesita cada una, están en [Instalar el agente](/mikroscope/es/install/). Lo que la versión publica para `linux/arm` y `linux/amd64` está compilado de forma cruzada y comprobado en CI. De las tres arquitecturas, solo `arm64` se ha ejecutado en hardware: en el único RB5009 de arriba. ## Véase también - [El coste del observador](/mikroscope/es/cost/): el presupuesto, la cifra actual y cómo medirla en tu propio equipo. - [El techo de muestreo](/mikroscope/es/cost/rate-ceiling/): las cinco ejecuciones medidas a 10, 50 y 100 Hz. - [El fichero y los demás destinos](/mikroscope/es/sinks/other/): los diez destinos en los que escribe `forward`. - [Linaje y licencia](/mikroscope/es/about/lineage/): de dónde vienen el código de despliegue y el cliente de la API. --- # La marca Nueve barras de muestra y la línea de su propia media, por qué nada de ello se dibuja con opacidad parcial, y el contraste que mide cada tono contra su página. Source: https://jmrplens.github.io/mikroscope/es/about/brand/ Esta página responde qué dice la marca de mikroscope, cómo está hecha y por qué sus colores son los que son — con el contraste de cada tono medido contra el fondo sobre el que se dibuja, y el coste de esa decisión dicho a su lado. ![La marca de mikroscope: nueve barras de muestra, tres de ellas por encima de la línea plana de su propia media](https://raw.githubusercontent.com/jmrplens/mikroscope/main/site/src/assets/mark-inline.svg) ## Un pico por encima de su propia media La marca es geometría, no un dibujo: nueve barras de muestra cuya envolvente es una ráfaga, cruzadas por la línea plana de su propia media. Esa es toda la tesis del proyecto en una forma — una media de un segundo informa de la línea, y el muestreo por debajo del segundo es lo que resuelve el pico que se alza sobre ella. La ráfaga es asimétrica como lo es una real: tres muestras tranquilas, una subida rápida, el pico, una bajada más lenta y otras tres tranquilas. El pico queda en el centro de un número impar de barras, así que la marca se equilibra sobre su propio centro. ## La línea es la media, y se calcula El generador dibuja la línea en la media aritmética de las alturas que recibe, no en un número elegido para que quede bien, y una barra toma el tono fuerte exactamente cuando está por encima de esa media. Así, la frase que dice la marca es una que el código hace cumplir: tres de las nueve muestras están por encima de su propia media, y son las tres a las que va el ojo. `TestTheLineSitsAtTheMeanOfTheBars` hace fallar la compilación si el dibujo y la aritmética se separan. ## Cuatro tonos, dos por tema, y ninguna opacidad Medidos contra el fondo sobre el que se dibuja cada uno, con el contraste tal como lo define WCAG 2.2: | Tema | Fondo | Por encima de la media | En la media o por debajo, y la línea | Separación | | ------ | --------- | ---------------------- | ------------------------------------ | ---------- | | Oscuro | `#0e1316` | `#fbbf24` **11,20:1** | `#c2740a` **5,16:1** | 2,17:1 | | Claro | `#ffffff` | `#633009` **10,73:1** | `#b45309` **5,02:1** | 2,14:1 | Una paleta por tema no es un refinamiento; es la única forma de que la marca se lea en ambos. Un ámbar lo bastante claro para leerse sobre la página casi negra da 1,67:1 sobre blanco (`#fbbf24`), y uno lo bastante oscuro para el blanco desaparece en la página oscura. Sobre blanco, la muestra que está por encima de la media es el tono más oscuro, así que la marca se lee en el mismo sentido en los dos temas. `TestEveryToneClearsAAInItsOwnTheme` vuelve a calcular los contrastes a partir del hexadecimal en cada ejecución y hace fallar la compilación si algún tono da menos de 4,5:1 sobre su propio fondo, o si los dos tonos de un tema están a menos de 2,0:1 entre sí. Un tono editado sin comprobarlo rompe la compilación en vez de publicarse. `TestNothingInTheMarkDependsOnOpacity` vigila la otra mitad de la decisión. ## Por qué las barras tranquilas no son opacidad Una barra tranquila dibujada con el color fuerte a una opacidad baja se ve bien y falla. Compuesto sobre la página, un 0,28 de `#f59e0b` da **1,73:1** sobre el fondo oscuro y un 0,42 de `#a16207` da **1,80:1** sobre blanco, donde AA pide 4,5:1. Llegar a 4,5 subiendo el alfa exige 0,68 en el tema oscuro y 0,96 en el claro — y entonces una barra tranquila es una barra fuerte y la única distinción que la marca existe para hacer desaparece. Así que la separación son dos tonos sólidos. Eso cuesta fuerza. Sólido contra sólido da unos **2,1:1** entre los dos grupos; dibujar la barra tranquila a 0,28 (0,42 sobre blanco) los separa unos 5:1 en el tema oscuro y 2,7:1 en el claro, con alfas que dan 1,73:1 y 1,80:1 contra su propia página y por tanto no llegan a AA. Lo que se gana es que nada en la marca se compone contra un fondo que este repositorio no controla — un README en GitHub, un `og:image` en un cliente de chat, un favicon sobre la interfaz del navegador — de modo que cada número de la tabla es el número que de verdad recibe el lector. ## Una norma de la casa, más estricta que el estándar WCAG 1.4.11 pide **3:1** a un objeto gráfico, y 1.4.3 exime a los logotipos de cualquier mínimo. 4,5:1 en cada barra es una norma de la casa más estricta que el estándar, elegida por el propietario el 2026-09-15 frente al cambio menor de subir los alfas hasta 3:1. ## El ámbar también significa umbral El ámbar también significa _umbral_ en los paneles de este proyecto, donde un panel se pone naranja antes de ponerse rojo. Los dos no chocan literalmente — los paneles usan los colores con nombre propios de Grafana y nunca estos hexadecimales — pero a un lector que ha aprendido en un panel que "ámbar significa mira esto" se le pide leer el mismo tono en la marca del proyecto. Ese es el coste de la elección, hecha a sabiendas (propietario, 2026-09-15). ## Las barras son proporciones, no paquetes Las alturas de las barras no son una captura real. La ráfaga medida en el RB5009 de referencia el 2026-09-15 tuvo un pico de 736 paquetes en una muestra de 20 ms frente a una mediana de 28, y 26× es un rango que ningún cuadrado por sí solo puede representar: las muestras tranquilas se reducen a puntos, o una escala logarítmica aplana precisamente el pico que la marca existe para mostrar. Las alturas de la marca son proporciones de la altura dibujada. ## El favicon es otro dibujo Nueve barras a dieciséis píxeles son un borrón, así que el favicon baja a cinco y conserva el pico por encima de la línea, que es la parte que lleva el significado. El `favicon.svg` del sitio es el único icono sin fondo propio. Lleva las dos paletas y cambia según el `prefers-color-scheme` del propio lector, la señal que sigue la propia interfaz del navegador, así que dibuja `#fbbf24`/`#c2740a` sobre una interfaz oscura y `#633009`/`#b45309` sobre una clara. Cada imagen rasterizada lleva en cambio su propio fondo `#0e1316`, porque un `.ico` no tiene forma de preguntar. ## Un generador, tres familias de ficheros La marca vive como un generador, `cmd/gen_brand`, y no como una carpeta de ficheros dibujados a mano, porque así cambiar la paleta o el número de barras es una edición en vez de nueve en cada uno de una docena de ficheros. Es una herramienta de tiempo de compilación y no es uno de los dos binarios publicados. Desde la raíz del repositorio: ```sh go run ./cmd/gen_brand mark -out brand # la marca y el favicon, por tema go run ./cmd/gen_brand compose -out brand # el banner, la imagen social y el og:image go run ./cmd/gen_brand icons -out site/public # el favicon y los iconos táctiles ``` `mark` es texto puro. `compose` lee los tres fondos `bg-*.png` del directorio en el que escribe y llama a `rsvg-convert` para los PNG que se publican. `icons` llama a `rsvg-convert` y a ImageMagick para el `.ico`, y es el único que escribe fuera de `brand/`. Cada coordenada se escribe con dos decimales, redondeada al más cercano y, en caso de empate, al par, así que los ficheros están pensados para reproducirse byte a byte en cualquier máquina; `TestMarkIsByteForByteReproducible` comprueba que dos ejecuciones en el mismo equipo dan los mismos cuatro ficheros de marca y de favicon. | Fichero | Dónde | Suborden | Qué es | | ------------------------------------------------------ | -------------- | --------- | ---------------------------------------------------------------------------------------------------------- | | `mark-dark.svg`, `mark-light.svg` | `brand/` | `mark` | La marca, una por tema | | `favicon-dark.svg`, `favicon-light.svg` | `brand/` | `mark` | La variante de cinco barras, una por tema | | `mark-inline.svg` | `brand/` | `mark` | La marca para una página que la incrusta: el tono fuerte es `currentColor`, el tranquilo `--ms-mark-quiet` | | `banner.svg` y `.png` | `brand/` | `compose` | 1280×320, para el README | | `social.svg` y `.png` | `brand/` | `compose` | 1280×640, la vista previa social del repositorio | | `og.svg` y `.png` | `brand/` | `compose` | 1200×630, el `og:image` de la documentación | | `background.png` | `brand/` | ninguna | El campo generado del que se recortan las tres composiciones | | `bg-banner.png`, `bg-social.png`, `bg-og.png` | `brand/` | ninguna | Esos recortes, que lee `compose` | | `favicon.svg` | `site/public/` | `icons` | Las dos paletas, cambiando según `prefers-color-scheme` | | `favicon-32x32.png` | `site/public/` | `icons` | 32 px, sobre su propio fondo | | `favicon.ico` | `site/public/` | `icons` | Tres dibujos, a 16, 32 y 48 px, en vez de uno escalado de tres formas | | `apple-touch-icon.png`, `icon-192.png`, `icon-512.png` | `site/public/` | `icons` | 180, 192 y 512 px, cada uno dibujado a su propio tamaño | | `icon-maskable-512.png` | `site/public/` | `icons` | Con más margen, para quedar dentro del 80 % central al que un lanzador puede recortar | Las cuatro últimas filas están escritas para un manifiesto de aplicación web. Este sitio no declara ninguno y solo enlaza `favicon.svg`, `favicon.ico` y `apple-touch-icon.png`, así que `favicon-32x32.png`, `icon-192.png`, `icon-512.png` e `icon-maskable-512.png` se publican sin consumidor: ningún lanzador lee el margen de la versión enmascarable. La marca de la cabecera de este sitio es `mark-inline.svg`, pintada con la paleta propia del sitio, así que el dibujo de la interfaz es el dibujo de `brand/`. ## El fondo No lo escribe el generador: `background.png` se generó una vez con inference.sh (`openai/gpt-image-2`, 1536×1024, `quality: high`, 0,16 $) y se conserva como imagen rasterizada. Todo lo que se dibuja encima es vectorial, así que el texto se mantiene nítido al tamaño al que se produzca la imagen. El prompt pedía un campo casi negro de barras de muestra verticales y tenues, cada vez más densas y cálidas hacia la derecha, y que el tercio izquierdo quedara vacío — que es donde van la marca y el texto, así que la composición nunca pelea con su propio fondo. Los tres recortes conservan todo el degradado de izquierda a derecha en vez de tomar una ventana del centro: ```sh magick background.png -resize 1280x -gravity center -crop 1280x320+0+0 +repage bg-banner.png magick background.png -resize 1280x -gravity center -crop 1280x640+0+0 +repage bg-social.png magick background.png -resize 1200x -gravity center -crop 1200x630+0+0 +repage bg-og.png ``` Las composiciones son claras sobre oscuro de principio a fin, así que toman los dos tonos del tema oscuro y no necesitan pareja de temas. Medidos sobre el fondo más oscuro del propio campo (`#020608`, muestreado del borde izquierdo), el título `#f6f3ee` da 18,38:1, el lema `#cfc6b8` 12,04:1, y los dos tonos de la marca 12,19:1 y 5,62:1 — más que en la página, porque el campo es más oscuro que ella. El lema es "Sub-second kernel telemetry from inside the router". ## Configurar la vista previa social A mano: Settings, luego Social preview, y subir `social.png`. GitHub no ofrece ninguna API para ello. ## Véase también - [Cinco dashboards, una sola lista](/mikroscope/es/dashboards/): donde el ámbar significa un umbral. - [Qué es](/mikroscope/es/start/): la tesis que dibuja la marca, en palabras. - [Linaje y licencia](/mikroscope/es/about/lineage/): de dónde vino el resto del código. --- # Linaje y licencia Qué partes de mikroscope vienen de cs-routeros-bouncer y de go-routeros, qué cambió por el camino y la licencia MIT con la que llegan ambas. Source: https://jmrplens.github.io/mikroscope/es/about/lineage/ Esta página responde de dónde viene el código de mikroscope: qué paquetes empezaron siendo de otro, qué se cambió al traerlos y bajo qué licencia. La atribución está también en el código, de modo que sobrevive a que se lea sin este sitio: en el comentario de documentación del paquete de `internal/router`, `internal/image`, `internal/chart` e `internal/dashboards`, en el `README.md` y el `LICENSE` de `internal/rosapi`, y en la cabecera de `.golangci.yml`. ## De dónde vino el muestreador El muestreador que demostró que esto funciona es `cmd/perfmon` en [cs-routeros-bouncer](https://github.com/jmrplens/cs-routeros-bouncer), construido para una prueba de rendimiento y conservado allí como instrumento de desarrollo. Los pasos de despliegue, el constructor de imágenes sin Docker, el gráfico determinista y el cliente de la API de RouterOS incorporado vienen de ese repositorio, bajo su licencia MIT. ## Qué se tomó, paquete a paquete | mikroscope | Tomado de | Qué cambió por el camino | | ---------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `internal/router` | cs-routeros-bouncer `cmd/perfmon`, los pasos de despliegue tras el PR #123 | Ampliado con puertos entrecomillados en cada `find`, el par opcional de reglas de cortafuegos de `--expose`, los ajustes de contenedor propios de mikroscope y un listado de cada escritura antes de hacerla | | Pruebas de `internal/router` | cs-routeros-bouncer `cmd/perfmon`, las pruebas de contención | Adaptadas a los nombres de opción de mikroscope y a dos reglas propias: puertos entrecomillados, y el par de `--expose` como parte del plan | | `internal/image` | cs-routeros-bouncer `cmd/perfmon/image.go` | Rehecho para el nombre del agente, la variante ARM (las imágenes arm declaran `v7`) y una compilación que goreleaser pueda reutilizar | | `internal/chart` | cs-routeros-bouncer `cmd/perfmon/chart.go` | El original tomaba su paleta de un tema de documentación; este lleva la suya propia, validada para la superficie clara (ΔE CVD entre pares adyacentes 9,1, visión normal 22,9) | | `internal/rosapi` | cs-routeros-bouncer `internal/rosapi`, tomado el 2026-09-11 | Solo la ruta de importación | | `.golangci.yml` | cs-routeros-bouncer, basado a su vez en `maratori/golangci-lint-config` | La ruta del módulo | Lo que trajo el PR #123, y lo que el instalador de mikroscope conserva de él, es la regla de contención: cada objeto creado lleva una única etiqueta exacta en el comentario, cada retirada selecciona por esa etiqueta más la identidad del objeto — nunca por patrón — y `uninstall` verifica por recuento de propiedad antes de dar el éxito por bueno. No se escribe nada sin haberlo listado antes. La salida del gráfico es determinista: la misma entrada da siempre los mismos bytes. ## Lo que se tomó como modelo, no como código `internal/dashboards` toma la forma de exportación de Grafana del `cmd/gen_dashboards` de ghchronicle, del propietario — "the shape, not the code", como dice su comentario de paquete — y `dashboards check` comprueba cada panel en un Grafana real igual que lo hace ghchronicle. Otras dos piezas están modeladas igual, a partir de un proyecto de referencia que esta documentación no nombra: las secciones con nombre de los paneles siguen el `sections.go` de ese proyecto, y la batería de extremo a extremo de `test/e2e/` sigue la suya. ## El cliente de la API de RouterOS `internal/rosapi` tiene dos generaciones de linaje. La copia de mikroscope es la de cs-routeros-bouncer, sin nada modificado para mikroscope; mikroscope la usa para las lecturas de la capa de la API, para `mark --log-markers` y para el relevo de `/tool fetch`. La copia del bouncer es a su vez una incorporación recortada de [`github.com/go-routeros/routeros/v3`](https://github.com/go-routeros/routeros) en **v3.0.1** (commit de upstream del 2025-02-16), MIT, copyright 2016 André Luiz dos Santos, cuyo fichero `LICENSE` sigue en el paquete. ### Por qué está incorporado Upstream está en la práctica sin mantener: su último commit es dieciocho meses anterior a la copia, y las pull requests e issues abiertas contra el modo asíncrono a mediados de 2026 (#31–#34) no han tenido respuesta de ningún mantenedor. No existe una alternativa mantenida — `swoga/go-routeros` es una copia que solo recibe actualizaciones de dependabot para sus GitHub Actions, y `jda/routeros-api-go` se detuvo en 2016. Incorporarlo mantiene el código compilable y permite a la copia corregir y recortar lo que usa. ### Qué difiere de upstream v3.0.1 Estos cambios se hicieron en cs-routeros-bouncer y llegan sin tocar: 1. **Se elimina el modo async/listen**, con sus pruebas y la rama asíncrona de `RunArgsContext`. Nada en el bouncer llamaba a `Async()`, `Listen()` ni a las variantes de ejecución que cancelan por contexto. Las constantes de tipo de sentencia pasaron a `reply.go`, que es quien las consume. 2. **El lector y el escritor del protocolo leen y escriben directamente**, en vez de despachar cada llamada a una goroutine nueva con un canal y un búfer de copia; solo el modo asíncrono eliminado cancelaba alguna vez una lectura en curso. A la escala de producción del bouncer — 22k entradas de address-list obtenidas en cada ciclo — esa capa costaba ~294 000 goroutines lanzadas, ~62 MB de basura y ~76 % de todas las asignaciones por ciclo de reconciliación. La reescritura se comprobó contra upstream intacto con una prueba diferencial: la huella SHA-256 sobre las 22 037 sentencias analizadas idéntica, las formas de error idénticas (`io.ErrUnexpectedEOF` al truncar, `*DeviceError` en `!trap`), y la batería de upstream en verde con `go test -race`. `proto/reader_shape_test.go` fija ese comportamiento. Esas cuatro cifras son de cs-routeros-bouncer, medidas en su propia máquina y contra su propia carga, no en un router y no por este proyecto; `internal/rosapi/README.md`, de donde salen, no registra ni fecha ni equipo, así que aquí no se puede dar ninguno de los dos. 3. **Se elimina el inicio de sesión MD5 por desafío anterior a 6.43.** Ese inicio de sesión solo existe antes de RouterOS 6.43 (2018), y responderlo supone hacer un hash MD5 de la contraseña. Un router que envía un desafío `ret` recibe `ErrLegacyLoginUnsupported` en su lugar. 4. **Se eliminan las pruebas del modo asíncrono y del modo listen** junto con el modo; las pruebas del inicio de sesión anterior a 6.43 comprueban el rechazo en vez del intercambio. El resto de la batería de upstream se conserva y pasa. Compara con la etiqueta `v3.0.1` de upstream, no con su rama master. ## Licencia mikroscope tiene licencia MIT, copyright 2026 jmrplens; el texto completo está en `LICENSE`, en la raíz del repositorio. El código tomado de cs-routeros-bouncer llega bajo la licencia MIT de ese repositorio, e `internal/rosapi` conserva el `LICENSE` MIT de go-routeros junto al código que cubre. ## Véase también - [Lo que el instalador rechaza](/mikroscope/es/security/installer/): las reglas de contención que heredó el código de despliegue, tal como están ahora. - [La capa de la API de RouterOS](/mikroscope/es/sinks/api-tier/): lo que lee el cliente incorporado. - [Grabar, marcar, dibujar](/mikroscope/es/record/): el gráfico determinista que dibuja `internal/chart`. - [En qué punto está](/mikroscope/es/about/status/): qué funciona hoy, y qué publica la versión actual.