mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
refactor(app_api): extract perf telemetry into own submodule
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.
This commit is contained in:
@@ -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::<i32>().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::<u64>().ok()?;
|
||||
let stime = rest.get(10)?.parse::<u64>().ok()?;
|
||||
Some((comm, ppid, utime, stime))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn read_total_jiffies() -> Option<u64> {
|
||||
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::<u64>().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::<i32>() {
|
||||
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::<u64>();
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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::<i32>().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::<u64>().ok()?;
|
||||
let stime = rest.get(10)?.parse::<u64>().ok()?;
|
||||
Some((comm, ppid, utime, stime))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn read_total_jiffies() -> Option<u64> {
|
||||
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::<u64>().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::<i32>() {
|
||||
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::<u64>();
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user