Skip to content

Push supervision

media.push.supervisor

Sapsan can actively send a stream onward: to CDNs and streaming platforms over RTMP, to another server over SRT or RIST, or into the network over UDP (multicast/unicast). A stream can have several pushes at once.

Reconnection

A pusher has no retry policy of its own — the stream raises it again. Every five seconds the stream reconciles the running pushes against the configuration and replaces a dead pusher with a new one; the reconnects counter counts exactly those replacements. An unreachable target therefore never "gives up" and needs no manual restart: it keeps being raised for as long as the push is enabled.

What is not counted as a reconnect:

  • editing a push setting — the pusher is restarted too, but that is the operator's decision, not a channel failure;
  • disabling and enabling (enabled: false) — disabling is not deletion: the setting and the accumulated counters stay, only the sending stops.

Disabling and deleting

A disabled push stays in the configuration under its name: the statistics show it as disabled, its counters are frozen at their last values, and enabling continues from those. Deleting the key deletes the statistics too: counters belong to the name.

Monitoring

Every push has statistics of its own — target, status, failure reason, rate, bytes, errors, reconnects. The field reference, the Prometheus series, and alerting recipes are on the Statistics page.

Next steps

Push statistics

Outgoing delivery is counted separately from ingest and per push: the key of the statistics is the pair "stream name, push name" — the same one used in the configuration. One stream can go out to five targets, and "the stream sends 40 Mbps" does not answer whether it reaches any particular one of them.

The push statistics option

The push view is a licence option of its own. It cuts the delivery channels only: the pushes section and the flat push_* fields in the Admin API, and the stream_push_* series in the node scrape and in the built-in Prometheus server. Collection itself never stops, and the counters always go into Retroview telemetry — so the analysis is available in Retroview even without the option.

The pushes section

GET /streamer/api-v4/streams/stats/{name} (and each item of GET /streamer/api-v4/streams/stats) carries an object keyed by push name:

"pushes": {
  "cdn-main": {
    "status": "running",
    "proto": "udp",
    "url": "udp://127.0.0.1:5500",
    "opened_at": "2026-08-13T11:17:53Z",
    "bitrate_kbit": 2113.4,
    "bytes_total": 19053048,
    "datagrams_total": 14478
  },
  "youtube": {
    "status": "error",
    "proto": "rtmp",
    "url": "rtmp://198.51.100.7/live/secret-key",
    "error": "rtmp connect failed: timeout after 5s",
    "reconnects_total": 22
  },
  "backup-dc": {"status": "disabled", "proto": "srt", "url": "srt://203.0.113.10:9000"}
}
Field What it means How to use it
status running, error, or disabled not running for longer than N minutes — alert; disabled is normal, it is a push the operator turned off
error the reason behind the error status the first thing to read when diagnosing: "connect failed", publish rejection, handshake rejection
proto, url transport and target exactly as the operator set them shows where the stream actually goes
opened_at when the current pusher started it gets younger on every reconnect — the channel is flapping
bitrate_kbit measured sending rate noticeably below the stream bitrate — delivery is not keeping up
errors_rate send errors per second growing on a live connection — the receiver or the channel is degrading
bytes_total bytes sent, cumulative reconciling outgoing volume per direction
errors_total send errors, cumulative —
reconnects_total restarts of a dead pusher growing while status: running — the channel breaks and recovers
retransmitted_packets_total transport retransmits (SRT, RIST) growing — losses on the way to the receiver
rtt_ms, rtt_max_ms RTT to the target: current and the maximum over the current pusher's lifetime; SRT pushes only, a reconnect restarts the maximum RTT noticeably above the configured latency — raise the push latency
error_events ring of recent events: send errors (time, text) and pusher stops (time, reason); maintained only for pushes carrying the user_id_<name> owner tag in labels the timeline next to the error field — the first cause of the current failure
closures_by_reason pusher stops totals by reason: error, peer_closed, disabled, reconfigure, server_restart operator stops are told from a dead target by the reason
datagrams_total, frames_total units sent: datagrams for packet transports, frames for RTMP —

Zero fields are omitted from the answer: an RTMP push has no datagrams, a UDP one has neither frames nor retransmits.

Next to the stream's own counters, as top-level fields of the same answer, are three sums across all pushes — push_bytes_total, push_errors_total, push_reconnects_total. They answer "how much did the stream send at all" without walking the map.

Active pushes

The list of working pushes across the cluster is not a walk over streams but a ready projection of the statistics plane:

curl -fsS -u "$ADMIN_USER:$ADMIN_PASSWORD" \
  'http://central:8080/central/api-v4/pushes/active?limit=100'

The same path exists on the streamer: /streamer/api-v4/pushes/active. A record's key is the triple "stream, push, node": a standby group keeps two records per stream×push pair, the active one and the waiting one, told apart by the node and the standby_status field. A record carries the target without secrets, the transport, the owner from the user_id_<name> tag when set, and current values: status, current pusher start time, rate, errors, reconnects, retransmits, bytes and datagrams, the pusher's lag behind the fanout ring, and RTT for SRT.

{
  "pushes": [
    {
      "stream": "tv1",
      "push": "cdn-main",
      "node": "node-7",
      "transport": "srt",
      "endpoint": "srt://203.0.113.10:9000",
      "user_id": "ivan",
      "status": "running",
      "opened_at": "2026-10-21T09:15:02Z",
      "bitrate_kbit": 2113.4,
      "rtt_ms": 34,
      "rtt_max_ms": 61,
      "error_events": [
        {"at": 1792845002, "close_reason": "error"},
        {"at": 1792845062, "error": "srt connect failed: timeout after 5s"}
      ],
      "closures_by_reason": {"error": 22, "reconfigure": 1}
    }
  ]
}

Events in error_events run from oldest to newest and are bounded by a ring: overflow evicts the oldest, and no full history is kept anywhere. The ring belongs to the push, not to the pusher — events survive a reconnect. The resource accepts the filters stream, push, user_id, node, transport, and limit; records come freshest first by pusher start time. A disabled push and a push without a pusher never make the list; an empty answer means no pushes are working.

Events, the stop totals by reason, and the protocol-level SRT depth (stream_push_srt_*) are maintained only for designated pushes — those whose configuration carries the user_id_<name> owner tag in labels. A push without an owner lives on the base series family and the status fields; removing the tag stops new points, and the recorded ones remain until the store window evicts them.

Prometheus series

Every push counter is a series of its own, labelled with push:

Series Labels
stream_push_bytes_total stream, push, proto
stream_push_errors_total stream, push, proto
stream_push_reconnects_total stream, push, proto
stream_push_retransmitted_packets_total stream, push, proto
stream_push_closures_total stream, push, proto, reason
stream_push_datagrams_total, stream_push_frames_total stream, push, proto

In the built-in Prometheus server the same counters carry the labels name (the stream) and push; on central, node is added to them. Protocol depth — stream_push_srt_* and stream_push_rist_* — comes in the node scrape together with the extended counters option. The stream_push_closures_total series counts pusher stops by reason from the closed dictionary and is wider than reconnects_total: that one counts only restarts after a pusher dies, this one — all stops; it is also maintained only for designated pushes.

# sending rate per target
rate(stream_push_bytes_total{stream="tv1"}[1m])

# the stream's total outgoing rate across all pushes
sum by (name) (rate(stream_push_bytes_total{name="tv1"}[1m]))

# breaking channels: reconnects over five minutes
increase(stream_push_reconnects_total[5m]) > 0

# losses on the way to the receiver over SRT/RIST
rate(stream_push_retransmitted_packets_total[1m])

# unreachable target: a streak of send-error stops
increase(stream_push_closures_total{reason="error"}[5m]) > 0

How to read the state

  • status: error with a non-empty error — nothing is being sent, and the reason is named. The push keeps raising itself every five seconds regardless.
  • status: running with a zero bitrate_kbit — the connection is there but the data is not: check whether the stream itself is running.
  • status: running with a growing reconnects_total — the channel breaks and recovers; look at opened_at, it shows the age of the current connection.
  • a growing closures_by_reason with reason error — the target is unreachable and the pusher keeps restarting; the pace is visible in the event timestamps of error_events. Stops with disabled and reconfigure are operator actions, not network failures.
  • rtt_max_ms of an SRT push noticeably above the configured latency — the latency budget does not cover loss recovery: raise the push latency.
  • status: disabled — the push is turned off in the configuration. Its counters are frozen at their last values; nothing is lost.

Configuration

Configure named stream pushes through central. enabled: false pauses delivery and retains the configuration.

Transport

Each named push selects its transport. Push supervision does not add a transport protocol.

Supported codecs

Codecs depend on the selected protocol: UDP, RTMP, SRT, RIST, SDI.