Skip to main content

cbtop/bricks/panels/load/
mod.rs

1//! Load control panel brick (Layer 3)
2//!
3//! Interactive controls for load testing - start/stop, backend selection,
4//! workload type, intensity slider, and real-time status display.
5
6mod render;
7mod types;
8
9pub use types::{ComputeBackend, LoadStats, WorkloadType};
10
11use crate::brick::{Brick, BrickAssertion, BrickBudget, BrickScore, BrickVerification};
12use presentar_terminal::Theme;
13use std::any::Any;
14
15/// Load control panel for interactive load testing
16pub struct LoadControlPanelBrick {
17    /// Selected compute backend
18    pub backend: ComputeBackend,
19    /// Selected workload type
20    pub workload: WorkloadType,
21    /// Intensity level (0.0 to 100.0)
22    pub intensity: f64,
23    /// Problem size (affects memory usage)
24    pub problem_size: usize,
25    /// Whether load test is running
26    pub is_running: bool,
27    /// Current load statistics
28    pub stats: LoadStats,
29    /// Error message if any
30    pub error: Option<String>,
31    /// Selected menu item (0=backend, 1=workload, 2=intensity, 3=size, 4=start/stop)
32    pub selected_item: usize,
33    /// Theme for rendering
34    pub theme: Theme,
35    /// Current ComputeBrick quality score
36    pub brick_score: Option<BrickScore>,
37    /// Current GFLOP/s throughput
38    pub gflops: f64,
39}
40
41impl LoadControlPanelBrick {
42    /// Create a new load control panel
43    pub fn new() -> Self {
44        Self {
45            backend: ComputeBackend::default(),
46            workload: WorkloadType::default(),
47            intensity: 50.0,
48            problem_size: 1024,
49            is_running: false,
50            stats: LoadStats::default(),
51            error: None,
52            selected_item: 0,
53            theme: Theme::tokyo_night(),
54            brick_score: None,
55            gflops: 0.0,
56        }
57    }
58
59    /// Cycle to next backend
60    pub fn next_backend(&mut self) {
61        let idx = ComputeBackend::ALL
62            .iter()
63            .position(|&b| b == self.backend)
64            .unwrap_or(0);
65        self.backend = ComputeBackend::ALL[(idx + 1) % ComputeBackend::ALL.len()];
66    }
67
68    /// Cycle to previous backend
69    pub fn prev_backend(&mut self) {
70        let idx = ComputeBackend::ALL
71            .iter()
72            .position(|&b| b == self.backend)
73            .unwrap_or(0);
74        self.backend =
75            ComputeBackend::ALL[(idx + ComputeBackend::ALL.len() - 1) % ComputeBackend::ALL.len()];
76    }
77
78    /// Cycle to next workload
79    pub fn next_workload(&mut self) {
80        let idx = WorkloadType::ALL
81            .iter()
82            .position(|&w| w == self.workload)
83            .unwrap_or(0);
84        self.workload = WorkloadType::ALL[(idx + 1) % WorkloadType::ALL.len()];
85    }
86
87    /// Cycle to previous workload
88    pub fn prev_workload(&mut self) {
89        let idx = WorkloadType::ALL
90            .iter()
91            .position(|&w| w == self.workload)
92            .unwrap_or(0);
93        self.workload =
94            WorkloadType::ALL[(idx + WorkloadType::ALL.len() - 1) % WorkloadType::ALL.len()];
95    }
96
97    /// Increase intensity
98    pub fn increase_intensity(&mut self) {
99        self.intensity = (self.intensity + 5.0).min(100.0);
100    }
101
102    /// Decrease intensity
103    pub fn decrease_intensity(&mut self) {
104        self.intensity = (self.intensity - 5.0).max(0.0);
105    }
106
107    /// Increase problem size
108    pub fn increase_size(&mut self) {
109        self.problem_size = (self.problem_size * 2).min(65536);
110    }
111
112    /// Decrease problem size
113    pub fn decrease_size(&mut self) {
114        self.problem_size = (self.problem_size / 2).max(64);
115    }
116
117    /// Toggle running state
118    pub fn toggle_running(&mut self) {
119        self.is_running = !self.is_running;
120        if self.is_running {
121            self.stats = LoadStats::default();
122            self.error = None;
123        }
124    }
125
126    /// Update statistics from load generator
127    pub fn update_stats(&mut self, stats: LoadStats) {
128        self.stats = stats;
129    }
130
131    /// Update ComputeBrick score
132    pub fn update_score(&mut self, score: BrickScore, gflops: f64) {
133        self.brick_score = Some(score);
134        self.gflops = gflops;
135    }
136
137    /// Set error message
138    pub fn set_error(&mut self, error: String) {
139        self.error = Some(error);
140        self.is_running = false;
141    }
142
143    /// Navigate to next menu item
144    pub fn next_item(&mut self) {
145        self.selected_item = (self.selected_item + 1) % 5;
146    }
147
148    /// Navigate to previous menu item
149    pub fn prev_item(&mut self) {
150        self.selected_item = (self.selected_item + 4) % 5;
151    }
152
153    /// Handle left key based on selected item
154    pub fn handle_left(&mut self) {
155        match self.selected_item {
156            0 => self.prev_backend(),
157            1 => self.prev_workload(),
158            2 => self.decrease_intensity(),
159            3 => self.decrease_size(),
160            4 => {} // No left action for button
161            _ => {}
162        }
163    }
164
165    /// Handle right key based on selected item
166    pub fn handle_right(&mut self) {
167        match self.selected_item {
168            0 => self.next_backend(),
169            1 => self.next_workload(),
170            2 => self.increase_intensity(),
171            3 => self.increase_size(),
172            4 => {} // No right action for button
173            _ => {}
174        }
175    }
176
177    /// Handle enter key
178    pub fn handle_enter(&mut self) {
179        if self.selected_item == 4 {
180            self.toggle_running();
181        }
182    }
183}
184
185impl Default for LoadControlPanelBrick {
186    fn default() -> Self {
187        Self::new()
188    }
189}
190
191impl Brick for LoadControlPanelBrick {
192    fn brick_name(&self) -> &'static str {
193        "load_control_panel"
194    }
195
196    fn assertions(&self) -> Vec<BrickAssertion> {
197        vec![
198            BrickAssertion::MinWidth(50),
199            BrickAssertion::MinHeight(20),
200            BrickAssertion::max_latency_ms(8),
201        ]
202    }
203
204    fn budget(&self) -> BrickBudget {
205        BrickBudget::FRAME_60FPS
206    }
207
208    fn verify(&self) -> BrickVerification {
209        let mut v = BrickVerification::new();
210        for assertion in self.assertions() {
211            v.check(&assertion);
212        }
213        v
214    }
215
216    fn as_any(&self) -> &dyn Any {
217        self
218    }
219}
220
221#[cfg(test)]
222mod tests;