From 118265595c0a7f03a7bb0fe77ee16a551affc887 Mon Sep 17 00:00:00 2001 From: "Leon Yang (Containers)" Date: Fri, 26 Apr 2024 07:11:10 -0700 Subject: Rename core_view to system_view Summary: Similar to previous diff. Update core_view to system_view since it contains basic system info and has shortcut 's' which comes from "system". Reviewed By: brianc118 Differential Revision: D56504775 fbshipit-source-id: 718f5bafbcddb22af8012d5e51528a616f1efe46 --- below/view/src/controllers/view_controllers.rs | 6 +- below/view/src/core_tabs.rs | 351 ------------------------- below/view/src/core_view.rs | 307 --------------------- below/view/src/filter_popup.rs | 2 +- below/view/src/help_menu.rs | 2 +- below/view/src/lib.rs | 22 +- below/view/src/system_tabs.rs | 351 +++++++++++++++++++++++++ below/view/src/system_view.rs | 307 +++++++++++++++++++++ 8 files changed, 674 insertions(+), 674 deletions(-) delete mode 100644 below/view/src/core_tabs.rs delete mode 100644 below/view/src/core_view.rs create mode 100644 below/view/src/system_tabs.rs create mode 100644 below/view/src/system_view.rs diff --git a/below/view/src/controllers/view_controllers.rs b/below/view/src/controllers/view_controllers.rs index 071adafa..c4210857 100644 --- a/below/view/src/controllers/view_controllers.rs +++ b/below/view/src/controllers/view_controllers.rs @@ -222,7 +222,7 @@ make_event_controller!( vec![Event::Char('s')], |_view: &mut StatsView, _cmd_vec: &[&str]| {}, |c: &mut Cursive, _cmd_vec: &[&str]| { - set_active_screen(c, "core_view_panel"); + set_active_screen(c, "system_view_panel"); let current_state = c .user_data::() @@ -239,7 +239,7 @@ make_event_controller!( } c.user_data::() .expect("No data stored in Cursive object!") - .main_view_state = MainViewState::Core; + .main_view_state = MainViewState::System; } ); @@ -307,7 +307,7 @@ make_event_controller!( // Bring cgroup_view to front set_active_screen(c, "cgroup_view_panel"); } - MainViewState::Core => {} + MainViewState::System => {} #[cfg(fbcode_build)] MainViewState::Gpu => {} } diff --git a/below/view/src/core_tabs.rs b/below/view/src/core_tabs.rs deleted file mode 100644 index e3fb66cb..00000000 --- a/below/view/src/core_tabs.rs +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use base_render::get_fixed_width; -use base_render::RenderConfigBuilder as Rc; -use common::util::get_prefix; -use cursive::utils::markup::StyledString; -use model::system::BtrfsModelFieldId; -use model::system::MemoryModelFieldId; -use model::system::SingleCpuModelFieldId; -use model::system::SingleDiskModelFieldId; -use model::system::SingleSlabModelFieldId; -use model::system::VmModelFieldId; -use model::BtrfsModel; -use model::Queriable; -use model::SingleSlabModel; - -use crate::core_view::CoreState; -use crate::core_view::CoreStateFieldId; -use crate::render::ViewItem; -use crate::stats_view::ColumnTitles; -use crate::stats_view::StateCommon; - -const FIELD_NAME_WIDTH: usize = 20; -const FIELD_WIDTH: usize = 20; - -pub trait CoreTab { - fn get_titles(&self) -> ColumnTitles { - ColumnTitles { - titles: vec![ - get_fixed_width("Field", FIELD_NAME_WIDTH), - get_fixed_width("Value", FIELD_WIDTH), - ], - pinned_titles: 1, - } - } - - fn get_rows(&self, state: &CoreState, offset: Option) -> Vec<(StyledString, String)>; -} - -#[derive(Default, Clone)] -pub struct CoreCpu; - -impl CoreTab for CoreCpu { - fn get_titles(&self) -> ColumnTitles { - ColumnTitles { - titles: enum_iterator::all::() - .map(|field_id| ViewItem::from_default(field_id).config.render_title()) - .collect(), - pinned_titles: 1, - } - } - - fn get_rows(&self, state: &CoreState, offset: Option) -> Vec<(StyledString, String)> { - let model = state.get_model(); - model - .cpus - .values() - .filter(|scm| { - if let Some((CoreStateFieldId::Cpu(field), filter)) = &state.filter_info { - match scm.query(field) { - None => true, - Some(value) => value.to_string().starts_with(filter), - } - } else { - true - } - }) - .chain(std::iter::once(&model.total_cpu)) - .map(|scm| { - ( - std::iter::once(SingleCpuModelFieldId::Idx) - .chain( - enum_iterator::all::() - .skip(offset.unwrap_or(0) + 1), - ) - .fold(StyledString::new(), |mut line, field_id| { - let view_item = ViewItem::from_default(field_id.clone()); - let rendered = - if field_id == SingleCpuModelFieldId::Idx && scm.idx == -1 { - view_item.config.render(Some("total".to_owned().into())) - } else { - view_item.render(scm) - }; - line.append(rendered); - line.append_plain(" "); - line - }), - "".to_owned(), - ) - }) - .collect() - } -} - -#[derive(Default, Clone)] -pub struct CoreMem; - -impl CoreTab for CoreMem { - fn get_rows(&self, state: &CoreState, _offset: Option) -> Vec<(StyledString, String)> { - let model = state.get_model(); - - enum_iterator::all::() - .map(|field_id| { - let mut line = StyledString::new(); - let item = - ViewItem::from_default(field_id).update(Rc::new().width(FIELD_NAME_WIDTH)); - line.append_plain(item.config.render_title()); - line.append_plain(" "); - line.append(item.update(Rc::new().width(FIELD_WIDTH)).render(&model.mem)); - line - }) - .filter(|s| { - if let Some((_, filter)) = &state.filter_info { - s.source().contains(filter) - } else { - true - } - }) - .map(|s| (s.clone(), "".into())) - .collect() - } -} - -#[derive(Default, Clone)] -pub struct CoreVm; - -impl CoreTab for CoreVm { - fn get_rows(&self, state: &CoreState, _offset: Option) -> Vec<(StyledString, String)> { - let model = state.get_model(); - - enum_iterator::all::() - .map(|field_id| { - let mut line = StyledString::new(); - let item = - ViewItem::from_default(field_id).update(Rc::new().width(FIELD_NAME_WIDTH)); - line.append_plain(item.config.render_title()); - line.append_plain(" "); - line.append(item.update(Rc::new().width(FIELD_WIDTH)).render(&model.vm)); - line - }) - .filter(|s| { - if let Some((_, filter)) = &state.filter_info { - s.source().contains(filter) - } else { - true - } - }) - .map(|s| (s.clone(), "".into())) - .collect() - } -} - -#[derive(Default, Clone)] -pub struct CoreSlab; - -impl CoreTab for CoreSlab { - fn get_titles(&self) -> ColumnTitles { - ColumnTitles { - titles: enum_iterator::all::() - .map(|field_id| ViewItem::from_default(field_id).config.render_title()) - .collect(), - pinned_titles: 1, - } - } - - fn get_rows(&self, state: &CoreState, _offset: Option) -> Vec<(StyledString, String)> { - let model = state.get_model(); - let mut slab: Vec<&SingleSlabModel> = model.slab.values().collect(); - - if let Some(CoreStateFieldId::Slab(sort_order)) = state.sort_order.as_ref() { - model::sort_queriables(&mut slab, sort_order, state.reverse); - } - - slab.into_iter() - .map(|ssm| { - enum_iterator::all::().fold( - StyledString::new(), - |mut line, field_id| { - let view_item = ViewItem::from_default(field_id.clone()); - line.append(view_item.render(ssm)); - line.append_plain(" "); - line - }, - ) - }) - .filter(|s| { - if let Some((_, filter)) = &state.filter_info { - s.source().contains(filter) - } else { - true - } - }) - .map(|s| (s.clone(), "".into())) - .collect() - } -} - -#[derive(Default, Clone)] -pub struct CoreDisk; - -impl CoreTab for CoreDisk { - fn get_titles(&self) -> ColumnTitles { - ColumnTitles { - titles: enum_iterator::all::() - .map(|field_id| ViewItem::from_default(field_id).config.render_title()) - .collect(), - pinned_titles: 1, - } - } - - fn get_rows(&self, state: &CoreState, offset: Option) -> Vec<(StyledString, String)> { - state - .get_model() - .disks - .iter() - .filter_map(|(dn, sdm)| { - // We use the partition parent id to check if it exists in collapsed_disk set. - let idx_major = format!("{}.0", sdm.major.unwrap_or(0)); - let idx = format!("{}.{}", sdm.major.unwrap_or(0), sdm.minor.unwrap_or(0)); - let collapse = state.collapsed_disk.contains(&idx_major) && sdm.minor != Some(0); - if state - .filter_info - .as_ref() - .map_or(!collapse, |(_, f)| dn.starts_with(f)) - { - Some(( - std::iter::once(SingleDiskModelFieldId::Name) - .chain( - enum_iterator::all::() - .skip(offset.unwrap_or(0) + 1), - ) - .fold(StyledString::new(), |mut line, field_id| { - let view_item = ViewItem::from_default(field_id.clone()); - let rendered = if field_id == SingleDiskModelFieldId::Name { - view_item - .update(Rc::new().indented_prefix(get_prefix(collapse))) - .render_indented(sdm) - } else { - view_item.render(sdm) - }; - line.append(rendered); - line.append_plain(" "); - line - }), - idx, - )) - } else { - None - } - }) - .collect() - } -} - -/// Renders corresponding Fields From BtrfsModel. -type BtrfsViewItem = ViewItem; - -#[derive(Default, Clone)] -pub struct CoreBtrfs { - pub view_items: Vec, -} - -impl CoreBtrfs { - fn new(view_items: Vec) -> Self { - Self { view_items } - } -} - -impl CoreTab for CoreBtrfs { - fn get_titles(&self) -> ColumnTitles { - ColumnTitles { - titles: enum_iterator::all::() - .map(|field_id| ViewItem::from_default(field_id).config.render_title()) - .collect(), - pinned_titles: 0, - } - } - - fn get_rows(&self, state: &CoreState, _offset: Option) -> Vec<(StyledString, String)> { - if let Some(btrfs_model) = state.get_model().btrfs.as_ref() { - let mut subvolumes: Vec<&BtrfsModel> = btrfs_model.values().collect(); - - if let Some(CoreStateFieldId::Btrfs(sort_order)) = state.sort_order.as_ref() { - model::sort_queriables(&mut subvolumes, sort_order, state.reverse); - } - - subvolumes - .iter() - .filter(|bmodel| { - if let Some((CoreStateFieldId::Btrfs(field), filter)) = &state.filter_info { - match bmodel.query(field) { - None => true, - Some(value) => value.to_string().contains(filter), - } - } else { - true - } - }) - .map(|bmodel| { - ( - enum_iterator::all::().fold( - StyledString::new(), - |mut line, field_id| { - let view_item = ViewItem::from_default(field_id); - let rendered = view_item.render(bmodel); - line.append(rendered); - line.append_plain(" "); - line - }, - ), - bmodel.name.as_ref().expect("No name for row").clone(), - ) - }) - .collect() - } else { - Vec::new() - } - } -} - -pub mod default_tabs { - use model::BtrfsModelFieldId::DiskBytes; - use model::BtrfsModelFieldId::DiskFraction; - use model::BtrfsModelFieldId::Name; - use once_cell::sync::Lazy; - - use super::*; - - pub static CORE_BTRFS_TAB: Lazy = Lazy::new(|| { - CoreBtrfs::new(vec![ - ViewItem::from_default(Name), - ViewItem::from_default(DiskFraction), - ViewItem::from_default(DiskBytes), - ]) - }); - pub enum CoreTabs { - Btrfs(&'static CoreBtrfs), - } -} diff --git a/below/view/src/core_view.rs b/below/view/src/core_view.rs deleted file mode 100644 index 6780e0b5..00000000 --- a/below/view/src/core_view.rs +++ /dev/null @@ -1,307 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::cell::Ref; -use std::cell::RefCell; -use std::cell::RefMut; -use std::collections::HashMap; -use std::collections::HashSet; -use std::rc::Rc; - -use cursive::utils::markup::StyledString; -use cursive::view::Nameable; -use cursive::views::NamedView; -use cursive::views::SelectView; -use cursive::views::ViewRef; -use cursive::Cursive; -use model::system::SystemModel; -use model::BtrfsModelFieldId; -use model::MemoryModelFieldId; -use model::SingleCpuModelFieldId; -use model::SingleDiskModelFieldId; -use model::SingleSlabModelFieldId; -use model::VmModelFieldId; - -use crate::core_tabs::*; -use crate::stats_view::ColumnTitles; -use crate::stats_view::StateCommon; -use crate::stats_view::StatsView; -use crate::stats_view::ViewBridge; -use crate::ViewState; - -pub type ViewType = StatsView; - -use crate::core_view::default_tabs::CORE_BTRFS_TAB; - -// TODO(T123679020): Ideally we want to decouple states for core view tabs. -// Each core view tab really deserves its own view and state -#[derive(Default)] -pub struct CoreState { - pub filter_info: Option<(CoreStateFieldId, String)>, - pub collapsed_disk: HashSet, - pub model: Rc>, - pub sort_order: Option, - pub sort_tags: HashMap, - pub reverse: bool, -} - -#[derive(PartialEq)] -pub enum CoreStateFieldId { - Disk(SingleDiskModelFieldId), - Btrfs(BtrfsModelFieldId), - Cpu(SingleCpuModelFieldId), - Mem(MemoryModelFieldId), - Vm(VmModelFieldId), - Slab(SingleSlabModelFieldId), -} - -impl std::string::ToString for CoreStateFieldId { - fn to_string(&self) -> String { - match self { - Self::Disk(field) => field.to_string(), - Self::Btrfs(field) => field.to_string(), - Self::Cpu(field) => field.to_string(), - Self::Mem(field) => field.to_string(), - Self::Vm(field) => field.to_string(), - Self::Slab(field) => field.to_string(), - } - } -} - -impl StateCommon for CoreState { - type ModelType = SystemModel; - type TagType = CoreStateFieldId; - type KeyType = String; - - fn get_filter_info(&self) -> &Option<(Self::TagType, String)> { - &self.filter_info - } - - fn is_filter_supported_from_tab_idx(&self, _tab: &str, idx: usize) -> bool { - // we only enable str filtering for first col for Core View - if idx == 0 { - return true; - } - false - } - - fn get_tag_from_tab_idx(&self, tab: &str, idx: usize) -> Self::TagType { - match tab { - "Btrfs" => { - let core_tab = self - .sort_tags - .get(tab) - .unwrap_or_else(|| panic!("Fail to find tab: {}", tab)); - let default_tabs::CoreTabs::Btrfs(core_tab) = core_tab; - Self::TagType::Btrfs( - core_tab - .view_items - .get(idx) - .expect("Out of title scope") - .field_id - .to_owned(), - ) - } - "CPU" => CoreStateFieldId::Cpu(SingleCpuModelFieldId::Idx), - "Disk" => CoreStateFieldId::Disk(SingleDiskModelFieldId::Name), - // tabs Mem and Vm have two columns 'Field' and 'Value'. 'Field' contains - // a list of all the FieldIds in MemoryModel and VmModel respectively. - // the field given to filter_info don't matter for these tabs because - // they don't use FieldId as column titles/selected col (it isn't used to filter) - "Mem" => CoreStateFieldId::Mem(MemoryModelFieldId::Total), - "Vm" => CoreStateFieldId::Vm(VmModelFieldId::PgpginPerSec), - "Slab" => CoreStateFieldId::Slab( - enum_iterator::all::() - .nth(idx) - .expect("Tag out of range"), - ), - _ => panic!("bug: got unsupported tab {}", tab), - } - } - - fn set_filter_from_tab_idx(&mut self, tab: &str, idx: usize, filter: Option) -> bool { - if !self.is_filter_supported_from_tab_idx(tab, idx) { - return false; - } - - if let Some(filter_text) = filter { - let title = self.get_tag_from_tab_idx(tab, idx); - self.filter_info = Some((title, filter_text)); - } else { - self.filter_info = None; - } - true - } - - fn set_sort_tag(&mut self, sort_order: Self::TagType, reverse: &mut bool) -> bool { - let sort_order = Some(sort_order); - if self.sort_order == sort_order { - *reverse = !*reverse; - } else { - *reverse = true; - self.sort_order = sort_order; - } - self.reverse = *reverse; - true - } - - fn set_sort_tag_from_tab_idx(&mut self, tab: &str, idx: usize, reverse: &mut bool) -> bool { - match tab { - "Btrfs" | "Slab" => { - let sort_order = self.get_tag_from_tab_idx(tab, idx); - self.set_sort_tag(sort_order, reverse) - } - // This is to notify that tab is not currently sortable - _ => false, - } - } - - fn set_sort_string(&mut self, selection: &str, reverse: &mut bool) -> bool { - use std::str::FromStr; - match BtrfsModelFieldId::from_str(selection) { - Ok(field_id) => self.set_sort_tag(CoreStateFieldId::Btrfs(field_id), reverse), - Err(_) => false, - } - } - - fn get_model(&self) -> Ref { - self.model.borrow() - } - - fn get_model_mut(&self) -> RefMut { - self.model.borrow_mut() - } - - fn new(model: Rc>) -> Self { - let mut sort_tags = HashMap::new(); - sort_tags.insert( - "Btrfs".into(), - default_tabs::CoreTabs::Btrfs(&*CORE_BTRFS_TAB), - ); - Self { - sort_order: None, - reverse: false, - sort_tags, - model, - ..Default::default() - } - } -} - -pub enum CoreView { - Cpu(CoreCpu), - Mem(CoreMem), - Vm(CoreVm), - Slab(CoreSlab), - Disk(CoreDisk), - Btrfs(CoreBtrfs), -} - -impl CoreView { - pub fn new(c: &mut Cursive) -> NamedView { - let mut list = SelectView::::new(); - list.set_on_submit(|c, idx: &String| { - let mut view = CoreView::get_core_view(c); - // We only care about disk not partition - if view.get_tab_view().get_cur_selected() == "Disk" && idx.ends_with(".0") { - if view.state.borrow_mut().collapsed_disk.contains(idx) { - view.state.borrow_mut().collapsed_disk.remove(idx); - } else { - view.state - .borrow_mut() - .collapsed_disk - .insert(idx.to_string()); - } - - view.refresh(c); - } - }); - - let tabs = vec![ - "CPU".into(), - "Mem".into(), - "Vm".into(), - "Slab".into(), - "Disk".into(), - "Btrfs".into(), - ]; - let mut tabs_map: HashMap = HashMap::new(); - tabs_map.insert("CPU".into(), CoreView::Cpu(Default::default())); - tabs_map.insert("Mem".into(), CoreView::Mem(Default::default())); - tabs_map.insert("Vm".into(), CoreView::Vm(Default::default())); - tabs_map.insert("Slab".into(), CoreView::Slab(Default::default())); - tabs_map.insert("Disk".into(), CoreView::Disk(Default::default())); - tabs_map.insert("Btrfs".into(), CoreView::Btrfs(Default::default())); - let user_data = c - .user_data::() - .expect("No data stored in Cursive Object!"); - StatsView::new( - "core", - tabs, - tabs_map, - list, - CoreState::new(user_data.system.clone()), - user_data.event_controllers.clone(), - user_data.cmd_controllers.clone(), - ) - .feed_data(c) - .with_name(Self::get_view_name()) - } - - pub fn get_core_view(c: &mut Cursive) -> ViewRef { - ViewType::get_view(c) - } - - pub fn refresh(c: &mut Cursive) { - Self::get_core_view(c).refresh(c); - } - - fn get_inner(&self) -> Box { - match self { - Self::Cpu(inner) => Box::new(inner.clone()), - Self::Mem(inner) => Box::new(inner.clone()), - Self::Vm(inner) => Box::new(inner.clone()), - Self::Slab(inner) => Box::new(inner.clone()), - Self::Disk(inner) => Box::new(inner.clone()), - Self::Btrfs(inner) => Box::new(inner.clone()), - } - } -} - -impl ViewBridge for CoreView { - type StateType = CoreState; - fn get_view_name() -> &'static str { - "core_view" - } - fn get_titles(&self) -> ColumnTitles { - self.get_inner().get_titles() - } - - fn get_rows( - &mut self, - state: &Self::StateType, - offset: Option, - ) -> Vec<(StyledString, String)> { - self.get_inner().get_rows(state, offset) - } - - fn on_select_update_cmd_palette( - _view: &Self::StateType, - selected_key: &String, - _current_tab: &str, - _selected_column: usize, - ) -> String { - selected_key.clone() - } -} diff --git a/below/view/src/filter_popup.rs b/below/view/src/filter_popup.rs index 42a3d5d7..a4f95077 100644 --- a/below/view/src/filter_popup.rs +++ b/below/view/src/filter_popup.rs @@ -37,7 +37,7 @@ fn set_cp_filter(c: &mut Cursive, field_info: Option<(String, String)>) { match state { MainViewState::Cgroup => crate::cgroup_view::ViewType::cp_filter(c, field_info), MainViewState::Process(_) => crate::process_view::ViewType::cp_filter(c, field_info), - MainViewState::Core => crate::core_view::ViewType::cp_filter(c, field_info), + MainViewState::System => crate::system_view::ViewType::cp_filter(c, field_info), #[cfg(fbcode_build)] MainViewState::Gpu => crate::gpu_view::ViewType::cp_filter(c, field_info), } diff --git a/below/view/src/help_menu.rs b/below/view/src/help_menu.rs index c8a4453f..856e1a1b 100644 --- a/below/view/src/help_menu.rs +++ b/below/view/src/help_menu.rs @@ -92,7 +92,7 @@ fn get_description(controller: &Controllers) -> &'static str { Controllers::Help => "Toggle help menu.", Controllers::Process => "Show process view.", Controllers::Cgroup => "Show cgroup view.", - Controllers::System => "Show system core view.", + Controllers::System => "Show system view.", Controllers::Gpu => "Show GPU view.", Controllers::GpuZoom => "Zoom into process view filtered by selected GPU.", Controllers::GpuProcess => "Zoom into process view for all GPU processes.", diff --git a/below/view/src/lib.rs b/below/view/src/lib.rs index 59859d07..8ecb2440 100644 --- a/below/view/src/lib.rs +++ b/below/view/src/lib.rs @@ -95,8 +95,6 @@ open_source_shim!(); mod cgroup_tabs; pub mod cgroup_view; pub mod command_palette; -mod core_tabs; -mod core_view; mod default_styles; mod filter_popup; mod help_menu; @@ -106,6 +104,8 @@ mod render; pub mod stats_view; mod status_bar; mod summary_view; +mod system_tabs; +mod system_view; mod tab_view; pub struct View { @@ -146,7 +146,7 @@ macro_rules! view_warn { crate::MainViewState::Cgroup => crate::cgroup_view::ViewType::cp_warn($c, &msg), crate::MainViewState::Process(_) => crate::process_view::ViewType::cp_warn($c, &msg), - crate::MainViewState::Core => crate::core_view::ViewType::cp_warn($c, &msg), + crate::MainViewState::System => crate::system_view::ViewType::cp_warn($c, &msg), #[cfg(fbcode_build)] crate::MainViewState::Gpu => crate::gpu_view::ViewType::cp_warn($c, &msg), } @@ -170,7 +170,7 @@ pub enum ProcessZoomState { pub enum MainViewState { Cgroup, Process(ProcessZoomState), - Core, + System, #[cfg(fbcode_build)] Gpu, } @@ -204,7 +204,7 @@ fn refresh(c: &mut Cursive) { match current_state { MainViewState::Cgroup => cgroup_view::CgroupView::refresh(c), MainViewState::Process(_) => process_view::ProcessView::refresh(c), - MainViewState::Core => core_view::CoreView::refresh(c), + MainViewState::System => system_view::SystemView::refresh(c), #[cfg(fbcode_build)] MainViewState::Gpu => gpu_view::GpuView::refresh(c), } @@ -406,7 +406,7 @@ impl View { let summary_view = summary_view::new(&mut self.inner); let cgroup_view = cgroup_view::CgroupView::new(&mut self.inner, &viewrc); let process_view = process_view::ProcessView::new(&mut self.inner); - let core_view = core_view::CoreView::new(&mut self.inner); + let system_view = system_view::SystemView::new(&mut self.inner); #[cfg(fbcode_build)] let gpu_view = gpu_view::GpuView::new(&mut self.inner); @@ -427,8 +427,8 @@ impl View { ))), ); main_view_screens.insert( - "core_view_panel".to_owned(), - screens_view.add_screen(BoxedView::boxed(ResizedView::with_full_screen(core_view))), + "system_view_panel".to_owned(), + screens_view.add_screen(BoxedView::boxed(ResizedView::with_full_screen(system_view))), ); #[cfg(fbcode_build)] main_view_screens.insert( @@ -468,8 +468,8 @@ impl View { set_active_screen(&mut self.inner, "process_view_panel") } viewrc::DefaultFrontView::System => { - *main_view_state = MainViewState::Core; - set_active_screen(&mut self.inner, "core_view_panel") + *main_view_state = MainViewState::System; + set_active_screen(&mut self.inner, "system_view_panel") } } } @@ -538,7 +538,7 @@ pub mod fake_view { user_data.main_view_screens = [ ("cgroup_view_panel".to_owned(), 0), ("process_view_panel".to_owned(), 0), - ("core_view_panel".to_owned(), 0), + ("system_view_panel".to_owned(), 0), ] .into(); inner.set_user_data(user_data); diff --git a/below/view/src/system_tabs.rs b/below/view/src/system_tabs.rs new file mode 100644 index 00000000..b7f03201 --- /dev/null +++ b/below/view/src/system_tabs.rs @@ -0,0 +1,351 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use base_render::get_fixed_width; +use base_render::RenderConfigBuilder as Rc; +use common::util::get_prefix; +use cursive::utils::markup::StyledString; +use model::system::BtrfsModelFieldId; +use model::system::MemoryModelFieldId; +use model::system::SingleCpuModelFieldId; +use model::system::SingleDiskModelFieldId; +use model::system::SingleSlabModelFieldId; +use model::system::VmModelFieldId; +use model::BtrfsModel; +use model::Queriable; +use model::SingleSlabModel; + +use crate::render::ViewItem; +use crate::stats_view::ColumnTitles; +use crate::stats_view::StateCommon; +use crate::system_view::SystemState; +use crate::system_view::SystemStateFieldId; + +const FIELD_NAME_WIDTH: usize = 20; +const FIELD_WIDTH: usize = 20; + +pub trait SystemTab { + fn get_titles(&self) -> ColumnTitles { + ColumnTitles { + titles: vec![ + get_fixed_width("Field", FIELD_NAME_WIDTH), + get_fixed_width("Value", FIELD_WIDTH), + ], + pinned_titles: 1, + } + } + + fn get_rows(&self, state: &SystemState, offset: Option) -> Vec<(StyledString, String)>; +} + +#[derive(Default, Clone)] +pub struct SystemCpu; + +impl SystemTab for SystemCpu { + fn get_titles(&self) -> ColumnTitles { + ColumnTitles { + titles: enum_iterator::all::() + .map(|field_id| ViewItem::from_default(field_id).config.render_title()) + .collect(), + pinned_titles: 1, + } + } + + fn get_rows(&self, state: &SystemState, offset: Option) -> Vec<(StyledString, String)> { + let model = state.get_model(); + model + .cpus + .values() + .filter(|scm| { + if let Some((SystemStateFieldId::Cpu(field), filter)) = &state.filter_info { + match scm.query(field) { + None => true, + Some(value) => value.to_string().starts_with(filter), + } + } else { + true + } + }) + .chain(std::iter::once(&model.total_cpu)) + .map(|scm| { + ( + std::iter::once(SingleCpuModelFieldId::Idx) + .chain( + enum_iterator::all::() + .skip(offset.unwrap_or(0) + 1), + ) + .fold(StyledString::new(), |mut line, field_id| { + let view_item = ViewItem::from_default(field_id.clone()); + let rendered = + if field_id == SingleCpuModelFieldId::Idx && scm.idx == -1 { + view_item.config.render(Some("total".to_owned().into())) + } else { + view_item.render(scm) + }; + line.append(rendered); + line.append_plain(" "); + line + }), + "".to_owned(), + ) + }) + .collect() + } +} + +#[derive(Default, Clone)] +pub struct SystemMem; + +impl SystemTab for SystemMem { + fn get_rows(&self, state: &SystemState, _offset: Option) -> Vec<(StyledString, String)> { + let model = state.get_model(); + + enum_iterator::all::() + .map(|field_id| { + let mut line = StyledString::new(); + let item = + ViewItem::from_default(field_id).update(Rc::new().width(FIELD_NAME_WIDTH)); + line.append_plain(item.config.render_title()); + line.append_plain(" "); + line.append(item.update(Rc::new().width(FIELD_WIDTH)).render(&model.mem)); + line + }) + .filter(|s| { + if let Some((_, filter)) = &state.filter_info { + s.source().contains(filter) + } else { + true + } + }) + .map(|s| (s.clone(), "".into())) + .collect() + } +} + +#[derive(Default, Clone)] +pub struct SystemVm; + +impl SystemTab for SystemVm { + fn get_rows(&self, state: &SystemState, _offset: Option) -> Vec<(StyledString, String)> { + let model = state.get_model(); + + enum_iterator::all::() + .map(|field_id| { + let mut line = StyledString::new(); + let item = + ViewItem::from_default(field_id).update(Rc::new().width(FIELD_NAME_WIDTH)); + line.append_plain(item.config.render_title()); + line.append_plain(" "); + line.append(item.update(Rc::new().width(FIELD_WIDTH)).render(&model.vm)); + line + }) + .filter(|s| { + if let Some((_, filter)) = &state.filter_info { + s.source().contains(filter) + } else { + true + } + }) + .map(|s| (s.clone(), "".into())) + .collect() + } +} + +#[derive(Default, Clone)] +pub struct SystemSlab; + +impl SystemTab for SystemSlab { + fn get_titles(&self) -> ColumnTitles { + ColumnTitles { + titles: enum_iterator::all::() + .map(|field_id| ViewItem::from_default(field_id).config.render_title()) + .collect(), + pinned_titles: 1, + } + } + + fn get_rows(&self, state: &SystemState, _offset: Option) -> Vec<(StyledString, String)> { + let model = state.get_model(); + let mut slab: Vec<&SingleSlabModel> = model.slab.values().collect(); + + if let Some(SystemStateFieldId::Slab(sort_order)) = state.sort_order.as_ref() { + model::sort_queriables(&mut slab, sort_order, state.reverse); + } + + slab.into_iter() + .map(|ssm| { + enum_iterator::all::().fold( + StyledString::new(), + |mut line, field_id| { + let view_item = ViewItem::from_default(field_id.clone()); + line.append(view_item.render(ssm)); + line.append_plain(" "); + line + }, + ) + }) + .filter(|s| { + if let Some((_, filter)) = &state.filter_info { + s.source().contains(filter) + } else { + true + } + }) + .map(|s| (s.clone(), "".into())) + .collect() + } +} + +#[derive(Default, Clone)] +pub struct SystemDisk; + +impl SystemTab for SystemDisk { + fn get_titles(&self) -> ColumnTitles { + ColumnTitles { + titles: enum_iterator::all::() + .map(|field_id| ViewItem::from_default(field_id).config.render_title()) + .collect(), + pinned_titles: 1, + } + } + + fn get_rows(&self, state: &SystemState, offset: Option) -> Vec<(StyledString, String)> { + state + .get_model() + .disks + .iter() + .filter_map(|(dn, sdm)| { + // We use the partition parent id to check if it exists in collapsed_disk set. + let idx_major = format!("{}.0", sdm.major.unwrap_or(0)); + let idx = format!("{}.{}", sdm.major.unwrap_or(0), sdm.minor.unwrap_or(0)); + let collapse = state.collapsed_disk.contains(&idx_major) && sdm.minor != Some(0); + if state + .filter_info + .as_ref() + .map_or(!collapse, |(_, f)| dn.starts_with(f)) + { + Some(( + std::iter::once(SingleDiskModelFieldId::Name) + .chain( + enum_iterator::all::() + .skip(offset.unwrap_or(0) + 1), + ) + .fold(StyledString::new(), |mut line, field_id| { + let view_item = ViewItem::from_default(field_id.clone()); + let rendered = if field_id == SingleDiskModelFieldId::Name { + view_item + .update(Rc::new().indented_prefix(get_prefix(collapse))) + .render_indented(sdm) + } else { + view_item.render(sdm) + }; + line.append(rendered); + line.append_plain(" "); + line + }), + idx, + )) + } else { + None + } + }) + .collect() + } +} + +/// Renders corresponding Fields From BtrfsModel. +type BtrfsViewItem = ViewItem; + +#[derive(Default, Clone)] +pub struct SystemBtrfs { + pub view_items: Vec, +} + +impl SystemBtrfs { + fn new(view_items: Vec) -> Self { + Self { view_items } + } +} + +impl SystemTab for SystemBtrfs { + fn get_titles(&self) -> ColumnTitles { + ColumnTitles { + titles: enum_iterator::all::() + .map(|field_id| ViewItem::from_default(field_id).config.render_title()) + .collect(), + pinned_titles: 0, + } + } + + fn get_rows(&self, state: &SystemState, _offset: Option) -> Vec<(StyledString, String)> { + if let Some(btrfs_model) = state.get_model().btrfs.as_ref() { + let mut subvolumes: Vec<&BtrfsModel> = btrfs_model.values().collect(); + + if let Some(SystemStateFieldId::Btrfs(sort_order)) = state.sort_order.as_ref() { + model::sort_queriables(&mut subvolumes, sort_order, state.reverse); + } + + subvolumes + .iter() + .filter(|bmodel| { + if let Some((SystemStateFieldId::Btrfs(field), filter)) = &state.filter_info { + match bmodel.query(field) { + None => true, + Some(value) => value.to_string().contains(filter), + } + } else { + true + } + }) + .map(|bmodel| { + ( + enum_iterator::all::().fold( + StyledString::new(), + |mut line, field_id| { + let view_item = ViewItem::from_default(field_id); + let rendered = view_item.render(bmodel); + line.append(rendered); + line.append_plain(" "); + line + }, + ), + bmodel.name.as_ref().expect("No name for row").clone(), + ) + }) + .collect() + } else { + Vec::new() + } + } +} + +pub mod default_tabs { + use model::BtrfsModelFieldId::DiskBytes; + use model::BtrfsModelFieldId::DiskFraction; + use model::BtrfsModelFieldId::Name; + use once_cell::sync::Lazy; + + use super::*; + + pub static SYSTEM_BTRFS_TAB: Lazy = Lazy::new(|| { + SystemBtrfs::new(vec![ + ViewItem::from_default(Name), + ViewItem::from_default(DiskFraction), + ViewItem::from_default(DiskBytes), + ]) + }); + pub enum SystemTabs { + Btrfs(&'static SystemBtrfs), + } +} diff --git a/below/view/src/system_view.rs b/below/view/src/system_view.rs new file mode 100644 index 00000000..cb05043d --- /dev/null +++ b/below/view/src/system_view.rs @@ -0,0 +1,307 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::cell::Ref; +use std::cell::RefCell; +use std::cell::RefMut; +use std::collections::HashMap; +use std::collections::HashSet; +use std::rc::Rc; + +use cursive::utils::markup::StyledString; +use cursive::view::Nameable; +use cursive::views::NamedView; +use cursive::views::SelectView; +use cursive::views::ViewRef; +use cursive::Cursive; +use model::system::SystemModel; +use model::BtrfsModelFieldId; +use model::MemoryModelFieldId; +use model::SingleCpuModelFieldId; +use model::SingleDiskModelFieldId; +use model::SingleSlabModelFieldId; +use model::VmModelFieldId; + +use crate::stats_view::ColumnTitles; +use crate::stats_view::StateCommon; +use crate::stats_view::StatsView; +use crate::stats_view::ViewBridge; +use crate::system_tabs::*; +use crate::ViewState; + +pub type ViewType = StatsView; + +use crate::system_view::default_tabs::SYSTEM_BTRFS_TAB; + +// TODO(T123679020): Ideally we want to decouple states for system view tabs. +// Each system view tab really deserves its own view and state +#[derive(Default)] +pub struct SystemState { + pub filter_info: Option<(SystemStateFieldId, String)>, + pub collapsed_disk: HashSet, + pub model: Rc>, + pub sort_order: Option, + pub sort_tags: HashMap, + pub reverse: bool, +} + +#[derive(PartialEq)] +pub enum SystemStateFieldId { + Disk(SingleDiskModelFieldId), + Btrfs(BtrfsModelFieldId), + Cpu(SingleCpuModelFieldId), + Mem(MemoryModelFieldId), + Vm(VmModelFieldId), + Slab(SingleSlabModelFieldId), +} + +impl std::string::ToString for SystemStateFieldId { + fn to_string(&self) -> String { + match self { + Self::Disk(field) => field.to_string(), + Self::Btrfs(field) => field.to_string(), + Self::Cpu(field) => field.to_string(), + Self::Mem(field) => field.to_string(), + Self::Vm(field) => field.to_string(), + Self::Slab(field) => field.to_string(), + } + } +} + +impl StateCommon for SystemState { + type ModelType = SystemModel; + type TagType = SystemStateFieldId; + type KeyType = String; + + fn get_filter_info(&self) -> &Option<(Self::TagType, String)> { + &self.filter_info + } + + fn is_filter_supported_from_tab_idx(&self, _tab: &str, idx: usize) -> bool { + // we only enable str filtering for first col for System View + if idx == 0 { + return true; + } + false + } + + fn get_tag_from_tab_idx(&self, tab: &str, idx: usize) -> Self::TagType { + match tab { + "Btrfs" => { + let system_tab = self + .sort_tags + .get(tab) + .unwrap_or_else(|| panic!("Fail to find tab: {}", tab)); + let default_tabs::SystemTabs::Btrfs(system_tab) = system_tab; + Self::TagType::Btrfs( + system_tab + .view_items + .get(idx) + .expect("Out of title scope") + .field_id + .to_owned(), + ) + } + "CPU" => SystemStateFieldId::Cpu(SingleCpuModelFieldId::Idx), + "Disk" => SystemStateFieldId::Disk(SingleDiskModelFieldId::Name), + // tabs Mem and Vm have two columns 'Field' and 'Value'. 'Field' contains + // a list of all the FieldIds in MemoryModel and VmModel respectively. + // the field given to filter_info don't matter for these tabs because + // they don't use FieldId as column titles/selected col (it isn't used to filter) + "Mem" => SystemStateFieldId::Mem(MemoryModelFieldId::Total), + "Vm" => SystemStateFieldId::Vm(VmModelFieldId::PgpginPerSec), + "Slab" => SystemStateFieldId::Slab( + enum_iterator::all::() + .nth(idx) + .expect("Tag out of range"), + ), + _ => panic!("bug: got unsupported tab {}", tab), + } + } + + fn set_filter_from_tab_idx(&mut self, tab: &str, idx: usize, filter: Option) -> bool { + if !self.is_filter_supported_from_tab_idx(tab, idx) { + return false; + } + + if let Some(filter_text) = filter { + let title = self.get_tag_from_tab_idx(tab, idx); + self.filter_info = Some((title, filter_text)); + } else { + self.filter_info = None; + } + true + } + + fn set_sort_tag(&mut self, sort_order: Self::TagType, reverse: &mut bool) -> bool { + let sort_order = Some(sort_order); + if self.sort_order == sort_order { + *reverse = !*reverse; + } else { + *reverse = true; + self.sort_order = sort_order; + } + self.reverse = *reverse; + true + } + + fn set_sort_tag_from_tab_idx(&mut self, tab: &str, idx: usize, reverse: &mut bool) -> bool { + match tab { + "Btrfs" | "Slab" => { + let sort_order = self.get_tag_from_tab_idx(tab, idx); + self.set_sort_tag(sort_order, reverse) + } + // This is to notify that tab is not currently sortable + _ => false, + } + } + + fn set_sort_string(&mut self, selection: &str, reverse: &mut bool) -> bool { + use std::str::FromStr; + match BtrfsModelFieldId::from_str(selection) { + Ok(field_id) => self.set_sort_tag(SystemStateFieldId::Btrfs(field_id), reverse), + Err(_) => false, + } + } + + fn get_model(&self) -> Ref { + self.model.borrow() + } + + fn get_model_mut(&self) -> RefMut { + self.model.borrow_mut() + } + + fn new(model: Rc>) -> Self { + let mut sort_tags = HashMap::new(); + sort_tags.insert( + "Btrfs".into(), + default_tabs::SystemTabs::Btrfs(&*SYSTEM_BTRFS_TAB), + ); + Self { + sort_order: None, + reverse: false, + sort_tags, + model, + ..Default::default() + } + } +} + +pub enum SystemView { + Cpu(SystemCpu), + Mem(SystemMem), + Vm(SystemVm), + Slab(SystemSlab), + Disk(SystemDisk), + Btrfs(SystemBtrfs), +} + +impl SystemView { + pub fn new(c: &mut Cursive) -> NamedView { + let mut list = SelectView::::new(); + list.set_on_submit(|c, idx: &String| { + let mut view = SystemView::get_system_view(c); + // We only care about disk not partition + if view.get_tab_view().get_cur_selected() == "Disk" && idx.ends_with(".0") { + if view.state.borrow_mut().collapsed_disk.contains(idx) { + view.state.borrow_mut().collapsed_disk.remove(idx); + } else { + view.state + .borrow_mut() + .collapsed_disk + .insert(idx.to_string()); + } + + view.refresh(c); + } + }); + + let tabs = vec![ + "CPU".into(), + "Mem".into(), + "Vm".into(), + "Slab".into(), + "Disk".into(), + "Btrfs".into(), + ]; + let mut tabs_map: HashMap = HashMap::new(); + tabs_map.insert("CPU".into(), SystemView::Cpu(Default::default())); + tabs_map.insert("Mem".into(), SystemView::Mem(Default::default())); + tabs_map.insert("Vm".into(), SystemView::Vm(Default::default())); + tabs_map.insert("Slab".into(), SystemView::Slab(Default::default())); + tabs_map.insert("Disk".into(), SystemView::Disk(Default::default())); + tabs_map.insert("Btrfs".into(), SystemView::Btrfs(Default::default())); + let user_data = c + .user_data::() + .expect("No data stored in Cursive Object!"); + StatsView::new( + "system", + tabs, + tabs_map, + list, + SystemState::new(user_data.system.clone()), + user_data.event_controllers.clone(), + user_data.cmd_controllers.clone(), + ) + .feed_data(c) + .with_name(Self::get_view_name()) + } + + pub fn get_system_view(c: &mut Cursive) -> ViewRef { + ViewType::get_view(c) + } + + pub fn refresh(c: &mut Cursive) { + Self::get_system_view(c).refresh(c); + } + + fn get_inner(&self) -> Box { + match self { + Self::Cpu(inner) => Box::new(inner.clone()), + Self::Mem(inner) => Box::new(inner.clone()), + Self::Vm(inner) => Box::new(inner.clone()), + Self::Slab(inner) => Box::new(inner.clone()), + Self::Disk(inner) => Box::new(inner.clone()), + Self::Btrfs(inner) => Box::new(inner.clone()), + } + } +} + +impl ViewBridge for SystemView { + type StateType = SystemState; + fn get_view_name() -> &'static str { + "system_view" + } + fn get_titles(&self) -> ColumnTitles { + self.get_inner().get_titles() + } + + fn get_rows( + &mut self, + state: &Self::StateType, + offset: Option, + ) -> Vec<(StyledString, String)> { + self.get_inner().get_rows(state, offset) + } + + fn on_select_update_cmd_palette( + _view: &Self::StateType, + selected_key: &String, + _current_tab: &str, + _selected_column: usize, + ) -> String { + selected_key.clone() + } +} -- cgit v1.2.3