Skip to main content

below_view/
cgroup_view.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::cell::Ref;
16use std::cell::RefCell;
17use std::cell::RefMut;
18use std::collections::HashMap;
19use std::collections::HashSet;
20use std::rc::Rc;
21
22use cursive::utils::markup::StyledString;
23use cursive::view::Nameable;
24use cursive::views::NamedView;
25use cursive::views::SelectView;
26use cursive::views::ViewRef;
27use cursive::Cursive;
28use model::CgroupCpuModelFieldId;
29use model::CgroupIoModelFieldId;
30use model::CgroupMemoryModelFieldId;
31use model::CgroupModel;
32use model::Queriable;
33use model::SingleCgroupModelFieldId;
34
35use crate::cgroup_tabs::default_tabs;
36use crate::cgroup_tabs::CgroupTab;
37use crate::render::ViewItem;
38use crate::stats_view::ColumnTitles;
39use crate::stats_view::StateCommon;
40use crate::stats_view::StatsView;
41use crate::stats_view::ViewBridge;
42use crate::ViewState;
43
44pub type ViewType = StatsView<CgroupView>;
45
46#[derive(Default)]
47pub struct CgroupState {
48    // Rc::RefCell is necessaray here since we will need to change the collapsed_cgroups
49    // when we traverse the cgroup tree recursively. And we can not pass the CgroupState as
50    // mutable.
51    pub collapsed_cgroups: Rc<RefCell<HashSet<String>>>,
52    pub current_selected_cgroup: String,
53    // cgroup row to move focus on. If set, on next refresh, selector will be
54    // moved to the cgroup
55    pub cgroup_to_focus: Option<String>,
56    pub filter_info: Option<(SingleCgroupModelFieldId, String)>,
57    pub sort_order: Option<SingleCgroupModelFieldId>,
58    pub sort_tags: HashMap<String, Vec<ViewItem<SingleCgroupModelFieldId>>>,
59    pub reverse: bool,
60    pub model: Rc<RefCell<CgroupModel>>,
61    pub collapse_all_top_level_cgroup: bool,
62}
63
64impl StateCommon for CgroupState {
65    type ModelType = CgroupModel;
66    type TagType = SingleCgroupModelFieldId;
67    type KeyType = String;
68
69    fn get_filter_info(&self) -> &Option<(Self::TagType, String)> {
70        &self.filter_info
71    }
72
73    fn is_filter_supported_from_tab_idx(&self, _tab: &str, idx: usize) -> bool {
74        // we only enable str filtering for first col (the rest are numeric cols)
75        if idx == 0 {
76            return true;
77        }
78        false
79    }
80
81    fn get_tag_from_tab_idx(&self, tab: &str, idx: usize) -> Self::TagType {
82        match idx {
83            0 => Self::TagType::Name,
84            _ => self
85                .sort_tags
86                .get(tab)
87                .unwrap_or_else(|| panic!("Fail to find tab: {}", tab))
88                .get(idx - 1)
89                .expect("Out of title scope")
90                .field_id
91                .to_owned(),
92        }
93    }
94
95    fn set_filter_from_tab_idx(&mut self, tab: &str, idx: usize, filter: Option<String>) -> bool {
96        if !self.is_filter_supported_from_tab_idx(tab, idx) {
97            return false;
98        }
99        if let Some(filter_text) = filter {
100            let title = self.get_tag_from_tab_idx(tab, idx);
101            self.filter_info = Some((title, filter_text));
102        } else {
103            self.filter_info = None;
104        }
105        true
106    }
107
108    fn set_sort_tag(&mut self, sort_order: Self::TagType, reverse: &mut bool) -> bool {
109        let sort_order = Some(sort_order);
110        if self.sort_order == sort_order {
111            *reverse = !*reverse;
112        } else {
113            *reverse = true;
114            self.sort_order = sort_order;
115        }
116        self.reverse = *reverse;
117        true
118    }
119
120    fn set_sort_tag_from_tab_idx(&mut self, tab: &str, idx: usize, reverse: &mut bool) -> bool {
121        let sort_order = self.get_tag_from_tab_idx(tab, idx);
122        self.set_sort_tag(sort_order, reverse)
123    }
124
125    fn set_sort_string(&mut self, selection: &str, reverse: &mut bool) -> bool {
126        use std::str::FromStr;
127        match Self::TagType::from_str(selection) {
128            Ok(field_id) => self.set_sort_tag(field_id, reverse),
129            Err(_) => false,
130        }
131    }
132
133    fn get_model(&self) -> Ref<Self::ModelType> {
134        self.model.borrow()
135    }
136
137    fn get_model_mut(&self) -> RefMut<Self::ModelType> {
138        self.model.borrow_mut()
139    }
140
141    fn new(model: Rc<RefCell<Self::ModelType>>) -> Self {
142        let mut sort_tags = HashMap::new();
143        sort_tags.insert("General".into(), default_tabs::get_general_items());
144        sort_tags.insert("CPU".into(), default_tabs::get_cpu_items());
145        sort_tags.insert("Mem".into(), default_tabs::get_mem_items());
146        sort_tags.insert("I/O".into(), default_tabs::get_io_items());
147        sort_tags.insert("Pressure".into(), default_tabs::get_pressure_items());
148        sort_tags.insert("Properties".into(), default_tabs::get_properties_items());
149        Self {
150            collapsed_cgroups: Rc::new(RefCell::new(HashSet::new())),
151            current_selected_cgroup: "<root>".into(),
152            cgroup_to_focus: None,
153            filter_info: None,
154            sort_order: None,
155            sort_tags,
156            reverse: false,
157            model,
158            collapse_all_top_level_cgroup: false,
159        }
160    }
161}
162
163impl CgroupState {
164    fn set_sort_order(&mut self, tag: SingleCgroupModelFieldId) {
165        self.sort_order = Some(tag);
166    }
167
168    fn set_reverse(&mut self, reverse: bool) {
169        self.reverse = reverse;
170    }
171
172    fn toggle_collapse_root_flag(&mut self) {
173        self.collapse_all_top_level_cgroup = !self.collapse_all_top_level_cgroup;
174    }
175
176    // Recursively fold open to given cgroup
177    fn uncollapse_cgroup(&mut self, cgroup: &str) {
178        // Root is always uncollapsed
179        if cgroup.is_empty() {
180            return;
181        }
182        self.collapse_all_top_level_cgroup = false;
183        let mut sub_cgroup = Some(cgroup);
184        while let Some(c) = sub_cgroup {
185            self.collapsed_cgroups.borrow_mut().remove(c);
186            sub_cgroup = c.rsplit_once('/').map(|(s, _)| s);
187        }
188    }
189
190    pub fn handle_state_for_entering_focus(&mut self, cgroup: String) {
191        self.uncollapse_cgroup(cgroup.as_str());
192        self.cgroup_to_focus = Some(cgroup);
193    }
194
195    pub fn collapse_selected_cgroup_children(&mut self) {
196        if let Some(cur) = self
197            .get_model()
198            .get_by_path_str(&self.current_selected_cgroup)
199        {
200            self.collapsed_cgroups
201                .borrow_mut()
202                .extend(cur.children.iter().map(|c| &c.data.full_path).cloned());
203        }
204    }
205}
206
207// TODO: Make CgroupView a collection of CgroupTab
208pub struct CgroupView {
209    tab: CgroupTab,
210}
211
212impl CgroupView {
213    pub fn new(c: &mut Cursive) -> NamedView<ViewType> {
214        let mut list = SelectView::new();
215        list.set_on_submit(|c, cgroup: &String| {
216            let mut view = CgroupView::get_cgroup_view(c);
217
218            // Select root will collapse or uncollapse all top level cgroup
219            if cgroup.is_empty() {
220                view.state.borrow_mut().toggle_collapse_root_flag();
221                view.state
222                    .borrow_mut()
223                    .collapsed_cgroups
224                    .borrow_mut()
225                    .clear();
226                return view.refresh(c);
227            } else if view.state.borrow().collapse_all_top_level_cgroup {
228                view.state.borrow_mut().toggle_collapse_root_flag();
229            }
230
231            if view
232                .state
233                .borrow()
234                .collapsed_cgroups
235                .borrow()
236                .contains(cgroup)
237            {
238                view.state
239                    .borrow_mut()
240                    .collapsed_cgroups
241                    .borrow_mut()
242                    .remove(cgroup);
243            } else {
244                view.state
245                    .borrow_mut()
246                    .collapsed_cgroups
247                    .borrow_mut()
248                    .insert(cgroup.to_string());
249            }
250
251            view.refresh(c);
252        });
253
254        let user_data = c
255            .user_data::<ViewState>()
256            .expect("No data stored in Cursive Object!");
257        let cgroup_name_config = base_render::RenderConfig {
258            width: user_data.viewrc.cgroup_name_width,
259            ..Default::default()
260        };
261        let tabs = vec![
262            "General".into(),
263            "CPU".into(),
264            "Mem".into(),
265            "I/O".into(),
266            "Pressure".into(),
267            "Properties".into(),
268        ];
269        let mut tabs_map: HashMap<String, CgroupView> = HashMap::new();
270        tabs_map.insert(
271            "General".into(),
272            CgroupView {
273                tab: CgroupTab::new(default_tabs::get_general_items(), &cgroup_name_config),
274            },
275        );
276        tabs_map.insert(
277            "CPU".into(),
278            CgroupView {
279                tab: CgroupTab::new(default_tabs::get_cpu_items(), &cgroup_name_config),
280            },
281        );
282        tabs_map.insert(
283            "Mem".into(),
284            CgroupView {
285                tab: CgroupTab::new(default_tabs::get_mem_items(), &cgroup_name_config),
286            },
287        );
288        tabs_map.insert(
289            "I/O".into(),
290            CgroupView {
291                tab: CgroupTab::new(default_tabs::get_io_items(), &cgroup_name_config),
292            },
293        );
294        tabs_map.insert(
295            "Pressure".into(),
296            CgroupView {
297                tab: CgroupTab::new(default_tabs::get_pressure_items(), &cgroup_name_config),
298            },
299        );
300        tabs_map.insert(
301            "Properties".into(),
302            CgroupView {
303                tab: CgroupTab::new(default_tabs::get_properties_items(), &cgroup_name_config),
304            },
305        );
306        let mut cgroup_state = CgroupState::new(user_data.cgroup.clone());
307        if user_data.viewrc.collapse_cgroups == Some(true) {
308            cgroup_state.collapse_all_top_level_cgroup = true;
309        }
310        StatsView::new(
311            "Cgroup",
312            tabs,
313            tabs_map,
314            list,
315            cgroup_state,
316            user_data.event_controllers.clone(),
317            user_data.cmd_controllers.clone(),
318        )
319        .feed_data(c)
320        .on_event('C', |c| {
321            let mut view = Self::get_cgroup_view(c);
322            view.state
323                .borrow_mut()
324                .set_sort_order(SingleCgroupModelFieldId::Cpu(
325                    CgroupCpuModelFieldId::UsagePct,
326                ));
327            view.state.borrow_mut().set_reverse(true);
328            view.refresh(c)
329        })
330        .on_event('M', |c| {
331            let mut view = Self::get_cgroup_view(c);
332            view.state
333                .borrow_mut()
334                .set_sort_order(SingleCgroupModelFieldId::Mem(
335                    CgroupMemoryModelFieldId::Total,
336                ));
337            view.state.borrow_mut().set_reverse(true);
338            view.refresh(c)
339        })
340        .on_event('D', |c| {
341            let mut view = Self::get_cgroup_view(c);
342            view.state
343                .borrow_mut()
344                .set_sort_order(SingleCgroupModelFieldId::Io(
345                    CgroupIoModelFieldId::RwbytesPerSec,
346                ));
347            view.state.borrow_mut().set_reverse(true);
348            view.refresh(c)
349        })
350        .on_event('=', |c| {
351            let mut view = Self::get_cgroup_view(c);
352            view.state.borrow_mut().collapse_selected_cgroup_children();
353            view.refresh(c);
354        })
355        .with_name(Self::get_view_name())
356    }
357
358    pub fn get_cgroup_view(c: &mut Cursive) -> ViewRef<ViewType> {
359        ViewType::get_view(c)
360    }
361
362    pub fn refresh(c: &mut Cursive) {
363        let mut view = Self::get_cgroup_view(c);
364        let cgroup_to_focus = view.state.borrow_mut().cgroup_to_focus.take();
365        if let Some(cgroup) = &cgroup_to_focus {
366            // Refresh before getting position to ensure cgroup is expanded
367            view.refresh(c);
368            let pos = view
369                .get_detail_view()
370                .iter()
371                .position(|(_row, key)| key == cgroup);
372            if let Some(pos) = pos {
373                view.get_detail_view().set_selection(pos)(c);
374            }
375        }
376        view.refresh(c);
377    }
378}
379
380impl ViewBridge for CgroupView {
381    type StateType = CgroupState;
382
383    fn get_view_name() -> &'static str {
384        "cgroup_view"
385    }
386    fn get_titles(&self) -> ColumnTitles {
387        self.tab.get_titles()
388    }
389
390    fn get_rows(
391        &mut self,
392        state: &Self::StateType,
393        offset: Option<usize>,
394    ) -> Vec<(StyledString, String)> {
395        self.tab.get_rows(state, offset)
396    }
397
398    fn on_select_update_state(state: &mut Self::StateType, selected_key: Option<&String>) {
399        state.current_selected_cgroup = selected_key.cloned().unwrap_or_default();
400    }
401
402    fn on_select_update_cmd_palette(
403        view: &Self::StateType,
404        selected_key: &String,
405        current_tab: &str,
406        selected_column: usize,
407    ) -> String {
408        let tag = if selected_column == 0 {
409            SingleCgroupModelFieldId::FullPath
410        } else {
411            view.get_tag_from_tab_idx(current_tab, selected_column)
412        };
413        let field_str = view
414            .model
415            .borrow()
416            .get_by_path_str(selected_key)
417            .and_then(|model| model.data.query(&tag))
418            .map_or("?".to_string(), |field| field.to_string());
419        format!(" {} : {} ", tag, field_str)
420    }
421}