From b5ae0ccf2830f1a00819270a2c8fc7300dbb72d2 Mon Sep 17 00:00:00 2001 From: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com> Date: Fri, 8 May 2026 14:18:32 +0200 Subject: [PATCH] refactor(app_api): extract perf telemetry into own submodule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lift PerformanceCpuSnapshot struct + the Linux /proc/stat parsers (parse_proc_stat_line / read_total_jiffies / collect_proc_stats) + the performance_cpu_snapshot Tauri command (~110 LOC) out of app_api/core.rs into app_api/perf.rs. Self-contained CPU-usage telemetry; nothing else in app_api references the helpers. This is the "telemetry" slice cucadmuh's plan called out for splitting core.rs's mixed runtime/platform/snapshot concerns. Other slices (cli-bridge, runtime-control, platform window/scrolling) can follow. app_api/core.rs: 235 → 122 LOC. lib.rs registration unchanged — performance_cpu_snapshot is re-exported through `pub(crate) use perf::*` in app_api/mod.rs. --- src-tauri/src/lib_commands/app_api/core.rs | 110 -------------------- src-tauri/src/lib_commands/app_api/mod.rs | 2 + src-tauri/src/lib_commands/app_api/perf.rs | 115 +++++++++++++++++++++ 3 files changed, 117 insertions(+), 110 deletions(-) create mode 100644 src-tauri/src/lib_commands/app_api/perf.rs diff --git a/src-tauri/src/lib_commands/app_api/core.rs b/src-tauri/src/lib_commands/app_api/core.rs index 1680e1c0..8e7ab975 100644 --- a/src-tauri/src/lib_commands/app_api/core.rs +++ b/src-tauri/src/lib_commands/app_api/core.rs @@ -1,17 +1,4 @@ use super::*; -use serde::Serialize; - -#[cfg(target_os = "linux")] -use std::fs; - -#[derive(Debug, Clone, Serialize)] -pub(crate) struct PerformanceCpuSnapshot { - pub supported: bool, - pub total_jiffies: u64, - pub app_jiffies: u64, - pub webkit_jiffies: u64, - pub logical_cpus: u32, -} #[tauri::command] pub(crate) fn greet(name: &str) -> String { @@ -134,102 +121,5 @@ pub(crate) fn set_subsonic_wire_user_agent( Ok(()) } -#[cfg(target_os = "linux")] -fn parse_proc_stat_line(stat_line: &str) -> Option<(String, i32, u64, u64)> { - let close_idx = stat_line.rfind(')')?; - let open_idx = stat_line.find('(')?; - if open_idx + 1 >= close_idx { - return None; - } - let comm = stat_line.get(open_idx + 1..close_idx)?.to_string(); - let after = stat_line.get(close_idx + 2..)?; - let mut parts = after.split_whitespace(); - let _state = parts.next()?; - let ppid = parts.next()?.parse::().ok()?; - let rest: Vec<&str> = parts.collect(); - // After `state` and `ppid`, remaining fields start at `pgrp` (field #5). - // `utime` = field #14 => rest[9], `stime` = field #15 => rest[10]. - let utime = rest.get(9)?.parse::().ok()?; - let stime = rest.get(10)?.parse::().ok()?; - Some((comm, ppid, utime, stime)) -} - -#[cfg(target_os = "linux")] -fn read_total_jiffies() -> Option { - let content = fs::read_to_string("/proc/stat").ok()?; - let line = content.lines().next()?; - let mut it = line.split_whitespace(); - if it.next()? != "cpu" { - return None; - } - Some(it.filter_map(|n| n.parse::().ok()).sum()) -} - -#[cfg(target_os = "linux")] -fn collect_proc_stats() -> Vec<(i32, String, i32, u64)> { - let mut rows = Vec::new(); - let entries = match fs::read_dir("/proc") { - Ok(v) => v, - Err(_) => return rows, - }; - for entry in entries.flatten() { - let name = entry.file_name(); - let pid = match name.to_string_lossy().parse::() { - Ok(v) => v, - Err(_) => continue, - }; - let stat_path = format!("/proc/{pid}/stat"); - let stat_line = match fs::read_to_string(stat_path) { - Ok(v) => v, - Err(_) => continue, - }; - if let Some((comm, ppid, utime, stime)) = parse_proc_stat_line(stat_line.trim()) { - rows.push((pid, comm, ppid, utime.saturating_add(stime))); - } - } - rows -} - -#[tauri::command] -pub(crate) fn performance_cpu_snapshot() -> PerformanceCpuSnapshot { - #[cfg(target_os = "linux")] - { - let total_jiffies = read_total_jiffies().unwrap_or(0); - let logical_cpus = std::thread::available_parallelism() - .map(|n| n.get() as u32) - .unwrap_or(1); - let self_pid = std::process::id() as i32; - let rows = collect_proc_stats(); - let app_jiffies = rows - .iter() - .find(|(pid, _, _, _)| *pid == self_pid) - .map(|(_, _, _, ticks)| *ticks) - .unwrap_or(0); - let webkit_jiffies = rows - .iter() - // Linux `/proc/*/stat` `comm` is capped to 15 chars, so - // "WebKitWebProcess" appears as "WebKitWebProces". - .filter(|(_, comm, ppid, _)| comm.starts_with("WebKitWebProces") && *ppid == self_pid) - .map(|(_, _, _, ticks)| *ticks) - .sum::(); - return PerformanceCpuSnapshot { - supported: true, - total_jiffies, - app_jiffies, - webkit_jiffies, - logical_cpus, - }; - } - #[cfg(not(target_os = "linux"))] - { - PerformanceCpuSnapshot { - supported: false, - total_jiffies: 0, - app_jiffies: 0, - webkit_jiffies: 0, - logical_cpus: 1, - } - } -} diff --git a/src-tauri/src/lib_commands/app_api/mod.rs b/src-tauri/src/lib_commands/app_api/mod.rs index 1cc36df2..abb50c86 100644 --- a/src-tauri/src/lib_commands/app_api/mod.rs +++ b/src-tauri/src/lib_commands/app_api/mod.rs @@ -2,12 +2,14 @@ use super::*; mod core; mod navidrome; +mod perf; mod remote; mod integration; mod analysis; pub(crate) use core::*; pub(crate) use navidrome::*; +pub(crate) use perf::*; pub(crate) use remote::*; pub(crate) use integration::*; pub(crate) use analysis::*; diff --git a/src-tauri/src/lib_commands/app_api/perf.rs b/src-tauri/src/lib_commands/app_api/perf.rs new file mode 100644 index 00000000..562d43ab --- /dev/null +++ b/src-tauri/src/lib_commands/app_api/perf.rs @@ -0,0 +1,115 @@ +//! Performance telemetry: process-level CPU snapshot for the Linux /proc +//! parser. Other platforms return `supported: false` — the frontend treats +//! that as "perf overlay not available" and hides itself. + +use serde::Serialize; + +#[cfg(target_os = "linux")] +use std::fs; + +#[derive(Debug, Clone, Serialize)] +pub(crate) struct PerformanceCpuSnapshot { + pub supported: bool, + pub total_jiffies: u64, + pub app_jiffies: u64, + pub webkit_jiffies: u64, + pub logical_cpus: u32, +} + +#[cfg(target_os = "linux")] +fn parse_proc_stat_line(stat_line: &str) -> Option<(String, i32, u64, u64)> { + let close_idx = stat_line.rfind(')')?; + let open_idx = stat_line.find('(')?; + if open_idx + 1 >= close_idx { + return None; + } + let comm = stat_line.get(open_idx + 1..close_idx)?.to_string(); + let after = stat_line.get(close_idx + 2..)?; + let mut parts = after.split_whitespace(); + let _state = parts.next()?; + let ppid = parts.next()?.parse::().ok()?; + let rest: Vec<&str> = parts.collect(); + // After `state` and `ppid`, remaining fields start at `pgrp` (field #5). + // `utime` = field #14 => rest[9], `stime` = field #15 => rest[10]. + let utime = rest.get(9)?.parse::().ok()?; + let stime = rest.get(10)?.parse::().ok()?; + Some((comm, ppid, utime, stime)) +} + +#[cfg(target_os = "linux")] +fn read_total_jiffies() -> Option { + let content = fs::read_to_string("/proc/stat").ok()?; + let line = content.lines().next()?; + let mut it = line.split_whitespace(); + if it.next()? != "cpu" { + return None; + } + Some(it.filter_map(|n| n.parse::().ok()).sum()) +} + +#[cfg(target_os = "linux")] +fn collect_proc_stats() -> Vec<(i32, String, i32, u64)> { + let mut rows = Vec::new(); + let entries = match fs::read_dir("/proc") { + Ok(v) => v, + Err(_) => return rows, + }; + for entry in entries.flatten() { + let name = entry.file_name(); + let pid = match name.to_string_lossy().parse::() { + Ok(v) => v, + Err(_) => continue, + }; + let stat_path = format!("/proc/{pid}/stat"); + let stat_line = match fs::read_to_string(stat_path) { + Ok(v) => v, + Err(_) => continue, + }; + if let Some((comm, ppid, utime, stime)) = parse_proc_stat_line(stat_line.trim()) { + rows.push((pid, comm, ppid, utime.saturating_add(stime))); + } + } + rows +} + +#[tauri::command] +pub(crate) fn performance_cpu_snapshot() -> PerformanceCpuSnapshot { + #[cfg(target_os = "linux")] + { + let total_jiffies = read_total_jiffies().unwrap_or(0); + let logical_cpus = std::thread::available_parallelism() + .map(|n| n.get() as u32) + .unwrap_or(1); + let self_pid = std::process::id() as i32; + let rows = collect_proc_stats(); + let app_jiffies = rows + .iter() + .find(|(pid, _, _, _)| *pid == self_pid) + .map(|(_, _, _, ticks)| *ticks) + .unwrap_or(0); + let webkit_jiffies = rows + .iter() + // Linux `/proc/*/stat` `comm` is capped to 15 chars, so + // "WebKitWebProcess" appears as "WebKitWebProces". + .filter(|(_, comm, ppid, _)| comm.starts_with("WebKitWebProces") && *ppid == self_pid) + .map(|(_, _, _, ticks)| *ticks) + .sum::(); + return PerformanceCpuSnapshot { + supported: true, + total_jiffies, + app_jiffies, + webkit_jiffies, + logical_cpus, + }; + } + #[cfg(not(target_os = "linux"))] + { + PerformanceCpuSnapshot { + supported: false, + total_jiffies: 0, + app_jiffies: 0, + webkit_jiffies: 0, + logical_cpus: 1, + } + } +}