halley-wl 0.3.1

Wayland backend and rendering implementation for the Halley Wayland compositor.
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::time::{Duration, Instant};

use eventline::debug;

use super::drm::TtyFrameQueueReport;

const FRAME_STATS_LOG_INTERVAL_SECS: u64 = 10;

#[derive(Debug)]
pub(super) struct TtyFrameStats {
    last_report_at: Instant,
    pub(super) queued_frames: u64,
    pub(super) completed_vblanks: u64,
    pub(super) page_flip_timeouts: u64,
    pub(super) page_flip_recoveries: u64,
    pub(super) vblank_mismatches: u64,
    direct_scanout_frames: u64,
    composed_frames: u64,
    sync_wait_count: u64,
    sync_wait_total_ns: u128,
    max_sync_wait: Duration,
}

impl TtyFrameStats {
    pub(super) fn new(now: Instant) -> Self {
        Self {
            last_report_at: now,
            queued_frames: 0,
            completed_vblanks: 0,
            page_flip_timeouts: 0,
            page_flip_recoveries: 0,
            vblank_mismatches: 0,
            direct_scanout_frames: 0,
            composed_frames: 0,
            sync_wait_count: 0,
            sync_wait_total_ns: 0,
            max_sync_wait: Duration::ZERO,
        }
    }
}

pub(super) fn record_tty_frame_queue(
    frame_stats: Option<&Rc<RefCell<TtyFrameStats>>>,
    report: &TtyFrameQueueReport,
) {
    let Some(frame_stats) = frame_stats else {
        return;
    };
    if !report.queued {
        return;
    }

    let mut stats = frame_stats.borrow_mut();
    stats.queued_frames += 1;
    if report.direct_scanout_active {
        stats.direct_scanout_frames += 1;
    }
    if report.composed {
        stats.composed_frames += 1;
    }
    if let Some(wait) = report.sync_wait {
        stats.sync_wait_count += 1;
        stats.sync_wait_total_ns += wait.as_nanos();
        stats.max_sync_wait = stats.max_sync_wait.max(wait);
    }
}

pub(super) fn maybe_log_tty_frame_stats(
    frame_stats: Option<&Rc<RefCell<TtyFrameStats>>>,
    output_frame_pending_since: &Rc<RefCell<HashMap<String, Instant>>>,
    now: Instant,
) {
    let Some(frame_stats) = frame_stats else {
        return;
    };

    let mut stats = frame_stats.borrow_mut();
    if now.saturating_duration_since(stats.last_report_at)
        < Duration::from_secs(FRAME_STATS_LOG_INTERVAL_SECS)
    {
        return;
    }

    let pending_since = output_frame_pending_since.borrow();
    let pending_frames = pending_since.len();
    let max_pending_age = pending_since
        .values()
        .map(|queued_at| now.saturating_duration_since(*queued_at))
        .max()
        .unwrap_or(Duration::ZERO);
    let avg_sync_wait = if stats.sync_wait_count == 0 {
        Duration::ZERO
    } else {
        Duration::from_nanos((stats.sync_wait_total_ns / stats.sync_wait_count as u128) as u64)
    };

    debug!(
        "tty frame stats: queued={} completed_vblanks={} page_flip_timeouts={} page_flip_recoveries={} vblank_mismatches={} direct_scanout={} composed={} sync_waits={} avg_sync_wait={:?} max_sync_wait={:?} pending_frames={} max_pending_age={:?}",
        stats.queued_frames,
        stats.completed_vblanks,
        stats.page_flip_timeouts,
        stats.page_flip_recoveries,
        stats.vblank_mismatches,
        stats.direct_scanout_frames,
        stats.composed_frames,
        stats.sync_wait_count,
        avg_sync_wait,
        stats.max_sync_wait,
        pending_frames,
        max_pending_age
    );
    stats.last_report_at = now;
}