feat(stats): expose duplicate_frames_skipped counter (closes #20)
Final piece of #20. The EncodeOutcome::SkippedDuplicate variant was introduced in #19 but its count was invisible — silent Ok(_) arm in encode_thread_loop. Now exposed as a stat. Changes: - stats.rs: PipelineStats gains duplicate_frames_skipped (window delta) and prev_duplicate_frames_skipped (running total). Snapshot field added. Display format places it after over_budget (both are counters). Reset clears window delta but preserves running total (same pattern as pipewire_dropped). - state_portal.rs: EncodeThread struct gains duplicate_count: Arc<AtomicU64>. Cloned for encode_thread_loop, stored for main-thread reads. encode_thread_loop now explicitly matches SkippedDuplicate and increments with Ordering::Relaxed. Stats snapshot code reads atomic and calls set_duplicate_frames_skipped after timing drain. What this enables: Diagnosing encoded_fps health. Examples: - capture_fps=60 encoded_fps=30 duplicate_frames_skipped=30 → healthy: encoder at 30fps target, 30 frames were true duplicates - capture_fps=60 encoded_fps=5 duplicate_frames_skipped=0 → problem: frames not being dedup'd but encoder can't keep up - capture_fps=1.7 encoded_fps=1.7 duplicate_frames_skipped=0 → healthy static: low fps because KWin damage-driven delivery Original #20 issues status: - 'encoded_fps stuck at ~30': FIXED via #19 (EncodeOutcome enum), now tracks capture_fps when below 30 - 'filler masking real fps': FIXED via #15/#18 (filler deleted) - 'duplicate count invisible': FIXED via this commit - 'unique_encoded_fps / delivered_fps': not implemented, deemed unnecessary now that the core metrics are trustworthy Tests: - cargo build --release: 0 new warnings (19 baseline preserved) - cargo test: 96 lib + 3 integration, 0 failed - SAFETY comments preserved verbatim - 2 files changed, +45/-6 lines
This commit is contained in:
+24
-4
@@ -34,6 +34,7 @@ struct EncodeThread {
|
||||
handle: Option<std::thread::JoinHandle<()>>,
|
||||
input_tx: crossbeam_channel::Sender<CpuNv12Frame>,
|
||||
timing_rx: crossbeam_channel::Receiver<EncodeThreadTiming>,
|
||||
duplicate_count: std::sync::Arc<std::sync::atomic::AtomicU64>,
|
||||
}
|
||||
|
||||
struct WebrtcThread {
|
||||
@@ -244,14 +245,26 @@ impl StatePortal {
|
||||
bitrate_rx,
|
||||
encoder_resolution_rx,
|
||||
)?;
|
||||
let duplicate_count = std::sync::Arc::new(
|
||||
std::sync::atomic::AtomicU64::new(0),
|
||||
);
|
||||
let duplicate_count_for_thread = duplicate_count.clone();
|
||||
let handle = std::thread::Builder::new()
|
||||
.name("wl-webrtc-encode".into())
|
||||
.spawn(move || encode_thread_loop(encode, input_rx, timing_tx))?;
|
||||
.spawn(move || {
|
||||
encode_thread_loop(
|
||||
encode,
|
||||
input_rx,
|
||||
timing_tx,
|
||||
duplicate_count_for_thread,
|
||||
)
|
||||
})?;
|
||||
self.enc_import = Some(import);
|
||||
self.enc_thread = Some(EncodeThread {
|
||||
handle: Some(handle),
|
||||
input_tx,
|
||||
timing_rx,
|
||||
duplicate_count,
|
||||
});
|
||||
|
||||
let wrtc = self.webrtc.take().ok_or_else(|| {
|
||||
@@ -331,6 +344,11 @@ impl StatePortal {
|
||||
timing.output_bytes,
|
||||
);
|
||||
}
|
||||
// Read duplicate counter (delta computed in setter)
|
||||
let total = enc_thread
|
||||
.duplicate_count
|
||||
.load(std::sync::atomic::Ordering::Relaxed);
|
||||
self.stats.set_duplicate_frames_skipped(total);
|
||||
}
|
||||
if let Some(ref webrtc_thread) = self.webrtc_thread {
|
||||
while let Ok((gap_ms, age_ms)) = webrtc_thread.sent_gap_rx.try_recv() {
|
||||
@@ -634,6 +652,7 @@ fn encode_thread_loop(
|
||||
mut encode: SwEncEncode,
|
||||
input_rx: crossbeam_channel::Receiver<CpuNv12Frame>,
|
||||
timing_tx: crossbeam_channel::Sender<EncodeThreadTiming>,
|
||||
duplicate_count: std::sync::Arc<std::sync::atomic::AtomicU64>,
|
||||
) {
|
||||
loop {
|
||||
match input_rx.recv() {
|
||||
@@ -647,10 +666,11 @@ fn encode_thread_loop(
|
||||
output_bytes: t.output_bytes,
|
||||
});
|
||||
}
|
||||
Ok(EncodeOutcome::SkippedDuplicate) => {
|
||||
duplicate_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
Ok(_) => {
|
||||
// SkippedPaused / SkippedDisconnected / SkippedDuplicate
|
||||
// Do not report timing; do not tick encoded_fps.
|
||||
// take_timing() intentionally NOT called — last_timing stays default.
|
||||
// SkippedPaused / SkippedDisconnected — no counter needed
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Encode thread error: {e}");
|
||||
|
||||
Reference in New Issue
Block a user