Replaced all-smi with own implementation

This commit is contained in:
jmb05
2026-11-02 16:20:51 +01:00
parent 00b2b1f64c
commit de1f731e63
7 changed files with 245 additions and 3627 deletions

3619
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,6 @@ serde_json = "1.0.154"
crossbeam-channel = "0.5.17"
jmb-logger-rs = { version = "0.2.2", git = "https://github.com/jmb05/jmb-logger-rs" }
sysinfo = "0.40.0"
all-smi = "0.28.1"
rustsni = "0.2.2"
thiserror = "2.0.21"
pulse = { package = "libpulse-binding", version = "2.30" }

View File

@@ -1,6 +1,7 @@
pub mod audio;
pub mod brightness_control;
mod commands;
mod gpu_utilization;
pub(crate) mod idle;
pub(crate) mod json_socket;
pub mod network;

View File

@@ -0,0 +1,193 @@
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use std::{fs, io, mem};
#[repr(C)]
#[derive(Default)]
struct PerfEventAttr {
type_: u32,
size: u32,
config: u64,
sample_period: u64,
sample_type: u64,
read_format: u64,
flags: u64,
wakeup_events: u32,
bp_type: u32,
config1: u64,
config2: u64,
branch_sample_type: u64,
sample_regs_user: u64,
sample_stack_user: u32,
clockid: i32,
sample_regs_intr: u64,
aux_watermark: u32,
sample_max_stack: u16,
reserved2: u16,
aux_sample_size: u32,
reserved3: u32,
sig_data: u64,
}
const PERF_TYPE_RAW: u32 = 4;
const PERF_FORMAT_TOTAL_TIME_ENABLED: u64 = 1;
const PERF_FORMAT_TOTAL_TIME_RUNNING: u64 = 2;
#[repr(C)]
struct ReadFormat {
value: u64,
time_enabled: u64,
time_running: u64,
}
impl ReadFormat {
fn calculate_utilization(&self, previous: &ReadFormat) -> f64 {
let busy_delta = self.value.saturating_sub(previous.value);
let elapsed_delta = self.time_enabled.saturating_sub(previous.time_enabled);
if elapsed_delta == 0 {
return 0.0;
}
(busy_delta as f64 / elapsed_delta as f64 * 100.0).clamp(0.0, 100.0)
}
}
fn read_number(path: &str) -> io::Result<u64> {
fs::read_to_string(path)?
.trim()
.parse()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
fn read_config(path: &str) -> io::Result<u64> {
let contents = fs::read_to_string(path)?;
let value = contents
.trim()
.strip_prefix("config=")
.unwrap_or(contents.trim());
u64::from_str_radix(value.trim_start_matches("0x"), 16)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
struct I915Engine {
name: String,
fd: OwnedFd,
}
impl Hash for I915Engine {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state)
}
}
impl PartialEq<Self> for I915Engine {
fn eq(&self, other: &Self) -> bool {
self.name.eq(&other.name)
}
}
impl Eq for I915Engine {}
impl I915Engine {
fn open(name: &str) -> io::Result<Self> {
let base = "/sys/bus/event_source/devices/i915";
let pmu_type = read_number(&format!("{base}/type")).unwrap() as u32;
let config = read_config(&format!("{base}/events/{name}")).unwrap();
let mut attr = PerfEventAttr::default();
attr.type_ = pmu_type;
attr.size = size_of::<PerfEventAttr>() as u32;
attr.config = config;
attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING;
// pid = -1 and cpu = -1: system-wide event, if permitted.
let fd = unsafe {
libc::syscall(
libc::SYS_perf_event_open,
&attr as *const PerfEventAttr,
-1i32, // all processes
0i32, // CPU 0
-1i32, // no event group
0u64,
) as i32
};
if fd < 0 {
return Err(io::Error::last_os_error());
}
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
Ok(Self {
name: name.to_owned(),
fd,
})
}
fn read(&self) -> io::Result<ReadFormat> {
let mut result = ReadFormat {
value: 0,
time_enabled: 0,
time_running: 0,
};
let size = mem::size_of::<ReadFormat>();
let read = unsafe {
libc::read(
self.fd.as_raw_fd(),
&mut result as *mut _ as *mut libc::c_void,
size,
)
};
if read != size as isize {
return Err(io::Error::last_os_error());
}
Ok(result)
}
}
pub struct I915UtilizationMonitor {
engines: HashMap<I915Engine, Option<ReadFormat>>,
}
impl I915UtilizationMonitor {
pub fn new() -> Option<Self> {
let mut engines: HashMap<_, Option<ReadFormat>> = HashMap::new();
let Ok(dir) = fs::read_dir("/sys/bus/event_source/devices/i915/events") else {
return None;
};
for entry in dir {
let Ok(entry) = entry else { continue };
let Ok(name) = String::from_utf8(entry.file_name().into_encoded_bytes()) else {
continue;
};
if name.ends_with("-busy") {
let Ok(engine) = I915Engine::open(&name) else {
continue;
};
engines.insert(engine, None);
}
}
Some(Self { engines })
}
pub fn calc_utilization(&mut self) -> u8 {
let mut values: Vec<u8> = Vec::new();
for (engine, sample) in &mut self.engines {
let new_sample = engine.read().unwrap();
if let Some(last) = sample {
values.push(new_sample.calculate_utilization(&last) as u8);
}
let _ = sample.insert(new_sample);
}
values
.iter()
.fold(0u8, |arg0: u8, other: &u8| arg0.max(*other))
}
}

View File

@@ -142,7 +142,7 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
fn init(registration_rx: Receiver<Sender<NetworkState>>) {
let (command_tx, mut command_rx) = tokio::sync::mpsc::unbounded_channel::<NetworkCommand>();
let mut instance = Self {
let instance = Self {
subscribers: vec![],
command_tx,
};

View File

@@ -1,6 +1,6 @@
use crate::LOGGER;
use crate::backend::gpu_utilization::I915UtilizationMonitor;
use crate::backend::{BackendThread, Registrator};
use all_smi::AllSmi;
use battery::State;
use battery::units::Time;
use battery::units::power::watt;
@@ -8,6 +8,7 @@ use battery::units::ratio::percent;
use battery::units::time::millisecond;
use crossbeam_channel::{Receiver, Sender};
use jmb_logger_rs::*;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use sysinfo::{Disks, System};
@@ -19,7 +20,7 @@ pub struct Stats {
pub cpu: Option<u8>,
pub ram: Option<u8>,
pub disks: Vec<(String, u8)>,
pub gpus: Vec<(String, u8)>,
pub gpu: Option<u8>,
pub battery_charge: u8,
pub battery_state: State,
pub battery_draw: f32,
@@ -28,8 +29,6 @@ pub struct Stats {
pub struct StatsBackend {
subscribers: Vec<Sender<Stats>>,
system: Option<System>,
disks: Option<Disks>,
}
impl BackendThread<Stats, ()> for StatsBackend {
@@ -44,23 +43,27 @@ impl BackendThread<Stats, ()> for StatsBackend {
}
fn init(registration_rx: Receiver<Sender<Stats>>) {
thread::spawn(move || {
let mut instance = Self {
subscribers: Vec::new(),
system: System::new().ok(),
disks: Disks::new().ok(),
};
let manager = battery::Manager::new();
let smi = AllSmi::new();
loop {
while let Ok(tx) = registration_rx.try_recv() {
let instance = Arc::new(Mutex::new(Self {
subscribers: Vec::new(),
}));
thread::spawn({
let instance = instance.clone();
move || {
while let Ok(tx) = registration_rx.recv() {
trace!(&LOGGER, "Added Stats Subscriber: {tx:?}");
instance.subscribers.push(tx);
instance.lock().unwrap().subscribers.push(tx);
}
}
});
thread::spawn(move || {
let manager = battery::Manager::new();
let mut system = System::new().ok();
let mut disks = Disks::new().ok();
let mut gpu_monitor = I915UtilizationMonitor::new();
loop {
let mut cpu = None;
let mut ram = None;
if let Some(system) = instance.system.as_mut() {
if let Some(system) = system.as_mut() {
system.refresh_cpu_usage();
system.refresh_memory();
@@ -69,7 +72,7 @@ impl BackendThread<Stats, ()> for StatsBackend {
}
let mut disk_stats = Vec::new();
if let Some(disks) = instance.disks.as_mut() {
if let Some(disks) = disks.as_mut() {
disks.refresh(true);
disk_stats = disks
.iter()
@@ -82,14 +85,7 @@ impl BackendThread<Stats, ()> for StatsBackend {
.collect();
}
let mut gpus: Vec<(String, u8)> = Vec::new();
if let Ok(smi) = &smi {
gpus = smi
.get_gpu_info()
.iter()
.map(|info| (info.name.clone(), info.utilization as u8))
.collect();
}
let gpu = gpu_monitor.as_mut().map(|v| v.calc_utilization());
let mut battery_charge: u8 = 255; // Use > 100 as invalid value
let mut battery_state = State::Unknown;
@@ -128,13 +124,13 @@ impl BackendThread<Stats, ()> for StatsBackend {
cpu,
ram,
disks: disk_stats,
gpus,
gpu,
battery_charge,
battery_state,
battery_draw,
battery_time,
};
instance.publish(msg);
instance.lock().unwrap().publish(msg);
thread::sleep(Duration::from_millis(POLL_INTERVAL));
}

View File

@@ -481,8 +481,8 @@ pub fn build(
}
}
if let Some(gpu) = stats.gpus.into_iter().next() {
gpu_label.set_label(&format!("{}%", gpu.1));
if let Some(gpu) = stats.gpu {
gpu_label.set_label(&format!("{}%", gpu));
}
set_battery_css_classes(&battery_widget, stats.battery_state, stats.battery_charge);