Die meisten MQTT-Broker berichten per MQTT über sich selbst. Abonniere $SYS/#, und der Broker veröffentlicht, wie viele Clients verbunden sind, wie viele Nachrichten und Bytes er empfangen und gesendet hat, wie viele Subscriptions und Retained Messages er hält und wie lange er schon läuft. Damit ist $SYS der schnellste Weg, einen Broker zu überwachen. Du brauchst keinen Exporter und keinen Agenten, nur einen MQTT-Client.
Dieser Guide zeigt, was $SYS bei gängigen Brokern liefert, wie du es richtig abonnierst, wie du rohe Zähler in brauchbare Raten umrechnest und wie du sie mit einem kurzen Python-Skript protokollierst und grafisch darstellst.
Was $SYS-Topics sind
Die MQTT-Spezifikation reserviert Topics, die mit $ beginnen, für interne Zwecke des Brokers. Clients sollen dort nicht publishen, und Wildcards treffen sie nicht versehentlich: Ein Abo auf # oder +/foo erhält nie $SYS-Nachrichten. Du musst $SYS/# (oder einen engeren Filter) gezielt abonnieren.
Der Inhalt ist nicht standardisiert. Mosquitto, EMQX, VerneMQ und andere veröffentlichen jeweils einen eigenen Baum, und manche Managed Services blenden ihn ganz aus oder beschränken ihn per ACL. Eine vollständige Referenz des Topic-Baums findest du unter MQTT $SYS-Topics. Hier geht es darum, diese Topics fürs Monitoring zu nutzen.
Die Topics, die sich lohnen
Das sind die Namen bei Mosquitto (2.x). Andere Broker veröffentlichen ähnliche Werte unter anderen Pfaden.
| Topic | Typ | Was es dir sagt |
|---|---|---|
$SYS/broker/clients/connected | Gauge | Aktuell verbundene Clients |
$SYS/broker/clients/maximum | Gauge | Höchste gleichzeitige Anzahl seit dem Start |
$SYS/broker/clients/disconnected | Gauge | Persistente Sessions, deren Client offline ist |
$SYS/broker/messages/received | Zähler | Empfangene Nachrichten seit dem Start (alle Pakettypen) |
$SYS/broker/messages/sent | Zähler | Gesendete Nachrichten seit dem Start |
$SYS/broker/publish/messages/dropped | Zähler | Verworfene PUBLISH-Nachrichten (Queue-Limits, Offline-Clients) |
$SYS/broker/bytes/received / sent | Zähler | Netzwerk-Bytes ein und aus |
$SYS/broker/load/messages/received/1min | Rate | Gleitender Durchschnitt über 1, 5 und 15 Minuten |
$SYS/broker/subscriptions/count | Gauge | Aktive Subscriptions |
$SYS/broker/retained messages/count | Gauge | Gespeicherte Retained Messages (beachte das Leerzeichen im Topic) |
$SYS/broker/uptime | Text | Zum Beispiel 86400 seconds |
$SYS/broker/version | Text | Versionsstring des Brokers |
Hier gibt es zwei Arten von Zahlen. Gauges (verbundene Clients, Subscriptions) sind so, wie sie sind, aussagekräftig. Zähler (Nachrichten, Bytes) steigen nur, also brauchst du die Differenz zweier Messwerte, um eine Rate zu erhalten. Mosquitto veröffentlicht unter $SYS/broker/load/... auch fertige gleitende Durchschnitte. Für einen schnellen Blick sind sie praktisch, aber du hast keine Kontrolle über das Zeitfenster.
Bei EMQX ist der Baum pro Node aufgebaut: $SYS/brokers/<node>/version, $SYS/brokers/<node>/uptime, dazu die Teilbäume stats/... und metrics/... sowie optionale Connect- und Disconnect-Events der Clients. Das Veröffentlichungsintervall steuert sys_msg_interval. Für Dashboards auf EMQX ist meist der eingebaute Prometheus-Endpoint die bessere Quelle. Siehe Monitoring mit Prometheus und Grafana.
$SYS abonnieren
Am schnellsten prüfst du das mit mosquitto_sub. Setze das Topic in einfache Anführungszeichen, sonst versucht die Shell, $SYS als Variable aufzulösen:
mosquitto_sub -h broker.connectmq.com -p 1883 \
-u "$MQTT_USER" -P "$MQTT_PASS" \
-v -t '$SYS/#'Wenn du nur ein paar Werte beobachten willst, abonniere mehrere enge Filter:
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'Dasselbe geht im Browser: Öffne den Online-MQTT-Client, verbinde dich und abonniere $SYS/#. Kommt nichts an, prüfe diese drei Punkte in dieser Reihenfolge:
- Der Broker hat
$SYSdeaktiviert. Bei Mosquitto schaltetsys_interval 0es ab. - Eine ACL verweigert den Zugriff. Viele Produktionskonfigurationen erlauben nur einem Admin-Benutzer,
$SYSzu lesen. MQTT-5-Clients bekommen ein SUBACK mit dem Reason Code0x87 Not authorized. Du kannst ihn mit dem Reason-Code-Nachschlagewerk entschlüsseln. - Du hast nur ein Intervall gewartet. Manche Werte erscheinen erst beim nächsten
sys_interval-Tick.
Das Update-Intervall einstellen
Bei Mosquitto ist das Intervall eine einzige Option:
# Publish $SYS updates every 10 seconds (default). 0 disables $SYS.
sys_interval 10Ein kürzeres Intervall liefert feinere Graphen, kostet aber etwas CPU und Bandbreite für jeden Subscriber auf $SYS/#. Für Dashboards reichen 10–30 Sekunden völlig. Der mosquitto.conf-Generator enthält diese Option, falls du deine Konfiguration neu aufbaust.
Zähler in Raten umrechnen
Zähler sind nur als Differenzen nützlich. Stand messages/received um 12:00:00 bei 1.204.500 und um 12:00:10 bei 1.210.500, hat der Broker in diesem Fenster (1210500 − 1204500) / 10 = 600 Nachrichten pro Sekunde empfangen. Zwei Regeln halten die Rechnung ehrlich:
- Nimm den Zeitpunkt, an dem du den Messwert empfangen hast, nicht das nominelle Intervall. Netzwerk-Jitter und Broker-Last verschieben die Ticks.
- Behandle Resets. Startet der Broker neu, beginnen die Zähler wieder bei null. Ist ein neuer Wert kleiner als der vorherige, verwirf diese Differenz, statt einen riesigen negativen Ausschlag zu zeichnen. Ein Rückgang von
uptimebestätigt den Neustart.
$SYS mit Python protokollieren und visualisieren
Dieses Skript (paho-mqtt 2.x) abonniert einige $SYS-Topics, berechnet Raten aus den Zählern, gibt pro Update eine Zeile aus und hängt alles an eine CSV-Datei an, die du später grafisch darstellen kannst:
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 -> (Zeitstempel, Wert)
out = csv.writer(open("sys_metrics.csv", "a", newline=""))
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print("Verbindung fehlgeschlagen:", 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]: # erster Messwert oder Broker-Neustart
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()Wichtig ist, in on_connect zu abonnieren: Nach einem Reconnect werden die Subscriptions so automatisch wiederhergestellt. Für einen Graphen aus der CSV-Datei reichen ein paar Zeilen pandas und 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()Für ein dauerhaftes Dashboard mit Historie und Alerting schreibst du dieselben Werte statt in eine CSV-Datei in Prometheus oder InfluxDB.
Teste, ob dein Monitoring reagiert
Ein Monitor, der nie eine Veränderung gesehen hat, ist nicht getestet. Erzeuge bekannte Last und prüfe, ob sich die Zahlen bewegen:
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 -lWährend des Laufs sollte messages/received/s ungefähr um deine Publish-Rate steigen. Öffne zehn zusätzliche Verbindungen im Online-Client oder mit einem Gerätesimulator, dann sollte clients/connected um zehn steigen. Bewegt sich keiner der Werte, liest du wahrscheinlich einen anderen Node als den, auf den du schreibst, oder ein Cluster meldet Werte pro Node.
Grenzen des $SYS-Monitorings
- Keine Historie.
$SYSzeigt nur den aktuellen Zustand. Speichern musst du ihn selbst. - Kein Alerting. Kombiniere es mit Prometheus, Grafana oder einem Skript, das dich alarmiert. Für Prüfungen von außen siehe Broker-Health-Checks.
- Brokerspezifisch. Topic-Namen und Bedeutungen ändern sich zwischen Brokern und manchmal zwischen Versionen.
- Überwacht vom System, das es überwacht. Ist der Broker down, schweigt
$SYS. Ein schweigender Monitor sollte einen Alarm auslösen und nicht wie ein ruhiger Tag aussehen.
$SYS-Topics sind die günstigste Observability, die ein MQTT-Broker bietet. Abonniere einmal $SYS/#, um zu sehen, was dein Broker veröffentlicht, behalte ein kleines Skript, das Zähler in Raten umrechnet, und beschränke den Zugriff, sodass nur dein Monitoring-Konto den Baum lesen kann.
Häufig gestellte Fragen
Warum sehe ich mit einem Abo auf # keine $SYS-Nachrichten?
Laut MQTT-Spezifikation dürfen Wildcard-Abos, die mit # oder + beginnen, keine Topics treffen, die mit $ anfangen. Abonniere $SYS/# ausdrücklich als eigenes Abo.
Wie oft werden $SYS-Topics aktualisiert?
Das hängt vom Broker ab. Mosquitto veröffentlicht sie alle sys_interval Sekunden (Standard 10, 0 schaltet sie ab). EMQX nutzt eine eigene Einstellung namens sys_msg_interval. Prüfe die Konfiguration deines Brokers.
Sind $SYS-Topics Teil des MQTT-Standards?
Nein. Die Spezifikation reserviert nur Topics, die mit $ beginnen, für den Broker. Namen und Werte unter $SYS sind brokerspezifisch, ein Dashboard für Mosquitto läuft also nicht unverändert auf EMQX oder einem anderen Broker.


