1#![deny(clippy::all)]
16
17use std::cell::RefCell;
56use std::collections::HashMap;
57use std::rc::Rc;
58use std::time::Duration;
59use std::time::SystemTime;
60
61use anyhow::Result;
62use common::logutil::get_last_log_to_display;
63use common::open_source_shim;
64use common::util::get_belowrc_cmd_section_key;
65use common::util::get_belowrc_filename;
66use common::util::get_belowrc_view_section_key;
67use crossterm::event::DisableMouseCapture;
68use crossterm::execute;
69use cursive::event::Event;
70use cursive::theme::BaseColor;
71use cursive::theme::Color;
72use cursive::theme::PaletteColor;
73use cursive::view::Nameable;
74use cursive::views::BoxedView;
75use cursive::views::LinearLayout;
76use cursive::views::NamedView;
77use cursive::views::OnEventView;
78use cursive::views::Panel;
79use cursive::views::ResizedView;
80use cursive::views::ScreensView;
81use cursive::Cursive;
82use cursive::CursiveRunnable;
83use cursive::ScreenId;
84use model::CgroupModel;
85#[cfg(fbcode_build)]
86use model::GpuModel;
87use model::Model;
88use model::NetworkModel;
89use model::ProcessModel;
90use model::SystemModel;
91use store::Advance;
92use toml::value::Value;
93use viewrc::ViewRc;
94extern crate render as base_render;
95
96open_source_shim!();
97
98mod cgroup_tabs;
99pub mod cgroup_view;
100pub mod command_palette;
101mod default_styles;
102mod filter_popup;
103mod help_menu;
104mod process_tabs;
105mod process_view;
106mod render;
107pub mod stats_view;
108mod status_bar;
109mod summary_view;
110mod system_tabs;
111mod system_view;
112mod tab_view;
113
114pub struct View {
115 inner: CursiveRunnable,
116}
117
118macro_rules! advance {
119 ($c:ident, $adv:ident, $dir:expr) => {
120 match $adv.advance($dir) {
121 Some(data) => {
122 $c.user_data::<ViewState>()
123 .expect("No user data set")
124 .update(data);
125 }
126 None => view_warn!(
127 $c,
128 "Data is not available{}",
129 if $dir == Direction::Forward {
130 " yet."
131 } else {
132 "."
133 }
134 ),
135 }
136 };
137}
138
139macro_rules! view_warn {
141 ($c:ident, $($args:tt)*) => {{
142 let state = $c
143 .user_data::<crate::ViewState>()
144 .expect("No user data set")
145 .main_view_state
146 .clone();
147 let msg = format!($($args)*);
148 match state {
149 crate::MainViewState::Cgroup => crate::cgroup_view::ViewType::cp_warn($c, &msg),
150 crate::MainViewState::Process(_) =>
151 crate::process_view::ViewType::cp_warn($c, &msg),
152 crate::MainViewState::System => crate::system_view::ViewType::cp_warn($c, &msg),
153 #[cfg(fbcode_build)]
154 crate::MainViewState::Gpu => crate::gpu_view::ViewType::cp_warn($c, &msg),
155 }
156 }};
157}
158
159pub mod controllers;
161pub mod viewrc;
162mod jump_popup;
164
165#[derive(Clone, Debug, PartialEq)]
166pub enum ProcessZoomState {
167 NoZoom,
168 Cgroup,
169 Pids,
170}
171
172#[derive(Clone, Debug, PartialEq)]
173pub enum MainViewState {
174 Cgroup,
175 Process(ProcessZoomState),
176 System,
177 #[cfg(fbcode_build)]
178 Gpu,
179}
180
181impl MainViewState {
182 pub fn is_process_zoom_state(&self) -> bool {
183 matches!(&self, &MainViewState::Process(zoom) if zoom != &ProcessZoomState::NoZoom)
184 }
185}
186
187#[derive(Clone)]
188pub enum ViewMode {
189 Live(Rc<RefCell<Advance>>),
190 Pause(Rc<RefCell<Advance>>),
191 Replay(Rc<RefCell<Advance>>),
192}
193
194fn refresh(c: &mut Cursive) {
197 status_bar::refresh(c);
198 summary_view::refresh(c);
199 let current_state = c
200 .user_data::<ViewState>()
201 .expect("No data stored in Cursive object!")
202 .main_view_state
203 .clone();
204 match current_state {
205 MainViewState::Cgroup => cgroup_view::CgroupView::refresh(c),
206 MainViewState::Process(_) => process_view::ProcessView::refresh(c),
207 MainViewState::System => system_view::SystemView::refresh(c),
208 #[cfg(fbcode_build)]
209 MainViewState::Gpu => gpu_view::GpuView::refresh(c),
210 }
211}
212
213pub fn set_active_screen(c: &mut Cursive, name: &str) {
214 let screen_id = *c
215 .user_data::<ViewState>()
216 .expect("No data stored in Cursive object!")
217 .main_view_screens
218 .get(name)
219 .unwrap_or_else(|| panic!("Failed to find screen id for {}", name));
220 c.call_on_name(
221 "main_view_screens",
222 |screens: &mut NamedView<ScreensView>| {
223 screens.get_mut().set_active_screen(screen_id);
224 screens
225 .get_mut()
226 .screen_mut()
227 .unwrap()
228 .take_focus(cursive::direction::Direction::none())
229 .ok();
230 },
231 )
232 .expect("failed to find main_view_screens");
233}
234
235pub struct ViewState {
236 pub time_elapsed: Duration,
237 pub lowest_time_elapsed: Duration,
241 pub timestamp: SystemTime,
242 pub model: Rc<RefCell<Model>>,
244 pub system: Rc<RefCell<SystemModel>>,
245 pub cgroup: Rc<RefCell<CgroupModel>>,
246 pub process: Rc<RefCell<ProcessModel>>,
247 pub network: Rc<RefCell<NetworkModel>>,
248 #[cfg(fbcode_build)]
249 pub gpu: Rc<RefCell<Option<GpuModel>>>,
250 pub main_view_state: MainViewState,
251 pub main_view_screens: HashMap<String, ScreenId>,
252 pub mode: ViewMode,
253 pub viewrc: ViewRc,
254 pub viewrc_error: Option<String>,
255 pub event_controllers: Rc<RefCell<HashMap<Event, controllers::Controllers>>>,
256 pub cmd_controllers: Rc<RefCell<HashMap<&'static str, controllers::Controllers>>>,
257}
258
259impl ViewState {
260 pub fn update(&mut self, model: Model) {
261 self.time_elapsed = model.time_elapsed;
262 if model.time_elapsed.as_secs() != 0 && model.time_elapsed < self.lowest_time_elapsed {
263 self.lowest_time_elapsed = model.time_elapsed;
264 }
265 self.timestamp = model.timestamp;
266 self.model.replace(model.clone());
267 self.system.replace(model.system);
268 self.cgroup.replace(model.cgroup);
269 self.process.replace(model.process);
270 self.network.replace(model.network);
271 #[cfg(fbcode_build)]
272 self.gpu.replace(model.gpu);
273 }
274
275 pub fn new_with_advance(
276 main_view_state: MainViewState,
277 model: Model,
278 mode: ViewMode,
279 viewrc: ViewRc,
280 viewrc_error: Option<String>,
281 ) -> Self {
282 Self {
283 time_elapsed: model.time_elapsed,
284 lowest_time_elapsed: model.time_elapsed,
285 timestamp: model.timestamp,
286 model: Rc::new(RefCell::new(model.clone())),
287 system: Rc::new(RefCell::new(model.system)),
288 cgroup: Rc::new(RefCell::new(model.cgroup)),
289 process: Rc::new(RefCell::new(model.process)),
290 network: Rc::new(RefCell::new(model.network)),
291 #[cfg(fbcode_build)]
292 gpu: Rc::new(RefCell::new(model.gpu)),
293 main_view_state,
294 main_view_screens: HashMap::new(),
295 mode,
296 viewrc,
297 viewrc_error,
298 event_controllers: Rc::new(RefCell::new(HashMap::new())),
299 cmd_controllers: Rc::new(RefCell::new(controllers::make_cmd_controller_map())),
300 }
301 }
302
303 pub fn view_mode_str(&self) -> &'static str {
304 match self.mode {
305 ViewMode::Live(_) => "live",
306 ViewMode::Pause(_) => "live-paused",
307 ViewMode::Replay(_) => "replay",
308 }
309 }
310
311 pub fn is_paused(&self) -> bool {
312 matches!(self.mode, ViewMode::Pause(_))
313 }
314}
315
316impl View {
317 pub fn new_with_advance(model: model::Model, mode: ViewMode) -> View {
318 let mut inner = cursive::CursiveRunnable::new(|| {
319 let backend = cursive::backends::crossterm::Backend::init().map(|backend| {
320 Box::new(cursive_buffered_backend::BufferedBackend::new(backend))
321 as Box<(dyn cursive::backend::Backend)>
322 });
323 execute!(std::io::stdout(), DisableMouseCapture).expect("Failed to disable mouse.");
324 backend
325 });
326 let (viewrc, viewrc_error) = viewrc::ViewRc::new();
327 inner.set_user_data(ViewState::new_with_advance(
328 MainViewState::Cgroup,
329 model,
330 mode,
331 viewrc,
332 viewrc_error,
333 ));
334 View { inner }
335 }
336
337 pub fn cb_sink(&mut self) -> &::cursive::CbSink {
338 self.inner.set_fps(4);
339 self.inner.cb_sink()
340 }
341
342 pub fn generate_event_controller_map(c: &mut Cursive, filename: String) {
346 let cmdrc_opt = match std::fs::read_to_string(filename) {
348 Ok(belowrc_str) => match belowrc_str.parse::<Value>() {
349 Ok(belowrc) => belowrc
350 .get(get_belowrc_cmd_section_key())
351 .map(|cmdrc| cmdrc.to_owned()),
352 Err(e) => {
353 view_warn!(c, "Failed to parse belowrc: {}", e);
354 None
355 }
356 },
357 _ => None,
358 };
359
360 let event_controller_map = controllers::make_event_controller_map(c, &cmdrc_opt);
361
362 c.user_data::<ViewState>()
363 .expect("No data stored in Cursive object!")
364 .event_controllers
365 .replace(event_controller_map);
366 }
367
368 pub fn run(&mut self) -> Result<()> {
369 let mut theme = self.inner.current_theme().clone();
370 theme.palette[PaletteColor::Background] = Color::TerminalDefault;
371 theme.palette[PaletteColor::View] = Color::TerminalDefault;
372 theme.palette[PaletteColor::Primary] = Color::TerminalDefault;
373 theme.palette[PaletteColor::Highlight] = Color::Dark(BaseColor::Cyan);
374 theme.palette[PaletteColor::HighlightText] = Color::Dark(BaseColor::Black);
375 theme.shadow = false;
376
377 self.inner.set_theme(theme);
378
379 self.inner
380 .add_global_callback(Event::CtrlChar('z'), |c| unsafe {
381 use crossterm::cursor::Hide;
382 use crossterm::cursor::Show;
383 use crossterm::terminal::EnterAlternateScreen;
384 use crossterm::terminal::LeaveAlternateScreen;
385
386 execute!(std::io::stdout(), LeaveAlternateScreen, Show)
391 .expect("Failed to reset tty");
392 crossterm::terminal::disable_raw_mode().expect("Failed to disable tty");
393
394 if libc::raise(libc::SIGTSTP) != 0 {
396 panic!("failed to SIGTSTP self");
397 }
398
399 crossterm::terminal::enable_raw_mode().expect("Failed to enable tty");
401 execute!(std::io::stdout(), EnterAlternateScreen, Hide)
402 .expect("Failed to setup tty");
403 c.on_event(Event::WindowResize);
405 });
406 self.inner.add_global_callback(Event::Refresh, |c| {
407 refresh(c);
408 });
409 self.inner.add_global_callback(Event::CtrlChar('r'), |c| {
410 c.clear();
411 refresh(c);
412 });
413
414 let init_warnings = get_last_log_to_display();
416
417 let status_bar = status_bar::new(&mut self.inner);
418 let summary_view = summary_view::new(&mut self.inner);
419 let cgroup_view = cgroup_view::CgroupView::new(&mut self.inner);
420 let process_view = process_view::ProcessView::new(&mut self.inner);
421 let system_view = system_view::SystemView::new(&mut self.inner);
422 #[cfg(fbcode_build)]
423 let gpu_view = gpu_view::GpuView::new(&mut self.inner);
424
425 let mut screens_view = ScreensView::new();
426 let main_view_screens = &mut self
427 .inner
428 .user_data::<ViewState>()
429 .expect("No data stored in Cursive object!")
430 .main_view_screens;
431 main_view_screens.insert(
432 "cgroup_view_panel".to_owned(),
433 screens_view.add_screen(BoxedView::boxed(ResizedView::with_full_screen(cgroup_view))),
434 );
435 main_view_screens.insert(
436 "process_view_panel".to_owned(),
437 screens_view.add_screen(BoxedView::boxed(ResizedView::with_full_screen(
438 process_view,
439 ))),
440 );
441 main_view_screens.insert(
442 "system_view_panel".to_owned(),
443 screens_view.add_screen(BoxedView::boxed(ResizedView::with_full_screen(system_view))),
444 );
445 #[cfg(fbcode_build)]
446 main_view_screens.insert(
447 "gpu_view_panel".to_owned(),
448 screens_view.add_screen(BoxedView::boxed(ResizedView::with_full_screen(gpu_view))),
449 );
450
451 self.inner
452 .add_fullscreen_layer(ResizedView::with_full_screen(
453 LinearLayout::vertical()
454 .child(Panel::new(status_bar))
455 .child(Panel::new(summary_view))
456 .child(
457 OnEventView::new(screens_view.with_name("main_view_screens"))
458 .with_name("dynamic_view"),
459 ),
460 ));
461
462 self.inner
463 .focus_name("dynamic_view")
464 .expect("Could not set focus at initialization!");
465
466 if let Some(view) = self
468 .inner
469 .user_data::<ViewState>()
470 .expect("No data stored in Cursive object!")
471 .viewrc
472 .default_view
473 .clone()
474 {
475 let main_view_state = &mut self
476 .inner
477 .user_data::<ViewState>()
478 .expect("No data stored in Cursive object!")
479 .main_view_state;
480 match view {
481 viewrc::DefaultFrontView::Cgroup => {
482 *main_view_state = MainViewState::Cgroup;
483 set_active_screen(&mut self.inner, "cgroup_view_panel")
484 }
485 viewrc::DefaultFrontView::Process => {
486 *main_view_state = MainViewState::Process(ProcessZoomState::NoZoom);
487 set_active_screen(&mut self.inner, "process_view_panel")
488 }
489 viewrc::DefaultFrontView::System => {
490 *main_view_state = MainViewState::System;
491 set_active_screen(&mut self.inner, "system_view_panel")
492 }
493 }
494 }
495
496 Self::generate_event_controller_map(&mut self.inner, get_belowrc_filename());
498 if let Some(msg) = &self
499 .inner
500 .user_data::<ViewState>()
501 .expect("No data stored in Cursive object!")
502 .viewrc_error
503 {
504 let msg = msg.clone();
505 let c = &mut self.inner;
506 view_warn!(c, "{}", msg);
507 }
508 if let Some(msg) = init_warnings {
509 let c = &mut self.inner;
510 view_warn!(c, "{}", msg);
511 }
512 self.inner.run();
513
514 Ok(())
515 }
516}
517
518#[cfg(test)]
519pub mod fake_view {
520 use std::cell::RefCell;
521 use std::path::PathBuf;
522 use std::rc::Rc;
523
524 use common::logutil::get_logger;
525 use cursive::views::DummyView;
526 use cursive::views::ViewRef;
527 use model::Collector;
528 use store::advance::new_advance_local;
529
530 use self::viewrc::ViewRc;
531 use super::*;
532 use crate::cgroup_view::CgroupView;
533 use crate::command_palette::CommandPalette;
534 use crate::stats_view::StatsView;
535 use crate::MainViewState;
536 use crate::ViewMode;
537 use crate::ViewState;
538
539 pub struct FakeView {
540 pub inner: CursiveRunnable,
541 }
542
543 #[allow(clippy::new_without_default)]
544 impl FakeView {
545 pub fn new() -> Self {
546 let time = SystemTime::now();
547 let logger = get_logger();
548 let advance = new_advance_local(logger.clone(), PathBuf::new(), time);
549 let mut collector = Collector::new(logger.clone(), Default::default());
550 let model = collector
551 .collect_and_update_model()
552 .expect("Fail to get model");
553
554 let mut inner = CursiveRunnable::dummy();
555 let mut user_data = ViewState::new_with_advance(
556 MainViewState::Cgroup,
557 model,
558 ViewMode::Live(Rc::new(RefCell::new(advance))),
559 ViewRc::default(),
560 None,
561 );
562 inner.add_layer(
564 ScreensView::single_screen(BoxedView::boxed(DummyView))
565 .with_name("main_view_screens"),
566 );
567 user_data.main_view_screens = [
568 ("cgroup_view_panel".to_owned(), 0),
569 ("process_view_panel".to_owned(), 0),
570 ("system_view_panel".to_owned(), 0),
571 ]
572 .into();
573 inner.set_user_data(user_data);
574
575 Self { inner }
576 }
577
578 pub fn add_cgroup_view(&mut self) {
579 let cgroup_view = CgroupView::new(&mut self.inner);
580 self.inner.add_layer(cgroup_view);
581 }
582
583 pub fn get_cmd_palette(&mut self, name: &str) -> ViewRef<CommandPalette> {
584 self.inner
585 .find_name::<StatsView<CgroupView>>(name)
586 .expect("Failed to dereference command palette")
587 .get_cmd_palette()
588 }
589 }
590}