# Coste del agente

Lo que le cuestan a un MikroTik RB5009 el agente, una conexión SSH y la capa de la API de RouterOS, medido desde el propio cgroup del agente y con el profiler de RouterOS, y cómo medir el agente en tu router.

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

Con los valores por defecto de la instalación — 10 Hz, los suelos por fuente por defecto, anillo
de 60 s — el agente le cuesta a un MikroTik RB5009 **un 2,69 % de un núcleo
y un 13,2 MiB de RSS**, leído desde su propio cgroup:

Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · 2026-09-18 · ventanas de 300 s en régimen estacionario (con el anillo ya lleno), conjunto completo de fuentes, la configuración que se envía —anillo de 60 s, límite de memoria derivado de él y el tope de contenedor de serie de 64M— con el colector reenviando a InfluxDB 3

El agente publica su propio coste en cada muestra, y la CI comprueba el presupuesto de tamaño de la
imagen.

## Presupuesto

El presupuesto es **≤ 2 % de un núcleo, ≤ 16 MiB de
RSS, ≤ 8 MiB de imagen**. La imagen arm64 publicada de 1.2.1
mide 6,38 MiB (6 690 304 B, medido sobre el artefacto de la versión el 2026-09-24); la
de 1.0.0 medía 6,1 MiB, la cifra que dan las notas de esa versión. Los otros
dos dependen de la cadencia y de cuánto le pidas leer al agente, así que la respuesta es una tabla y
no un número: [Techo de muestreo](https://docs.jmrp.io/mikroscope/es/cost/rate-ceiling/) es esa tabla.

**La memoria está dentro del presupuesto y la CPU no.** 13,2 MiB frente a
los 16 MiB que pide el presupuesto, y un 2,69 % frente
al 2 %, con todas las fuentes leídas, entre ellas los tiempos de perf,
buddyinfo, los contadores ECC de MTD, slabinfo, el log del kernel y los eventos de cgroup. El
tráfico por interfaz no está en ese conjunto: el contenedor no lo ve, y viene de la capa de la API.

La campaña del 2026-09-15 estaba por encima en los dos. Su anillo guardaba 300 s en vez de 60 y su
límite de memoria era un 40 MiB fijo que nunca apretaba, lo que dejaba al mismo agente en 31,3 MiB;
ninguna de las dos cifras decía nada sobre la cadencia. Nada del muestreo cambió entre las dos
campañas: la CPU pasó del 2,85 % al 2,69 %, que es el ruido entre dos ventanas.

### Bucle de shell como base

El presupuesto dice lo que se le permite costar al agente. La comparación que suele querer quien lee
es contra la forma obvia: un bucle de shell de busybox. El 2026-09-11, en el mismo router, un bucle
que leía a 10 Hz el conjunto de ficheros que leía entonces el agente costó
**2,40–2,48 % de un núcleo**, mientras que las lecturas en sí rondaban
los 0,77 ms por muestra.

Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · Linux 5.6.3 · 2026-09-11 · un bucle de shell de busybox leyendo a 10 Hz el conjunto de ficheros que leía entonces el agente, siete ficheros, con un fork por iteración, en un contenedor del router; dos ejecuciones de 60 s

Así que la mayor parte del coste del bucle de shell no estaba 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, y por
eso el agente es un binario y no un script. Aun así, las dos cifras no se pueden poner una al lado de
la otra. Hoy el agente lee mucho más — perf, buddyinfo, los contadores ECC de la MTD, slabinfo, el
log del kernel — y el bucle no se ha vuelto a medir con ese conjunto, así que no se puede comparar
con 2,69 %.

> **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 lleva en cada muestra, en el bloque `self`, como `cpu_us` y `rss`; el
> colector los representa como `mikroscope_self_cpu_usec_total` y `mikroscope_self_rss_bytes`. Dos
> lecturas del `/metrics` del colector separadas 60 s, en régimen estacionario y con el anillo ya
> lleno, son la medida: el agente no sirve exposición propia. El `/tool profile` de RouterOS
> muestra el mismo proceso como `mikroscope-agent`.

Quien tira del agente se suma a eso. Además del colector y de `record`, un `doctor` independiente
lee el anillo una vez, en una sola petición: el anillo entero cuando no guarda más de 10 000
muestras, y si no las 10 000 más recientes. Con los valores por defecto es el anillo entero de 60 s,
unos 1,9 MB a 10 Hz, y esa petición se sirve en el núcleo del muestreador; el coste de esa lectura en
el RB5009 no se ha medido.

## Límite de memoria

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; las opciones que usó
cada ejecución están junto a ella en [Techo de muestreo](https://docs.jmrp.io/mikroscope/es/cost/rate-ceiling/).

`install` deriva `MEM_LIMIT_MB` del anillo: cadencia × buffer × el tamaño de línea, por 2,5, con un
mínimo de 16 MiB y un máximo de tres cuartos de `memory-max`, así que la instalación por defecto
escribe 16 para un anillo de 60 s a 10 Hz. El factor está medido. En el RB5009 (RouterOS 7.24.2,
10 Hz, un anillo de 300 s, todas las fuentes activas) el 2026-09-17, cuatro límites en cuatro
ventanas de unas 12 000 muestras cada una:

| límite | múltiplo del anillo | RSS      | CPU por muestra | Lo que costó                  |
| ------ | ------------------- | -------- | --------------- | ----------------------------- |
| 40 MiB | 4,0×                | 32,9 MiB | 2 657 µs        | el límite nunca aprieta       |
| 24 MiB | 2,4×                | 26,3 MiB | 2 780 µs        | ningún coste medible          |
| 21 MiB | 2,1×                | 23,5 MiB | 3 250 µs        | +22 %, y subiendo             |
| 18 MiB | 1,8×                | 20,4 MiB | 14 800 µs       | +457 %, el peor tick de 52 ms |

Las filas son las que registra el comentario de `memLimitRingFactor`
en [`internal/router/options.go`](https://github.com/jmrplens/mikroscope/blob/main/internal/router/options.go). El factor de 2,5× serían 25 MiB a 300 s; la ventana medida
más cercana fue la de 24 MiB, y ninguna de las cuatro ventanas se anotó con su dispersión. El mismo
precipicio se midió desde el otro lado el 2026-09-12: un 9,38 % de un núcleo
con 14 MiB frente a un 1,39 % con margen, las dos con el anillo lleno.

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 685 µs a 10 Hz con los suelos por defecto, y
[las seis ejecuciones](https://docs.jmrp.io/mikroscope/es/cost/rate-ceiling/) lo dan a cada cadencia. El análisis por sí
solo no se midió en el A72.

## Coste de SSH

Cada conexión SSH le cuesta al RB5009 un 20–27 % de CPU mientras dura.

Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.1 · 2026-08-26 · una conexión ssh, mientras dura

Esa lectura es anterior al agente y se tomó con RouterOS 7.24.1. Con 7.24.2, el 2026-09-11, el mismo
coste apareció en `/tool profile` como un 17–33 % en una o dos instantáneas, no en una ventana
medida. Por eso la CLI agrupa todas las lecturas en una sola conexión — `doctor` es una, `status` es
una, las preguntas de estado de `install` son una — y cada escritura suma otra, más 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.

## Coste de la capa API

Medido con el propio [profiler](https://help.mikrotik.com/docs/spaces/ROS/pages/8323153/Profiler)
de RouterOS, una vez con el colector parado y otra con él haciendo una ronda cada segundo:

Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · 2026-09-16 · `/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`; descartadas las cinco primeras instantáneas de un segundo de cada perfil, porque 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, 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. Esa tabla es un único perfil de 60 s por condición, sin dispersión.

Una configuración mayor se midió como A/B en el mismo router el 2026-09-19, el día de la
actualización de RouterOS que lo dejó en 7.24.4; no quedó registrado sobre qué versión corrió el A/B
en sí. Todas las interfaces salvo `lo` en `--interfaces` (16) y `--conntrack-every 10s`, 8 min sin
ella frente a 14 min con ella: el `cpu-load` medio pasó de 7,12 % a 7,91 % y la ocupación media del
kernel de 7,70 % a 8,46 %, unos +0,8 puntos. Es una cota inferior, porque el tráfico del bridge bajó
de 48,0 a 38,9 Mbit/s entre las ventanas. El p50 de `cpu-load` se quedó en 6 y el p95 pasó de 16 a
18.

`--conntrack-every` pregunta `/ip/firewall/connection/print count-only`: una llamada
tardó 1,3 ms con 6 212 entradas, la mediana de diez
llamadas; la más rápida tardó 1,1 ms y la primera 71 ms, en frío.

Medido en RB5009UG+S+ · 4 × 1,4 GHz Cortex-A72 · RouterOS 7.24.2 · Linux 5.6.3 · 2026-09-11 · la tabla de conexiones contada por la API binaria de RouterOS, `/ip/firewall/connection/print count-only`, diez llamadas

## Medir en tu router

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://<collector host>:9124/metrics | grep -E 'self_cpu_usec_total|self_rss_bytes'
sleep 60
curl -s http://<collector host>:9124/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. O deja
que la shell haga la división, en tres minutos:

```sh
U=http://<equipo del colector>:9124/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)}'
```

Tres cosas que cabe esperar:

- **El coste y la memoria suben hasta que el anillo se llena.** Con el anillo por defecto de 60 s a
  10 Hz el agente guarda 600 muestras ya codificadas; una cifra tomada en el primer minuto tras la
  instalación se mide sobre un heap casi vacío y sale baja. Espera a que pase `BUFFER_S` antes de
  citar una cifra de régimen estacionario.
- **Una instantánea lo exagera.** Un `/snapshot` de 60 s entrega unas 600 líneas, unos 1,9 MB, y el
  `self.cpu_us` de esas muestras incluye el coste de servirlas; leer el coste en instantáneas
  repetidas lo exagera más.
- **`mikroscope_slipped_total` es el número que importa.** Un muestreador que cuesta algo más pero
  nunca pierde ticks te está diciendo la verdad; uno que los pierde, no.

La dirección es la del colector, no la del agente: el agente no sirve ningún `/metrics`, y esos dos
contadores llegan al colector en cada muestra. Sin destino de Prometheus configurado, esos mismos dos
números son `cpu_us` y `rss` en `mikroscope_self` en InfluxDB, Telegraf, stdout y los almacenes SQL,
`self.cpu_us` y `self.rss` en los documentos de muestra de Elasticsearch, `self.cpu_us` y
`self.rss_bytes` en Graphite, y `mikroscope.self.cpu.time` / `mikroscope.self.memory{kind="rss"}` en
OTLP. Medida en una placa que no sea un RB5009, con la cadencia, la fecha y lo que hacía el router,
la cifra va en un
[informe de placa](https://github.com/jmrplens/mikroscope/issues/new?template=2-board-report.yml).

## Véase también

- [Techo de muestreo](https://docs.jmrp.io/mikroscope/es/cost/rate-ceiling/): las seis ejecuciones, los valores por
  defecto que usó cada una y lo que compra muestrear más rápido.
- [Límites de la evidencia](https://docs.jmrp.io/mikroscope/es/cost/limits/): lo que estas cifras no pueden decirte
  sobre otro equipo u otra carga.
- [Probado en](https://docs.jmrp.io/mikroscope/es/about/status/): el equipo, las versiones de RouterOS y cada campaña
  que hay detrás de estas cifras.
