Observability: Prometheus, Grafana, and the honest boundary¶
SYNAPSE does not ship its own Grafana — it makes the hub a first-class citizen of the observability stack you already run. One flag exposes the metrics, one scrape job collects them, one committed dashboard renders them, and one rules file pages you when the fleet misbehaves.
What lives where¶
Two planes, deliberately separate:
/metricson the hub (this page) — the live process deciding: connection gauges and monotonic decision counters (claims granted and denied, releases, directed and broadcast chat, auth failures, rate-limit rejections, federation denials, waiter takeovers and quarantines, the dead-letter ledger). Scrape-safe: reading it does no I/O. Counters reset with the process — Prometheusrate()/increase()expect exactly that.- The dashboard's store feeds and the cockpit — log analytics:
/reliability.json,/events.json,/causality.json,/metrics.jsonare derived from the durable event store, deterministic over a given log, and available with the hub down. They answer what happened;/metricsanswers what the process is doing right now. Neither duplicates the other, and each document says which one it is.
Five minutes to a working dashboard¶
- Expose the metrics. Start the hub with
--metrics(and, off loopback,--metrics-token):
synapse hub --metrics --metrics-token "$(cat ~/.synapse-metrics-token)"
curl -H "Authorization: Bearer $(cat ~/.synapse-metrics-token)" \
http://localhost:8876/metrics | head
-
Scrape. Merge
integrations/observability/prometheus-scrape.ymlinto yourprometheus.ymlscrape_configs:. With a metrics token, usecredentials_file— never inline the secret in the config. -
Import the dashboard. In Grafana: Dashboards → Import → upload
grafana-dashboard-synapse-hub.jsonand pick your Prometheus datasource. You get liveness stats, message and claim-decision rates, refusal counts, the dead-letter ledger, and takeover/quarantine activity. -
Alert. Load
prometheus-alerts.ymlviarule_files:. Eight rules ship: hub down, dead letters growing, claims denied faster than granted, auth failures, takeover quarantine, federation denials, and — from the log-derived textfiles below — reliability findings and causal-health anomalies. The thresholds are starting points sized for a single-workstation fleet — tune them to your chatter before paging anyone at 03:00.
Reading the decision counters¶
The counters count decisions, not intents: a denied claim the client retries and wins counts once as denied and once as granted, because both happened. The signals that matter operationally:
synapse_claims_denied_totaloutpacingsynapse_claims_granted_totalis contention —synapse causality contentionnames the fight.synapse_dead_lettersclimbing means someone writes to a name nobody holds —synapse doctornames the unread addressee,syn inbox --asdrains it, and the gauge falls when the addressee connects. Its companionsynapse_dead_letter_targetscounts the distinct names currently blackholed; the ledger ages quiet names out, so a value that stays above zero (theSynapsePersistentDeadLettersalert watches it over an hour) is a persistent gap, not a passing miss.synapse dead-letterslists them. A hub given adead_letter_escalation_thresholdturns that passive gauge into an active signal: it broadcasts a notice and journals adead_letter_escalationaudit event each time a target's undelivered count crosses the threshold. When the blackholed target's namespace is owned by a peer hub — resolved through the same relay routes an operator relay uses — the escalation also forwards a pointer to that peer (the target and its count, never a message body) over the federation transport and records adead_letter_forwardingaudit event, so the hub that can actually reach the missing reader learns of the gap. The forward leaves a matching audit on both hubs, distinguished by adirectionfield —outon the origin that sent it,inon the owning hub that received it (naming the verified sending peer) — and the owning hub broadcasts the incoming pointer to its own operators. Query both withsynapse event-query --kind dead_letter_escalationand--kind dead_letter_forwarding.- Mailbox pending is not the dead-letter ledger. Dead letters record the
send-time fact that no socket matched; the mailbox projection counts durable
directed chats after an identity's receiver-acknowledged watermark, even across
hub restart.
synapse who,synapse status, andsynapse doctorrenderN undelivered messages pending for <identity>from the additive WHO snapshot; to keep old test/diagnostic identities from flooding an operator terminal, a fullwhoview sorts positive counts largest-first and shows the top 20 plus total identities/messages.synapse who --all-mailbox-pending(or--all) expands the complete retained projection;--projectfilters before the bound. A mailbox-enabledsynapse armreplay advances the watermark, whilesyn inbox --as <identity>remains the human-readable body inspection path. A hub without a durable event store reports the projection unavailable instead of zero. The watermark proves only that a mailbox client accepted the frame—never that a model read or acted on it. The hub projection itself retains at most 512 recently touched identities. Eviction removes only a cached count, not journal evidence or message bodies; durable journal compaction remains an explicit operator retention decision. - Any movement on
synapse_auth_failures_totalorsynapse_federation_denied_totalon a locked-down hub deserves a look atsynapse event-query --kind error— expected during key rotation, suspicious otherwise. synapse_takeover_quarantines_totalmoving means two waiters fought over one identity until the hub pinned it —syn reap --stalecleans the corpse that caused it.
Log-derived signals (the analytics plane, in Prometheus)¶
The store-derived analytics — reliability findings and causal-health
anomalies — reach the same Prometheus and alerting plane through the
node_exporter textfile collector, so they show up next to the live
counters without the hub needing to be up:
# a timer writes these into node_exporter's --collector.textfile.directory
synapse reliability ~/synapse/hub.db --textfile /var/lib/node_exporter/synapse_reliability.prom
synapse causality health ~/synapse/hub.db --textfile /var/lib/node_exporter/synapse_health.prom
The reliability file carries synapse_reliability_findings{kind=...} (all
four evidence kinds, zero included so an alert can fire the first time a
kind goes positive) and synapse_reliability_owner_findings{owner,kind}.
The health file carries synapse_causal_health_anomalies{shape=...}
(orphaned/dangling/stale), its total, and tasks scanned. Every value is
deterministic over a given log — the same log renders the same file — and
every file is valid exposition (the suite parses each back with the
Prometheus client parser, so node_exporter never silently rejects one).
These are evidence gauges, not grades: alert on a positive count, then
read the finding with synapse reliability or synapse causality health
to see what it is.
Traces¶
Coordination causality exports to OpenTelemetry independently of this
page: synapse causality otel --out spans.json writes span records with
deterministic ids, --endpoint pushes OTLP/HTTP to a collector, and
--watch keeps the projection live. See the CLI reference — that surface
reads the durable log, so it belongs to the analytics plane and works
with the hub down.
One fleet scorecard¶
synapse fleet-scorecard ~/synapse/hub.db --out fleet-scorecard.json
composes those durable analytics with opt-in accounting, advisory live-claim
contention, evidence-only reliability findings, and optional benchmark history
(--trend bench-trend.db). The portable JSON retains the complete source
reports and is written atomically with owner-only permissions.
With pip install 'synapse-channel[otel]', point the same command at an
OTLP/HTTP collector base:
synapse fleet-scorecard ~/synapse/hub.db \
--service-name workstation-hub \
--endpoint http://127.0.0.1:4318
The command appends /v1/traces and /v1/metrics, sends the existing
deterministic task traces, and publishes scorecard gauges for opt-in calls,
tokens and cost, conflict pairs, reliability finding counts, trace volume, and
the latest comparable benchmark values. It does not collect usage itself, rank
agents, pre-empt claims, or backfill historic metric timestamps. The two OTLP
posts are fail-visible but not transactional: a failure exits nonzero even if
the collector accepted the other signal.