mirror of
https://codeberg.org/icewind/palantir.git
synced 2026-06-03 18:24:08 +02:00
librehardwaremonitor integration
This commit is contained in:
parent
0bb314e5d3
commit
bfb8f20439
8 changed files with 358 additions and 62 deletions
38
src/data.rs
38
src/data.rs
|
|
@ -182,3 +182,41 @@ impl SensorData for DiskUsage {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PowerUsage {
|
||||
pub cpu_uj: u64,
|
||||
pub cpu_packages_uj: Vec<u64>,
|
||||
pub gpu_uj: u64,
|
||||
}
|
||||
|
||||
impl PowerUsage {
|
||||
pub fn write<W: Write>(&self, mut w: W, hostname: &str) {
|
||||
writeln!(
|
||||
&mut w,
|
||||
r#"total_power{{host="{}", device="cpu"}} {:.3}"#,
|
||||
hostname,
|
||||
self.cpu_uj as f64 / 1_000_000.0
|
||||
)
|
||||
.ok();
|
||||
for (i, package) in self.cpu_packages_uj.iter().enumerate() {
|
||||
writeln!(
|
||||
&mut w,
|
||||
r#"package_power{{host="{}", package="{}", device="cpu"}} {:.3}"#,
|
||||
hostname,
|
||||
i,
|
||||
*package as f64 / 1_000_000.0
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
if self.gpu_uj > 0 {
|
||||
writeln!(
|
||||
&mut w,
|
||||
r#"total_power{{host="{}", device="gpu"}} {:.3}"#,
|
||||
hostname,
|
||||
self.gpu_uj as f64 / 1_000_000.0
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::data::PowerUsage;
|
||||
use crate::linux::gpu::gpu_power;
|
||||
use crate::{Error, Result};
|
||||
use std::fmt::Write;
|
||||
|
|
@ -7,44 +8,6 @@ use tracing::warn;
|
|||
|
||||
static CAN_READ: AtomicBool = AtomicBool::new(true);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PowerUsage {
|
||||
cpu_uj: u64,
|
||||
cpu_packages_uj: Vec<u64>,
|
||||
gpu_uj: u64,
|
||||
}
|
||||
|
||||
impl PowerUsage {
|
||||
pub fn write<W: Write>(&self, mut w: W, hostname: &str) {
|
||||
writeln!(
|
||||
&mut w,
|
||||
r#"total_power{{host="{}", device="cpu"}} {:.3}"#,
|
||||
hostname,
|
||||
self.cpu_uj as f64 / 1_000_000.0
|
||||
)
|
||||
.ok();
|
||||
for (i, package) in self.cpu_packages_uj.iter().enumerate() {
|
||||
writeln!(
|
||||
&mut w,
|
||||
r#"package_power{{host="{}", package="{}", device="cpu"}} {:.3}"#,
|
||||
hostname,
|
||||
i,
|
||||
*package as f64 / 1_000_000.0
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
if self.gpu_uj > 0 {
|
||||
writeln!(
|
||||
&mut w,
|
||||
r#"total_power{{host="{}", device="gpu"}} {:.3}"#,
|
||||
hostname,
|
||||
self.gpu_uj as f64 / 1_000_000.0
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn power_usage() -> Result<Option<PowerUsage>> {
|
||||
if !CAN_READ.load(Ordering::Relaxed) {
|
||||
return Ok(None);
|
||||
|
|
|
|||
23
src/main.rs
23
src/main.rs
|
|
@ -1,4 +1,5 @@
|
|||
use bollard::Docker;
|
||||
use clap::Parser;
|
||||
use color_eyre::{Report, Result};
|
||||
use futures_util::pin_mut;
|
||||
use futures_util::StreamExt;
|
||||
|
|
@ -26,6 +27,14 @@ impl From<Report> for ReportRejection {
|
|||
|
||||
impl Reject for ReportRejection {}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Port to listen to
|
||||
#[arg(short, long)]
|
||||
port: Option<u16>,
|
||||
}
|
||||
|
||||
async fn serve_inner(docker: Option<Docker>, sensors: &Sensors) -> Result<String> {
|
||||
let mut metrics = get_metrics(sensors)?;
|
||||
if let Some(docker) = docker {
|
||||
|
|
@ -50,12 +59,16 @@ async fn serve_metrics(docker: Option<Docker>, sensors: Arc<Sensors>) -> Result<
|
|||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
let args = Args::parse();
|
||||
|
||||
let host_port: u16 = dotenvy::var("PORT")
|
||||
.ok()
|
||||
.map(|port| port.parse())
|
||||
.transpose()?
|
||||
.unwrap_or(80);
|
||||
let host_port = match args.port {
|
||||
Some(port) => port,
|
||||
None => dotenvy::var("PORT")
|
||||
.ok()
|
||||
.map(|port| port.parse())
|
||||
.transpose()?
|
||||
.unwrap_or(80),
|
||||
};
|
||||
|
||||
let mdns = dotenvy::var("DISABLE_MDNS").is_ok();
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use once_cell::sync::Lazy;
|
|||
use os_thread_local::ThreadLocal;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Mutex;
|
||||
use std::thread::spawn;
|
||||
use sysinfo::{ComponentExt, DiskExt, NetworkExt, System, SystemExt};
|
||||
|
||||
pub struct Sensors {
|
||||
|
|
@ -26,6 +27,7 @@ static WMI: Lazy<ThreadLocal<WmiSensor>> =
|
|||
|
||||
impl Sensors {
|
||||
pub fn new() -> Result<Sensors> {
|
||||
spawn(wmi::update_power);
|
||||
let mut system = System::new_all();
|
||||
system.refresh_all();
|
||||
println!("{:?}", system);
|
||||
|
|
@ -96,6 +98,9 @@ pub fn get_metrics(sensors: &Sensors) -> Result<String> {
|
|||
if let Some(disk_usage) = WMI.with(|wmi| wmi.disk_usage())? {
|
||||
disk_usage.write(&mut result, &hostname);
|
||||
}
|
||||
let hwmon_data = WMI.with(|wmi| wmi.hwmon())?;
|
||||
hwmon_data.temperature.write(&mut result, &hostname);
|
||||
hwmon_data.power.write(&mut result, &hostname);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
|
|
|||
132
src/win/wmi.rs
132
src/win/wmi.rs
|
|
@ -1,46 +1,54 @@
|
|||
use crate::data::DiskStats;
|
||||
use crate::data::{DiskStats, PowerUsage, Temperatures};
|
||||
use crate::Result;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Mutex;
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
use wmi::{COMLibrary, WMIConnection};
|
||||
|
||||
pub struct WmiSensor {
|
||||
wmi_con: WMIConnection,
|
||||
wmi_hwmon_con: Option<WMIConnection>,
|
||||
}
|
||||
|
||||
impl WmiSensor {
|
||||
pub fn new() -> Result<Self> {
|
||||
let com_con = COMLibrary::new()?;
|
||||
let wmi_con = WMIConnection::new(com_con.into())?;
|
||||
let wmi_con = WMIConnection::new(com_con)?;
|
||||
let wmi_hwmon_con =
|
||||
WMIConnection::with_namespace_path("ROOT\\LibreHardwareMonitor", com_con).ok();
|
||||
|
||||
Ok(WmiSensor { wmi_con })
|
||||
Ok(WmiSensor {
|
||||
wmi_con,
|
||||
wmi_hwmon_con,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn gpu_mem(&self) -> Result<u64> {
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(non_camel_case_types)]
|
||||
struct Win32_PerfFormattedData_GPUPerformanceCounters_GPUAdapterMemory {
|
||||
#[serde(rename = "Win32_PerfFormattedData_GPUPerformanceCounters_GPUAdapterMemory")]
|
||||
struct GPUAdapterMemory {
|
||||
#[serde(rename = "DedicatedUsage")]
|
||||
dedicated_usage: u64,
|
||||
}
|
||||
|
||||
let results: Vec<Win32_PerfFormattedData_GPUPerformanceCounters_GPUAdapterMemory> =
|
||||
self.wmi_con.query()?;
|
||||
let results: Vec<GPUAdapterMemory> = self.wmi_con.query()?;
|
||||
Ok(results.iter().map(|result| result.dedicated_usage).sum())
|
||||
}
|
||||
|
||||
pub fn gpu_usage(&self) -> Result<HashMap<String, u32>> {
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(non_camel_case_types)]
|
||||
struct Win32_PerfFormattedData_GPUPerformanceCounters_GPUEngine {
|
||||
#[serde(rename = "Win32_PerfFormattedData_GPUPerformanceCounters_GPUEngine")]
|
||||
struct GPUEngine {
|
||||
#[serde(rename = "Name")]
|
||||
name: String,
|
||||
#[serde(rename = "UtilizationPercentage")]
|
||||
usage: u32,
|
||||
}
|
||||
|
||||
let results: Vec<Win32_PerfFormattedData_GPUPerformanceCounters_GPUEngine> =
|
||||
self.wmi_con.query()?;
|
||||
let results: Vec<GPUEngine> = self.wmi_con.query()?;
|
||||
|
||||
let mut data = HashMap::default();
|
||||
|
||||
|
|
@ -56,8 +64,8 @@ impl WmiSensor {
|
|||
|
||||
pub fn disk_usage(&self) -> Result<Option<DiskStats>> {
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(non_camel_case_types)]
|
||||
struct Win32_PerfRawData_Counters_FileSystemDiskActivity {
|
||||
#[serde(rename = "Win32_PerfRawData_Counters_FileSystemDiskActivity")]
|
||||
struct FileSystemDiskActivity {
|
||||
#[serde(rename = "Name")]
|
||||
name: String,
|
||||
#[serde(rename = "FileSystemBytesRead")]
|
||||
|
|
@ -66,8 +74,7 @@ impl WmiSensor {
|
|||
written: u64,
|
||||
}
|
||||
|
||||
let results: Vec<Win32_PerfRawData_Counters_FileSystemDiskActivity> =
|
||||
self.wmi_con.query()?;
|
||||
let results: Vec<FileSystemDiskActivity> = self.wmi_con.query()?;
|
||||
for result in results {
|
||||
if result.name == "_Total" {
|
||||
return Ok(Some(DiskStats {
|
||||
|
|
@ -79,4 +86,99 @@ impl WmiSensor {
|
|||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn hwmon(&self) -> Result<HwMonData> {
|
||||
let sensors: Vec<Sensor> = match self.wmi_hwmon_con.as_ref() {
|
||||
Some(wmi) => wmi.query()?,
|
||||
None => Vec::default(),
|
||||
};
|
||||
|
||||
let temperature = Temperatures {
|
||||
cpu: avg_sensors(&sensors, |sensor| {
|
||||
sensor.sensor_type == "Temperature"
|
||||
&& sensor.name.starts_with("CPU Core")
|
||||
&& !sensor.name.contains("Distance")
|
||||
}),
|
||||
gpu: avg_sensors(&sensors, |sensor| {
|
||||
sensor.sensor_type == "Temperature" && sensor.name == "GPU Core"
|
||||
}),
|
||||
};
|
||||
Ok(HwMonData {
|
||||
temperature,
|
||||
power: power(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
#[allow(dead_code)]
|
||||
struct Sensor {
|
||||
identifier: String,
|
||||
name: String,
|
||||
sensor_type: String,
|
||||
value: f32,
|
||||
}
|
||||
|
||||
fn avg_sensors(sensors: &[Sensor], filter: impl Fn(&Sensor) -> bool) -> f32 {
|
||||
let count = sensors.iter().filter(|sensor| filter(*sensor)).count();
|
||||
let total: f32 = sensors
|
||||
.iter()
|
||||
.filter_map(|sensor| filter(sensor).then_some(sensor.value))
|
||||
.sum();
|
||||
total / count as f32
|
||||
}
|
||||
|
||||
pub struct HwMonData {
|
||||
pub temperature: Temperatures,
|
||||
pub power: PowerUsage,
|
||||
}
|
||||
|
||||
static CPU_POWER_UJ: AtomicU64 = AtomicU64::new(0);
|
||||
static GPU_POWER_UJ: AtomicU64 = AtomicU64::new(0);
|
||||
static POWER_LAST_READ: Mutex<Option<Instant>> = Mutex::new(None);
|
||||
|
||||
fn get_power_elapsed() -> Option<Duration> {
|
||||
let mut last_read = POWER_LAST_READ.lock().unwrap();
|
||||
let now = Instant::now();
|
||||
let elapsed = last_read.as_ref().map(|last_read| now - *last_read);
|
||||
*last_read = Some(now);
|
||||
elapsed
|
||||
}
|
||||
|
||||
fn get_sensor(sensors: &[Sensor], ty: &str, name: &str) -> Option<f32> {
|
||||
sensors.iter().find_map(|sensor| {
|
||||
(sensor.sensor_type == ty && sensor.name == name).then_some(sensor.value)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_power() {
|
||||
let Ok(com_con) = COMLibrary::new() else {return;};
|
||||
if let Ok(wmi_con) = WMIConnection::with_namespace_path("ROOT\\LibreHardwareMonitor", com_con) {
|
||||
loop {
|
||||
if let Some(elapsed) = get_power_elapsed() {
|
||||
let Ok(sensors) = wmi_con.query::<Sensor>() else {return;};
|
||||
let sensors: Vec<Sensor> = sensors;
|
||||
let Some(cpu_current_power) = get_sensor(&sensors, "Power", "CPU Package") else {return;};
|
||||
let Some(gpu_current_power) = get_sensor(&sensors, "Power", "GPU Package") else {return;};
|
||||
|
||||
let elapsed_sec = elapsed.as_secs_f32();
|
||||
|
||||
let cpu_power = cpu_current_power * elapsed_sec * 1_000_000.0;
|
||||
let gpu_power = gpu_current_power * elapsed_sec * 1_000_000.0;
|
||||
|
||||
CPU_POWER_UJ.fetch_add(cpu_power as u64, Ordering::SeqCst);
|
||||
GPU_POWER_UJ.fetch_add(gpu_power as u64, Ordering::SeqCst);
|
||||
}
|
||||
sleep(Duration::from_millis(500));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn power() -> PowerUsage {
|
||||
PowerUsage {
|
||||
cpu_uj: CPU_POWER_UJ.load(Ordering::SeqCst),
|
||||
cpu_packages_uj: Vec::default(),
|
||||
gpu_uj: GPU_POWER_UJ.load(Ordering::SeqCst),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue