prepare for windows impl

This commit is contained in:
Robin Appelman 2023-05-17 22:55:01 +02:00
commit d12b70d11e
16 changed files with 550 additions and 279 deletions

131
src/data.rs Normal file
View file

@ -0,0 +1,131 @@
use crate::SensorData;
use std::array::IntoIter;
use std::fmt::Write;
#[derive(Debug, Clone, Default)]
pub struct Temperatures {
pub cpu: f32,
pub gpu: f32,
}
impl IntoIterator for Temperatures {
type Item = (&'static str, f32);
type IntoIter = IntoIter<Self::Item, 2>;
fn into_iter(self) -> Self::IntoIter {
[("cpu", self.cpu), ("gpu", self.gpu)].into_iter()
}
}
impl SensorData for Temperatures {
fn write<W: Write>(&self, mut w: W, hostname: &str) {
for (label, temp) in self.clone() {
if temp != 0.0 {
writeln!(
&mut w,
"temperature{{host=\"{}\", sensor=\"{}\"}} {:.1}",
hostname, label, temp
)
.ok();
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Memory {
pub total: u64,
pub free: u64,
pub available: u64,
}
impl SensorData for Memory {
fn write<W: Write>(&self, mut w: W, hostname: &str) {
writeln!(
&mut w,
"memory_total{{host=\"{}\"}} {}",
hostname, self.total
)
.ok();
writeln!(
&mut w,
"memory_available{{host=\"{}\"}} {}",
hostname, self.available
)
.ok();
writeln!(&mut w, "memory_free{{host=\"{}\"}} {}", hostname, self.free).ok();
}
}
#[derive(Debug, Clone, Default)]
pub struct GpuMemory {
pub total: u64,
pub free: u64,
}
impl SensorData for GpuMemory {
fn write<W: Write>(&self, mut w: W, hostname: &str) {
writeln!(
&mut w,
"gpu_memory_total{{host=\"{}\"}} {}",
hostname, self.total
)
.ok();
writeln!(
&mut w,
"gpu_memory_free{{host=\"{}\"}} {}",
hostname, self.free
)
.ok();
}
}
pub struct CpuTime(pub f32);
impl SensorData for CpuTime {
fn write<W: Write>(&self, mut w: W, hostname: &str) {
writeln!(w, "cpu_time{{host=\"{}\"}} {:.3}", hostname, self.0).ok();
}
}
#[derive(Debug, Clone, Default)]
pub struct NetStats {
pub interface: String,
pub bytes_sent: u64,
pub bytes_received: u64,
}
impl SensorData for NetStats {
fn write<W: Write>(&self, mut w: W, hostname: &str) {
if self.bytes_received > 0 || self.bytes_sent > 0 {
writeln!(
&mut w,
"net_sent{{host=\"{}\", network=\"{}\"}} {}",
hostname, self.interface, self.bytes_sent
)
.ok();
writeln!(
&mut w,
"net_received{{host=\"{}\", network=\"{}\"}} {}",
hostname, self.interface, self.bytes_received
)
.ok();
}
}
}
pub struct GpuUsage {
pub system: &'static str,
pub usage: u32,
}
impl GpuUsage {
pub fn write<W: Write>(&self, mut w: W, hostname: &str) {
writeln!(
&mut w,
r#"gpu_usage{{host="{}", system="{}"}} {:.3}"#,
hostname, self.system, self.usage,
)
.ok();
}
}

View file

@ -1,27 +1,28 @@
pub mod disk;
pub mod docker;
pub mod gpu;
pub mod hwmon;
pub mod power;
pub mod sensors;
use crate::disk::zfs::pools;
use crate::disk::*;
use crate::sensors::*;
use std::ffi::NulError;
use std::fmt::Write;
use std::io;
use std::num::{ParseFloatError, ParseIntError};
use std::str::Utf8Error;
use std::sync::Mutex;
use sysconf::SysconfError;
use std::string::FromUtf8Error;
pub mod data;
pub mod docker;
#[cfg(not(feature = "sysinfo"))]
mod linux;
#[cfg(feature = "sysinfo")]
mod sys;
#[cfg(not(feature = "sysinfo"))]
pub use linux::{get_metrics, Sensors};
#[cfg(feature = "sysinfo")]
pub use sys::{get_metrics, Sensors};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Io(#[from] io::Error),
#[error("Unsupported sysconf")]
Sysconf(SysconfError),
Io(#[from] std::io::Error),
#[error("{0}")]
Other(String),
#[error("Non UTF8 hostname")]
InvalidHostName,
#[error(transparent)]
@ -36,98 +37,14 @@ pub enum Error {
StatVfs,
}
impl From<SysconfError> for Error {
fn from(value: SysconfError) -> Self {
Error::Sysconf(value)
impl From<FromUtf8Error> for Error {
fn from(err: FromUtf8Error) -> Self {
Self::InvalidStringData(err.utf8_error())
}
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub struct Sensors {
pub hostname: String,
cpu: Mutex<CpuTimeSource>,
temp: Mutex<TemperatureSource>,
net: Mutex<NetworkSource>,
mem: Mutex<MemorySource>,
disk_stats: Mutex<DiskStatSource>,
disk_usage: Mutex<DiskUsageSource>,
}
impl Sensors {
pub fn new() -> Result<Sensors> {
Ok(Sensors {
hostname: hostname()?,
cpu: Mutex::new(CpuTimeSource::new()?),
temp: Mutex::new(TemperatureSource::new()?),
net: Mutex::new(NetworkSource::new()?),
mem: Mutex::new(MemorySource::new()?),
disk_stats: Mutex::new(DiskStatSource::new()?),
disk_usage: Mutex::new(DiskUsageSource::new()?),
})
}
}
pub fn get_metrics(sensors: &Sensors) -> Result<String> {
let hostname = &sensors.hostname;
let mut disk_source = sensors.disk_stats.lock().unwrap();
let mut disk_usage_source = sensors.disk_usage.lock().unwrap();
let disks = disk_source.read()?;
let disk_usage = disk_usage_source.read()?;
let cpu = sensors.cpu.lock().unwrap().read()?;
let memory = sensors.mem.lock().unwrap().read()?;
let temperatures = sensors.temp.lock().unwrap().read()?;
let mut net = sensors.net.lock().unwrap();
let networks = net.read()?;
let pools = pools();
let mut result = String::with_capacity(256);
cpu.write(&mut result, &hostname);
memory.write(&mut result, &hostname);
for pool in pools {
writeln!(
&mut result,
"zfs_pool_size{{host=\"{}\", pool=\"{}\"}} {}",
hostname, pool.name, pool.size
)
.ok();
writeln!(
&mut result,
"zfs_pool_free{{host=\"{}\", pool=\"{}\"}} {}",
hostname, pool.name, pool.free
)
.ok();
}
for network in networks {
if let Ok(network) = network {
network.write(&mut result, &hostname);
}
}
for disk in disks {
if let Ok(disk) = disk {
disk.write(&mut result, hostname);
}
}
for disk in disk_usage {
if let Ok(disk) = disk {
disk.write(&mut result, hostname);
}
}
for (label, temp) in temperatures {
if temp != 0.0 {
writeln!(
&mut result,
"temperature{{host=\"{}\", sensor=\"{}\"}} {:.1}",
hostname, label, temp
)
.ok();
}
}
Ok(result)
}
pub trait SensorData {
/// Write sensor data in prometheus compatible format
fn write<W: Write>(&self, w: W, hostname: &str);
@ -147,3 +64,9 @@ pub trait MultiSensorSource {
fn read(&mut self) -> Result<Self::Iter<'_>>;
}
pub fn hostname() -> Result<String> {
hostname::get()?
.into_string()
.map_err(|_| Error::InvalidHostName)
}

View file

@ -1,5 +1,5 @@
use crate::disk::DiskUsage;
use color_eyre::Result;
use crate::linux::disk::DiskUsage;
use crate::Result;
use std::fmt::Write;
use std::fs::read_to_string;
use std::process::Command;

View file

@ -1,6 +1,5 @@
use crate::hwmon::FileSource;
use crate::sensors::Memory;
use std::fmt::Write;
use crate::data::{GpuMemory, GpuUsage};
use crate::linux::hwmon::FileSource;
use std::fs::read_to_string;
use std::str::FromStr;
use std::sync::atomic::{AtomicU64, Ordering};
@ -10,61 +9,23 @@ use std::time::{Duration, Instant};
pub mod nvidia;
pub fn gpu_metrics<W: Write>(mut out: W, hostname: &str) {
if let Some(memory) = memory() {
writeln!(
&mut out,
"gpu_memory_total{{host=\"{}\"}} {}",
hostname, memory.total
)
.ok();
writeln!(
&mut out,
"gpu_memory_free{{host=\"{}\"}} {}",
hostname, memory.free
)
.ok();
}
for usage in utilization() {
usage.write(&mut out, hostname);
}
}
fn read_num<T: FromStr>(path: &str) -> Option<T> {
read_to_string(path).ok()?.trim().parse().ok()
}
pub fn memory() -> Option<Memory> {
pub fn memory() -> Option<GpuMemory> {
if let Some(nv_mem) = nvidia::memory() {
return Some(nv_mem);
}
// 1 gpu should be enough for everyone
let used = read_num::<u64>("/sys/class/drm/card0/device/mem_info_vram_used")?;
let total = read_num("/sys/class/drm/card0/device/mem_info_vram_total")?;
Some(Memory {
Some(GpuMemory {
total,
free: total - used,
available: total - used,
})
}
pub struct GpuUsage {
pub system: &'static str,
pub usage: u32,
}
impl GpuUsage {
pub fn write<W: Write>(&self, mut w: W, hostname: &str) {
writeln!(
&mut w,
r#"gpu_usage{{host="{}", system="{}"}} {:.3}"#,
hostname, self.system, self.usage,
)
.ok();
}
}
pub fn utilization() -> impl Iterator<Item = GpuUsage> {
let nv_usage = nvidia::utilization();

View file

@ -1,5 +1,4 @@
use crate::gpu::GpuUsage;
use crate::sensors::Memory;
use crate::data::{GpuMemory, GpuUsage};
use nvml_wrapper::enum_wrappers::device::TemperatureSensor;
use nvml_wrapper::{Device, Nvml};
use once_cell::sync::Lazy;
@ -22,12 +21,11 @@ pub fn power() -> Option<u64> {
.map(|mj| mj * 1_000)
}
pub fn memory() -> Option<Memory> {
pub fn memory() -> Option<GpuMemory> {
let mem = device()?.memory_info().ok()?;
Some(Memory {
Some(GpuMemory {
total: mem.total,
free: mem.free,
available: mem.free,
})
}

123
src/linux/mod.rs Normal file
View file

@ -0,0 +1,123 @@
pub mod disk;
pub mod gpu;
pub mod hwmon;
pub mod power;
pub mod sensors;
use self::disk::zfs::pools;
use self::disk::*;
use self::sensors::*;
use crate::linux::disk::zfs::arcstats;
use crate::linux::gpu::{update_gpu_power, utilization};
use crate::linux::power::power_usage;
use crate::{hostname, Error, MultiSensorSource, Result, SensorData, SensorSource};
use std::fmt::Write;
use std::sync::Mutex;
use sysconf::SysconfError;
impl From<SysconfError> for Error {
fn from(_: SysconfError) -> Self {
Error::Other("Unsupported sysconf".into())
}
}
pub struct Sensors {
pub hostname: String,
cpu: Mutex<CpuTimeSource>,
temp: Mutex<TemperatureSource>,
net: Mutex<NetworkSource>,
mem: Mutex<MemorySource>,
disk_stats: Mutex<DiskStatSource>,
disk_usage: Mutex<DiskUsageSource>,
}
impl Sensors {
pub fn new() -> Result<Sensors> {
std::thread::spawn(update_gpu_power);
Ok(Sensors {
hostname: hostname()?,
cpu: Mutex::new(CpuTimeSource::new()?),
temp: Mutex::new(TemperatureSource::new()?),
net: Mutex::new(NetworkSource::new()?),
mem: Mutex::new(MemorySource::new()?),
disk_stats: Mutex::new(DiskStatSource::new()?),
disk_usage: Mutex::new(DiskUsageSource::new()?),
})
}
}
pub fn get_metrics(sensors: &Sensors) -> Result<String> {
let hostname = &sensors.hostname;
let mut disk_source = sensors.disk_stats.lock().unwrap();
let mut disk_usage_source = sensors.disk_usage.lock().unwrap();
let disks = disk_source.read()?;
let disk_usage = disk_usage_source.read()?;
let cpu = sensors.cpu.lock().unwrap().read()?;
let memory = sensors.mem.lock().unwrap().read()?;
let temperatures = sensors.temp.lock().unwrap().read()?;
let mut net = sensors.net.lock().unwrap();
let networks = net.read()?;
let pools = pools();
let mut result = String::with_capacity(256);
cpu.write(&mut result, &hostname);
memory.write(&mut result, &hostname);
for pool in pools {
writeln!(
&mut result,
"zfs_pool_size{{host=\"{}\", pool=\"{}\"}} {}",
hostname, pool.name, pool.size
)
.ok();
writeln!(
&mut result,
"zfs_pool_free{{host=\"{}\", pool=\"{}\"}} {}",
hostname, pool.name, pool.free
)
.ok();
}
for network in networks {
if let Ok(network) = network {
network.write(&mut result, &hostname);
}
}
for disk in disks {
if let Ok(disk) = disk {
disk.write(&mut result, hostname);
}
}
for disk in disk_usage {
if let Ok(disk) = disk {
disk.write(&mut result, hostname);
}
}
for (label, temp) in temperatures {
if temp != 0.0 {
writeln!(
&mut result,
"temperature{{host=\"{}\", sensor=\"{}\"}} {:.1}",
hostname, label, temp
)
.ok();
}
}
if let Some(power) = power_usage()? {
power.write(&mut result, &sensors.hostname);
}
if let Some(arc) = arcstats()? {
arc.write(&mut result, &sensors.hostname);
}
if let Some(memory) = gpu::memory() {
memory.write(&mut result, &sensors.hostname)
}
for usage in utilization() {
usage.write(&mut result, &sensors.hostname);
}
Ok(result)
}

View file

@ -1,5 +1,5 @@
use crate::gpu::gpu_power;
use color_eyre::{Report, Result};
use crate::linux::gpu::gpu_power;
use crate::{Error, Result};
use std::fmt::Write;
use std::fs::{read_dir, read_to_string};
use std::sync::atomic::{AtomicBool, Ordering};
@ -63,7 +63,7 @@ pub fn power_usage() -> Result<Option<PowerUsage>> {
if package
.file_name()
.to_str()
.ok_or_else(|| Report::msg("Invalid name"))?
.ok_or_else(|| Error::Other("Invalid name".into()))?
.starts_with("intel-rapl")
{
let mut package_path = package.path();
@ -86,7 +86,7 @@ pub fn power_usage() -> Result<Option<PowerUsage>> {
}
usage.gpu_uj = gpu_power();
if let Some(nvidia_power) = crate::gpu::nvidia::power() {
if let Some(nvidia_power) = crate::linux::gpu::nvidia::power() {
usage.gpu_uj = nvidia_power;
}

View file

@ -1,67 +1,11 @@
use crate::hwmon::{Device, FileSource};
use crate::{Error, MultiSensorSource, Result, SensorData, SensorSource};
use std::array::IntoIter;
use std::fmt::Write;
use crate::data::{CpuTime, Memory, NetStats, Temperatures};
use crate::linux::hwmon::{Device, FileSource};
use crate::{Error, MultiSensorSource, Result, SensorSource};
use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader, ErrorKind, Read, Seek};
use sysconf::{sysconf, SysconfVariable};
#[derive(Debug, Clone, Default)]
pub struct Temperatures {
cpu: f32,
gpu: f32,
}
impl IntoIterator for Temperatures {
type Item = (&'static str, f32);
type IntoIter = IntoIter<Self::Item, 2>;
fn into_iter(self) -> Self::IntoIter {
[("cpu", self.cpu), ("gpu", self.gpu)].into_iter()
}
}
impl SensorData for Temperatures {
fn write<W: Write>(&self, mut w: W, hostname: &str) {
for (label, temp) in self.clone() {
if temp != 0.0 {
writeln!(
&mut w,
"temperature{{host=\"{}\", sensor=\"{}\"}} {:.1}",
hostname, label, temp
)
.ok();
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Memory {
pub total: u64,
pub free: u64,
pub available: u64,
}
impl SensorData for Memory {
fn write<W: Write>(&self, mut w: W, hostname: &str) {
writeln!(
&mut w,
"memory_total{{host=\"{}\"}} {}",
hostname, self.total
)
.ok();
writeln!(
&mut w,
"memory_available{{host=\"{}\"}} {}",
hostname, self.available
)
.ok();
writeln!(&mut w, "memory_free{{host=\"{}\"}} {}", hostname, self.free).ok();
}
}
pub struct TemperatureSource {
cpu_sensors: Vec<FileSource>,
gpu_sensors: Vec<FileSource>,
@ -117,10 +61,16 @@ impl SensorSource for TemperatureSource {
type Data = Temperatures;
fn read(&mut self) -> Result<Self::Data> {
Ok(Temperatures {
let mut result = Temperatures {
cpu: average_sensors(&mut self.cpu_sensors) / 1000.0,
gpu: average_sensors(&mut self.gpu_sensors) / 1000.0,
})
};
if let Some(gpu) = super::gpu::nvidia::temperature() {
result.gpu = gpu;
}
Ok(result)
}
}
@ -165,14 +115,6 @@ impl SensorSource for MemorySource {
}
}
pub struct CpuTime(f32);
impl SensorData for CpuTime {
fn write<W: Write>(&self, mut w: W, hostname: &str) {
writeln!(w, "cpu_time{{host=\"{}\"}} {:.3}", hostname, self.0).ok();
}
}
pub struct CpuTimeSource {
source: BufReader<File>,
buff: Vec<u8>,
@ -216,32 +158,6 @@ impl SensorSource for CpuTimeSource {
}
}
#[derive(Debug, Clone, Default)]
pub struct NetStats {
pub interface: String,
pub bytes_sent: u64,
pub bytes_received: u64,
}
impl SensorData for NetStats {
fn write<W: Write>(&self, mut w: W, hostname: &str) {
if self.bytes_received > 0 || self.bytes_sent > 0 {
writeln!(
&mut w,
"net_sent{{host=\"{}\", network=\"{}\"}} {}",
hostname, self.interface, self.bytes_sent
)
.ok();
writeln!(
&mut w,
"net_received{{host=\"{}\", network=\"{}\"}} {}",
hostname, self.interface, self.bytes_received
)
.ok();
}
}
}
pub struct NetworkSource {
source: File,
buff: String,
@ -326,9 +242,3 @@ impl<'a> Iterator for NetworkStatParser<'a> {
Some(NetworkSource::parse_line(line))
}
}
pub fn hostname() -> Result<String> {
hostname::get()?
.into_string()
.map_err(|_| Error::InvalidHostName)
}

View file

@ -3,10 +3,7 @@ use color_eyre::{Report, Result};
use futures_util::pin_mut;
use futures_util::StreamExt;
use libmdns::Responder;
use palantir::disk::zfs::arcstats;
use palantir::docker::{get_docker, stat, Container};
use palantir::gpu::{gpu_metrics, update_gpu_power};
use palantir::power::power_usage;
use palantir::{get_metrics, Sensors};
use std::sync::Arc;
use std::time::Duration;
@ -39,13 +36,6 @@ async fn serve_inner(docker: Option<Docker>, sensors: &Sensors) -> Result<String
container.write(&mut metrics, &sensors.hostname);
}
}
if let Some(power) = power_usage()? {
power.write(&mut metrics, &sensors.hostname);
}
if let Some(arc) = arcstats()? {
arc.write(&mut metrics, &sensors.hostname);
}
gpu_metrics(&mut metrics, &sensors.hostname);
Ok(metrics)
}
@ -86,8 +76,6 @@ async fn main() -> Result<()> {
));
}
std::thread::spawn(update_gpu_power);
let metrics = warp::path!("metrics")
.and(docker)
.and(sensors)

33
src/sys/mod.rs Normal file
View file

@ -0,0 +1,33 @@
use crate::data::Temperatures;
use crate::hostname;
use crate::Result;
use std::fmt::Write;
use sysinfo::{ComponentExt, System, SystemExt};
pub struct Sensors {
pub hostname: String,
}
impl Sensors {
pub fn new() -> Result<Sensors> {
let s = System::new_all();
for component in s.components() {
println!("{} :{}°C", component.label(), component.temperature());
}
Ok(Sensors {
hostname: hostname()?,
})
}
}
fn temps() -> Temperatures {
Temperatures { cpu: 0.0, gpu: 0.0 }
}
pub fn get_metrics(sensors: &Sensors) -> Result<String> {
let hostname = &sensors.hostname;
let mut result = String::with_capacity(256);
Ok(result)
}