1use 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 pub collapsed_cgroups: Rc<RefCell<HashSet<String>>>,
52 pub current_selected_cgroup: String,
53 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 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 fn uncollapse_cgroup(&mut self, cgroup: &str) {
178 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
207pub 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 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 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}