treewide: move application-specific crates to crates/
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I47d36cb7d1dec5ab0c892190015cfc576a6a6964
This commit is contained in:
parent
b68503d320
commit
6e772ffefb
31 changed files with 2106 additions and 2108 deletions
11
crates/pscand-core/src/helpers/mod.rs
Normal file
11
crates/pscand-core/src/helpers/mod.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
pub mod power;
|
||||
pub mod process;
|
||||
pub mod resource;
|
||||
pub mod sensor;
|
||||
pub mod system;
|
||||
|
||||
pub use power::PowerHelper;
|
||||
pub use process::ProcessHelper;
|
||||
pub use resource::ResourceHelper;
|
||||
pub use sensor::SensorHelper;
|
||||
pub use system::SystemHelper;
|
||||
157
crates/pscand-core/src/helpers/power.rs
Normal file
157
crates/pscand-core/src/helpers/power.rs
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct PowerHelper;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BatteryInfo {
|
||||
pub name: String,
|
||||
pub status: String,
|
||||
pub capacity: u32,
|
||||
pub charge_percent: i32,
|
||||
pub voltage: f64,
|
||||
pub current_now: i64,
|
||||
pub power_now: i64,
|
||||
pub present: bool,
|
||||
}
|
||||
|
||||
impl PowerHelper {
|
||||
pub fn battery_info() -> std::io::Result<Option<BatteryInfo>> {
|
||||
let battery_path = PathBuf::from("/sys/class/power_supply");
|
||||
|
||||
for entry in fs::read_dir(&battery_path)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
let type_path = path.join("type");
|
||||
|
||||
if type_path.exists() {
|
||||
let battery_type = fs::read_to_string(&type_path)?.trim().to_string();
|
||||
if battery_type == "Battery" {
|
||||
let present_path = path.join("present");
|
||||
let present = fs::read_to_string(&present_path)
|
||||
.map(|s| s.trim() == "1")
|
||||
.unwrap_or(false);
|
||||
|
||||
if !present {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = fs::read_to_string(path.join("name"))
|
||||
.unwrap_or_else(|_| "Unknown".to_string())
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let status = fs::read_to_string(path.join("status"))
|
||||
.unwrap_or_else(|_| "Unknown".to_string())
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
let capacity = fs::read_to_string(path.join("capacity"))
|
||||
.ok()
|
||||
.and_then(|s| s.trim().parse::<u32>().ok())
|
||||
.unwrap_or(0);
|
||||
|
||||
let charge_now = fs::read_to_string(path.join("charge_now"))
|
||||
.or_else(|_| fs::read_to_string(path.join("energy_now")))
|
||||
.ok()
|
||||
.and_then(|s| s.trim().parse::<i64>().ok())
|
||||
.unwrap_or(0);
|
||||
|
||||
let charge_full = fs::read_to_string(path.join("charge_full"))
|
||||
.or_else(|_| fs::read_to_string(path.join("energy_full")))
|
||||
.ok()
|
||||
.and_then(|s| s.trim().parse::<i64>().ok())
|
||||
.unwrap_or(1);
|
||||
|
||||
let charge_percent = if charge_full > 0 {
|
||||
((charge_now as f64 / charge_full as f64) * 100.0) as i32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let voltage = fs::read_to_string(path.join("voltage_now"))
|
||||
.ok()
|
||||
.and_then(|s| s.trim().parse::<f64>().ok())
|
||||
.unwrap_or(0.0)
|
||||
/ 1_000_000.0;
|
||||
|
||||
let current_now = fs::read_to_string(path.join("current_now"))
|
||||
.ok()
|
||||
.and_then(|s| s.trim().parse::<i64>().ok())
|
||||
.unwrap_or(0);
|
||||
|
||||
let power_now = fs::read_to_string(path.join("power_now"))
|
||||
.ok()
|
||||
.and_then(|s| s.trim().parse::<i64>().ok())
|
||||
.unwrap_or(0);
|
||||
|
||||
return Ok(Some(BatteryInfo {
|
||||
name,
|
||||
status,
|
||||
capacity,
|
||||
charge_percent,
|
||||
voltage,
|
||||
current_now,
|
||||
power_now,
|
||||
present,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn power_supplies() -> std::io::Result<HashMap<String, HashMap<String, String>>> {
|
||||
let mut supplies = HashMap::new();
|
||||
let power_supply_path = PathBuf::from("/sys/class/power_supply");
|
||||
|
||||
if !power_supply_path.exists() {
|
||||
return Ok(supplies);
|
||||
}
|
||||
|
||||
for entry in fs::read_dir(&power_supply_path)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
let name = path
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut info = HashMap::new();
|
||||
|
||||
for attr in [
|
||||
"type",
|
||||
"status",
|
||||
"capacity",
|
||||
"voltage_now",
|
||||
"power_now",
|
||||
"online",
|
||||
] {
|
||||
let attr_path = path.join(attr);
|
||||
if attr_path.exists() {
|
||||
if let Ok(content) = fs::read_to_string(&attr_path) {
|
||||
info.insert(attr.to_string(), content.trim().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !info.is_empty() {
|
||||
supplies.insert(name, info);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(supplies)
|
||||
}
|
||||
|
||||
pub fn suspend_state() -> std::io::Result<String> {
|
||||
let state_path = PathBuf::from("/sys/power/state");
|
||||
fs::read_to_string(state_path).map(|s| s.trim().to_string())
|
||||
}
|
||||
|
||||
pub fn mem_sleep_state() -> std::io::Result<String> {
|
||||
let state_path = PathBuf::from("/sys/power/mem_sleep");
|
||||
fs::read_to_string(state_path).map(|s| s.trim().to_string())
|
||||
}
|
||||
}
|
||||
136
crates/pscand-core/src/helpers/process.rs
Normal file
136
crates/pscand-core/src/helpers/process.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
|
||||
pub struct ProcessHelper;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProcessInfo {
|
||||
pub pid: u32,
|
||||
pub name: String,
|
||||
pub state: String,
|
||||
pub ppid: u32,
|
||||
pub memory_kb: u64,
|
||||
pub cpu_percent: f32,
|
||||
}
|
||||
|
||||
impl ProcessHelper {
|
||||
pub fn list_processes() -> std::io::Result<Vec<ProcessInfo>> {
|
||||
let mut processes = Vec::new();
|
||||
let proc_path = fs::read_dir("/proc")?;
|
||||
|
||||
for entry in proc_path.flatten() {
|
||||
let path = entry.path();
|
||||
if !path.is_dir() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pid: u32 = match path.file_name() {
|
||||
Some(name) => match name.to_str() {
|
||||
Some(s) => s.parse().ok(),
|
||||
None => None,
|
||||
}
|
||||
.ok_or(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"invalid pid",
|
||||
))?,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
if let Ok(info) = Self::process_info(pid) {
|
||||
processes.push(info);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(processes)
|
||||
}
|
||||
|
||||
pub fn process_info(pid: u32) -> std::io::Result<ProcessInfo> {
|
||||
let status_path = format!("/proc/{}/status", pid);
|
||||
let content = fs::read_to_string(status_path)?;
|
||||
|
||||
let mut name = String::new();
|
||||
let mut state = String::new();
|
||||
let mut ppid: u32 = 0;
|
||||
let mut memory_kb: u64 = 0;
|
||||
|
||||
for line in content.lines() {
|
||||
if line.starts_with("Name:") {
|
||||
name = line
|
||||
.split_whitespace()
|
||||
.skip(1)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
} else if line.starts_with("State:") {
|
||||
state = line.split_whitespace().nth(1).unwrap_or("").to_string();
|
||||
} else if line.starts_with("PPid:") {
|
||||
ppid = line
|
||||
.split_whitespace()
|
||||
.nth(1)
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(0);
|
||||
} else if line.starts_with("VmRSS:") {
|
||||
memory_kb = line
|
||||
.split_whitespace()
|
||||
.nth(1)
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(0);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ProcessInfo {
|
||||
pid,
|
||||
name,
|
||||
state,
|
||||
ppid,
|
||||
memory_kb,
|
||||
cpu_percent: 0.0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn zombie_processes() -> std::io::Result<Vec<ProcessInfo>> {
|
||||
Ok(Self::list_processes()?
|
||||
.into_iter()
|
||||
.filter(|p| p.state.starts_with('Z'))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn process_count() -> std::io::Result<HashMap<String, usize>> {
|
||||
let mut counts = HashMap::new();
|
||||
counts.insert("total".to_string(), 0);
|
||||
counts.insert("running".to_string(), 0);
|
||||
counts.insert("sleeping".to_string(), 0);
|
||||
counts.insert("zombie".to_string(), 0);
|
||||
|
||||
for proc in Self::list_processes()? {
|
||||
*counts.get_mut("total").unwrap() += 1;
|
||||
|
||||
let first_char = proc.state.chars().next().unwrap_or(' ');
|
||||
match first_char {
|
||||
'R' => *counts.get_mut("running").unwrap() += 1,
|
||||
'S' | 'D' => *counts.get_mut("sleeping").unwrap() += 1,
|
||||
'Z' => *counts.get_mut("zombie").unwrap() += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(counts)
|
||||
}
|
||||
|
||||
pub fn top_memory_processes(count: usize) -> std::io::Result<Vec<ProcessInfo>> {
|
||||
let mut processes = Self::list_processes()?;
|
||||
processes.sort_by(|a, b| b.memory_kb.cmp(&a.memory_kb));
|
||||
processes.truncate(count);
|
||||
Ok(processes)
|
||||
}
|
||||
|
||||
pub fn top_cpu_processes(count: usize) -> std::io::Result<Vec<ProcessInfo>> {
|
||||
let mut processes = Self::list_processes()?;
|
||||
processes.sort_by(|a, b| {
|
||||
b.cpu_percent
|
||||
.partial_cmp(&a.cpu_percent)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
processes.truncate(count);
|
||||
Ok(processes)
|
||||
}
|
||||
}
|
||||
123
crates/pscand-core/src/helpers/resource.rs
Normal file
123
crates/pscand-core/src/helpers/resource.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
|
||||
pub struct ResourceHelper;
|
||||
|
||||
impl ResourceHelper {
|
||||
pub fn cpu_usage() -> std::io::Result<HashMap<String, f64>> {
|
||||
let content = fs::read_to_string("/proc/stat")?;
|
||||
let mut result = HashMap::new();
|
||||
|
||||
for line in content.lines() {
|
||||
if line.starts_with("cpu") {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() >= 5 {
|
||||
let cpu = parts[0];
|
||||
let values: Vec<u64> = parts[1..]
|
||||
.iter()
|
||||
.take(7)
|
||||
.filter_map(|s| s.parse().ok())
|
||||
.collect();
|
||||
|
||||
if values.len() >= 4 {
|
||||
let user = values[0] as f64;
|
||||
let nice = values[1] as f64;
|
||||
let system = values[2] as f64;
|
||||
let idle = values[3] as f64;
|
||||
let iowait = values.get(4).copied().unwrap_or(0) as f64;
|
||||
let irq = values.get(5).copied().unwrap_or(0) as f64;
|
||||
let softirq = values.get(6).copied().unwrap_or(0) as f64;
|
||||
|
||||
let total = user + nice + system + idle + iowait + irq + softirq;
|
||||
let active = user + nice + system + irq + softirq;
|
||||
|
||||
if cpu == "cpu" {
|
||||
result.insert("total_user".to_string(), user);
|
||||
result.insert("total_nice".to_string(), nice);
|
||||
result.insert("total_system".to_string(), system);
|
||||
result.insert("total_idle".to_string(), idle);
|
||||
result.insert("total_iowait".to_string(), iowait);
|
||||
result.insert(
|
||||
"total_usage_percent".to_string(),
|
||||
(active / total) * 100.0,
|
||||
);
|
||||
} else {
|
||||
let core = cpu.replace("cpu", "core_");
|
||||
result.insert(format!("{}_user", core), user);
|
||||
result.insert(
|
||||
format!("{}_usage_percent", core),
|
||||
(active / total) * 100.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn memory_info() -> std::io::Result<HashMap<String, u64>> {
|
||||
let content = fs::read_to_string("/proc/meminfo")?;
|
||||
let mut result = HashMap::new();
|
||||
|
||||
for line in content.lines() {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() >= 2 {
|
||||
let key = parts[0].trim_end_matches(':');
|
||||
if let Ok(value) = parts[1].parse::<u64>() {
|
||||
result.insert(key.to_string(), value * 1024);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn disk_stats() -> std::io::Result<HashMap<String, HashMap<String, u64>>> {
|
||||
let content = fs::read_to_string("/proc/diskstats")?;
|
||||
let mut result = HashMap::new();
|
||||
|
||||
for line in content.lines() {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() >= 14 {
|
||||
let device = parts[2].to_string();
|
||||
let mut stats = HashMap::new();
|
||||
stats.insert("reads_completed".to_string(), parts[3].parse().unwrap_or(0));
|
||||
stats.insert("reads_merged".to_string(), parts[4].parse().unwrap_or(0));
|
||||
stats.insert("sectors_read".to_string(), parts[5].parse().unwrap_or(0));
|
||||
stats.insert("reads_ms".to_string(), parts[6].parse().unwrap_or(0));
|
||||
stats.insert(
|
||||
"writes_completed".to_string(),
|
||||
parts[7].parse().unwrap_or(0),
|
||||
);
|
||||
stats.insert("writes_merged".to_string(), parts[8].parse().unwrap_or(0));
|
||||
stats.insert("sectors_written".to_string(), parts[9].parse().unwrap_or(0));
|
||||
stats.insert("writes_ms".to_string(), parts[10].parse().unwrap_or(0));
|
||||
result.insert(device, stats);
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn net_dev() -> std::io::Result<HashMap<String, HashMap<String, u64>>> {
|
||||
let content = fs::read_to_string("/proc/net/dev")?;
|
||||
let mut result = HashMap::new();
|
||||
|
||||
for line in content.lines().skip(2) {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.len() >= 17 {
|
||||
let iface = parts[0].trim_end_matches(':');
|
||||
let mut stats = HashMap::new();
|
||||
stats.insert("rx_bytes".to_string(), parts[1].parse().unwrap_or(0));
|
||||
stats.insert("rx_packets".to_string(), parts[2].parse().unwrap_or(0));
|
||||
stats.insert("rx_errors".to_string(), parts[3].parse().unwrap_or(0));
|
||||
stats.insert("rx_dropped".to_string(), parts[4].parse().unwrap_or(0));
|
||||
stats.insert("tx_bytes".to_string(), parts[9].parse().unwrap_or(0));
|
||||
stats.insert("tx_packets".to_string(), parts[10].parse().unwrap_or(0));
|
||||
stats.insert("tx_errors".to_string(), parts[11].parse().unwrap_or(0));
|
||||
stats.insert("tx_dropped".to_string(), parts[12].parse().unwrap_or(0));
|
||||
result.insert(iface.to_string(), stats);
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
102
crates/pscand-core/src/helpers/sensor.rs
Normal file
102
crates/pscand-core/src/helpers/sensor.rs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct SensorHelper;
|
||||
|
||||
impl SensorHelper {
|
||||
pub fn discover_hwmon() -> std::io::Result<Vec<PathBuf>> {
|
||||
let hwmon_path = PathBuf::from("/sys/class/hwmon");
|
||||
let mut hwmons = Vec::new();
|
||||
|
||||
if hwmon_path.exists() {
|
||||
for entry in fs::read_dir(&hwmon_path)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
hwmons.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(hwmons)
|
||||
}
|
||||
|
||||
pub fn read_hwmon_sensor(hwmon_path: &Path, sensor: &str) -> std::io::Result<Option<f64>> {
|
||||
let sensor_path = hwmon_path.join(sensor);
|
||||
if sensor_path.exists() {
|
||||
let content = fs::read_to_string(sensor_path)?;
|
||||
Ok(content.trim().parse::<f64>().ok())
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hwmon_info(hwmon_path: &Path) -> std::io::Result<HashMap<String, String>> {
|
||||
let mut info = HashMap::new();
|
||||
|
||||
let name_path = hwmon_path.join("name");
|
||||
if name_path.exists() {
|
||||
info.insert(
|
||||
"name".to_string(),
|
||||
fs::read_to_string(name_path)?.trim().to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Ok(files) = fs::read_dir(hwmon_path) {
|
||||
for file in files.flatten() {
|
||||
let filename = file.file_name().to_string_lossy().to_string();
|
||||
if filename.starts_with("temp") && filename.ends_with("_input") {
|
||||
let id = filename
|
||||
.trim_start_matches("temp")
|
||||
.trim_end_matches("_input");
|
||||
if let Some(t) = Self::read_hwmon_sensor(hwmon_path, &filename)
|
||||
.ok()
|
||||
.flatten()
|
||||
{
|
||||
info.insert(format!("temp_{}_celsius", id), format!("{}", t / 1000.0));
|
||||
}
|
||||
}
|
||||
if filename.starts_with("fan") && filename.ends_with("_input") {
|
||||
let id = filename
|
||||
.trim_start_matches("fan")
|
||||
.trim_end_matches("_input");
|
||||
if let Some(f) = Self::read_hwmon_sensor(hwmon_path, &filename)
|
||||
.ok()
|
||||
.flatten()
|
||||
{
|
||||
info.insert(format!("fan_{}_rpm", id), format!("{}", f));
|
||||
}
|
||||
}
|
||||
if filename.starts_with("in") && filename.ends_with("_input") {
|
||||
let id = filename.trim_start_matches("in").trim_end_matches("_input");
|
||||
if let Some(v) = Self::read_hwmon_sensor(hwmon_path, &filename)
|
||||
.ok()
|
||||
.flatten()
|
||||
{
|
||||
info.insert(format!("voltage_{}_mv", id), format!("{}", v / 1000.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
pub fn all_sensors() -> std::io::Result<HashMap<String, HashMap<String, String>>> {
|
||||
let mut all = HashMap::new();
|
||||
|
||||
for hwmon in Self::discover_hwmon()? {
|
||||
if let Ok(info) = Self::hwmon_info(&hwmon) {
|
||||
let name = info.get("name").cloned().unwrap_or_else(|| {
|
||||
hwmon
|
||||
.file_name()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| "unknown".to_string())
|
||||
});
|
||||
all.insert(name, info);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(all)
|
||||
}
|
||||
}
|
||||
45
crates/pscand-core/src/helpers/system.rs
Normal file
45
crates/pscand-core/src/helpers/system.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use std::fs;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct SystemHelper;
|
||||
|
||||
impl SystemHelper {
|
||||
pub fn uptime() -> std::io::Result<Duration> {
|
||||
let uptime_secs = fs::read_to_string("/proc/uptime")?
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.and_then(|s| s.parse::<f64>().ok())
|
||||
.unwrap_or(0.0);
|
||||
Ok(Duration::from_secs_f64(uptime_secs))
|
||||
}
|
||||
|
||||
pub fn boot_id() -> std::io::Result<String> {
|
||||
fs::read_to_string("/proc/sys/kernel/random/boot_id").map(|s| s.trim().to_string())
|
||||
}
|
||||
|
||||
pub fn load_average() -> std::io::Result<(f64, f64, f64)> {
|
||||
let content = fs::read_to_string("/proc/loadavg")?;
|
||||
let mut parts = content.split_whitespace();
|
||||
let load1 = parts
|
||||
.next()
|
||||
.and_then(|s| s.parse::<f64>().ok())
|
||||
.unwrap_or(0.0);
|
||||
let load5 = parts
|
||||
.next()
|
||||
.and_then(|s| s.parse::<f64>().ok())
|
||||
.unwrap_or(0.0);
|
||||
let load15 = parts
|
||||
.next()
|
||||
.and_then(|s| s.parse::<f64>().ok())
|
||||
.unwrap_or(0.0);
|
||||
Ok((load1, load5, load15))
|
||||
}
|
||||
|
||||
pub fn hostname() -> std::io::Result<String> {
|
||||
fs::read_to_string("/proc/sys/kernel/hostname").map(|s| s.trim().to_string())
|
||||
}
|
||||
|
||||
pub fn kernel_version() -> std::io::Result<String> {
|
||||
fs::read_to_string("/proc/sys/kernel/osrelease").map(|s| s.trim().to_string())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue