Skip to main content

cbtop/app/
mod.rs

1//! cbtop Application State Machine
2//!
3//! Manages the TUI application lifecycle using presentar-terminal.
4//! Implements REAL load generation using trueno compute bricks.
5//!
6//! Citations:
7//! - [Gregg 2020] "Systems Performance" 2nd ed. Addison-Wesley. ISBN:978-0-13-682015-4
8//! - [Hennessy & Patterson 2017] "Computer Architecture" 6th ed. ISBN:978-0-12-811905-1
9
10mod hardware;
11mod input;
12mod metrics;
13mod panels;
14mod render;
15
16pub use hardware::{DiskMetrics, HardwareInfo, LoadMetrics, MemoryBreakdown, NetworkMetrics};
17pub use panels::ActivePanel;
18
19use std::time::{Duration, Instant};
20
21use presentar_terminal::direct::{CellBuffer, DiffRenderer};
22use presentar_terminal::{ColorMode, Theme};
23
24use crate::bricks::generators::SimdLoadBrick;
25use crate::bricks::panels::{
26    ConfigPanelBrick, CpuPanelBrick, GpuPanelBrick, HelpPanelBrick, LoadControlPanelBrick,
27    MemoryPanelBrick, OverviewPanelBrick, PciePanelBrick, ThermalPanelBrick,
28};
29use crate::config::{ComputeBackend, Config, WorkloadType};
30use crate::error::CbtopError;
31use crate::ring_buffer::RingBuffer;
32
33/// Application state
34pub struct CbtopApp {
35    /// Configuration
36    pub(crate) config: Config,
37    /// Active panel
38    pub(crate) active_panel: ActivePanel,
39    /// Is load generation running
40    pub(crate) is_running: bool,
41    /// Current load intensity
42    pub(crate) intensity: f64,
43    /// Current backend
44    pub(crate) backend: ComputeBackend,
45    /// Current workload
46    pub(crate) workload: WorkloadType,
47    /// Problem size
48    pub(crate) problem_size: usize,
49
50    /// Hardware information
51    pub(crate) hardware: HardwareInfo,
52    /// SIMD load generator (real compute)
53    pub(crate) simd_generator: SimdLoadBrick,
54    /// Real-time load metrics
55    pub(crate) load_metrics: LoadMetrics,
56
57    /// CPU usage history (REAL from /proc/stat)
58    pub(crate) cpu_history: RingBuffer<f64>,
59    /// Bricks/sec history
60    pub(crate) bricks_history: RingBuffer<f64>,
61    /// Frame times for FPS calculation
62    pub(crate) frame_times: RingBuffer<f64>,
63    /// Last CPU stat for delta calculation
64    pub(crate) last_cpu_stat: Option<(u64, u64)>,
65    /// Last network stat for rate calculation (rx_bytes, tx_bytes, time)
66    pub(crate) last_network_stat: Option<(u64, u64, Instant)>,
67
68    /// Panel Bricks
69    pub(crate) overview_panel: OverviewPanelBrick,
70    pub(crate) cpu_panel: CpuPanelBrick,
71    pub(crate) gpu_panel: GpuPanelBrick,
72    pub(crate) help_panel: HelpPanelBrick,
73    pub(crate) memory_panel: MemoryPanelBrick,
74    pub(crate) thermal_panel: ThermalPanelBrick,
75    pub(crate) pcie_panel: PciePanelBrick,
76    pub(crate) load_panel: LoadControlPanelBrick,
77    pub(crate) config_panel: ConfigPanelBrick,
78
79    /// Terminal buffer
80    pub(crate) buffer: CellBuffer,
81    /// Renderer
82    pub(crate) renderer: DiffRenderer,
83    /// Theme for colors
84    pub(crate) theme: Theme,
85    /// Last frame time
86    pub(crate) last_frame: Instant,
87    /// Frame count
88    pub(crate) frame_count: u64,
89
90    /// Should quit
91    pub(crate) should_quit: bool,
92}
93
94impl CbtopApp {
95    /// Create new application with config
96    pub fn new(config: Config) -> Result<Self, CbtopError> {
97        let (width, height) = crossterm::terminal::size()?;
98
99        // Detect hardware at startup
100        let hardware = HardwareInfo::detect();
101
102        // Create SIMD load generator with configured problem size
103        let mut simd_generator = SimdLoadBrick::new(config.problem_size);
104        simd_generator.set_intensity(config.load_profile.intensity());
105
106        Ok(Self {
107            config: config.clone(),
108            active_panel: ActivePanel::Overview,
109            is_running: false,
110            intensity: config.load_profile.intensity(),
111            backend: config.backend,
112            workload: config.workload,
113            problem_size: config.problem_size,
114            hardware,
115            simd_generator,
116            load_metrics: LoadMetrics::default(),
117            cpu_history: RingBuffer::new(120),
118            bricks_history: RingBuffer::new(120),
119            frame_times: RingBuffer::new(60),
120            last_cpu_stat: None,
121            last_network_stat: None,
122            overview_panel: OverviewPanelBrick::new(),
123            cpu_panel: CpuPanelBrick::new(),
124            gpu_panel: GpuPanelBrick::new(),
125            help_panel: HelpPanelBrick::new(),
126            memory_panel: MemoryPanelBrick::new(),
127            thermal_panel: ThermalPanelBrick::new(),
128            pcie_panel: PciePanelBrick::new(),
129            load_panel: LoadControlPanelBrick::new(),
130            config_panel: ConfigPanelBrick::new(),
131            buffer: CellBuffer::new(width, height),
132            renderer: DiffRenderer::with_color_mode(ColorMode::TrueColor),
133            theme: Theme::tokyo_night(),
134            last_frame: Instant::now(),
135            frame_count: 0,
136            should_quit: false,
137        })
138    }
139
140    /// Run the application main loop
141    pub fn run(&mut self) -> Result<(), CbtopError> {
142        use crossterm::{
143            event::{self, Event, KeyEventKind},
144            terminal::{
145                disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
146            },
147            ExecutableCommand,
148        };
149        use std::io::stdout;
150
151        // Enter TUI mode
152        enable_raw_mode()?;
153        stdout().execute(EnterAlternateScreen)?;
154        stdout().execute(crossterm::cursor::Hide)?;
155
156        let refresh = Duration::from_millis(self.config.refresh_ms);
157
158        while !self.should_quit {
159            // Handle events
160            if event::poll(refresh)? {
161                if let Event::Key(key) = event::read()? {
162                    if key.kind == KeyEventKind::Press {
163                        self.handle_key(key.code);
164                    }
165                }
166            }
167
168            // Run REAL load generation if enabled
169            if self.is_running {
170                self.run_load_iteration();
171            }
172
173            // Collect REAL metrics
174            self.collect_real_metrics();
175
176            // Render
177            self.render()?;
178        }
179
180        // Cleanup
181        stdout().execute(crossterm::cursor::Show)?;
182        stdout().execute(LeaveAlternateScreen)?;
183        disable_raw_mode()?;
184
185        Ok(())
186    }
187}