cpu time sensor

This commit is contained in:
Robin Appelman 2023-05-03 21:31:56 +02:00
commit 5dd7380e2b
5 changed files with 192 additions and 77 deletions

128
Cargo.lock generated
View file

@ -34,7 +34,7 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [ dependencies = [
"winapi", "winapi 0.3.9",
] ]
[[package]] [[package]]
@ -113,7 +113,7 @@ dependencies = [
"tokio", "tokio",
"tokio-util 0.7.3", "tokio-util 0.7.3",
"url", "url",
"winapi", "winapi 0.3.9",
] ]
[[package]] [[package]]
@ -213,7 +213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b37feaa84e6861e00a1f5e5aa8da3ee56d605c9992d33e082786754828e20865" checksum = "b37feaa84e6861e00a1f5e5aa8da3ee56d605c9992d33e082786754828e20865"
dependencies = [ dependencies = [
"nix", "nix",
"winapi", "winapi 0.3.9",
] ]
[[package]] [[package]]
@ -247,7 +247,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim 0.9.3", "strsim 0.9.3",
"syn", "syn 1.0.98",
] ]
[[package]] [[package]]
@ -261,7 +261,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim 0.10.0", "strsim 0.10.0",
"syn", "syn 1.0.98",
] ]
[[package]] [[package]]
@ -272,7 +272,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [ dependencies = [
"darling_core 0.10.2", "darling_core 0.10.2",
"quote", "quote",
"syn", "syn 1.0.98",
] ]
[[package]] [[package]]
@ -283,7 +283,7 @@ checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [ dependencies = [
"darling_core 0.13.4", "darling_core 0.13.4",
"quote", "quote",
"syn", "syn 1.0.98",
] ]
[[package]] [[package]]
@ -311,6 +311,27 @@ version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi 0.3.9",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]] [[package]]
name = "eyre" name = "eyre"
version = "0.6.8" version = "0.6.8"
@ -370,7 +391,7 @@ checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.98",
] ]
[[package]] [[package]]
@ -500,7 +521,7 @@ checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
dependencies = [ dependencies = [
"libc", "libc",
"match_cfg", "match_cfg",
"winapi", "winapi 0.3.9",
] ]
[[package]] [[package]]
@ -604,7 +625,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9" checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9"
dependencies = [ dependencies = [
"libc", "libc",
"winapi", "winapi 0.3.9",
] ]
[[package]] [[package]]
@ -638,6 +659,16 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
[[package]]
name = "kernel32-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
dependencies = [
"winapi 0.2.8",
"winapi-build",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -657,7 +688,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"winapi", "winapi 0.3.9",
] ]
[[package]] [[package]]
@ -858,6 +889,8 @@ dependencies = [
"nvml-wrapper", "nvml-wrapper",
"once_cell", "once_cell",
"regex", "regex",
"sysconf",
"thiserror",
"tokio", "tokio",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
@ -887,7 +920,7 @@ checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.98",
] ]
[[package]] [[package]]
@ -910,9 +943,9 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.40" version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -925,9 +958,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.20" version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -992,7 +1025,7 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [ dependencies = [
"winapi", "winapi 0.3.9",
] ]
[[package]] [[package]]
@ -1036,7 +1069,7 @@ checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.98",
] ]
[[package]] [[package]]
@ -1081,7 +1114,7 @@ dependencies = [
"darling 0.13.4", "darling 0.13.4",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.98",
] ]
[[package]] [[package]]
@ -1136,7 +1169,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
dependencies = [ dependencies = [
"libc", "libc",
"winapi", "winapi 0.3.9",
] ]
[[package]] [[package]]
@ -1168,6 +1201,29 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "syn"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "sysconf"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59e93f5d45535f49b6a05ef7ac2f0f795d28de494cf53a512751602c9849bea3"
dependencies = [
"errno",
"kernel32-sys",
"libc",
"winapi 0.2.8",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.3.0" version = "3.3.0"
@ -1179,27 +1235,27 @@ dependencies = [
"libc", "libc",
"redox_syscall", "redox_syscall",
"remove_dir_all", "remove_dir_all",
"winapi", "winapi 0.3.9",
] ]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.31" version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.31" version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.15",
] ]
[[package]] [[package]]
@ -1241,7 +1297,7 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"winapi", "winapi 0.3.9",
] ]
[[package]] [[package]]
@ -1252,7 +1308,7 @@ checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.98",
] ]
[[package]] [[package]]
@ -1334,7 +1390,7 @@ checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.98",
] ]
[[package]] [[package]]
@ -1528,6 +1584,12 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@ -1538,6 +1600,12 @@ dependencies = [
"winapi-x86_64-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu",
] ]
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
[[package]] [[package]]
name = "winapi-i686-pc-windows-gnu" name = "winapi-i686-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"
@ -1602,5 +1670,5 @@ dependencies = [
"darling 0.10.2", "darling 0.10.2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.98",
] ]

View file

@ -22,6 +22,8 @@ tracing = "0.1.35"
tracing-subscriber = "0.3.11" tracing-subscriber = "0.3.11"
nvml-wrapper = "0.8.0" nvml-wrapper = "0.8.0"
if-addrs = "0.7.0" if-addrs = "0.7.0"
sysconf = "0.3.4"
thiserror = "1.0.40"
[dev-dependencies] [dev-dependencies]
iai = "0.1.1" iai = "0.1.1"

View file

@ -1,5 +1,5 @@
use crate::{Error, Result};
use ahash::{AHashSet, AHasher}; use ahash::{AHashSet, AHasher};
use color_eyre::{Report, Result};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use std::ffi::CString; use std::ffi::CString;
@ -85,7 +85,7 @@ fn statvfs(path: &str) -> Result<libc::statvfs> {
let vfs = unsafe { vfs.assume_init() }; let vfs = unsafe { vfs.assume_init() };
Ok(vfs) Ok(vfs)
} else { } else {
Err(Report::msg("Failed to stat vfs")) Err(Error::StatVfs)
} }
} }

View file

@ -9,14 +9,46 @@ use crate::disk::disk_usage;
use crate::disk::zfs::pools; use crate::disk::zfs::pools;
use crate::disk::*; use crate::disk::*;
use crate::sensors::*; use crate::sensors::*;
use color_eyre::Result; use std::ffi::NulError;
use std::fmt::Write; use std::fmt::Write;
use std::io; use std::io;
use std::num::{ParseFloatError, ParseIntError};
use std::str::Utf8Error;
use sysconf::SysconfError;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Io(#[from] io::Error),
#[error("Unsupported sysconf")]
Sysconf(SysconfError),
#[error("Non UTF8 hostname")]
InvalidHostName,
#[error(transparent)]
InvalidIntData(#[from] ParseIntError),
#[error(transparent)]
InvalidFloatData(#[from] ParseFloatError),
#[error(transparent)]
InvalidStringData(#[from] Utf8Error),
#[error(transparent)]
InvalidCStringData(#[from] NulError),
#[error("Failed to query vfs stats")]
StatVfs,
}
impl From<SysconfError> for Error {
fn from(value: SysconfError) -> Self {
Error::Sysconf(value)
}
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub fn get_metrics() -> Result<String> { pub fn get_metrics() -> Result<String> {
let disk_usage = disk_usage()?; let disk_usage = disk_usage()?;
let disks = disk_stats()?; let disks = disk_stats()?;
let cpu = cpu_time()?; let mut cpu_source = CpuTimeSource::new()?;
let cpu = cpu_source.read()?;
let hostname = hostname()?; let hostname = hostname()?;
let memory = memory()?; let memory = memory()?;
let mut temp_source = TemperatureSource::new()?; let mut temp_source = TemperatureSource::new()?;
@ -24,8 +56,8 @@ pub fn get_metrics() -> Result<String> {
let pools = pools(); let pools = pools();
let networks = network_stats()?; let networks = network_stats()?;
let mut result = String::with_capacity(256); let mut result = String::with_capacity(256);
writeln!(&mut result, "cpu_time{{host=\"{}\"}} {:.3}", hostname, cpu).ok();
cpu.write(&mut result, &hostname);
memory.write(&mut result, &hostname); memory.write(&mut result, &hostname);
for pool in pools { for pool in pools {
@ -112,5 +144,5 @@ pub trait SensorData {
pub trait SensorSource { pub trait SensorSource {
type Data: SensorData; type Data: SensorData;
fn read(&mut self) -> io::Result<Self::Data>; fn read(&mut self) -> Result<Self::Data>;
} }

View file

@ -1,12 +1,12 @@
use crate::disk::IoStats; use crate::disk::IoStats;
use crate::hwmon::{Device, FileSource}; use crate::hwmon::{Device, FileSource};
use crate::{SensorData, SensorSource}; use crate::{Error, Result, SensorData, SensorSource};
use color_eyre::{Report, Result};
use std::array::IntoIter; use std::array::IntoIter;
use std::fmt::Write; use std::fmt::Write;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader, ErrorKind, Seek};
use sysconf::{sysconf, SysconfVariable};
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Temperatures { pub struct Temperatures {
@ -69,7 +69,7 @@ pub struct TemperatureSource {
} }
impl TemperatureSource { impl TemperatureSource {
pub fn new() -> io::Result<TemperatureSource> { pub fn new() -> Result<TemperatureSource> {
let mut cpu_sensors = Vec::new(); let mut cpu_sensors = Vec::new();
let mut gpu_sensors = Vec::new(); let mut gpu_sensors = Vec::new();
@ -117,7 +117,7 @@ fn average_sensors(sensors: &mut [FileSource]) -> f32 {
impl SensorSource for TemperatureSource { impl SensorSource for TemperatureSource {
type Data = Temperatures; type Data = Temperatures;
fn read(&mut self) -> io::Result<Self::Data> { fn read(&mut self) -> Result<Self::Data> {
Ok(Temperatures { Ok(Temperatures {
cpu: average_sensors(&mut self.cpu_sensors) / 1000.0, cpu: average_sensors(&mut self.cpu_sensors) / 1000.0,
gpu: average_sensors(&mut self.gpu_sensors) / 1000.0, gpu: average_sensors(&mut self.gpu_sensors) / 1000.0,
@ -150,21 +150,54 @@ pub fn memory() -> Result<Memory> {
Ok(mem) Ok(mem)
} }
pub fn cpu_time() -> Result<f32> { pub struct CpuTime(f32);
let stat = BufReader::new(File::open("/proc/stat")?);
let line = stat impl SensorData for CpuTime {
.lines() fn write<W: Write>(&self, mut w: W, hostname: &str) {
.next() writeln!(w, "cpu_time{{host=\"{}\"}} {:.3}", hostname, self.0).ok();
.ok_or_else(|| Report::msg("Invalid /proc/stat"))??; }
}
pub struct CpuTimeSource {
source: BufReader<File>,
buff: Vec<u8>,
cpu_count: f32,
}
impl CpuTimeSource {
pub fn new() -> Result<CpuTimeSource> {
Ok(CpuTimeSource {
source: BufReader::new(File::open("/proc/stat")?),
buff: Vec::new(),
cpu_count: sysconf(SysconfVariable::ScNprocessorsOnln)? as f32,
})
}
}
impl SensorSource for CpuTimeSource {
type Data = CpuTime;
fn read(&mut self) -> Result<Self::Data> {
self.buff.clear();
self.source.rewind()?;
self.source.read_until(b'\n', &mut self.buff)?;
let line = std::str::from_utf8(&self.buff)?;
let mut parts = line.split_ascii_whitespace(); let mut parts = line.split_ascii_whitespace();
if let (_cpu, Some(user), _nice, Some(system)) = if let (_cpu, Some(user), _nice, Some(system)) =
(parts.next(), parts.next(), parts.next(), parts.next()) (parts.next(), parts.next(), parts.next(), parts.next())
{ {
let user: f32 = user.parse()?; let user: f32 = user.parse()?;
let system: f32 = system.parse()?; let system: f32 = system.parse()?;
Ok((user + system) / (clock_ticks()? as f32) / (cpu_count()? as f32)) let clock_ticks = sysconf(SysconfVariable::ScClkTck)?;
Ok(CpuTime(
(user + system) / (clock_ticks as f32) / self.cpu_count,
))
} else { } else {
Err(Report::msg("Invalid /proc/stat")) Err(io::Error::from(ErrorKind::InvalidData).into())
}
} }
} }
@ -216,25 +249,5 @@ pub fn network_stats() -> Result<impl Iterator<Item = IoStats>> {
pub fn hostname() -> Result<String> { pub fn hostname() -> Result<String> {
hostname::get()? hostname::get()?
.into_string() .into_string()
.map_err(|_| Report::msg("non utf8 hostname")) .map_err(|_| Error::InvalidHostName)
}
pub fn clock_ticks() -> Result<u64> {
let result = unsafe { libc::sysconf(libc::_SC_CLK_TCK) };
if result > 0 {
Ok(result as u64)
} else {
Err(Report::msg("Failed to get clock ticks"))
}
}
fn cpu_count() -> Result<u64> {
let result = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) };
if result < 0 {
Err(Report::msg("Failed to get cpu count"))
} else {
Ok(result as u64)
}
} }