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.
| Topic | Tipo | Qué te dice |
|---|---|---|
$SYS/broker/clients/connected | gauge | Clientes conectados ahora mismo |
$SYS/broker/clients/maximum | gauge | Máximo de conexiones simultáneas desde el arranque |
$SYS/broker/clients/disconnected | gauge | Sesiones persistentes cuyo cliente está desconectado |
$SYS/broker/messages/received | contador | Mensajes recibidos desde el arranque (todos los tipos de paquete) |
$SYS/broker/messages/sent | contador | Mensajes enviados desde el arranque |
$SYS/broker/publish/messages/dropped | contador | Mensajes PUBLISH descartados (límites de cola, clientes desconectados) |
$SYS/broker/bytes/received / sent | contador | Bytes de red entrantes y salientes |
$SYS/broker/load/messages/received/1min | tasa | Media móvil de 1, 5 y 15 minutos |
$SYS/broker/subscriptions/count | gauge | Suscripciones activas |
$SYS/broker/retained messages/count | gauge | Mensajes retenidos almacenados (ojo al espacio en el topic) |
$SYS/broker/uptime | texto | Por ejemplo 86400 seconds |
$SYS/broker/version | texto | Versió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:
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:
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:
- El broker tiene
$SYSdesactivado. En Mosquitto,sys_interval 0lo apaga. - Una ACL lo deniega. Muchas configuraciones de producción solo permiten leer
$SYSa un usuario administrador. Los clientes MQTT 5 reciben un SUBACK con el reason code0x87 Not authorized. Puedes decodificarlo con el buscador de reason codes. - 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:
# Publish $SYS updates every 10 seconds (default). 0 disables $SYS.
sys_interval 10Un 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
uptimeconfirma 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:
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:
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:
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 -lDurante 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.
$SYSmuestra 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,
$SYSse 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.


