daat-locus 0.4.0

A long-running local agent runtime with memory, workflows, apps, and sleep-time self-improvement.
use std::time::{Duration, Instant};

const TUI_PROFILE_SUMMARY_INTERVAL: Duration = Duration::from_secs(2);
const TUI_SLOW_FRAME_WARN_THRESHOLD: Duration = Duration::from_millis(32);
const TUI_HIGH_FPS_WARN_THRESHOLD: f64 = 30.0;
const TUI_PROFILE_ENV: &str = "DAAT_LOCUS_TUI_PROFILE";

pub(super) struct TuiFrameProfiler {
    enabled: bool,
    window_started_at: Instant,
    frames: u64,
    slow_frames: u64,
    total_frame: Duration,
    total_prep: Duration,
    total_draw: Duration,
    total_activity: Duration,
    total_command: Duration,
    max_frame: Duration,
    max_activity: Duration,
    last_committed_cells: usize,
    last_live_cells: usize,
}

pub(super) struct TuiFrameTiming {
    pub(super) committed_cells: usize,
    pub(super) live_cells: usize,
    pub(super) frame: Duration,
    pub(super) prep: Duration,
    pub(super) draw: Duration,
    pub(super) activity: Duration,
    pub(super) command: Duration,
}

impl TuiFrameProfiler {
    pub(super) fn new() -> Self {
        let enabled = std::env::var(TUI_PROFILE_ENV).is_ok_and(|value| {
            matches!(
                value.trim().to_ascii_lowercase().as_str(),
                "1" | "true" | "yes" | "on"
            )
        });
        Self {
            enabled,
            window_started_at: Instant::now(),
            frames: 0,
            slow_frames: 0,
            total_frame: Duration::ZERO,
            total_prep: Duration::ZERO,
            total_draw: Duration::ZERO,
            total_activity: Duration::ZERO,
            total_command: Duration::ZERO,
            max_frame: Duration::ZERO,
            max_activity: Duration::ZERO,
            last_committed_cells: 0,
            last_live_cells: 0,
        }
    }

    pub(super) fn record(&mut self, timing: &TuiFrameTiming) {
        self.frames = self.frames.saturating_add(1);
        if timing.frame >= TUI_SLOW_FRAME_WARN_THRESHOLD {
            self.slow_frames = self.slow_frames.saturating_add(1);
        }
        self.total_frame += timing.frame;
        self.total_prep += timing.prep;
        self.total_draw += timing.draw;
        self.total_activity += timing.activity;
        self.total_command += timing.command;
        self.max_frame = self.max_frame.max(timing.frame);
        self.max_activity = self.max_activity.max(timing.activity);
        self.last_committed_cells = timing.committed_cells;
        self.last_live_cells = timing.live_cells;

        let elapsed = self.window_started_at.elapsed();
        if elapsed < TUI_PROFILE_SUMMARY_INTERVAL {
            return;
        }
        let frame_count = u32::try_from(self.frames).unwrap_or(u32::MAX);
        let fps = f64::from(frame_count) / elapsed.as_secs_f64().max(0.001);
        let should_warn = fps >= TUI_HIGH_FPS_WARN_THRESHOLD || self.slow_frames > 0;
        if self.enabled || should_warn {
            let frame_count = f64::from(frame_count.max(1));
            let message = format!(
                "tui frame profile fps={fps:.1} frames={} slow_frames={} avg_frame_ms={:.2} max_frame_ms={:.2} avg_prep_ms={:.2} avg_draw_ms={:.2} avg_activity_ms={:.2} max_activity_ms={:.2} avg_command_ms={:.2} committed_cells={} live_cells={}",
                self.frames,
                self.slow_frames,
                duration_ms(self.total_frame) / frame_count,
                duration_ms(self.max_frame),
                duration_ms(self.total_prep) / frame_count,
                duration_ms(self.total_draw) / frame_count,
                duration_ms(self.total_activity) / frame_count,
                duration_ms(self.max_activity),
                duration_ms(self.total_command) / frame_count,
                self.last_committed_cells,
                self.last_live_cells,
            );
            if should_warn {
                tracing::warn!("{message}");
            } else {
                tracing::info!("{message}");
            }
        }
        self.reset_window();
    }

    fn reset_window(&mut self) {
        self.window_started_at = Instant::now();
        self.frames = 0;
        self.slow_frames = 0;
        self.total_frame = Duration::ZERO;
        self.total_prep = Duration::ZERO;
        self.total_draw = Duration::ZERO;
        self.total_activity = Duration::ZERO;
        self.total_command = Duration::ZERO;
        self.max_frame = Duration::ZERO;
        self.max_activity = Duration::ZERO;
    }
}

fn duration_ms(duration: Duration) -> f64 {
    duration.as_secs_f64() * 1000.0
}