A maioria dos brokers MQTT informa sobre si mesma via MQTT. Assine $SYS/# e o broker publica quantos clientes estão conectados, quantas mensagens e quantos bytes recebeu e enviou, quantas assinaturas e mensagens retidas mantém e há quanto tempo está no ar. Isso faz do $SYS a forma mais rápida de monitorar um broker. Você não precisa de exporter nem de agente, só de um cliente MQTT.
Este guia mostra o que o $SYS expõe nos brokers mais comuns, como assiná-lo corretamente, como transformar contadores brutos em taxas úteis e como registrá-las e plotá-las com um pequeno script Python.
O que são os tópicos $SYS
A especificação MQTT reserva os tópicos que começam com $ para uso interno do broker. Clientes não devem publicar ali, e os curingas não os capturam por acidente: uma assinatura de # ou +/foo nunca recebe mensagens $SYS. Você precisa assinar $SYS/# (ou um filtro mais restrito) de propósito.
O conteúdo não é padronizado. Mosquitto, EMQX, VerneMQ e outros publicam cada um a sua própria árvore, e alguns serviços gerenciados a escondem por completo ou a restringem com ACLs. Para uma referência completa da árvore de tópicos, veja tópicos $SYS do MQTT. Este artigo trata de como usar esses tópicos para monitoramento.
Os tópicos que valem a pena acompanhar
Estes são os nomes do Mosquitto (2.x). Outros brokers publicam valores parecidos em outros caminhos.
| Tópico | Tipo | O que informa |
|---|---|---|
$SYS/broker/clients/connected | gauge | Clientes conectados agora |
$SYS/broker/clients/maximum | gauge | Maior número de conexões simultâneas desde o início |
$SYS/broker/clients/disconnected | gauge | Sessões persistentes cujo cliente está offline |
$SYS/broker/messages/received | contador | Mensagens recebidas desde o início (todos os tipos de pacote) |
$SYS/broker/messages/sent | contador | Mensagens enviadas desde o início |
$SYS/broker/publish/messages/dropped | contador | Mensagens PUBLISH descartadas (limites de fila, clientes offline) |
$SYS/broker/bytes/received / sent | contador | Bytes de rede de entrada e saída |
$SYS/broker/load/messages/received/1min | taxa | Média móvel de 1, 5 e 15 minutos |
$SYS/broker/subscriptions/count | gauge | Assinaturas ativas |
$SYS/broker/retained messages/count | gauge | Mensagens retidas armazenadas (repare no espaço no tópico) |
$SYS/broker/uptime | texto | Por exemplo 86400 seconds |
$SYS/broker/version | texto | Versão do broker |
Há dois tipos de número aqui. Os gauges (clientes conectados, assinaturas) fazem sentido como estão. Os contadores (mensagens, bytes) só crescem, então você precisa da diferença entre duas amostras para obter uma taxa. O Mosquitto também publica médias móveis prontas em $SYS/broker/load/.... Elas ajudam numa olhada rápida, mas você perde o controle sobre a janela.
No EMQX, a árvore é por nó: $SYS/brokers/<node>/version, $SYS/brokers/<node>/uptime, além das subárvores stats/... e metrics/... e de eventos opcionais de conexão e desconexão de clientes. O intervalo de publicação é definido por sys_msg_interval. Para dashboards no EMQX, o endpoint Prometheus embutido costuma ser uma fonte melhor. Veja monitoramento com Prometheus e Grafana.
Assinar $SYS
A verificação mais rápida é com o mosquitto_sub. Coloque o tópico entre aspas simples, senão o shell tenta expandir $SYS como variável:
mosquitto_sub -h broker.connectmq.com -p 1883 \
-u "$MQTT_USER" -P "$MQTT_PASS" \
-v -t '$SYS/#'Para acompanhar só alguns valores, assine vários filtros específicos:
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'Você pode fazer o mesmo no navegador: abra o cliente MQTT online, conecte-se e assine $SYS/#. Se nada chegar, verifique estas três coisas, nesta ordem:
- O broker está com
$SYSdesativado. No Mosquitto,sys_interval 0desliga o recurso. - Uma ACL bloqueia o acesso. Muitas configurações de produção só permitem que um usuário administrador leia
$SYS. Clientes MQTT 5 recebem um SUBACK com o reason code0x87 Not authorized. Decodifique-o com a consulta de reason codes. - Você esperou só um intervalo. Alguns valores só aparecem no próximo tick de
sys_interval.
Configurar o intervalo de atualização
No Mosquitto, o intervalo é uma única opção:
# Publish $SYS updates every 10 seconds (default). 0 disables $SYS.
sys_interval 10Um intervalo menor gera gráficos mais detalhados, mas custa um pouco de CPU e de banda para cada assinante de $SYS/#. Para dashboards, 10–30 segundos bastam. O gerador de mosquitto.conf inclui essa opção se você montar a configuração do zero.
Transformar contadores em taxas
Contadores só são úteis como diferenças. Se messages/received era 1.204.500 às 12:00:00 e 1.210.500 às 12:00:10, o broker recebeu (1210500 − 1204500) / 10 = 600 mensagens por segundo nessa janela. Duas regras mantêm a conta honesta:
- Use o horário em que você recebeu a amostra, não o intervalo nominal. Jitter de rede e carga do broker deslocam os ticks.
- Trate reinicializações. Quando o broker reinicia, os contadores voltam a zero. Se um valor novo for menor que o anterior, descarte essa diferença em vez de plotar um pico negativo enorme. Uma queda no
uptimeconfirma a reinicialização.
Registrar e plotar o $SYS com Python
Este script (paho-mqtt 2.x) assina alguns tópicos $SYS, calcula taxas a partir dos contadores, imprime uma linha por atualização e grava tudo em um CSV que você pode plotar depois:
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 = {} # tópico -> (timestamp, 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("falha na conexão:", 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]: # primeira amostra ou reinício do 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()Assinar dentro de on_connect é importante: depois de uma reconexão, as assinaturas são restauradas automaticamente. Para plotar o CSV, bastam algumas linhas de pandas e 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 um dashboard permanente, com histórico e alertas, envie os mesmos valores para o Prometheus ou o InfluxDB em vez de um CSV.
Teste se o seu monitoramento reage
Um monitor que nunca viu uma mudança não foi testado. Gere uma carga conhecida e verifique se os números se mexem:
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 a execução, messages/received/s deve subir mais ou menos na taxa em que você publica. Abra dez conexões extras no cliente online ou com um simulador de dispositivos e clients/connected deve subir dez. Se nenhum dos dois se mexer, provavelmente você está lendo um nó diferente daquele em que escreve, ou um cluster está informando valores por nó.
Limites do monitoramento com $SYS
- Sem histórico. O
$SYSmostra apenas o estado atual. Você mesmo precisa armazená-lo. - Sem alertas. Combine com Prometheus, Grafana ou um script que avise você. Para verificações de fora para dentro, veja health checks do broker.
- Específico de cada broker. Nomes de tópicos e significados mudam entre brokers e, às vezes, entre versões.
- Monitorado pelo próprio sistema que monitora. Se o broker cair, o
$SYSfica em silêncio. Um monitor em silêncio deve gerar um alerta, não parecer um dia tranquilo.
Os tópicos $SYS são a observabilidade mais barata que um broker MQTT oferece. Assine $SYS/# uma vez para ver o que o seu broker publica, mantenha um pequeno script que transforma contadores em taxas e restrinja o acesso para que só a sua conta de monitoramento leia a árvore.
Perguntas frequentes
Por que assinar # não mostra as mensagens $SYS?
A especificação MQTT diz que assinaturas com curinga que começam com # ou + não devem corresponder a tópicos que começam com $. Assine $SYS/# explicitamente, em uma assinatura separada.
Com que frequência os tópicos $SYS são atualizados?
Depende do broker. O Mosquitto os publica a cada sys_interval segundos (10 por padrão; 0 desativa). O EMQX usa a própria configuração sys_msg_interval. Verifique a configuração do seu broker.
Os tópicos $SYS fazem parte do padrão MQTT?
Não. A especificação só reserva os tópicos que começam com $ para uso do broker. Os nomes e valores em $SYS variam de broker para broker, então um dashboard feito para o Mosquitto não funciona sem mudanças no EMQX ou em outro broker.


