Skip to content

Implement pipeline stats/analytics#572

Draft
joaoantoniocardoso wants to merge 45 commits intomavlink:masterfrom
joaoantoniocardoso:stats
Draft

Implement pipeline stats/analytics#572
joaoantoniocardoso wants to merge 45 commits intomavlink:masterfrom
joaoantoniocardoso:stats

Conversation

@joaoantoniocardoso
Copy link
Copy Markdown
Collaborator

@joaoantoniocardoso joaoantoniocardoso commented Feb 16, 2026

To be merged after #564.

For an example use-case, see https://github.com/joaoantoniocardoso/mcm-test-harness

Impact: ~3% CPU at 1Hz probing at full level.

Frontend will be implemented in another PR, after enough validation.

@joaoantoniocardoso joaoantoniocardoso force-pushed the stats branch 3 times, most recently from 2955533 to 1db0b93 Compare February 22, 2026 02:59
Add lower_thread_priority() (nice 10) and lower_to_background_priority()
(SCHED_OTHER, nice 19) to allow non-GStreamer threads and auxiliary
pipelines to yield CPU to realtime video stream threads.

Adds libc as a Linux-only dependency.
… runner

Refactor PipelineRunner::try_new into try_new/try_new_inner with a
realtime_threads parameter. Add try_new_background() that sets GStreamer
streaming threads to SCHED_OTHER with nice 19 instead of SCHED_RR
realtime, for auxiliary pipelines (e.g. thumbnail generation) that must
not preempt video stream threads.
Replace the Rust `image` crate-based thumbnail pipeline with a fully
GStreamer-native approach using videoscale, videoconvert, and jpegenc.
This eliminates the need to decode to raw RGB, copy frame data out of
GStreamer, and re-encode in userspace.

Key changes:
- Decode → videoscale → capsfilter → videoconvert → jpegenc → appsink
  replaces decode → appsink → image::thumbnail → image::write_to
- JPEG quality and target resolution are updated dynamically per request
  by modifying jpegenc and capsfilter properties at runtime
- Software decoders are limited to max-threads=1 to reduce CPU usage
- Pipeline runs at background priority via try_new_background()
- Optional pad blocker (MCM_THUMBNAIL_PAD_BLOCKER=1) pauses data flow
  between snapshots to further reduce idle CPU
- Removes the `image` crate dependency
…rease timeout for thumbnail capture

Sending an upstream ForceKeyUnit event ensures the encoder produces a
keyframe immediately rather than waiting for the next natural one (which
can be many seconds away with default x264enc settings). The receive
timeout is raised from 2 s to 5 s to accommodate the round-trip.
Set nice=10 via setpriority() on the main thread, all Tokio worker
threads, the TURN server runtime threads, and the thumbnail generation
thread. This ensures GStreamer pipeline threads (which run at SCHED_RR
realtime when CAP_SYS_NICE is available) are always preferred by the OS
scheduler, reducing latency spikes on resource-constrained systems.

Also replaces the #[tokio::main] attribute macro with an explicit
runtime builder so that on_thread_start can be set for worker threads.
…ndant depay/repay

Previously the RTSP sink received RTP-encapsulated data from the rtp_tee,
requiring a depay/repay cycle (e.g. rtph264depay ! rtph264pay) inside the
RTSP server pipeline. This was wasteful and introduced unnecessary CPU
overhead.

Route the RTSP sink through the video_tee instead, feeding raw encoded
video (e.g. H264) directly to the RTP payloader. Update the RTSP server
to detect the stream format from video caps structure names instead of
RTP encoding-name fields.
Replace the shared-memory IPC bridge (shmsink → socket → shmsrc) with
an in-process appsink/appsrc pair. This eliminates filesystem socket
overhead and enables direct buffer passing between the source pipeline
and the RTSP server's internal pipeline.

The appsink callback rebases PTS/DTS so each new RTSP client connection
sees timestamps starting from 0, preventing the server's queue from
holding stale buffers. A media-configure callback on the RTSP factory
configures the appsrc caps and resets the PTS offset on each new
connection.
During WebRTC connection setup, webrtcbin cannot consume data until the
DTLS handshake completes (~200-300ms). The leaky queue accumulates stale
RTP packets during this period, and because input rate equals output rate
in steady state, the queue level never drains — adding permanent latency
to every WebRTC session.

After the connection state transitions to Connected, reduce the queue's
max-size-time from the initial 1s backstop to one frame interval. The
leaky=downstream policy immediately flushes the stale handshake backlog.
Once the queue fully drains (underrun signal), dynamic sizing kicks in:
each overrun grows max-size-time by one frame interval (capped at 10x),
letting the queue self-tune to the minimum size that avoids frame drops
under the current CPU load.
Even with fec-type=None on the transceiver, webrtcbin still creates
rtpulpfecenc and rtpredenc elements that copy every RTP buffer for no
benefit. Once the peer connection is established, surgically unlink
and remove them.

Also strip FEC/RED payload types from the SDP offer so the peer
never negotiates them.
The wrap_fn trace and actix_web::middleware::Logger were redundant with
TracingLogger and added measurable per-request overhead.
The DOT pipeline graph endpoint is a debugging tool that should not be
active in production. It now returns 404 unless --enable-dot is passed.
Add a StreamStatusState enum (Running / Idle / Stopped) to the API
response and a disable_lazy flag to ExtendedConfiguration so users can
opt out of lazy pipeline suspension per stream.
Introduce per-stream consumer_count / idle tracking and a watcher loop
that suspends pipelines to GStreamer NULL after a 5-second grace period
with zero consumers, then resumes on demand.

RTSP flow is managed through a new RtspFlowHandle that wraps a valve
element: the valve drops buffers when no RTSP clients are connected and
opens on the first client_connected callback, while updating the shared
consumer_count.

The old logic that set pipelines to NULL when no tee source pads were
linked is removed in favour of the new watcher-driven lifecycle.

The pipeline runner watchdog now skips position checks while the
pipeline is not in Playing state.
Wake idle pipelines before WebRTC session setup and thumbnail capture,
waiting for data flow (query_position advancing) rather than just the
Playing state to ensure sinks can negotiate successfully.

Track consumer_count in add_session / remove_session and only decrement
on the first successful sink removal to avoid usize underflow from
duplicate cleanup calls.

Expose StreamStatusState (Running / Idle / Stopped) in the streams
information API.
…sconnect

Maintain an ActiveSessions list per WebSocket connection so that when a
client disconnects without sending EndSession (e.g. browser navigated
away), all its orphaned sessions are cleaned up and consumer_count is
correctly decremented.

Also filter stopped streams from the WebRTC producer list so idle
pipelines remain discoverable.
The test stream had no extended_configuration, defaulting to lazy idle
enabled. This caused the pipeline to suspend during the 5-second
prepare wait and then resume on the first session cycle, creating a
fragile window where WebRTC negotiation could fail for one session.

Since this test focuses on thread leak detection, not idle lifecycle,
set disable_lazy: true to keep the pipeline continuously running.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant