Pruebas de brokers

Monitorizar un broker MQTT con los topics $SYS

Usa los topics $SYS de MQTT para monitorizar un broker: qué publican Mosquitto y EMQX, cómo suscribirte a $SYS/#, calcular tasas y graficarlas en Python.

Actualizado · 8 min de lectura

Monitorizar un broker MQTT con los topics $SYS

La mayoría de los brokers MQTT informan sobre sí mismos a través de MQTT. Suscríbete a $SYS/# y el broker publica cuántos clientes están conectados, cuántos mensajes y bytes ha recibido y enviado, cuántas suscripciones y mensajes retenidos tiene y cuánto tiempo lleva en marcha. Eso convierte a $SYS en la forma más rápida de monitorizar un broker. No necesitas exporter ni agente, solo un cliente MQTT.

Esta guía explica qué expone $SYS en los brokers más comunes, cómo suscribirte correctamente, cómo convertir contadores en tasas útiles y cómo registrarlas y graficarlas con un pequeño script de Python.

Qué son los topics $SYS

La especificación MQTT reserva los topics que empiezan por $ para uso interno del broker. Los clientes no deberían publicar ahí, y los comodines no los capturan por accidente: una suscripción a # o a +/foo nunca recibe mensajes $SYS. Tienes que suscribirte a $SYS/# (o a un filtro más concreto) de forma intencionada.

El contenido no está estandarizado. Mosquitto, EMQX, VerneMQ y otros publican cada uno su propio árbol, y algunos servicios gestionados lo ocultan por completo o lo restringen con ACL. Para una referencia completa del árbol de topics, consulta topics $SYS de MQTT. Este artículo trata de cómo usar esos topics para monitorizar.

Los topics que vale la pena vigilar

Estos son los nombres de Mosquitto (2.x). Otros brokers publican valores parecidos con otras rutas.

TopicTipoQué te dice
$SYS/broker/clients/connectedgaugeClientes conectados ahora mismo
$SYS/broker/clients/maximumgaugeMáximo de conexiones simultáneas desde el arranque
$SYS/broker/clients/disconnectedgaugeSesiones persistentes cuyo cliente está desconectado
$SYS/broker/messages/receivedcontadorMensajes recibidos desde el arranque (todos los tipos de paquete)
$SYS/broker/messages/sentcontadorMensajes enviados desde el arranque
$SYS/broker/publish/messages/droppedcontadorMensajes PUBLISH descartados (límites de cola, clientes desconectados)
$SYS/broker/bytes/received / sentcontadorBytes de red entrantes y salientes
$SYS/broker/load/messages/received/1mintasaMedia móvil de 1, 5 y 15 minutos
$SYS/broker/subscriptions/countgaugeSuscripciones activas
$SYS/broker/retained messages/countgaugeMensajes retenidos almacenados (ojo al espacio en el topic)
$SYS/broker/uptimetextoPor ejemplo 86400 seconds
$SYS/broker/versiontextoVersión del broker

Aquí hay dos tipos de números. Los gauges (clientes conectados, suscripciones) tienen sentido tal cual. Los contadores (mensajes, bytes) solo crecen, así que necesitas la diferencia entre dos muestras para obtener una tasa. Mosquitto también publica medias móviles ya calculadas en $SYS/broker/load/.... Son prácticas para echar un vistazo rápido, pero pierdes el control sobre la ventana.

En EMQX el árbol es por nodo: $SYS/brokers/<node>/version, $SYS/brokers/<node>/uptime, además de los subárboles stats/... y metrics/... y eventos opcionales de conexión y desconexión de clientes. El intervalo de publicación se configura con sys_msg_interval. Para dashboards en EMQX suele ser mejor su endpoint Prometheus integrado. Consulta monitorización con Prometheus y Grafana.

Suscribirte a $SYS

La comprobación más rápida es con mosquitto_sub. Pon el topic entre comillas simples, o la shell intentará expandir $SYS como si fuera una variable:

Volcar todo el árbol $SYS
mosquitto_sub -h broker.connectmq.com -p 1883 \
  -u "$MQTT_USER" -P "$MQTT_PASS" \
  -v -t '$SYS/#'

Para vigilar solo unos pocos valores, suscríbete a varios filtros concretos:

Solo clientes y carga de mensajes
mosquitto_sub -h broker.connectmq.com -p 1883 \
  -u "$MQTT_USER" -P "$MQTT_PASS" -v \
  -t '$SYS/broker/clients/connected' \
  -t '$SYS/broker/load/messages/+/1min' \
  -t '$SYS/broker/uptime'

Puedes hacer lo mismo en el navegador: abre el cliente MQTT online, conéctate y suscríbete a $SYS/#. Si no llega nada, revisa estas tres cosas en este orden:

  1. El broker tiene $SYS desactivado. En Mosquitto, sys_interval 0 lo apaga.
  2. Una ACL lo deniega. Muchas configuraciones de producción solo permiten leer $SYS a un usuario administrador. Los clientes MQTT 5 reciben un SUBACK con el reason code 0x87 Not authorized. Puedes decodificarlo con el buscador de reason codes.
  3. Solo esperaste un intervalo. Algunos valores solo aparecen tras el siguiente tick de sys_interval.

Configurar el intervalo de actualización

En Mosquitto, el intervalo es una sola opción:

mosquitto.conf
# Publish $SYS updates every 10 seconds (default). 0 disables $SYS.
sys_interval 10

Un intervalo más corto da gráficas más finas, pero cuesta algo de CPU y ancho de banda por cada suscriptor de $SYS/#. Para dashboards bastan 10–30 segundos. El generador de mosquitto.conf incluye esta opción si construyes tu configuración desde cero.

Convertir contadores en tasas

Los contadores solo son útiles como diferencias. Si messages/received valía 1.204.500 a las 12:00:00 y 1.210.500 a las 12:00:10, el broker recibió (1210500 − 1204500) / 10 = 600 mensajes por segundo en esa ventana. Dos reglas mantienen los cálculos honestos:

  • Usa la hora a la que recibiste la muestra, no el intervalo nominal. El jitter de red y la carga del broker desplazan los ticks.
  • Gestiona los reinicios. Cuando el broker se reinicia, los contadores vuelven a cero. Si un valor nuevo es menor que el anterior, descarta esa diferencia en lugar de graficar un enorme pico negativo. Una caída de uptime confirma el reinicio.

Registrar y graficar $SYS con Python

Este script (paho-mqtt 2.x) se suscribe a algunos topics $SYS, calcula tasas a partir de los contadores, imprime una línea por actualización y lo añade todo a un CSV que puedes graficar después:

sys_monitor.py
import csv, os, time
import paho.mqtt.client as mqtt

HOST, PORT = "broker.connectmq.com", 1883
TOPICS = {
    "$SYS/broker/clients/connected": "gauge",
    "$SYS/broker/subscriptions/count": "gauge",
    "$SYS/broker/messages/received": "counter",
    "$SYS/broker/messages/sent": "counter",
    "$SYS/broker/bytes/received": "counter",
    "$SYS/broker/bytes/sent": "counter",
}
last = {}  # topic -> (marca de tiempo, valor)
out = csv.writer(open("sys_metrics.csv", "a", newline=""))

def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print("conexión fallida:", reason_code)
        return
    client.subscribe([(t, 0) for t in TOPICS])

def on_message(client, userdata, msg):
    now = time.time()
    try:
        value = float(msg.payload.decode().split()[0])
    except ValueError:
        return
    name = msg.topic.removeprefix("$SYS/broker/")
    if TOPICS[msg.topic] == "counter":
        prev = last.get(msg.topic)
        last[msg.topic] = (now, value)
        if prev is None or value < prev[1]:   # primera muestra o reinicio del broker
            return
        value = (value - prev[1]) / (now - prev[0])
        name += "/s"
    out.writerow([int(now), name, round(value, 2)])
    print(f"{time.strftime('%H:%M:%S')}  {name:<24} {value:>12.1f}")

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="testmqtt-sys-monitor")
client.username_pw_set(os.getenv("MQTT_USER"), os.getenv("MQTT_PASS"))
client.on_connect = on_connect
client.on_message = on_message
client.connect(HOST, PORT, keepalive=30)
client.loop_forever()

Es importante suscribirse dentro de on_connect: así las suscripciones se restauran automáticamente tras una reconexión. Para graficar el CSV bastan unas líneas de pandas y matplotlib:

plot.py
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv("sys_metrics.csv", names=["ts", "metric", "value"])
df["ts"] = pd.to_datetime(df["ts"], unit="s")
df.pivot_table(index="ts", columns="metric", values="value").plot(subplots=True, figsize=(10, 8))
plt.tight_layout()
plt.show()

Para un dashboard permanente con histórico y alertas, envía los mismos valores a Prometheus o InfluxDB en lugar de a un CSV.

Comprobar que tu monitorización reacciona

Un monitor que nunca ha visto un cambio no está probado. Genera una carga conocida y comprueba que los números se mueven:

Publicar 1.000 mensajes y observar la tasa
for i in $(seq 1 1000); do echo "{\"i\":$i}"; done | \
  mosquitto_pub -h broker.connectmq.com -u "$MQTT_USER" -P "$MQTT_PASS" \
  -t testmqtt/sys-monitor/load -l

Durante la prueba, messages/received/s debería subir aproximadamente al ritmo al que publicas. Abre diez conexiones extra en el cliente online o con un simulador de dispositivos y clients/connected debería subir diez. Si ninguno se mueve, probablemente estés leyendo un nodo distinto de aquel en el que escribes, o un clúster está informando de valores por nodo.

Límites de la monitorización con $SYS

  • Sin histórico. $SYS muestra solo el estado actual. Tienes que almacenarlo tú.
  • Sin alertas. Combínalo con Prometheus, Grafana o un script que te avise. Para comprobaciones desde fuera, consulta los health checks del broker.
  • Específico de cada broker. Los nombres de los topics y su significado cambian entre brokers, y a veces entre versiones.
  • Lo vigila el mismo sistema que monitoriza. Si el broker cae, $SYS se queda en silencio. Un monitor en silencio debe lanzar una alerta, no parecer un día tranquilo.

Los topics $SYS son la observabilidad más barata que ofrece un broker MQTT. Suscríbete una vez a $SYS/# para ver qué publica tu broker, guarda un pequeño script que convierta contadores en tasas y restringe el acceso para que solo tu cuenta de monitorización pueda leer el árbol.

Preguntas frecuentes

¿Por qué al suscribirme a # no veo los mensajes $SYS?

La especificación MQTT dice que las suscripciones con comodín que empiezan por # o + no deben coincidir con topics que empiezan por $. Suscríbete a $SYS/# de forma explícita, en una suscripción aparte.

¿Cada cuánto se actualizan los topics $SYS?

Depende del broker. Mosquitto los publica cada sys_interval segundos (10 por defecto; 0 los desactiva). EMQX usa su propio ajuste sys_msg_interval. Revisa la configuración de tu broker.

¿Los topics $SYS forman parte del estándar MQTT?

No. La especificación solo reserva los topics que empiezan por $ para uso del broker. Los nombres y valores bajo $SYS dependen de cada broker, así que un dashboard hecho para Mosquitto no funcionará sin cambios en EMQX ni en otro broker.