Observability
Overview
ifm-dashboard is an observability/telemetry consumption layer only. It does not participate in the IFM runtime data path (audio, transport, relay, or discovery). Nodes continue operating normally if the dashboard is unavailable.
There are two telemetry channels, both produced by ifm-core (the protocol's single source of truth):
- Mesh telemetry plane (primary — the dashboard's real source). Every node with
NodeConfig.telemetryenabled (default) publishes its JSON observability snapshot on the reserved control topic/ifm/telemetry/v1every 5 seconds. Frames ride the gossip control plane exactly like station records / relay announcements — no polling, no inbound connections — so ANY node that is in the mesh sees every other node's live state. The dashboard is such a node: it joins the real network through the rendezvous (like every app) and reads the frames. - Local observability server (optional, for same-machine debugging). A node can additionally serve read-only JSON snapshots on
127.0.0.1:<port>(NodeConfig.observability_port).
Responsibilities
- Discover/track all running IFM nodes — via the observability interface; node listings are read-only snapshots
- Show node status and health — CPU, memory, network, gossip, DHT, audio pipeline
- Observe node-level logs — streamed log lines from each connected node
- Aggregate application/system logs — where nodes expose structured log output
- Monitor connections and peers — per-peer connection state, RTT, transport mode
- Monitor relay activity — pool membership, forwarded packets, uptime (read-only)
- Monitor audio/stream state and metrics — opus encode/decode health, jitter buffer stats, VAD state, microphone level
- Provide historical observability — retained packet logs, frequency uptime, connection history (configurable retention)
- Provide debugging/diagnostic information — packet captures, topology snapshots, node introspection
Architectural Constraints
| Constraint | Detail |
|---|---|
| No runtime dependency | Dashboard may be taken offline; IFM nodes continue operating |
| Observes only | Never broadcasts, tunes, announces, or sends control commands — all interactions are read-only |
| Observer node | The dashboard runs a real ifm-core node so it can see the REMOTE mesh: it joins through the rendezvous like every app. Being in the mesh is what lets it observe it; it never produces traffic (its own telemetry frame is the only thing it sends, like every node's) |
| Telemetry plane | Telemetry rides the reserved control topic (/ifm/telemetry/v1), the same class of tiny control-plane traffic as station records / relay announcements. Frames are advisory observer data — nothing in the protocol ever acts on them |
| Telemetry interface | Consumes the telemetry frames + discovery records + its own transport view, all produced by ifm-core (the protocol's single source of truth). The dashboard is a Rust/Tauri application; the SDK is not involved |
| Passive forwarding | Dashboard never becomes a dependency of the IFM runtime data path |
| Transport agnostic | Observability data rides the mesh control plane (any transport), plus the optional localhost HTTP channel for same-machine debugging |
Telemetry Interface
ifm-core's Node exposes an observability server on 127.0.0.1:<port> (NodeConfig.observability_port, disabled by default). Each request builds a read-only snapshot under short-lived locks; the server runs on its own thread with timeouts, so a slow or missing dashboard never stalls the node.
| Topic | Endpoint | Description |
|---|---|---|
node.info | GET /info | Node identity, version, transport, listen addresses, uptime |
node.health | GET /health | Gossip counters, DHT, peers, audio pipeline metrics |
node.peers | GET /peers | Connected peers with addresses + transport |
node.relay | GET /relay | Relay pool status, serving state, forwarded packet count |
node.audio | GET /audio | Opus health, jitter buffer stats, VAD state, live-edge |
node.p2p | GET /p2p | P2P health: latency, jitter, loss, upload, role, active path |
node.frequencies | GET /frequencies | Tuned frequencies + on-air station channels, listener count |
node.logs | GET /logs | Reserved — structured log streaming is a later phase (returns { lines: [] } today) |
| — | GET /snapshot | Everything above in one JSON document (dashboard poll) |
Mesh Telemetry Plane
- Transport (
crates/transport): the reserved gossip topic/ifm/telemetry/v1carries [TelemetryFrame]s —{ node_id, ts_ms, data }wheredatais the node's opaque JSON snapshot. Every transport (libp2p QUIC, in-memory, shared) auto-subscribes at startup, keeps the freshest frame per node (TTL-bounded — a silent node drops out on its own), and exposespublish_telemetry/telemetry_frames. - Core (
crates/core/src/telemetry.rs):Node::publish_telemetry()serializes the node'sobservability_snapshot()and publishes it; a background thread does this everyTELEMETRY_INTERVAL(5s) whenNodeConfig.telemetryis on (default).Node::telemetry()returns the freshest decoded frame per node (MeshTelemetry). - Dashboard:
mesh_telemetry(Tauri command) exposesNode::telemetry()to the WebView.
Implementation
Node side — crates/core/src/observability.rs
NodeSnapshot— serde types covering every topic above (info, health, peers, relay, p2p, paths, audio, frequencies).- The
p2pblock +pathslist come from the Path Manager (crates/core/src/path.rs, spec §19a): every candidate path — direct peers and pool relays — is scored from measured RTT, jitter, loss, bandwidth, hops and stability, best-first. The dashboard shows the current P2P health (latency/jitter/loss/upload/role/path) and the ranked alternatives, so an operator sees why the node is on its current path and which relay could beat it. ObservabilityServer— a tiny std-only HTTP server (no new dependencies) bound to127.0.0.1:<port>. One request per connection, read timeout, JSON responses. Holds aWeak<Node>and exits when the node is dropped.- Wired in
Node::build: whenNodeConfig.observability_portisSome, the server starts on that port. Desktop apps pick per-app ports (14320 + AppKind), so the dashboard can scan a known range.
Dashboard side — packages/ifm-dashboard
A Tauri 2 desktop application whose Rust backend runs a real ifm-core observer node (AppKind::Dashboard via ifm-desktop):
node_startboots the observer node with the default REMOTE entry — it fetches the rendezvous manifest (bootstrap.ifm.json), dials the entry, and discovers the mesh (DHT + control topics), exactly like every app.mesh_telemetryreturns the freshest telemetry frame per node heard on the mesh (its own included).station_list/relay_listreturn the signed discovery records.node_info/node_stats/node_peersexpose the observer's own transport view.- The React frontend polls those commands every ~2.5s and renders live panels: node cards with per-node P2P health (latency / jitter / loss / upload / role / path), stations, relays, topology graph (React Flow), packet feed.
Deployment
ifm-dashboard is a standalone desktop application that:
- Joins the real IFM network through the rendezvous as an observer node (LAN-only under
IFM_LAN=1, same as every app) - Never broadcasts, tunes, or announces — it cannot affect the network
- Is updated independently of IFM node releases (backward-compatible interface)
It is internal tooling: it is built and run by IFM developers and operators during development and operations. It is never shipped to end users.