Ir al contenido

El coste del observador

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 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 · · 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.

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 · · 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.

Dimensiona el límite de memoria según los datos

Sección titulada «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.

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 lo dan a cada cadencia. El análisis por sí solo no se midió en el A72.

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:

Ventana de terminal
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.