Testes de brokers

Monitorar um broker MQTT com os tópicos $SYS

Use os tópicos $SYS do MQTT para monitorar um broker: o que Mosquitto e EMQX publicam, como assinar $SYS/#, transformar contadores em taxas e plotar em Python.

Atualizado · 8 min de leitura

Monitorar um broker MQTT com os tópicos $SYS

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ópicoTipoO que informa
$SYS/broker/clients/connectedgaugeClientes conectados agora
$SYS/broker/clients/maximumgaugeMaior número de conexões simultâneas desde o início
$SYS/broker/clients/disconnectedgaugeSessões persistentes cujo cliente está offline
$SYS/broker/messages/receivedcontadorMensagens recebidas desde o início (todos os tipos de pacote)
$SYS/broker/messages/sentcontadorMensagens enviadas desde o início
$SYS/broker/publish/messages/droppedcontadorMensagens PUBLISH descartadas (limites de fila, clientes offline)
$SYS/broker/bytes/received / sentcontadorBytes de rede de entrada e saída
$SYS/broker/load/messages/received/1mintaxaMédia móvel de 1, 5 e 15 minutos
$SYS/broker/subscriptions/countgaugeAssinaturas ativas
$SYS/broker/retained messages/countgaugeMensagens retidas armazenadas (repare no espaço no tópico)
$SYS/broker/uptimetextoPor exemplo 86400 seconds
$SYS/broker/versiontextoVersã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:

Despejar toda a árvore $SYS
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:

Somente clientes e carga de mensagens
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:

  1. O broker está com $SYS desativado. No Mosquitto, sys_interval 0 desliga o recurso.
  2. 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 code 0x87 Not authorized. Decodifique-o com a consulta de reason codes.
  3. 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:

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

Um 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 uptime confirma 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:

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 = {}  # 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:

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 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:

Publicar 1.000 mensagens e observar a taxa
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 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 $SYS mostra 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 $SYS fica 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.