├── .gitignore ├── COPYRIGHT ├── src ├── params.rs ├── types.rs ├── main.rs ├── ui.rs ├── event.rs ├── ui │ ├── service.rs │ ├── process.rs │ └── memory.rs └── app.rs ├── Cargo.toml ├── README.md ├── LICENSE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | **/ignore 2 | **/target 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Short version for non-lawyers: 2 | 3 | The iceray project is licensed under Apache 2.0 terms. 4 | 5 | 6 | Longer version: 7 | 8 | Copyrights in the iceray project are retained by their contributors. No 9 | copyright assignment is required to contribute to the iceray project. 10 | 11 | For full authorship information, see the version control history. 12 | 13 | iceray is licensed under the Apache License, Version 2.0 14 | or , at your option. 15 | -------------------------------------------------------------------------------- /src/params.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | use crate::types::Pages; 4 | 5 | use structopt::StructOpt; 6 | 7 | /// iceray - iceoryx introspection 8 | #[derive(StructOpt, Debug)] 9 | #[structopt(name = "iceray")] 10 | pub struct Params { 11 | /// The update interal in milliseonds 12 | #[structopt(short, long, default_value = "1000")] 13 | pub update_interval: u64, 14 | /// The initial page to show on startup 15 | #[structopt(short, long, default_value = "Memory")] 16 | pub initial_page: Pages, 17 | } 18 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | use std::str::FromStr; 4 | 5 | #[derive(Debug)] 6 | pub enum Pages { 7 | Overview, 8 | Memory, 9 | Processes, 10 | Services, 11 | } 12 | 13 | impl FromStr for Pages { 14 | type Err = String; 15 | 16 | fn from_str(s: &str) -> Result { 17 | match s.trim() { 18 | "Overview" => Ok(Pages::Overview), 19 | "Memory" => Ok(Pages::Memory), 20 | "Processes" => Ok(Pages::Processes), 21 | "Services" => Ok(Pages::Services), 22 | _ => Err("Could not parse page type!".to_string()), 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2018" 3 | name = "iceray" 4 | version = "0.1.0" 5 | authors = ["elBoberido "] 6 | description = "Introspection TUI for Eclipse iceoryx written in Rust" 7 | readme = "README.md" 8 | keywords = [ 9 | "iceoryx", 10 | "introspection" 11 | ] 12 | license = "Apache-2.0" 13 | repository = "https://github.com/elBoberido/iceray" 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | iceoryx-rs = { version = "0.1.0" } 19 | termion = "2.0" 20 | ratatui = { version = "0.23", default-features = false, features = ["termion"] } 21 | structopt = "0.3" 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iceray - Introspection TUI for Eclipse iceoryx 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/iceray.svg)](https://crates.io/crates/iceray) 4 | 5 | An introspection TUI for [Eclipse iceoryx](https://github.com/eclipse-iceoryx/iceoryx) based on [iceoryx-rs](https://crates.io/crates/iceoryx-rs). 6 | 7 | ![iceray-memory](https://user-images.githubusercontent.com/56729607/74793362-66326a80-52c0-11ea-831d-a05b7043339d.png) 8 | ![iceray-processes](https://user-images.githubusercontent.com/56729607/74793376-6f233c00-52c0-11ea-9d9d-a303b7c0c17f.png) 9 | ![iceray-services](https://user-images.githubusercontent.com/56729607/74793387-76e2e080-52c0-11ea-8bfa-a83f258d3c22.png) 10 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | mod params; 4 | 5 | mod app; 6 | mod event; 7 | mod types; 8 | mod ui; 9 | 10 | use crate::app::App; 11 | use crate::event::{Config, Event, Events}; 12 | 13 | use iceoryx_rs::Runtime; 14 | 15 | use structopt::StructOpt; 16 | 17 | use termion::input::MouseTerminal; 18 | use termion::raw::IntoRawMode; 19 | use termion::screen::IntoAlternateScreen; 20 | 21 | use ratatui::backend::TermionBackend; 22 | use ratatui::Terminal; 23 | 24 | use std::io; 25 | use std::time::Duration; 26 | 27 | use std::error::Error; 28 | 29 | fn main() -> Result<(), Box> { 30 | let params = params::Params::from_args(); 31 | 32 | Runtime::init("iceray"); 33 | 34 | let events = Events::new(Config { 35 | tick_rate: Duration::from_millis(params.update_interval), 36 | ..Config::default() 37 | }); 38 | 39 | let stdout = io::stdout().into_raw_mode()?; 40 | let stdout = MouseTerminal::from(stdout); 41 | let backend = TermionBackend::new(stdout.into_alternate_screen()?); 42 | let mut terminal = Terminal::new(backend)?; 43 | terminal.hide_cursor()?; 44 | 45 | let mut app = App::new("iceray - iceoryx Introspection"); 46 | app.tabs.index = params.initial_page as usize; 47 | ui::draw(&mut terminal, &mut app)?; 48 | 49 | loop { 50 | match events.next()? { 51 | Event::Input(key) => { 52 | app.on_key(key); 53 | } 54 | Event::Mouse(m) => { 55 | app.on_mouse(m); 56 | } 57 | Event::Tick => { 58 | app.on_tick(); 59 | } 60 | } 61 | if app.should_quit { 62 | break; 63 | } 64 | 65 | ui::draw(&mut terminal, &mut app)?; 66 | } 67 | 68 | Ok(()) 69 | } 70 | -------------------------------------------------------------------------------- /src/ui.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | mod memory; 4 | mod process; 5 | mod service; 6 | 7 | use crate::App; 8 | 9 | use ratatui::backend::Backend; 10 | use ratatui::layout::{Constraint, Layout, Rect}; 11 | use ratatui::style::{Color, Style}; 12 | use ratatui::text::{Line, Span}; 13 | use ratatui::widgets::{Block, Borders, Paragraph, Tabs, Wrap}; 14 | use ratatui::{Frame, Terminal}; 15 | 16 | use std::io; 17 | 18 | pub fn draw(terminal: &mut Terminal, app: &mut App) -> Result<(), io::Error> { 19 | terminal.draw(|mut frame| { 20 | let chunks = Layout::default() 21 | .constraints([Constraint::Length(3), Constraint::Min(3)].as_ref()) 22 | .split(frame.size()); 23 | 24 | draw_tabbar(&mut frame, chunks[0], app); 25 | draw_main_view(&mut frame, chunks[1], app); 26 | })?; 27 | 28 | Ok(()) 29 | } 30 | 31 | fn draw_tabbar(frame: &mut Frame, area: Rect, app: &App) 32 | where 33 | B: Backend, 34 | { 35 | let tabs = Tabs::new(app.tabs.titles.iter().cloned().map(Line::from).collect()) 36 | .block( 37 | Block::default() 38 | .borders(Borders::ALL) 39 | .title("Introspection Topics"), 40 | ) 41 | .select(app.tabs.index) 42 | .style(Style::default()) 43 | .highlight_style(Style::default().fg(Color::Yellow)); 44 | 45 | frame.render_widget(tabs, area); 46 | } 47 | 48 | fn draw_main_view(frame: &mut Frame, area: Rect, app: &App) 49 | where 50 | B: Backend, 51 | { 52 | match app.tabs.index { 53 | 0 => { 54 | let mut text = Vec::::new(); 55 | 56 | text.push(Line::from(vec![Span::raw("unimplemented!")])); 57 | text.push(Line::from(vec![Span::raw("")])); 58 | text.push(Line::from(vec![Span::raw( 59 | "use arrow keys to navigate to the next page!", 60 | )])); 61 | 62 | let paragraph = Paragraph::new(text) 63 | .block(Block::default().borders(Borders::ALL).title("Overview")) 64 | .wrap(Wrap { trim: false }); 65 | 66 | frame.render_widget(paragraph, area); 67 | } 68 | 1 => memory::draw(frame, area, app), 69 | 2 => process::draw(frame, area, app), 70 | 3 => service::draw(frame, area, app), 71 | _ => {} 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/event.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | use std::io; 4 | use std::sync::mpsc; 5 | use std::thread; 6 | use std::time::Duration; 7 | 8 | use termion::event::Key; 9 | use termion::event::MouseEvent; 10 | use termion::input::TermRead; 11 | 12 | pub enum Event { 13 | Input(I), 14 | Mouse(M), 15 | Tick, 16 | } 17 | 18 | /// A small event handler that wrap termion input and tick events. Each event 19 | /// type is handled in its own thread and returned to a common `Receiver` 20 | pub struct Events { 21 | rx: mpsc::Receiver>, 22 | _input_handle: thread::JoinHandle<()>, 23 | _tick_handle: thread::JoinHandle<()>, 24 | } 25 | 26 | #[derive(Debug, Clone, Copy)] 27 | pub struct Config { 28 | pub exit_key: Key, 29 | pub tick_rate: Duration, 30 | } 31 | 32 | impl Default for Config { 33 | fn default() -> Config { 34 | Config { 35 | exit_key: Key::Char('q'), 36 | tick_rate: Duration::from_millis(250), 37 | } 38 | } 39 | } 40 | 41 | impl Events { 42 | pub fn new(config: Config) -> Self { 43 | let (tx, rx) = mpsc::channel(); 44 | let input_handle = { 45 | let tx = tx.clone(); 46 | thread::spawn(move || { 47 | let stdin = io::stdin(); 48 | for evt in stdin.events() { 49 | match evt { 50 | Ok(termion::event::Event::Key(key)) => { 51 | if tx.send(Event::Input(key)).is_err() { 52 | return; 53 | } 54 | if key == config.exit_key { 55 | return; 56 | } 57 | } 58 | Ok(termion::event::Event::Mouse(mouse)) => { 59 | if tx.send(Event::Mouse(mouse)).is_err() { 60 | return; 61 | } 62 | } 63 | _ => {} 64 | } 65 | } 66 | }) 67 | }; 68 | let tick_handle = { 69 | let tx = tx.clone(); 70 | thread::spawn(move || { 71 | let tx = tx.clone(); 72 | loop { 73 | tx.send(Event::Tick).unwrap(); 74 | thread::sleep(config.tick_rate); 75 | } 76 | }) 77 | }; 78 | Events { 79 | rx, 80 | _input_handle: input_handle, 81 | _tick_handle: tick_handle, 82 | } 83 | } 84 | 85 | pub fn next(&self) -> Result, mpsc::RecvError> { 86 | self.rx.recv() 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/ui/service.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | use crate::app::App; 4 | 5 | use ratatui::backend::Backend; 6 | use ratatui::layout::{Constraint, Direction, Layout, Rect}; 7 | use ratatui::style::{Color, Modifier, Style}; 8 | use ratatui::text::{Line, Span}; 9 | use ratatui::widgets::{Block, Borders, Paragraph, Wrap}; 10 | use ratatui::Frame; 11 | 12 | pub fn draw(frame: &mut Frame, area: Rect, app: &App) 13 | where 14 | B: Backend, 15 | { 16 | let chunks = Layout::default() 17 | .direction(Direction::Horizontal) 18 | .constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)].as_ref()) 19 | .split(area); 20 | 21 | draw_service_list(frame, chunks[0], app); 22 | draw_service_details(frame, chunks[1], app); 23 | } 24 | 25 | pub fn draw_service_list(frame: &mut Frame, area: Rect, app: &App) 26 | where 27 | B: Backend, 28 | { 29 | let mut text = Vec::::new(); 30 | 31 | text.push(Line::from(vec![Span::raw("")])); 32 | 33 | for (index, (service, _)) in app.services.map.iter().enumerate() { 34 | let style = if app.services.selection.0 == index { 35 | Style::default().fg(Color::Yellow) 36 | } else { 37 | Style::default() 38 | }; 39 | text.push(Line::from(vec![Span::styled( 40 | format!( 41 | "{} • {} • {}", 42 | (*service).service_id, 43 | (*service).instance_id, 44 | (*service).event_id 45 | ), 46 | style, 47 | )])); 48 | } 49 | 50 | let paragraph = Paragraph::new(text) 51 | .block(Block::default().borders(Borders::ALL).title("Service List")) 52 | .wrap(Wrap { trim: false }); 53 | 54 | frame.render_widget(paragraph, area); 55 | } 56 | 57 | pub fn draw_service_details(frame: &mut Frame, area: Rect, app: &App) 58 | where 59 | B: Backend, 60 | { 61 | let mut text = Vec::::new(); 62 | 63 | text.push(Line::from(vec![Span::raw("")])); 64 | 65 | if let Some(details) = app.services.map.get(&app.services.selection.1) { 66 | text.push(Line::from(vec![ 67 | Span::styled("Name: ", Style::default().add_modifier(Modifier::BOLD)), 68 | Span::raw(format!( 69 | "{} • {} • {}", 70 | app.services.selection.1.service_id, 71 | app.services.selection.1.instance_id, 72 | app.services.selection.1.event_id, 73 | )), 74 | ])); 75 | 76 | text.push(Line::from(vec![ 77 | Span::styled( 78 | format!("Processes with corresponding Publisher Ports: "), 79 | Style::default().add_modifier(Modifier::BOLD), 80 | ), 81 | Span::raw(format!("{}", details.publisher_processes.len())), 82 | ])); 83 | 84 | for process in details.publisher_processes.iter() { 85 | text.push(Line::from(vec![Span::raw(format!(" • {}", process))])); 86 | } 87 | 88 | text.push(Line::from(vec![ 89 | Span::styled( 90 | format!("Processes with corresponding Subscriber Ports: ",), 91 | Style::default().add_modifier(Modifier::BOLD), 92 | ), 93 | Span::raw(format!("{}", details.subscriber_processes.len())), 94 | ])); 95 | 96 | for process in details.subscriber_processes.iter() { 97 | text.push(Line::from(vec![Span::raw(format!(" • {}", process))])); 98 | } 99 | } 100 | 101 | let paragraph = Paragraph::new(text) 102 | .block( 103 | Block::default() 104 | .borders(Borders::ALL) 105 | .title("Service Details"), 106 | ) 107 | .wrap(Wrap { trim: true }); 108 | 109 | frame.render_widget(paragraph, area); 110 | } 111 | -------------------------------------------------------------------------------- /src/ui/process.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | use crate::app::App; 4 | 5 | use ratatui::backend::Backend; 6 | use ratatui::layout::{Constraint, Direction, Layout, Rect}; 7 | use ratatui::style::{Color, Modifier, Style}; 8 | use ratatui::text::{Line, Span}; 9 | use ratatui::widgets::{Block, Borders, Paragraph, Wrap}; 10 | use ratatui::Frame; 11 | 12 | pub fn draw(frame: &mut Frame, area: Rect, app: &App) 13 | where 14 | B: Backend, 15 | { 16 | let chunks = Layout::default() 17 | .direction(Direction::Horizontal) 18 | .constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)].as_ref()) 19 | .split(area); 20 | 21 | draw_process_list(frame, chunks[0], app); 22 | draw_process_details(frame, chunks[1], app); 23 | } 24 | 25 | pub fn draw_process_list(frame: &mut Frame, area: Rect, app: &App) 26 | where 27 | B: Backend, 28 | { 29 | let mut text = Vec::::new(); 30 | 31 | text.push(Line::from(vec![Span::raw("")])); 32 | 33 | text.push(Line::from(vec![Span::styled( 34 | " PID | Publisher | Subscriber | Nodes | Process Name", 35 | Style::default().add_modifier(Modifier::BOLD), 36 | )])); 37 | 38 | text.push(Line::from(vec![Span::raw( 39 | " ----------------------------------------------------------", 40 | )])); 41 | 42 | for (index, (process_name, details)) in app.processes.map.iter().enumerate() { 43 | let style = if app.processes.selection.0 == index { 44 | Style::default().fg(Color::Yellow) 45 | } else { 46 | Style::default() 47 | }; 48 | 49 | text.push(Line::from(vec![ 50 | Span::styled(format!(" {:>6} | ", details.pid), style), 51 | Span::styled(format!("{:>9} | ", details.publisher_ports.len()), style), 52 | Span::styled(format!("{:>10} | ", details.subscriber_ports.len()), style), 53 | Span::styled(format!("{:>5} | ", details.nodes.len()), style), 54 | Span::styled(format!("{}", process_name), style), 55 | ])); 56 | } 57 | 58 | let paragraph = Paragraph::new(text) 59 | .block(Block::default().borders(Borders::ALL).title("Process List")) 60 | .wrap(Wrap { trim: false }); 61 | 62 | frame.render_widget(paragraph, area); 63 | } 64 | 65 | pub fn draw_process_details(frame: &mut Frame, area: Rect, app: &App) 66 | where 67 | B: Backend, 68 | { 69 | let mut text = Vec::::new(); 70 | 71 | text.push(Line::from(vec![Span::raw("")])); 72 | 73 | let process_name = &app.processes.selection.1; 74 | if let Some(details) = app.processes.map.get(process_name) { 75 | text.push(Line::from(vec![ 76 | Span::styled(" Name: ", Style::default().add_modifier(Modifier::BOLD)), 77 | Span::raw(format!("{}", process_name)), 78 | ])); 79 | 80 | text.push(Line::from(vec![ 81 | Span::styled(" PID: ", Style::default().add_modifier(Modifier::BOLD)), 82 | Span::raw(format!("{:}", details.pid)), 83 | ])); 84 | 85 | text.push(Line::from(vec![ 86 | Span::styled( 87 | " Publisher Ports: ", 88 | Style::default().add_modifier(Modifier::BOLD), 89 | ), 90 | Span::raw(format!("{:}", details.publisher_ports.len())), 91 | ])); 92 | for port in details.publisher_ports.iter() { 93 | text.push(Line::from(vec![Span::raw(format!( 94 | " • {} • {} • {}", 95 | port.service_id, port.instance_id, port.event_id 96 | ))])); 97 | } 98 | 99 | text.push(Line::from(vec![ 100 | Span::styled( 101 | " Subscriber Ports: ", 102 | Style::default().add_modifier(Modifier::BOLD), 103 | ), 104 | Span::raw(format!("{:}", details.subscriber_ports.len())), 105 | ])); 106 | for port in details.subscriber_ports.iter() { 107 | text.push(Line::from(vec![Span::raw(format!( 108 | " • {} • {} • {}", 109 | port.service_id, port.instance_id, port.event_id 110 | ))])); 111 | } 112 | 113 | text.push(Line::from(vec![ 114 | Span::styled(" Nodes: ", Style::default().add_modifier(Modifier::BOLD)), 115 | Span::raw(format!("{:}", details.nodes.len())), 116 | ])); 117 | if details.nodes.len() >= 1 { 118 | text.push(Line::from(vec![Span::styled( 119 | " Listing nodes needs implementation!", 120 | Style::default().fg(Color::Red), 121 | )])); 122 | } 123 | } 124 | 125 | let paragraph = Paragraph::new(text) 126 | .block( 127 | Block::default() 128 | .borders(Borders::ALL) 129 | .title("Process Details"), 130 | ) 131 | .wrap(Wrap { trim: true }); 132 | 133 | frame.render_widget(paragraph, area); 134 | } 135 | -------------------------------------------------------------------------------- /src/ui/memory.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | use crate::app::{App, USED_CHUNKS_HISTORY_SIZE}; 4 | 5 | use ratatui::backend::Backend; 6 | use ratatui::layout::{Constraint, Direction, Layout, Rect}; 7 | use ratatui::style::{Color, Modifier, Style}; 8 | use ratatui::text::{Line, Span}; 9 | use ratatui::widgets::{Block, Borders, Paragraph, Wrap}; 10 | use ratatui::Frame; 11 | 12 | use ratatui::widgets::canvas::{Canvas, Line as CanvasLine}; 13 | 14 | pub fn draw(frame: &mut Frame, area: Rect, app: &App) 15 | where 16 | B: Backend, 17 | { 18 | let chunks = Layout::default() 19 | .direction(Direction::Horizontal) 20 | .constraints([Constraint::Length(80), Constraint::Min(0)].as_ref()) 21 | .split(area); 22 | 23 | draw_mempool_segments(frame, chunks[0], app); 24 | draw_graph(frame, chunks[1], app); 25 | } 26 | 27 | pub fn draw_mempool_segments(frame: &mut Frame, area: Rect, app: &App) 28 | where 29 | B: Backend, 30 | { 31 | let mut text = Vec::::new(); 32 | 33 | let sample = if let Some(sample) = app.memory.segments.as_ref() { 34 | sample 35 | } else { 36 | return; 37 | }; 38 | 39 | (*sample).memory_segments().into_iter().for_each(|segment| { 40 | let segment_id = segment.segment_id(); 41 | text.push(Line::from(vec![Span::raw("")])); 42 | text.push(Line::from(vec![ 43 | Span::styled( 44 | format!("Segment {}", segment_id), 45 | Style::default().add_modifier(Modifier::BOLD), 46 | ), 47 | Span::raw(format!( 48 | " [writer: {} - reader: {}]", 49 | segment.writer_group().unwrap_or("##Error##".to_string()), 50 | segment.reader_group().unwrap_or("##Error##".to_string()) 51 | )), 52 | ])); 53 | 54 | text.push(Line::from(vec![Span::raw("")])); 55 | 56 | text.push(Line::from(vec![Span::styled( 57 | " MemPool | Chunks In Use | Total | Min Free | Chunk Size | Payload Size", 58 | Style::default().add_modifier(Modifier::BOLD), 59 | )])); 60 | 61 | text.push(Line::from(vec![Span::raw( 62 | " -------------------------------------------------------------------------", 63 | )])); 64 | 65 | segment 66 | .mempools() 67 | .into_iter() 68 | .enumerate() 69 | .for_each(|(index, mempool)| { 70 | let style = if app.memory.selection == (segment_id, index) { 71 | Style::default().fg(Color::Yellow) 72 | } else { 73 | Style::default() 74 | }; 75 | 76 | let used_chunks_style = 77 | if mempool.used_chunks as f64 / mempool.total_number_of_chunks as f64 > 0.95 { 78 | Style::default().fg(Color::Red) 79 | } else { 80 | style 81 | }; 82 | let min_free_chunks_style = if mempool.min_free_chunks as f64 83 | / mempool.total_number_of_chunks as f64 84 | > 0.05 85 | { 86 | style 87 | } else { 88 | Style::default().fg(Color::Red) 89 | }; 90 | 91 | text.push(Line::from(vec![ 92 | Span::styled(format!(" {:>7} | ", index,), style), 93 | Span::styled(format!("{:>13}", mempool.used_chunks,), used_chunks_style), 94 | Span::styled( 95 | format!(" | {:>8} | ", mempool.total_number_of_chunks), 96 | style, 97 | ), 98 | Span::styled( 99 | format!("{:>8}", mempool.min_free_chunks), 100 | min_free_chunks_style, 101 | ), 102 | Span::styled( 103 | format!( 104 | " | {:>10} | {:>12}", 105 | mempool.chunk_size, mempool.payload_size 106 | ), 107 | style, 108 | ), 109 | ])); 110 | }); 111 | }); 112 | 113 | let paragraph = Paragraph::new(text) 114 | .block( 115 | Block::default() 116 | .borders(Borders::ALL) 117 | .title("Segment & MemPool Info"), 118 | ) 119 | .wrap(Wrap { trim: false }); 120 | 121 | frame.render_widget(paragraph, area); 122 | } 123 | 124 | pub fn draw_graph(frame: &mut Frame, area: Rect, app: &App) 125 | where 126 | B: Backend, 127 | { 128 | let (segment, mempool) = app.memory.selection; 129 | 130 | let chart_title = format!("Chunks In Use [Segment {} - MemPool {}]", segment, mempool); 131 | 132 | let bottom = -1.0; 133 | let left = 0.0; 134 | let top = 101.0; 135 | let right = USED_CHUNKS_HISTORY_SIZE as f64; 136 | 137 | let canvas = Canvas::default() 138 | .block( 139 | Block::default() 140 | .borders(Borders::ALL) 141 | .title(&chart_title as &str), 142 | ) 143 | .paint(|ctx| { 144 | if area.width < 4 || area.height < 4 { 145 | return; 146 | } 147 | if let Some(history) = app.memory.used_chunks_history.get(&(segment, mempool)) { 148 | let mut pos = USED_CHUNKS_HISTORY_SIZE - history.len(); 149 | let mut last = None; 150 | history.iter().for_each(|value| { 151 | if let Some(last) = last { 152 | ctx.draw(&CanvasLine { 153 | x1: pos as f64, 154 | y1: last, 155 | x2: pos as f64, 156 | y2: *value, 157 | color: Color::Yellow, 158 | }); 159 | } 160 | let pos_next = pos + 1; 161 | ctx.draw(&CanvasLine { 162 | x1: pos as f64, 163 | y1: *value, 164 | x2: pos_next as f64, 165 | y2: *value, 166 | color: Color::Yellow, 167 | }); 168 | pos = pos_next; 169 | last = Some(*value); 170 | }); 171 | } 172 | ctx.print( 173 | left, 174 | top, 175 | Span::styled("100%", Style::default().fg(Color::White)), 176 | ); 177 | ctx.print( 178 | left, 179 | bottom + (top - bottom) / 2.0, 180 | Span::styled("50%", Style::default().fg(Color::White)), 181 | ); 182 | ctx.print( 183 | left, 184 | bottom, 185 | Span::styled("0%", Style::default().fg(Color::White)), 186 | ); 187 | }) 188 | .x_bounds([left, right]) 189 | .y_bounds([bottom, top]); 190 | 191 | frame.render_widget(canvas, area); 192 | } 193 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/app.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | use iceoryx_rs::introspection::{ 4 | MemPoolIntrospection, MemPoolIntrospectionTopic, PortIntrospection, PortIntrospectionTopic, 5 | ProcessIntrospection, ProcessIntrospectionTopic, ServiceDescription, 6 | }; 7 | use iceoryx_rs::st::{Sample, SampleReceiver}; 8 | 9 | use termion::event::{Key, MouseEvent}; 10 | 11 | use std::collections::BTreeMap; 12 | use std::collections::HashMap; 13 | use std::collections::VecDeque; 14 | 15 | pub struct TabsState<'a> { 16 | pub titles: Vec<&'a str>, 17 | pub index: usize, 18 | } 19 | 20 | impl<'a> TabsState<'a> { 21 | pub fn new(titles: Vec<&'a str>) -> TabsState { 22 | TabsState { titles, index: 0 } 23 | } 24 | pub fn next(&mut self) { 25 | let maybe_next_index = self.index + 1; 26 | if maybe_next_index < self.titles.len() { 27 | self.index = maybe_next_index; 28 | } 29 | } 30 | 31 | pub fn previous(&mut self) { 32 | if self.index > 0 { 33 | self.index -= 1; 34 | } 35 | } 36 | } 37 | 38 | pub const USED_CHUNKS_HISTORY_SIZE: usize = 120; 39 | 40 | pub struct MemorySegments { 41 | sample_receiver: SampleReceiver, 42 | pub segments: Option>, 43 | pub used_chunks_history: HashMap<(u32, usize), VecDeque>, 44 | pub selection: (u32, usize), 45 | } 46 | 47 | impl MemorySegments { 48 | pub fn new() -> Self { 49 | let inactive_sub = MemPoolIntrospection::new().expect("Mempool introspection subscriber"); 50 | let (subscriber, sample_receive_token) = inactive_sub.subscribe(); 51 | 52 | Self { 53 | sample_receiver: subscriber.get_sample_receiver(sample_receive_token), 54 | segments: None, 55 | used_chunks_history: HashMap::with_capacity(USED_CHUNKS_HISTORY_SIZE), 56 | selection: (0, 0), 57 | } 58 | } 59 | 60 | pub fn update(&mut self) { 61 | if let Some(sample) = self.sample_receiver.take() { 62 | // update history 63 | sample 64 | .memory_segments() 65 | .into_iter() 66 | .for_each(|memory_segment| { 67 | memory_segment.mempools().into_iter().enumerate().for_each( 68 | |(index, mempool)| { 69 | let history = self 70 | .used_chunks_history 71 | .entry((memory_segment.segment_id(), index)) 72 | .or_insert(VecDeque::new()); 73 | 74 | if history.len() >= USED_CHUNKS_HISTORY_SIZE { 75 | history.drain(0..1); 76 | } 77 | 78 | history.push_back( 79 | mempool.used_chunks as f64 / mempool.total_number_of_chunks as f64 80 | * 100f64, 81 | ); 82 | }, 83 | ) 84 | }); 85 | 86 | self.segments = Some(sample); 87 | } 88 | } 89 | 90 | fn selection_next(&mut self) { 91 | let sample = if let Some(sample) = self.segments.as_ref() { 92 | sample 93 | } else { 94 | return; 95 | }; 96 | 97 | let mut next_segment = self.selection.0; 98 | let mut next_mempool = self.selection.1 as usize + 1; 99 | while let Some(segment) = sample 100 | .memory_segments() 101 | .into_iter() 102 | .nth(next_segment as usize) 103 | { 104 | let number_of_mempools = segment.mempools().into_iter().size_hint().0; 105 | if number_of_mempools > next_mempool { 106 | self.selection = (next_segment, next_mempool); 107 | return; 108 | } 109 | next_segment += 1; 110 | next_mempool = 0; 111 | } 112 | } 113 | 114 | fn selection_previous(&mut self) { 115 | if self.selection.1 > 0 { 116 | self.selection.1 = self.selection.1 - 1; 117 | return; 118 | } 119 | 120 | if self.selection.0 == 0 { 121 | return; 122 | } 123 | 124 | let sample = if let Some(sample) = self.segments.as_ref() { 125 | sample 126 | } else { 127 | return; 128 | }; 129 | 130 | if let Some(segment) = sample 131 | .memory_segments() 132 | .into_iter() 133 | .nth(self.selection.0 as usize - 1) 134 | { 135 | let number_of_mempools = segment.mempools().into_iter().size_hint().0; 136 | if number_of_mempools > 0 { 137 | self.selection = (self.selection.0 - 1, number_of_mempools - 1); 138 | } 139 | } 140 | } 141 | } 142 | 143 | pub struct ProcessDetails { 144 | pub pid: i32, 145 | pub publisher_ports: Vec, 146 | pub subscriber_ports: Vec, 147 | pub nodes: Vec, 148 | } 149 | 150 | pub struct ProcessList { 151 | sample_receiver: SampleReceiver, 152 | pub list: Option>, //TODO use HashMap 153 | pub map: BTreeMap, 154 | pub selection: (usize, String), 155 | } 156 | 157 | impl ProcessList { 158 | pub fn new() -> Self { 159 | let inactive_sub = ProcessIntrospection::new().expect("Process introspection subscriber"); 160 | let (subscriber, sample_receive_token) = inactive_sub.subscribe(); 161 | 162 | Self { 163 | sample_receiver: subscriber.get_sample_receiver(sample_receive_token), 164 | list: None, 165 | map: BTreeMap::new(), 166 | selection: (0, "".to_string()), 167 | } 168 | } 169 | 170 | pub fn update(&mut self) { 171 | if let Some(list) = self.sample_receiver.take() { 172 | self.map.clear(); 173 | 174 | list.processes().into_iter().for_each(|process| { 175 | if let Some(process_name) = process.name() { 176 | let _details = self.map.entry(process_name).or_insert(ProcessDetails { 177 | pid: process.pid(), 178 | publisher_ports: Vec::new(), 179 | subscriber_ports: Vec::new(), 180 | nodes: Vec::new(), 181 | }); 182 | // details.nodes.push(node); 183 | } 184 | }); 185 | 186 | // check if selection is still at the right position 187 | let found = self 188 | .map 189 | .keys() 190 | .nth(self.selection.0) 191 | .map_or(false, |key| *key == self.selection.1); 192 | 193 | if !found { 194 | if let Some((index, key)) = self 195 | .map 196 | .keys() 197 | .enumerate() 198 | .find(|(_, key)| **key == self.selection.1) 199 | { 200 | self.selection.0 = index; 201 | self.selection.1 = key.clone(); 202 | } else { 203 | self.set_selection(self.selection.0); 204 | } 205 | } 206 | } 207 | } 208 | 209 | fn set_selection(&mut self, index: usize) { 210 | // check if out of bounds 211 | let mut index = index; 212 | let service_count = self.map.len(); 213 | if index >= service_count { 214 | index = if service_count > 0 { 215 | service_count - 1 216 | } else { 217 | 0 218 | }; 219 | } 220 | 221 | if let Some(key) = self.map.keys().nth(index) { 222 | self.selection.0 = index; 223 | self.selection.1 = key.clone(); 224 | } 225 | } 226 | 227 | fn selection_next(&mut self) { 228 | self.set_selection(self.selection.0 + 1); 229 | } 230 | 231 | fn selection_previous(&mut self) { 232 | if self.selection.0 > 0 { 233 | self.set_selection(self.selection.0 - 1); 234 | } 235 | } 236 | } 237 | 238 | pub struct ServiceDetails { 239 | pub publisher_processes: Vec, 240 | pub subscriber_processes: Vec, 241 | } 242 | 243 | pub struct ServiceList { 244 | sample_receiver: SampleReceiver, 245 | pub map: BTreeMap, 246 | pub selection: (usize, ServiceDescription), 247 | } 248 | 249 | impl ServiceList { 250 | pub fn new() -> Self { 251 | let inactive_sub = PortIntrospection::new().expect("Port introspection subscriber"); 252 | let (subscriber, sample_receive_token) = inactive_sub.subscribe(); 253 | 254 | Self { 255 | sample_receiver: subscriber.get_sample_receiver(sample_receive_token), 256 | map: BTreeMap::new(), 257 | selection: (0, ServiceDescription::default()), 258 | } 259 | } 260 | 261 | pub fn update(&mut self, processes: &mut ProcessList) { 262 | if let Some(ports) = self.sample_receiver.take() { 263 | self.map.clear(); 264 | for (_, process_details) in processes.map.iter_mut() { 265 | process_details.publisher_ports.clear(); 266 | process_details.subscriber_ports.clear(); 267 | process_details.nodes.clear(); 268 | } 269 | 270 | ports.publisher_ports().into_iter().for_each(|publisher| { 271 | if let Some(service_description) = publisher.service_description() { 272 | let details = 273 | self.map 274 | .entry(service_description.clone()) 275 | .or_insert(ServiceDetails { 276 | publisher_processes: Vec::new(), 277 | subscriber_processes: Vec::new(), 278 | }); 279 | if let Some(process_name) = publisher.process_name() { 280 | if let Some(process_details) = processes.map.get_mut(&process_name).as_mut() 281 | { 282 | process_details.publisher_ports.push(service_description); 283 | } 284 | details.publisher_processes.push(process_name); 285 | } 286 | } 287 | }); 288 | 289 | ports.subscriber_ports().into_iter().for_each(|subscriber| { 290 | if let Some(service_description) = subscriber.service_description() { 291 | let details = 292 | self.map 293 | .entry(service_description.clone()) 294 | .or_insert(ServiceDetails { 295 | publisher_processes: Vec::new(), 296 | subscriber_processes: Vec::new(), 297 | }); 298 | if let Some(process_name) = subscriber.process_name() { 299 | if let Some(process_details) = processes.map.get_mut(&process_name).as_mut() 300 | { 301 | process_details.subscriber_ports.push(service_description); 302 | } 303 | details.subscriber_processes.push(process_name); 304 | } 305 | } 306 | }); 307 | 308 | // check if selection is still at the right position 309 | let found = self 310 | .map 311 | .keys() 312 | .nth(self.selection.0) 313 | .map_or(false, |key| *key == self.selection.1); 314 | 315 | if !found { 316 | if let Some((index, key)) = self 317 | .map 318 | .keys() 319 | .enumerate() 320 | .find(|(_, key)| **key == self.selection.1) 321 | { 322 | self.selection.0 = index; 323 | self.selection.1 = key.clone(); 324 | } else { 325 | self.set_selection(self.selection.0); 326 | } 327 | } 328 | } 329 | } 330 | 331 | fn set_selection(&mut self, index: usize) { 332 | // check if out of bounds 333 | let mut index = index; 334 | let service_count = self.map.len(); 335 | if index >= service_count { 336 | index = if service_count > 0 { 337 | service_count - 1 338 | } else { 339 | 0 340 | }; 341 | } 342 | 343 | if let Some(key) = self.map.keys().nth(index) { 344 | self.selection.0 = index; 345 | self.selection.1 = key.clone(); 346 | } 347 | } 348 | 349 | fn selection_next(&mut self) { 350 | self.set_selection(self.selection.0 + 1); 351 | } 352 | 353 | fn selection_previous(&mut self) { 354 | if self.selection.0 > 0 { 355 | self.set_selection(self.selection.0 - 1); 356 | } 357 | } 358 | } 359 | 360 | pub struct App<'a> { 361 | pub title: &'a str, 362 | pub should_quit: bool, 363 | pub mouse_hold_position: Option<(u16, u16)>, 364 | pub tabs: TabsState<'a>, 365 | 366 | pub memory: MemorySegments, 367 | pub processes: ProcessList, 368 | pub services: ServiceList, 369 | } 370 | 371 | impl<'a> App<'a> { 372 | pub fn new(title: &'a str) -> Self { 373 | App { 374 | title, 375 | should_quit: false, 376 | mouse_hold_position: None, 377 | tabs: TabsState::new(vec!["Overview", "Memory", "Processes", "Services"]), 378 | 379 | memory: MemorySegments::new(), 380 | processes: ProcessList::new(), 381 | services: ServiceList::new(), 382 | } 383 | } 384 | 385 | pub fn on_key(&mut self, k: Key) { 386 | match k { 387 | Key::Char('q') => { 388 | self.should_quit = true; 389 | } 390 | Key::Right => { 391 | self.tabs.next(); 392 | } 393 | Key::Left => { 394 | self.tabs.previous(); 395 | } 396 | Key::Up => match self.tabs.index { 397 | 1 => self.memory.selection_previous(), 398 | 2 => self.processes.selection_previous(), 399 | 3 => self.services.selection_previous(), 400 | _ => (), 401 | }, 402 | Key::Down => match self.tabs.index { 403 | 1 => self.memory.selection_next(), 404 | 2 => self.processes.selection_next(), 405 | 3 => self.services.selection_next(), 406 | _ => (), 407 | }, 408 | _ => {} 409 | } 410 | } 411 | 412 | pub fn on_mouse(&mut self, m: MouseEvent) { 413 | match m { 414 | MouseEvent::Press(_, x, y) => self.mouse_hold_position = Some((x, y)), 415 | MouseEvent::Hold(x, y) => self.mouse_hold_position = Some((x, y)), 416 | _ => self.mouse_hold_position = None, 417 | } 418 | } 419 | 420 | pub fn on_tick(&mut self) { 421 | self.memory.update(); 422 | self.processes.update(); 423 | self.services.update(&mut self.processes); 424 | } 425 | } 426 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.0.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "atty" 25 | version = "0.2.14" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 28 | dependencies = [ 29 | "hermit-abi", 30 | "libc", 31 | "winapi", 32 | ] 33 | 34 | [[package]] 35 | name = "bitflags" 36 | version = "1.3.2" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 39 | 40 | [[package]] 41 | name = "bitflags" 42 | version = "2.4.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 45 | 46 | [[package]] 47 | name = "byteorder" 48 | version = "1.4.3" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 51 | 52 | [[package]] 53 | name = "cassowary" 54 | version = "0.3.0" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 57 | 58 | [[package]] 59 | name = "cc" 60 | version = "1.0.83" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 63 | dependencies = [ 64 | "libc", 65 | ] 66 | 67 | [[package]] 68 | name = "clap" 69 | version = "2.34.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 72 | dependencies = [ 73 | "ansi_term", 74 | "atty", 75 | "bitflags 1.3.2", 76 | "strsim", 77 | "textwrap", 78 | "unicode-width", 79 | "vec_map", 80 | ] 81 | 82 | [[package]] 83 | name = "cpp" 84 | version = "0.5.9" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "bfa65869ef853e45c60e9828aa08cdd1398cb6e13f3911d9cb2a079b144fcd64" 87 | dependencies = [ 88 | "cpp_macros", 89 | ] 90 | 91 | [[package]] 92 | name = "cpp_build" 93 | version = "0.5.9" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "0e361fae2caf9758164b24da3eedd7f7d7451be30d90d8e7b5d2be29a2f0cf5b" 96 | dependencies = [ 97 | "cc", 98 | "cpp_common", 99 | "lazy_static", 100 | "proc-macro2", 101 | "regex", 102 | "syn 2.0.29", 103 | "unicode-xid", 104 | ] 105 | 106 | [[package]] 107 | name = "cpp_common" 108 | version = "0.5.9" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "3e1a2532e4ed4ea13031c13bc7bc0dbca4aae32df48e9d77f0d1e743179f2ea1" 111 | dependencies = [ 112 | "lazy_static", 113 | "proc-macro2", 114 | "syn 2.0.29", 115 | ] 116 | 117 | [[package]] 118 | name = "cpp_macros" 119 | version = "0.5.9" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "47ec9cc90633446f779ef481a9ce5a0077107dd5b87016440448d908625a83fd" 122 | dependencies = [ 123 | "aho-corasick", 124 | "byteorder", 125 | "cpp_common", 126 | "lazy_static", 127 | "proc-macro2", 128 | "quote", 129 | "syn 2.0.29", 130 | ] 131 | 132 | [[package]] 133 | name = "either" 134 | version = "1.9.0" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 137 | 138 | [[package]] 139 | name = "heck" 140 | version = "0.3.3" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 143 | dependencies = [ 144 | "unicode-segmentation", 145 | ] 146 | 147 | [[package]] 148 | name = "heck" 149 | version = "0.4.1" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 152 | 153 | [[package]] 154 | name = "hermit-abi" 155 | version = "0.1.19" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 158 | dependencies = [ 159 | "libc", 160 | ] 161 | 162 | [[package]] 163 | name = "iceoryx-rs" 164 | version = "0.1.0" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "0236c32fc3e283616bd78a6e4b9b5f4265bb8641245cfef4be701e98df1197ac" 167 | dependencies = [ 168 | "iceoryx-sys", 169 | "thiserror", 170 | ] 171 | 172 | [[package]] 173 | name = "iceoryx-sys" 174 | version = "0.1.0" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "982a35e19046d61aa909f46c2a7bf7d38c1549a745aedf9eca8a06cb0747a528" 177 | dependencies = [ 178 | "cpp", 179 | "cpp_build", 180 | "thiserror", 181 | ] 182 | 183 | [[package]] 184 | name = "iceray" 185 | version = "0.1.0" 186 | dependencies = [ 187 | "iceoryx-rs", 188 | "ratatui", 189 | "structopt", 190 | "termion", 191 | ] 192 | 193 | [[package]] 194 | name = "indoc" 195 | version = "2.0.3" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4" 198 | 199 | [[package]] 200 | name = "itertools" 201 | version = "0.11.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" 204 | dependencies = [ 205 | "either", 206 | ] 207 | 208 | [[package]] 209 | name = "lazy_static" 210 | version = "1.4.0" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 213 | 214 | [[package]] 215 | name = "libc" 216 | version = "0.2.147" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 219 | 220 | [[package]] 221 | name = "memchr" 222 | version = "2.5.0" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 225 | 226 | [[package]] 227 | name = "numtoa" 228 | version = "0.1.0" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" 231 | 232 | [[package]] 233 | name = "paste" 234 | version = "1.0.14" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" 237 | 238 | [[package]] 239 | name = "proc-macro-error" 240 | version = "1.0.4" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 243 | dependencies = [ 244 | "proc-macro-error-attr", 245 | "proc-macro2", 246 | "quote", 247 | "syn 1.0.109", 248 | "version_check", 249 | ] 250 | 251 | [[package]] 252 | name = "proc-macro-error-attr" 253 | version = "1.0.4" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 256 | dependencies = [ 257 | "proc-macro2", 258 | "quote", 259 | "version_check", 260 | ] 261 | 262 | [[package]] 263 | name = "proc-macro2" 264 | version = "1.0.66" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 267 | dependencies = [ 268 | "unicode-ident", 269 | ] 270 | 271 | [[package]] 272 | name = "quote" 273 | version = "1.0.33" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 276 | dependencies = [ 277 | "proc-macro2", 278 | ] 279 | 280 | [[package]] 281 | name = "ratatui" 282 | version = "0.23.0" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "2e2e4cd95294a85c3b4446e63ef054eea43e0205b1fd60120c16b74ff7ff96ad" 285 | dependencies = [ 286 | "bitflags 2.4.0", 287 | "cassowary", 288 | "indoc", 289 | "itertools", 290 | "paste", 291 | "strum", 292 | "termion", 293 | "unicode-segmentation", 294 | "unicode-width", 295 | ] 296 | 297 | [[package]] 298 | name = "redox_syscall" 299 | version = "0.2.16" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 302 | dependencies = [ 303 | "bitflags 1.3.2", 304 | ] 305 | 306 | [[package]] 307 | name = "redox_termios" 308 | version = "0.1.2" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" 311 | dependencies = [ 312 | "redox_syscall", 313 | ] 314 | 315 | [[package]] 316 | name = "regex" 317 | version = "1.9.4" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" 320 | dependencies = [ 321 | "aho-corasick", 322 | "memchr", 323 | "regex-automata", 324 | "regex-syntax", 325 | ] 326 | 327 | [[package]] 328 | name = "regex-automata" 329 | version = "0.3.7" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" 332 | dependencies = [ 333 | "aho-corasick", 334 | "memchr", 335 | "regex-syntax", 336 | ] 337 | 338 | [[package]] 339 | name = "regex-syntax" 340 | version = "0.7.5" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" 343 | 344 | [[package]] 345 | name = "rustversion" 346 | version = "1.0.14" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 349 | 350 | [[package]] 351 | name = "strsim" 352 | version = "0.8.0" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 355 | 356 | [[package]] 357 | name = "structopt" 358 | version = "0.3.26" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 361 | dependencies = [ 362 | "clap", 363 | "lazy_static", 364 | "structopt-derive", 365 | ] 366 | 367 | [[package]] 368 | name = "structopt-derive" 369 | version = "0.4.18" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 372 | dependencies = [ 373 | "heck 0.3.3", 374 | "proc-macro-error", 375 | "proc-macro2", 376 | "quote", 377 | "syn 1.0.109", 378 | ] 379 | 380 | [[package]] 381 | name = "strum" 382 | version = "0.25.0" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" 385 | dependencies = [ 386 | "strum_macros", 387 | ] 388 | 389 | [[package]] 390 | name = "strum_macros" 391 | version = "0.25.2" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" 394 | dependencies = [ 395 | "heck 0.4.1", 396 | "proc-macro2", 397 | "quote", 398 | "rustversion", 399 | "syn 2.0.29", 400 | ] 401 | 402 | [[package]] 403 | name = "syn" 404 | version = "1.0.109" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 407 | dependencies = [ 408 | "proc-macro2", 409 | "quote", 410 | "unicode-ident", 411 | ] 412 | 413 | [[package]] 414 | name = "syn" 415 | version = "2.0.29" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" 418 | dependencies = [ 419 | "proc-macro2", 420 | "quote", 421 | "unicode-ident", 422 | ] 423 | 424 | [[package]] 425 | name = "termion" 426 | version = "2.0.1" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "659c1f379f3408c7e5e84c7d0da6d93404e3800b6b9d063ba24436419302ec90" 429 | dependencies = [ 430 | "libc", 431 | "numtoa", 432 | "redox_syscall", 433 | "redox_termios", 434 | ] 435 | 436 | [[package]] 437 | name = "textwrap" 438 | version = "0.11.0" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 441 | dependencies = [ 442 | "unicode-width", 443 | ] 444 | 445 | [[package]] 446 | name = "thiserror" 447 | version = "1.0.47" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" 450 | dependencies = [ 451 | "thiserror-impl", 452 | ] 453 | 454 | [[package]] 455 | name = "thiserror-impl" 456 | version = "1.0.47" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" 459 | dependencies = [ 460 | "proc-macro2", 461 | "quote", 462 | "syn 2.0.29", 463 | ] 464 | 465 | [[package]] 466 | name = "unicode-ident" 467 | version = "1.0.11" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 470 | 471 | [[package]] 472 | name = "unicode-segmentation" 473 | version = "1.10.1" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 476 | 477 | [[package]] 478 | name = "unicode-width" 479 | version = "0.1.10" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 482 | 483 | [[package]] 484 | name = "unicode-xid" 485 | version = "0.2.4" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 488 | 489 | [[package]] 490 | name = "vec_map" 491 | version = "0.8.2" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 494 | 495 | [[package]] 496 | name = "version_check" 497 | version = "0.9.4" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 500 | 501 | [[package]] 502 | name = "winapi" 503 | version = "0.3.9" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 506 | dependencies = [ 507 | "winapi-i686-pc-windows-gnu", 508 | "winapi-x86_64-pc-windows-gnu", 509 | ] 510 | 511 | [[package]] 512 | name = "winapi-i686-pc-windows-gnu" 513 | version = "0.4.0" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 516 | 517 | [[package]] 518 | name = "winapi-x86_64-pc-windows-gnu" 519 | version = "0.4.0" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 522 | --------------------------------------------------------------------------------