From 969fb6a17d709d0071d52df9e853158b77ffe255 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Mon, 23 Feb 2026 18:58:03 +0100 Subject: [PATCH] refactor: implement background monitoring thread and fix sysinfo API usage --- src-tauri/src/commands.rs | 61 ---------- src-tauri/src/commands/mod.rs | 0 src-tauri/src/main.rs | 85 +++---------- src-tauri/src/models.rs | 112 ++++------------- src-tauri/src/monitor.rs | 159 ++++++++++++++++++++++++ src-tauri/src/profiler.rs | 222 ---------------------------------- 6 files changed, 201 insertions(+), 438 deletions(-) delete mode 100644 src-tauri/src/commands.rs create mode 100644 src-tauri/src/commands/mod.rs create mode 100644 src-tauri/src/monitor.rs delete mode 100644 src-tauri/src/profiler.rs diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs deleted file mode 100644 index d28c599..0000000 --- a/src-tauri/src/commands.rs +++ /dev/null @@ -1,61 +0,0 @@ -use tauri::State; -use crate::models::*; -use crate::profiler::*; -use chrono::Utc; -use std::process::Command as StdCommand; - -#[tauri::command] -pub fn get_system_stats(state: State, minimal: bool) -> SystemStats { - let mut sys = state.sys.lock().unwrap(); - let mut profiling = state.profiling.lock().unwrap(); - let mut pss_cache = state.pss_cache.lock().unwrap(); - let self_pid = std::process::id(); - let syspulse_pids = get_syspulse_pids(self_pid, &sys); - let snapshot = collect_snapshot(&mut sys, &syspulse_pids, &mut pss_cache, profiling.is_active); - if profiling.is_active { profiling.snapshots.push(snapshot.clone()); } - let recording_duration = profiling.start_time.map(|s| (Utc::now() - s).num_seconds() as u64).unwrap_or(0); - let display_processes = if minimal && !profiling.is_active { Vec::new() } else { - let mut p = snapshot.processes.clone(); - p.sort_by(|a, b| b.cpu_usage.partial_cmp(&a.cpu_usage).unwrap_or(std::cmp::Ordering::Equal)); - p.truncate(50); - p - }; - SystemStats { cpu_usage: snapshot.cpu_usage, total_memory: sys.total_memory(), used_memory: sys.used_memory(), processes: display_processes, is_recording: profiling.is_active, recording_duration } -} - -#[tauri::command] -pub fn get_initial_report(state: State) -> Option { state.initial_report.lock().unwrap().clone() } - -#[tauri::command] -pub fn save_report(report: Report) -> Result { - let json = serde_json::to_string_pretty(&report).map_err(|e| e.to_string())?; - let path = format!("syspulse_report_{}.json", Utc::now().format("%Y%m%d_%H%M%S")); - std::fs::write(&path, json).map_err(|e| e.to_string())?; - Ok(path) -} - -#[tauri::command] -pub fn start_profiling(state: State) { - let mut profiling = state.profiling.lock().unwrap(); - profiling.is_active = true; profiling.mode = ProfilingMode::Global; profiling.target_pid = None; profiling.start_time = Some(Utc::now()); profiling.snapshots.clear(); -} - -#[tauri::command] -pub fn start_targeted_profiling(state: State, pid: u32) { - let mut profiling = state.profiling.lock().unwrap(); - profiling.is_active = true; profiling.mode = ProfilingMode::Targeted; profiling.target_pid = Some(pid); profiling.start_time = Some(Utc::now()); profiling.snapshots.clear(); -} - -#[tauri::command] -pub fn stop_profiling(state: State) -> Report { - let mut profiling = state.profiling.lock().unwrap(); - profiling.is_active = false; - let snapshots: Vec = profiling.snapshots.drain(..).collect(); - generate_report(profiling.start_time.unwrap_or(Utc::now()), snapshots, profiling.mode, profiling.target_pid) -} - -#[tauri::command] -pub fn run_as_admin(command: String) -> Result { - let output = StdCommand::new("pkexec").arg("sh").arg("-c").arg(&command).output().map_err(|e| e.to_string())?; - if output.status.success() { Ok(String::from_utf8_lossy(&output.stdout).to_string()) } else { Err(String::from_utf8_lossy(&output.stderr).to_string()) } -} diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index bdeb950..1cdb3d8 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,85 +4,34 @@ )] mod models; +mod monitor; +mod commands; // We'll keep this but gut it mod cli; -mod profiler; -mod commands; -use sysinfo::System; +use tauri::{State, Manager}; use std::sync::Mutex; -use chrono::Utc; -use std::fs; -use clap::Parser; -use std::path::PathBuf; -use std::time::Duration; -use std::collections::HashMap; +use crate::monitor::Monitor; +use crate::models::GlobalStats; -use crate::models::*; -use crate::cli::Cli; -use crate::profiler::*; -use crate::commands::*; +struct AppState { + monitor: Monitor, +} + +#[tauri::command] +fn get_latest_stats(state: State) -> GlobalStats { + state.monitor.get_latest() +} fn main() { - let cli = Cli::parse(); - let mut initial_report: Option = None; - - if let Some(file_path) = cli.file { - if let Ok(content) = fs::read_to_string(file_path) { - if let Ok(report) = serde_json::from_str(&content) { - initial_report = Some(report); - } - } - } - - if cli.headless { - println!("⚡ SysPulse: Starting headless profiling for {}s (interval: {}ms)...", cli.duration, cli.interval); - let mut sys = System::new_all(); - let mut pss_cache = HashMap::new(); - let start_time = Utc::now(); - let mut snapshots = Vec::new(); - let self_pid = std::process::id(); - - for i in 0..(cli.duration * 1000 / cli.interval) { - let syspulse_pids = get_syspulse_pids(self_pid, &sys); - snapshots.push(collect_snapshot(&mut sys, &syspulse_pids, &mut pss_cache, true)); - std::thread::sleep(Duration::from_millis(cli.interval)); - if (i + 1) % (1000 / cli.interval) == 0 { - println!(" Progress: {}/{}s", (i + 1) * cli.interval / 1000, cli.duration); - } - } - let report = generate_report(start_time, snapshots, ProfilingMode::Global, None); - let json = serde_json::to_string_pretty(&report).unwrap(); - let out_path = cli.output.unwrap_or_else(|| PathBuf::from(format!("syspulse_report_{}.json", Utc::now().format("%Y%m%d_%H%M%S")))); - fs::write(&out_path, json).expect("Failed to write report"); - println!("✅ Report saved to: {:?}", out_path); - if cli.gui { - initial_report = Some(report); - } else { - return; - } - } + let monitor = Monitor::new(); + monitor.start(); tauri::Builder::default() .manage(AppState { - sys: Mutex::new(System::new_all()), - profiling: Mutex::new(ProfilingSession { - is_active: false, - mode: ProfilingMode::Global, - target_pid: None, - start_time: None, - snapshots: Vec::new() - }), - initial_report: Mutex::new(initial_report), - pss_cache: Mutex::new(HashMap::new()) + monitor }) .invoke_handler(tauri::generate_handler![ - get_system_stats, - get_initial_report, - start_profiling, - start_targeted_profiling, - stop_profiling, - run_as_admin, - save_report + get_latest_stats ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index 4a39717..8147cd9 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -1,100 +1,38 @@ use serde::{Serialize, Deserialize}; -use chrono::{DateTime, Utc}; -use std::sync::Mutex; -use sysinfo::System; use std::collections::HashMap; -#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Debug)] -pub enum ProfilingMode { - Global, - Targeted, -} - -#[derive(Serialize, Clone)] -pub struct SystemStats { - pub cpu_usage: Vec, - pub total_memory: u64, - pub used_memory: u64, - pub processes: Vec, - pub is_recording: bool, - pub recording_duration: u64, -} - -#[derive(Serialize, Clone, Debug)] -pub struct ProcessStats { +#[derive(Clone, Serialize, Debug)] +pub struct ProcessNode { pub pid: u32, - pub parent_pid: Option, pub name: String, - pub cpu_usage: f32, - pub memory: u64, - pub status: String, - pub user_id: Option, - pub is_syspulse: bool, + pub cpu_self: f32, // CPU usage of this specific process + pub cpu_children: f32, // Sum of children's CPU (recursive) + pub mem_rss: u64, // Resident Set Size (Self) + pub mem_children: u64, // Sum of children's Mem + pub children: Vec, } -#[derive(Clone)] -pub struct Snapshot { - pub timestamp: DateTime, - pub cpu_usage: Vec, - pub used_memory: u64, - pub processes: Vec, +impl ProcessNode { + pub fn total_cpu(&self) -> f32 { + self.cpu_self + self.cpu_children + } + + pub fn total_mem(&self) -> u64 { + self.mem_rss + self.mem_children + } } -pub struct ProfilingSession { - pub is_active: bool, - pub mode: ProfilingMode, - pub target_pid: Option, - pub start_time: Option>, - pub snapshots: Vec, -} - -pub struct AppState { - pub sys: Mutex, - pub profiling: Mutex, - pub initial_report: Mutex>, - pub pss_cache: Mutex)>>, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct Report { - pub start_time: String, - pub end_time: String, - pub duration_seconds: i64, - pub mode: ProfilingMode, - pub target_name: Option, - pub timeline: Vec, - pub aggregated_processes: Vec, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct TimelinePoint { - pub time: String, +#[derive(Clone, Serialize)] +pub struct GlobalStats { pub cpu_total: f32, - pub mem_total_gb: f32, - pub cpu_profiler: f32, - pub mem_profiler_gb: f32, + pub mem_used: u64, + pub mem_total: u64, + pub process_tree: Vec, // Top-level roots (e.g. systemd/init or orphans) + pub process_count: usize, } -#[derive(Serialize, Deserialize, Clone)] -pub struct ProcessHistoryPoint { - pub time: String, - pub cpu_usage: f32, - pub memory_mb: f32, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct AggregatedProcess { - pub pid: u32, - pub name: String, - pub avg_cpu: f32, - pub peak_cpu: f32, - pub avg_memory_mb: f32, - pub peak_memory_mb: f32, - pub inclusive_avg_cpu: f32, - pub inclusive_avg_memory_mb: f32, - pub instance_count: usize, - pub warnings: Vec, - pub history: Vec, - pub is_syspulse: bool, - pub children: Vec, +#[derive(Clone, Copy, PartialEq)] +pub enum MemoryMode { + Rss, // Fast, default for monitoring + Pss, // Slow, accurate for profiling } diff --git a/src-tauri/src/monitor.rs b/src-tauri/src/monitor.rs new file mode 100644 index 0000000..ec1bf04 --- /dev/null +++ b/src-tauri/src/monitor.rs @@ -0,0 +1,159 @@ +use sysinfo::System; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; +use std::collections::{HashMap, HashSet}; +use crate::models::*; + +pub struct Monitor { + data: Arc>, + running: Arc>, +} + +impl Monitor { + pub fn new() -> Self { + let stats = GlobalStats { + cpu_total: 0.0, + mem_used: 0, + mem_total: 0, + process_tree: Vec::new(), + process_count: 0, + }; + + Monitor { + data: Arc::new(Mutex::new(stats)), + running: Arc::new(Mutex::new(false)), + } + } + + pub fn get_latest(&self) -> GlobalStats { + let lock = self.data.lock().unwrap(); + lock.clone() + } + + pub fn start(&self) { + let data_ref = self.data.clone(); + let running_ref = self.running.clone(); + + *running_ref.lock().unwrap() = true; + + thread::spawn(move || { + let mut sys = System::new_all(); + + // Initial wait to let sysinfo calculate CPU diff + thread::sleep(Duration::from_millis(500)); + sys.refresh_all(); + + loop { + if !*running_ref.lock().unwrap() { + break; + } + + // 1. Refresh System State (Efficiently) + sys.refresh_cpu_all(); + sys.refresh_memory(); + sys.refresh_processes(sysinfo::ProcessesToUpdate::All, true); + + // 2. Calculate Global Stats + let cpu_total = sys.global_cpu_usage(); + let mem_used = sys.used_memory(); + let mem_total = sys.total_memory(); + let process_count = sys.processes().len(); + + // 3. Build Process Tree + let tree = build_process_tree(&sys); + + // 4. Update Shared State + { + let mut lock = data_ref.lock().unwrap(); + lock.cpu_total = cpu_total; + lock.mem_used = mem_used; + lock.mem_total = mem_total; + lock.process_tree = tree; + lock.process_count = process_count; + } + + // 5. Sleep (Aim for 1s interval) + thread::sleep(Duration::from_secs(1)); + } + }); + } + + pub fn stop(&self) { + *self.running.lock().unwrap() = false; + } +} + +fn build_process_tree(sys: &System) -> Vec { + // 1. Collect all raw nodes + let mut raw_nodes: HashMap = HashMap::new(); + let mut ppid_map: HashMap = HashMap::new(); + + for (pid, proc) in sys.processes() { + let pid_u32 = pid.as_u32(); + + raw_nodes.insert(pid_u32, ProcessNode { + pid: pid_u32, + name: proc.name().to_string_lossy().to_string(), + cpu_self: proc.cpu_usage(), + cpu_children: 0.0, + mem_rss: proc.memory(), + mem_children: 0, + children: Vec::new(), + }); + + if let Some(parent) = proc.parent() { + ppid_map.insert(pid_u32, parent.as_u32()); + } + } + + // 2. Build Hierarchy (Bottom-Up or Map-Based) + let mut children_map: HashMap> = HashMap::new(); + for (child, parent) in &ppid_map { + children_map.entry(*parent).or_default().push(*child); + } + + // Identify roots + let roots: Vec = raw_nodes.keys() + .filter(|pid| !ppid_map.contains_key(pid)) // Has no parent in the list + .cloned() + .collect(); + + // 3. Recursive Tree Builder + fn build_recursive( + pid: u32, + nodes_map: &mut HashMap, + child_map: &HashMap> + ) -> Option { + if let Some(mut node) = nodes_map.remove(&pid) { + let children_pids = child_map.get(&pid).cloned().unwrap_or_default(); + + for c_pid in children_pids { + if let Some(child_node) = build_recursive(c_pid, nodes_map, child_map) { + node.cpu_children += child_node.total_cpu(); + node.mem_children += child_node.total_mem(); + node.children.push(child_node); + } + } + + // Sort children by Mem impact by default + node.children.sort_by(|a, b| b.total_mem().cmp(&a.total_mem())); + + Some(node) + } else { + None + } + } + + let mut tree = Vec::new(); + for root_pid in roots { + if let Some(node) = build_recursive(root_pid, &mut raw_nodes, &children_map) { + tree.push(node); + } + } + + // Sort roots by total CPU + tree.sort_by(|a, b| b.total_cpu().partial_cmp(&a.total_cpu()).unwrap_or(std::cmp::Ordering::Equal)); + + tree +} diff --git a/src-tauri/src/profiler.rs b/src-tauri/src/profiler.rs deleted file mode 100644 index aef5468..0000000 --- a/src-tauri/src/profiler.rs +++ /dev/null @@ -1,222 +0,0 @@ -use sysinfo::System; -use chrono::{DateTime, Utc}; -use std::collections::{HashMap, HashSet}; -use std::fs; -use crate::models::*; - -pub fn get_pss(pid: u32) -> Option { - let path = format!("/proc/{}/smaps_rollup", pid); - if let Ok(contents) = fs::read_to_string(path) { - for line in contents.lines() { - if line.starts_with("Pss:") { - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() >= 2 { - if let Ok(kb) = parts[1].parse::() { - return Some(kb * 1024); - } - } - } - } - } - None -} - -pub fn get_syspulse_pids(self_pid: u32, sys: &System) -> HashSet { - let mut res = HashSet::new(); - res.insert(self_pid); - let mut children_map: HashMap> = HashMap::new(); - for (pid, p) in sys.processes() { - if let Some(ppid) = p.parent() { - children_map.entry(ppid.as_u32()).or_default().push(pid.as_u32()); - } - } - let mut stack = vec![self_pid]; - while let Some(pid) = stack.pop() { - if let Some(children) = children_map.get(&pid) { - for &c in children { - if res.insert(c) { - stack.push(c); - } - } - } - } - res -} - -pub fn collect_snapshot( - sys: &mut System, - syspulse_pids: &HashSet, - pss_cache: &mut HashMap)>, - collect_pss: bool -) -> Snapshot { - sys.refresh_cpu_all(); - sys.refresh_memory(); - sys.refresh_processes(sysinfo::ProcessesToUpdate::All, true); - - let cpu_usage: Vec = sys.cpus().iter().map(|cpu| cpu.cpu_usage()).collect(); - let used_memory = sys.used_memory(); - let now = Utc::now(); - - let processes: Vec = sys.processes().iter() - .map(|(pid, p)| { - let pid_u32 = pid.as_u32(); - let rss = p.memory(); - let is_syspulse = syspulse_pids.contains(&pid_u32); - - let mut memory = rss; - if collect_pss && (rss > 20 * 1024 * 1024 || is_syspulse) { - let needs_refresh = match pss_cache.get(&pid_u32) { - Some((_, last)) => (now - *last).num_seconds() > 5, - None => true, - }; - - if needs_refresh { - if let Some(pss) = get_pss(pid_u32) { - pss_cache.insert(pid_u32, (pss, now)); - memory = pss; - } - } else if let Some((cached_pss, _)) = pss_cache.get(&pid_u32) { - memory = *cached_pss; - } - } - - ProcessStats { - pid: pid_u32, - parent_pid: p.parent().map(|pp| pp.as_u32()), - name: p.name().to_string_lossy().to_string(), - cpu_usage: p.cpu_usage(), - memory, - status: format!("{:?}", p.status()), - user_id: p.user_id().map(|u| u.to_string()), - is_syspulse, - } - }).collect(); - - Snapshot { - timestamp: now, - cpu_usage, - used_memory, - processes, - } -} - -pub fn generate_report(start_time: DateTime, snapshots: Vec, mode: ProfilingMode, target_pid: Option) -> Report { - let end_time = Utc::now(); - let duration = (end_time - start_time).num_seconds(); - - let timeline: Vec = snapshots.iter().map(|s| { - let cpu_total = s.cpu_usage.iter().sum::() / s.cpu_usage.len() as f32; - let mem_total_gb = s.used_memory as f32 / 1024.0 / 1024.0 / 1024.0; - let profiler_stats = s.processes.iter().filter(|p| p.is_syspulse).fold((0.0, 0), |acc, p| (acc.0 + p.cpu_usage, acc.1 + p.memory)); - TimelinePoint { - time: s.timestamp.format("%H:%M:%S").to_string(), - cpu_total, - mem_total_gb, - cpu_profiler: profiler_stats.0 / s.cpu_usage.len() as f32, - mem_profiler_gb: profiler_stats.1 as f32 / 1024.0 / 1024.0 / 1024.0, - } - }).collect(); - - let mut target_name = None; - if let Some(tpid) = target_pid { - if let Some(snapshot) = snapshots.first() { - if let Some(p) = snapshot.processes.iter().find(|p| p.pid == tpid) { target_name = Some(p.name.clone()); } - } - } - - let mut pid_map: HashMap, Vec, f32, f32, bool, bool)> = HashMap::new(); - let num_snapshots = snapshots.len() as f32; - - for snapshot in &snapshots { - for proc in &snapshot.processes { - let entry = pid_map.entry(proc.pid).or_insert_with(|| (proc.name.clone(), proc.parent_pid, Vec::new(), 0.0, 0.0, proc.is_syspulse, false)); - let mem_mb = proc.memory as f32 / 1024.0 / 1024.0; - entry.2.push(ProcessHistoryPoint { time: snapshot.timestamp.format("%H:%M:%S").to_string(), cpu_usage: proc.cpu_usage, memory_mb: mem_mb }); - if proc.cpu_usage > entry.3 { entry.3 = proc.cpu_usage; } - if mem_mb > entry.4 { entry.4 = mem_mb; } - if proc.status.contains("Zombie") { entry.6 = true; } - } - } - - let mut nodes: HashMap = pid_map.into_iter().map(|(pid, (name, _, history, peak_cpu, peak_mem, is_syspulse, is_zombie))| { - let total_cpu: f32 = history.iter().map(|h| h.cpu_usage).sum(); - let total_mem: f32 = history.iter().map(|h| h.memory_mb).sum(); - let mut warnings = Vec::new(); - if is_zombie { warnings.push("Zombie".to_string()); } - if peak_cpu > 80.0 { warnings.push("High Peak".to_string()); } - (pid, AggregatedProcess { pid, name, avg_cpu: total_cpu / num_snapshots, peak_cpu, avg_memory_mb: total_mem / num_snapshots, peak_memory_mb: peak_mem, inclusive_avg_cpu: 0.0, inclusive_avg_memory_mb: 0.0, instance_count: 1, warnings, history, is_syspulse, children: Vec::new() }) - }).collect(); - - let mut child_to_parent = HashMap::new(); - for snapshot in &snapshots { - for proc in &snapshot.processes { - if let Some(ppid) = proc.parent_pid { if nodes.contains_key(&ppid) { child_to_parent.insert(proc.pid, ppid); } } - } - } - - let mut child_map: HashMap> = HashMap::new(); - for (&child, &parent) in &child_to_parent { child_map.entry(parent).or_default().push(child); } - - let root_pids: Vec = nodes.keys().filter(|pid| !child_to_parent.contains_key(pid)).cloned().collect(); - - fn build_node(pid: u32, nodes: &mut HashMap, child_map: &HashMap>) -> Option { - let mut node = nodes.remove(&pid)?; - let children_pids = child_map.get(&pid).cloned().unwrap_or_default(); - let mut inc_cpu = node.avg_cpu; - let mut inc_mem = node.avg_memory_mb; - for c_pid in children_pids { - if let Some(child_node) = build_node(c_pid, nodes, child_map) { - inc_cpu += child_node.inclusive_avg_cpu; - inc_mem += child_node.inclusive_avg_memory_mb; - node.children.push(child_node); - } - } - node.inclusive_avg_cpu = inc_cpu; - node.inclusive_avg_memory_mb = inc_mem; - Some(node) - } - - let mut final_roots = Vec::new(); - for pid in root_pids { if let Some(root_node) = build_node(pid, &mut nodes, &child_map) { final_roots.push(root_node); } } - let remaining_pids: Vec = nodes.keys().cloned().collect(); - for pid in remaining_pids { if let Some(node) = build_node(pid, &mut nodes, &child_map) { final_roots.push(node); } } - - if mode == ProfilingMode::Global { - let mut name_groups: HashMap = HashMap::new(); - fn flatten_to_groups(node: AggregatedProcess, groups: &mut HashMap) { - let entry = groups.entry(node.name.clone()).or_insert_with(|| { - let mut base = node.clone(); - base.children = Vec::new(); base.instance_count = 0; base.inclusive_avg_cpu = 0.0; base.inclusive_avg_memory_mb = 0.0; base.avg_cpu = 0.0; base.avg_memory_mb = 0.0; - base - }); - entry.avg_cpu += node.avg_cpu; entry.avg_memory_mb += node.avg_memory_mb; entry.instance_count += 1; - if node.peak_cpu > entry.peak_cpu { entry.peak_cpu = node.peak_cpu; } - if node.peak_memory_mb > entry.peak_memory_mb { entry.peak_memory_mb = node.peak_memory_mb; } - for child in node.children { flatten_to_groups(child, groups); } - } - for root in final_roots { flatten_to_groups(root, &mut name_groups); } - let mut flattened: Vec = name_groups.into_values().collect(); - flattened.sort_by(|a, b| b.avg_cpu.partial_cmp(&a.avg_cpu).unwrap_or(std::cmp::Ordering::Equal)); - Report { start_time: start_time.to_rfc3339(), end_time: end_time.to_rfc3339(), duration_seconds: duration, mode, target_name, timeline, aggregated_processes: flattened } - } else { - let mut targeted_roots = Vec::new(); - if let Some(tpid) = target_pid { - fn extract_target(nodes: Vec, tpid: u32) -> (Vec, Option) { - let mut remaining = Vec::new(); let mut found = None; - for mut node in nodes { - if node.pid == tpid { found = Some(node); } - else { - let (new_children, sub_found) = extract_target(node.children, tpid); - node.children = new_children; - if let Some(f) = sub_found { found = Some(f); } - remaining.push(node); - } - } - (remaining, found) - } - let (_, target_node) = extract_target(final_roots, tpid); - if let Some(t) = target_node { targeted_roots.push(t); } - } - Report { start_time: start_time.to_rfc3339(), end_time: end_time.to_rfc3339(), duration_seconds: duration, mode, target_name, timeline, aggregated_processes: targeted_roots } - } -}