diff options
author | Dennis Kobert <dennis@kobert.dev> | 2025-04-02 16:47:34 +0200 |
---|---|---|
committer | Dennis Kobert <dennis@kobert.dev> | 2025-04-02 16:49:43 +0200 |
commit | a56c2e8ab39d7247d2b4c8959c306ffa07520d01 (patch) | |
tree | 7564e8190174276e0ae78959e9512c0ee82055f3 | |
parent | eb32f2c998e1efc55edcb78899df7967bd531cc3 (diff) |
Implement etop
-rw-r--r-- | Cargo.lock | 9 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | energy-monitor/Cargo.toml | 10 | ||||
-rw-r--r-- | energy-monitor/src/energy.rs | 71 | ||||
-rw-r--r-- | energy-monitor/src/main.rs | 214 | ||||
-rw-r--r-- | energy-monitor/src/process.rs | 97 | ||||
-rw-r--r-- | energy-monitor/src/ui.rs | 391 | ||||
-rw-r--r-- | src/energy.rs | 6 | ||||
-rw-r--r-- | src/socket.rs | 39 |
9 files changed, 823 insertions, 17 deletions
@@ -1664,6 +1664,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067" [[package]] +name = "energy-monitor" +version = "0.1.0" +dependencies = [ + "crossterm", + "procfs", + "ratatui", +] + +[[package]] name = "enum-as-inner" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1,3 +1,6 @@ +[workspace] +members = [ "energy-monitor"] + [package] name = "power_sched" version = "0.1.0" diff --git a/energy-monitor/Cargo.toml b/energy-monitor/Cargo.toml new file mode 100644 index 0000000..f67cfe5 --- /dev/null +++ b/energy-monitor/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "energy-monitor" +version = "0.1.0" +edition = "2021" + +[dependencies] +ratatui = "0.29.0" +crossterm = "0.28.0" +procfs = { version = "0.17.0", default-features = false } + diff --git a/energy-monitor/src/energy.rs b/energy-monitor/src/energy.rs new file mode 100644 index 0000000..55b6e53 --- /dev/null +++ b/energy-monitor/src/energy.rs @@ -0,0 +1,71 @@ +use std::{ + collections::HashMap, + io::{self, BufRead, BufReader, Write}, +}; + +pub type Pid = i32; +use crate::MAX_HISTORY_SIZE; + +#[derive(Debug, Clone)] +pub struct ProcessInfo { + pub pid: Pid, + pub name: String, + pub energy: f64, + pub tree_energy: f64, + pub history: Vec<(f64, f64)>, // (timestamp, energy) +} + +impl ProcessInfo { + pub fn new(pid: Pid, name: String) -> Self { + Self { + pid, + name, + energy: 0., + tree_energy: 0., + history: Vec::new(), + } + } + + pub fn add_history_point(&mut self, timestamp: f64, energy: f64) { + self.history.push((timestamp, energy)); + + // Keep history bounded to a reasonable size + if self.history.len() > MAX_HISTORY_SIZE { + // Keep a minute of history at 1sec interval + self.history.remove(0); + } + } +} + +pub fn connect_to_service(path: &str) -> io::Result<std::os::unix::net::UnixStream> { + std::os::unix::net::UnixStream::connect(path) +} + +pub fn request_all_processes( + stream: &mut std::os::unix::net::UnixStream, +) -> io::Result<HashMap<Pid, (f64, f64)>> { + // Write -1 to get all processes + stream.write_all(b"-1\n")?; + + let mut reader = BufReader::new(stream); + let mut data = Vec::new(); + let mut result = HashMap::new(); + + reader.read_until(b'#', &mut data)?; + + // Parse each line of the format "pid,energy,tree_energy" + for process_line in std::str::from_utf8(data.as_slice()).unwrap().lines() { + let parts: Vec<&str> = process_line.split(',').collect(); + if parts.len() >= 3 { + if let (Ok(pid), Ok(energy), Ok(tree_energy)) = ( + parts[0].parse::<Pid>(), + parts[1].parse::<f64>(), + parts[2].parse::<f64>(), + ) { + result.insert(pid, (energy, tree_energy)); + } + } + } + + Ok(result) +} diff --git a/energy-monitor/src/main.rs b/energy-monitor/src/main.rs new file mode 100644 index 0000000..b07bfc2 --- /dev/null +++ b/energy-monitor/src/main.rs @@ -0,0 +1,214 @@ +use std::{ + collections::{HashMap, HashSet}, + error::Error, + io, + time::{Duration, Instant}, +}; + +use crossterm::{ + event::{ + self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, KeyModifiers, + }, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use ratatui::{backend::CrosstermBackend, style::Color, widgets::ListState, Terminal}; + +mod energy; +mod process; +mod ui; + +use energy::Pid; +use process::ProcessData; + +const SOCKET_PATH: &str = "/tmp/pm-sched"; +const UPDATE_INTERVAL: Duration = Duration::from_secs(1); +const MAX_HISTORY_SIZE: usize = 60; // Keep a minute's worth of data (at 1 sec update interval) + +enum AppTab { + ProcessList, + DetailedView, +} + +struct App { + process_data: ProcessData, + selected_tab: AppTab, + table_state: ListState, + selected_processes: HashSet<Pid>, + process_colors: HashMap<Pid, Color>, + available_colors: Vec<Color>, + color_index: usize, + last_update: Instant, + should_quit: bool, +} + +impl App { + fn new() -> Self { + // Set up available colors for the graphs + let available_colors = vec![ + Color::Red, + Color::Green, + Color::Yellow, + Color::Blue, + Color::Magenta, + Color::Cyan, + Color::LightRed, + Color::LightGreen, + Color::LightYellow, + Color::LightBlue, + Color::LightMagenta, + Color::LightCyan, + ]; + + let mut app = Self { + process_data: ProcessData::new(), + selected_tab: AppTab::ProcessList, + table_state: ListState::default(), + selected_processes: HashSet::new(), + process_colors: HashMap::new(), + available_colors, + color_index: 0, + last_update: Instant::now(), + should_quit: false, + }; + + // Start with the first process selected + app.table_state.select(Some(0)); + + app + } + + fn on_tick(&mut self) { + if self.last_update.elapsed() >= UPDATE_INTERVAL { + self.process_data.update(); + self.last_update = Instant::now(); + + // Make sure our selection is still valid after the update + if let Some(i) = self.table_state.selected() { + if i >= self.process_data.processes.len() { + self.table_state + .select(Some(self.process_data.processes.len().saturating_sub(1))); + } + } + } + } + + fn on_up(&mut self) { + let i = match self.table_state.selected() { + Some(i) => { + if i == 0 { + self.process_data.processes.len() - 1 + } else { + i - 1 + } + } + None => 0, + }; + self.table_state.select(Some(i)); + } + + fn on_down(&mut self) { + let i = match self.table_state.selected() { + Some(i) => { + if i >= self.process_data.processes.len() - 1 { + 0 + } else { + i + 1 + } + } + None => 0, + }; + self.table_state.select(Some(i)); + } + + fn on_toggle_select(&mut self) { + if let Some(i) = self.table_state.selected() { + if i < self.process_data.processes.len() { + let pid = self.process_data.processes[i]; + + if self.selected_processes.contains(&pid) { + self.selected_processes.remove(&pid); + self.process_colors.remove(&pid); + } else { + self.selected_processes.insert(pid); + self.assign_color(pid); + } + } + } + } + + fn assign_color(&mut self, pid: Pid) { + if let std::collections::hash_map::Entry::Vacant(e) = self.process_colors.entry(pid) { + let color = self.available_colors[self.color_index]; + e.insert(color); + self.color_index = (self.color_index + 1) % self.available_colors.len(); + } + } + + fn on_tab(&mut self) { + self.selected_tab = match self.selected_tab { + AppTab::ProcessList => AppTab::DetailedView, + AppTab::DetailedView => AppTab::ProcessList, + }; + } +} + +fn main() -> Result<(), Box<dyn Error>> { + // Set up terminal + enable_raw_mode()?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + // Create app and run event loop + let mut app = App::new(); + + app.process_data.update(); + + // Run the main application loop + let tick_rate = Duration::from_millis(100); + let mut last_tick = Instant::now(); + + while !app.should_quit { + terminal.draw(|f| ui::draw(f, &mut app))?; + + let timeout = tick_rate + .checked_sub(last_tick.elapsed()) + .unwrap_or_else(|| Duration::from_secs(0)); + + if crossterm::event::poll(timeout)? { + if let Event::Key(key) = event::read()? { + if key.kind == KeyEventKind::Press { + match key.code { + KeyCode::Char('q') => app.should_quit = true, + KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { + app.should_quit = true; + } + KeyCode::Up | KeyCode::Char('k') => app.on_up(), + KeyCode::Down | KeyCode::Char('j') => app.on_down(), + KeyCode::Char(' ') => app.on_toggle_select(), + KeyCode::Tab => app.on_tab(), + _ => {} + } + } + } + } + + if last_tick.elapsed() >= tick_rate { + app.on_tick(); + last_tick = Instant::now(); + } + } + + // Restore terminal + disable_raw_mode()?; + execute!( + terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + )?; + terminal.show_cursor()?; + + Ok(()) +} diff --git a/energy-monitor/src/process.rs b/energy-monitor/src/process.rs new file mode 100644 index 0000000..d35641e --- /dev/null +++ b/energy-monitor/src/process.rs @@ -0,0 +1,97 @@ +use std::{collections::HashMap, os::unix::net::UnixStream, time::Instant}; + +use procfs::process::Process; + +use crate::energy::{connect_to_service, request_all_processes, Pid, ProcessInfo}; + +use crate::SOCKET_PATH; + +pub struct ProcessData { + pub processes: Vec<Pid>, + pub total_energy_history: Vec<(f64, f64)>, + pub started_at: Instant, + pub socket: UnixStream, + pub process_info: HashMap<Pid, ProcessInfo>, +} + +impl ProcessData { + pub fn new() -> Self { + Self { + processes: Vec::new(), + total_energy_history: Vec::new(), + started_at: Instant::now(), + process_info: HashMap::new(), + socket: connect_to_service(SOCKET_PATH).expect("Failed to connect to socket"), + } + } + + pub fn update(&mut self) { + self.fetch_processes(); + } + + pub fn fetch_processes(&mut self) { + let energy_data = match request_all_processes(&mut self.socket) { + Ok(data) => data, + Err(_) => return, // Can't get energy data + }; + + // Use process data with procfs to get process names + let mut updated_processes = Vec::new(); + let mut total_energy = 0.0; + + let elapsed = self.started_at.elapsed().as_secs_f64(); + for (pid, (energy, tree_energy)) in energy_data { + let process_info = self.process_info.entry(pid).or_insert({ + let name = get_process_name(pid).unwrap_or_else(|| format!("Process {}", pid)); + + ProcessInfo::new(pid, name) + }); + + let process_delta = energy - process_info.energy; + let delta = tree_energy - process_info.tree_energy; + process_info.energy = energy; + process_info.tree_energy = tree_energy; + process_info.add_history_point(elapsed, delta); + + updated_processes.push(pid); + total_energy += process_delta; + } + + // Sort by energy consumption (descending) + updated_processes.sort_by(|a, b| { + self.process_info[b] + .energy + .partial_cmp(&self.process_info[a].energy) + .unwrap() + }); + + // Update our local process list + self.processes = updated_processes; + + // TODO: garbage collect process_info + + // Add total energy to history + let elapsed = self.started_at.elapsed().as_secs_f64(); + self.add_total_energy_point(elapsed, total_energy); + } + + fn add_total_energy_point(&mut self, timestamp: f64, energy: f64) { + self.total_energy_history.push((timestamp, energy)); + + // Keep history bounded + if self.total_energy_history.len() > 60 { + self.total_energy_history.remove(0); + } + } +} + +// Helper function to get process name using procfs +fn get_process_name(pid: Pid) -> Option<String> { + match Process::new(pid) { + Ok(process) => match process.stat() { + Ok(stat) => Some(stat.comm), + Err(_) => None, + }, + Err(_) => None, + } +} diff --git a/energy-monitor/src/ui.rs b/energy-monitor/src/ui.rs new file mode 100644 index 0000000..276a3df --- /dev/null +++ b/energy-monitor/src/ui.rs @@ -0,0 +1,391 @@ +use ratatui::{ + layout::{Constraint, Direction, Layout, Rect}, + style::{Color, Modifier, Style}, + symbols, + text::{Line, Span}, + widgets::{Axis, Block, Borders, Chart, Dataset, List, ListItem, Paragraph, Tabs}, + Frame, +}; + +use crate::{App, AppTab}; + +pub fn draw(f: &mut Frame, app: &mut App) { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(3), + Constraint::Min(0), + Constraint::Length(3), + ]) + .split(f.area()); + + // Draw header with tabs + let tab_items = vec![ + Line::from(Span::styled( + "Process List", + Style::default().fg(Color::White), + )), + Line::from(Span::styled( + "Detailed View", + Style::default().fg(Color::White), + )), + ]; + + let tabs = Tabs::new(tab_items) + .block( + Block::default() + .borders(Borders::ALL) + .title("Energy Monitor"), + ) + .select(match app.selected_tab { + AppTab::ProcessList => 0, + AppTab::DetailedView => 1, + }) + .style(Style::default().fg(Color::White)) + .highlight_style(Style::default().fg(Color::Yellow)); + f.render_widget(tabs, chunks[0]); + + // Draw content based on selected tab + match app.selected_tab { + AppTab::ProcessList => draw_process_list(f, app, chunks[1]), + AppTab::DetailedView => draw_detailed_view(f, app, chunks[1]), + } + + // Draw footer with instructions + let footer_spans = vec![ + Span::styled("q", Style::default().fg(Color::Yellow)), + Span::raw(" Quit | "), + Span::styled("↑/k ↓/j", Style::default().fg(Color::Yellow)), + Span::raw(" Navigate | "), + Span::styled("Space", Style::default().fg(Color::Yellow)), + Span::raw(" Select | "), + Span::styled("Tab", Style::default().fg(Color::Yellow)), + Span::raw(" Change Tab"), + ]; + + let footer = + Paragraph::new(Line::from(footer_spans)).block(Block::default().borders(Borders::ALL)); + f.render_widget(footer, chunks[2]); +} + +fn draw_process_list(f: &mut Frame, app: &mut App, area: Rect) { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(60), Constraint::Percentage(40)]) + .split(area); + + // Draw process list + let processes: Vec<ListItem> = app + .process_data + .processes + .iter() + .map(|pid| { + let p = &app.process_data.process_info[pid]; + let is_selected = app.selected_processes.contains(&p.pid); + let style = if is_selected { + Style::default().fg(app + .process_colors + .get(&p.pid) + .copied() + .unwrap_or(Color::White)) + } else { + Style::default() + }; + + let content = vec![ + Span::styled( + format!("{}{:>5} ", if is_selected { "* " } else { " " }, p.pid), + style, + ), + Span::styled( + format!("{:20}", p.name.chars().take(20).collect::<String>()), + style, + ), + Span::styled(format!(" {:8.2}J", p.energy), style), + Span::styled(format!(" {:8.2}J", p.tree_energy), style), + Span::styled( + format!(" {:8.2}W", p.history.last().map(|(_, x)| x).unwrap_or(&0.)), + style, + ), + ]; + + let line = Line::from(content); + ListItem::new(line) + }) + .collect(); + + let processes_list = List::new(processes) + .block( + Block::default() + .borders(Borders::ALL) + .title("Processes by Energy"), + ) + .highlight_style(Style::default().add_modifier(Modifier::REVERSED)) + .highlight_symbol("> "); + f.render_stateful_widget(processes_list, chunks[0], &mut app.table_state); + + // Draw energy graph + draw_energy_graph(f, app, chunks[1]); +} + +fn draw_detailed_view(f: &mut Frame, app: &mut App, area: Rect) { + let selected_pid = app + .table_state + .selected() + .and_then(|i| app.process_data.processes.get(i)); + + if let Some(pid) = selected_pid { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(5), Constraint::Min(0)]) + .split(area); + + // Draw process details + if let Some(process) = app.process_data.process_info.get(pid) { + let details = vec![ + Line::from(vec![ + Span::raw("PID: "), + Span::styled( + format!("{}", process.pid), + Style::default().fg(Color::Yellow), + ), + ]), + Line::from(vec![ + Span::raw("Name: "), + Span::styled(&process.name, Style::default().fg(Color::Yellow)), + ]), + Line::from(vec![ + Span::raw("Energy: "), + Span::styled( + format!("{:.2}J", process.energy), + Style::default().fg(Color::Yellow), + ), + ]), + Line::from(vec![ + Span::raw("Tree Energy: "), + Span::styled( + format!("{:.2}J", process.tree_energy), + Style::default().fg(Color::Yellow), + ), + ]), + ]; + + let details_widget = Paragraph::new(details).block( + Block::default() + .borders(Borders::ALL) + .title("Process Details"), + ); + f.render_widget(details_widget, chunks[0]); + + // Draw process energy history graph + let start_time = if let Some((first_time, _)) = process.history.first() { + *first_time + } else { + 0.0 + }; + + let end_time = if let Some((last_time, _)) = process.history.last() { + *last_time + } else { + start_time + 60.0 + }; + + let dataset = Dataset::default() + .name(process.name.clone()) + .marker(symbols::Marker::Braille) + .style( + Style::default().fg(app + .process_colors + .get(&pid) + .copied() + .unwrap_or(Color::Yellow)), + ) + .data(&process.history); + + let chart = Chart::new(vec![dataset]) + .block( + Block::default() + .borders(Borders::ALL) + .title("Energy Usage History"), + ) + .x_axis( + Axis::default() + .title("Time (s)") + .style(Style::default().fg(Color::Gray)) + .bounds([start_time, end_time]) + .labels(vec![ + Span::styled( + format!("{:.0}", start_time), + Style::default().fg(Color::White), + ), + Span::styled( + format!("{:.0}", (start_time + end_time) / 2.0), + Style::default().fg(Color::White), + ), + Span::styled( + format!("{:.0}", end_time), + Style::default().fg(Color::White), + ), + ]), + ) + .y_axis( + Axis::default() + .title("Energy (J)") + .style(Style::default().fg(Color::Gray)) + .bounds([ + 0.0, + process + .history + .iter() + .map(|(_, e)| *e) + .fold(0.0f64, f64::max) + * 1.2, + ]) + .labels(vec![ + Span::styled("0", Style::default().fg(Color::White)), + Span::styled( + format!( + "{:.2}", + process + .history + .iter() + .map(|(_, e)| *e) + .fold(0.0f64, f64::max) + / 2.0 + ), + Style::default().fg(Color::White), + ), + Span::styled( + format!( + "{:.2}", + process + .history + .iter() + .map(|(_, e)| *e) + .fold(0.0f64, f64::max) + ), + Style::default().fg(Color::White), + ), + ]), + ); + + f.render_widget(chart, chunks[1]); + } + } else { + let message = Paragraph::new("No process selected") + .block(Block::default().borders(Borders::ALL)) + .alignment(ratatui::layout::Alignment::Center); + f.render_widget(message, area); + } +} + +fn draw_energy_graph(f: &mut Frame, app: &mut App, area: Rect) { + // Create datasets for each selected process and the total + let mut datasets = Vec::new(); + + // Add total system energy dataset + if !app.process_data.total_energy_history.is_empty() { + datasets.push( + Dataset::default() + .name("Total System Energy") + .marker(symbols::Marker::Braille) + .style(Style::default().fg(Color::White)) + .data(&app.process_data.total_energy_history), + ); + } + + // Add selected processes datasets + for pid in &app.selected_processes { + if let Some(process) = app.process_data.process_info.get(pid) { + if !process.history.is_empty() { + datasets.push( + Dataset::default() + .name(process.name.clone()) + .marker(symbols::Marker::Braille) + .style( + Style::default().fg(app + .process_colors + .get(pid) + .copied() + .unwrap_or(Color::White)), + ) + .data(&process.history), + ); + } + } + } + + // Get time bounds + let start_time = app + .process_data + .total_energy_history + .first() + .map(|(t, _)| *t) + .unwrap_or(0.0); + + let end_time = app + .process_data + .total_energy_history + .last() + .map(|(t, _)| *t) + .unwrap_or(start_time + 60.0); + + let max_total = app + .process_data + .total_energy_history + .iter() + .map(|(_, e)| *e) + .fold(0.0f64, f64::max); + + let max_processes = app + .selected_processes + .iter() + .filter_map(|pid| app.process_data.process_info.get(pid)) + .flat_map(|process| process.history.iter().map(|(_, e)| *e)) + .fold(0.0f64, f64::max); + + let max_energy = f64::max(max_total, max_processes); + + // Create chart widget + let chart = Chart::new(datasets) + .block(Block::default().borders(Borders::ALL).title("Energy Usage")) + .x_axis( + Axis::default() + .title("Time (s)") + .style(Style::default().fg(Color::Gray)) + .bounds([start_time, end_time]) + .labels(vec![ + Span::styled( + format!("{:.0}", start_time), + Style::default().fg(Color::White), + ), + Span::styled( + format!("{:.0}", (start_time + end_time) / 2.0), + Style::default().fg(Color::White), + ), + Span::styled( + format!("{:.0}", end_time), + Style::default().fg(Color::White), + ), + ]), + ) + .y_axis( + Axis::default() + .title("Power (W)") + .style(Style::default().fg(Color::Gray)) + .bounds([0.0, max_energy * 1.2]) + .labels(vec![ + Span::styled("0", Style::default().fg(Color::White)), + Span::styled( + format!("{:.2}", max_energy / 2.0), + Style::default().fg(Color::White), + ), + Span::styled( + format!("{:.2}", max_energy), + Style::default().fg(Color::White), + ), + ]), + ); + + f.render_widget(chart, area); +} diff --git a/src/energy.rs b/src/energy.rs index 9c90ab9..3f6931d 100644 --- a/src/energy.rs +++ b/src/energy.rs @@ -19,7 +19,7 @@ pub use budget::BudgetPolicy; pub use trackers::{KernelDriver, PerfEstimator}; const IDLE_CONSUMPTION_W: f64 = 7.; -const UPDATE_INTERVAL: Duration = Duration::from_millis(3); +const UPDATE_INTERVAL: Duration = Duration::from_millis(10); pub enum Request { NewTask(Pid, Arc<TaskInfo>), @@ -122,6 +122,7 @@ pub struct EnergyService { } impl EnergyService { + #[allow(clippy::too_many_arguments)] pub fn new( estimator: Box<dyn Estimator>, budget_policy: Box<dyn BudgetPolicy>, @@ -244,7 +245,7 @@ impl EnergyService { .unwrap_or(0.); for pid in &self.active_processes { let mut process_info = self.process_info.write().unwrap(); - if let Some(info) = process_info.get_mut(&pid) { + if let Some(info) = process_info.get_mut(pid) { if info .task_info .read_time_since_last_schedule() @@ -255,6 +256,7 @@ impl EnergyService { } if let Some(energy) = self.estimator.read_consumption(*pid as u64) { info.energy += energy * self.bias; + info.tree_energy += energy * self.bias; self.estimator.update_information( *pid as u64, info.task_info.read_cpu(), diff --git a/src/socket.rs b/src/socket.rs index 316c662..e2ecdbf 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -35,21 +35,9 @@ impl LoggingSocketService { break; } if let Ok(pid) = line.trim().parse::<Pid>() { - if let Some(info) = self.process_info.read().unwrap().get(&pid).clone() { - socket - .write_all( - format!( - "pid: {pid} process: {}J process tree: {}J\n", - info.energy, info.tree_energy - ) - .as_bytes(), - ) - .unwrap(); - } else { - socket - .write_all(format!("Unknown pid: {pid}\n").as_bytes()) - .unwrap(); - } + socket + .write_all(self.handle_request(pid).as_bytes()) + .unwrap(); } else { socket .write_all( @@ -63,6 +51,27 @@ impl LoggingSocketService { } }); } + + fn handle_request(&self, pid: i32) -> String { + let mut output = String::new(); + use std::fmt::Write; + if pid == -1 { + for (pid, info) in self.process_info.read().unwrap().iter() { + writeln!(&mut output, "{pid},{},{}", info.energy, info.tree_energy).unwrap(); + } + writeln!(&mut output, "#",).unwrap(); + return output; + } + + if let Some(info) = self.process_info.read().unwrap().get(&pid) { + format!( + "pid: {pid} process: {}J process tree: {}J\n", + info.energy, info.tree_energy + ) + } else { + format!("Unknown pid: {pid}\n") + } + } } pub fn start_logging_socket_service( |