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

119
Cargo.lock generated
View file

@ -165,6 +165,12 @@ dependencies = [
"tracing-error", "tracing-error",
] ]
[[package]]
name = "core-foundation-sys"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.7" version = "0.2.7"
@ -174,6 +180,49 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crossbeam-channel"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset 0.8.0",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -280,6 +329,12 @@ 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 = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.32" version = "0.8.32"
@ -699,6 +754,15 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "memoffset"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "mime" name = "mime"
version = "0.3.17" version = "0.3.17"
@ -773,7 +837,7 @@ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
"libc", "libc",
"memoffset", "memoffset 0.6.5",
] ]
[[package]] [[package]]
@ -788,6 +852,15 @@ dependencies = [
"static_assertions", "static_assertions",
] ]
[[package]]
name = "ntapi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
dependencies = [
"winapi 0.3.9",
]
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.46.0" version = "0.46.0"
@ -877,6 +950,7 @@ dependencies = [
"once_cell", "once_cell",
"regex", "regex",
"sysconf", "sysconf",
"sysinfo",
"thiserror", "thiserror",
"tokio", "tokio",
"tracing", "tracing",
@ -976,6 +1050,28 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "rayon"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.8.1" version = "1.8.1"
@ -1018,6 +1114,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.160" version = "1.0.160"
@ -1186,6 +1288,21 @@ dependencies = [
"winapi 0.2.8", "winapi 0.2.8",
] ]
[[package]]
name = "sysinfo"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02f1dc6930a439cc5d154221b5387d153f8183529b07c19aca24ea31e0a167e1"
dependencies = [
"cfg-if",
"core-foundation-sys",
"libc",
"ntapi",
"once_cell",
"rayon",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.40" version = "1.0.40"

View file

@ -24,6 +24,7 @@ nvml-wrapper = "0.8.0"
if-addrs = "0.7.0" if-addrs = "0.7.0"
sysconf = "0.3.4" sysconf = "0.3.4"
thiserror = "1.0.40" thiserror = "1.0.40"
sysinfo = { version = "0.29.0", optional = true }
[dev-dependencies] [dev-dependencies]
iai = "0.1.1" iai = "0.1.1"

69
flake.lock generated
View file

@ -1,5 +1,23 @@
{ {
"nodes": { "nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"naersk": { "naersk": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
@ -33,14 +51,16 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1682669017, "lastModified": 1684280442,
"narHash": "sha256-Vi+p4y3wnl0/4gcwTdmCO398kKlDaUrNROtf3GOD2NY=", "narHash": "sha256-nC1/kfh6tpMQSLQalbNTNnireIlxvLLugrjZdasNh+I=",
"path": "/nix/store/wm2cdd01f8jqbxpw817nv5j3sw6p93g8-source", "owner": "NixOS",
"rev": "7449971a3ecf857b4a554cf79b1d9dcc1a4647d8", "repo": "nixpkgs",
"type": "path" "rev": "6c591e7adc514090a77209f56c9d0c551ab8530d",
"type": "github"
}, },
"original": { "original": {
"id": "nixpkgs", "id": "nixpkgs",
"ref": "nixos-22.11",
"type": "indirect" "type": "indirect"
} }
}, },
@ -48,9 +68,31 @@
"inputs": { "inputs": {
"naersk": "naersk", "naersk": "naersk",
"nixpkgs": "nixpkgs_2", "nixpkgs": "nixpkgs_2",
"rust-overlay": "rust-overlay",
"utils": "utils" "utils": "utils"
} }
}, },
"rust-overlay": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1684290028,
"narHash": "sha256-IWWfoF5aU8wzxJ6ixuW+3KRlppYvmhUP5v6owWiXJMQ=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "ded1f327ff2bdf0eb4bbb945865441ac636c423e",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": { "systems": {
"locked": { "locked": {
"lastModified": 1681028828, "lastModified": 1681028828,
@ -66,9 +108,24 @@
"type": "github" "type": "github"
} }
}, },
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": { "utils": {
"inputs": { "inputs": {
"systems": "systems" "systems": "systems_2"
}, },
"locked": { "locked": {
"lastModified": 1681202837, "lastModified": 1681202837,

View file

@ -1,7 +1,10 @@
{ {
inputs = { inputs = {
nixpkgs.url = "nixpkgs/nixos-22.11";
utils.url = "github:numtide/flake-utils"; utils.url = "github:numtide/flake-utils";
naersk.url = "github:nix-community/naersk"; naersk.url = "github:nix-community/naersk";
rust-overlay.url = "github:oxalica/rust-overlay";
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
}; };
outputs = { outputs = {
@ -9,9 +12,24 @@
nixpkgs, nixpkgs,
utils, utils,
naersk, naersk,
rust-overlay,
}: }:
utils.lib.eachDefaultSystem (system: let utils.lib.eachDefaultSystem (system: let
pkgs = nixpkgs.legacyPackages."${system}"; overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs {
inherit system overlays;
};
pkgs-cross-mingw = import nixpkgs {
crossSystem = {
config = "x86_64-w64-mingw32";
};
inherit system overlays;
};
mingw_w64_cc = pkgs-cross-mingw.stdenv.cc;
mingw_w64 = pkgs-cross-mingw.windows.mingw_w64;
windows = pkgs-cross-mingw.windows;
naersk-lib = naersk.lib."${system}"; naersk-lib = naersk.lib."${system}";
in rec { in rec {
# `nix build` # `nix build`
@ -34,7 +52,18 @@
# `nix develop` # `nix develop`
devShell = pkgs.mkShell { devShell = pkgs.mkShell {
nativeBuildInputs = with pkgs; [cargo bacon]; nativeBuildInputs = with pkgs; [
(rust-bin.stable.latest.default.override {
targets = [ "x86_64-pc-windows-gnu" ];
})
bacon
mingw_w64_cc
];
depsBuildBuild = [ pkgs.wine64 ];
# buildInputs = [ windows.pthreads ];
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER = "${mingw_w64_cc.targetPrefix}cc";
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUNNER = "wine64";
}; };
}) })
// { // {

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::ffi::NulError;
use std::fmt::Write; use std::fmt::Write;
use std::io;
use std::num::{ParseFloatError, ParseIntError}; use std::num::{ParseFloatError, ParseIntError};
use std::str::Utf8Error; use std::str::Utf8Error;
use std::sync::Mutex; use std::string::FromUtf8Error;
use sysconf::SysconfError;
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)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
#[error(transparent)] #[error(transparent)]
Io(#[from] io::Error), Io(#[from] std::io::Error),
#[error("Unsupported sysconf")] #[error("{0}")]
Sysconf(SysconfError), Other(String),
#[error("Non UTF8 hostname")] #[error("Non UTF8 hostname")]
InvalidHostName, InvalidHostName,
#[error(transparent)] #[error(transparent)]
@ -36,98 +37,14 @@ pub enum Error {
StatVfs, StatVfs,
} }
impl From<SysconfError> for Error { impl From<FromUtf8Error> for Error {
fn from(value: SysconfError) -> Self { fn from(err: FromUtf8Error) -> Self {
Error::Sysconf(value) Self::InvalidStringData(err.utf8_error())
} }
} }
pub type Result<T, E = Error> = std::result::Result<T, E>; 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 { pub trait SensorData {
/// Write sensor data in prometheus compatible format /// Write sensor data in prometheus compatible format
fn write<W: Write>(&self, w: W, hostname: &str); fn write<W: Write>(&self, w: W, hostname: &str);
@ -147,3 +64,9 @@ pub trait MultiSensorSource {
fn read(&mut self) -> Result<Self::Iter<'_>>; 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 crate::linux::disk::DiskUsage;
use color_eyre::Result; use crate::Result;
use std::fmt::Write; use std::fmt::Write;
use std::fs::read_to_string; use std::fs::read_to_string;
use std::process::Command; use std::process::Command;

View file

@ -1,6 +1,5 @@
use crate::hwmon::FileSource; use crate::data::{GpuMemory, GpuUsage};
use crate::sensors::Memory; use crate::linux::hwmon::FileSource;
use std::fmt::Write;
use std::fs::read_to_string; use std::fs::read_to_string;
use std::str::FromStr; use std::str::FromStr;
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
@ -10,61 +9,23 @@ use std::time::{Duration, Instant};
pub mod nvidia; 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> { fn read_num<T: FromStr>(path: &str) -> Option<T> {
read_to_string(path).ok()?.trim().parse().ok() 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() { if let Some(nv_mem) = nvidia::memory() {
return Some(nv_mem); return Some(nv_mem);
} }
// 1 gpu should be enough for everyone // 1 gpu should be enough for everyone
let used = read_num::<u64>("/sys/class/drm/card0/device/mem_info_vram_used")?; 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")?; let total = read_num("/sys/class/drm/card0/device/mem_info_vram_total")?;
Some(Memory { Some(GpuMemory {
total, total,
free: total - used, 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> { pub fn utilization() -> impl Iterator<Item = GpuUsage> {
let nv_usage = nvidia::utilization(); let nv_usage = nvidia::utilization();

View file

@ -1,5 +1,4 @@
use crate::gpu::GpuUsage; use crate::data::{GpuMemory, GpuUsage};
use crate::sensors::Memory;
use nvml_wrapper::enum_wrappers::device::TemperatureSensor; use nvml_wrapper::enum_wrappers::device::TemperatureSensor;
use nvml_wrapper::{Device, Nvml}; use nvml_wrapper::{Device, Nvml};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@ -22,12 +21,11 @@ pub fn power() -> Option<u64> {
.map(|mj| mj * 1_000) .map(|mj| mj * 1_000)
} }
pub fn memory() -> Option<Memory> { pub fn memory() -> Option<GpuMemory> {
let mem = device()?.memory_info().ok()?; let mem = device()?.memory_info().ok()?;
Some(Memory { Some(GpuMemory {
total: mem.total, total: mem.total,
free: mem.free, 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 crate::linux::gpu::gpu_power;
use color_eyre::{Report, Result}; use crate::{Error, Result};
use std::fmt::Write; use std::fmt::Write;
use std::fs::{read_dir, read_to_string}; use std::fs::{read_dir, read_to_string};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
@ -63,7 +63,7 @@ pub fn power_usage() -> Result<Option<PowerUsage>> {
if package if package
.file_name() .file_name()
.to_str() .to_str()
.ok_or_else(|| Report::msg("Invalid name"))? .ok_or_else(|| Error::Other("Invalid name".into()))?
.starts_with("intel-rapl") .starts_with("intel-rapl")
{ {
let mut package_path = package.path(); let mut package_path = package.path();
@ -86,7 +86,7 @@ pub fn power_usage() -> Result<Option<PowerUsage>> {
} }
usage.gpu_uj = gpu_power(); 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; usage.gpu_uj = nvidia_power;
} }

View file

@ -1,67 +1,11 @@
use crate::hwmon::{Device, FileSource}; use crate::data::{CpuTime, Memory, NetStats, Temperatures};
use crate::{Error, MultiSensorSource, Result, SensorData, SensorSource}; use crate::linux::hwmon::{Device, FileSource};
use std::array::IntoIter; use crate::{Error, MultiSensorSource, Result, SensorSource};
use std::fmt::Write;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::{BufRead, BufReader, ErrorKind, Read, Seek}; use std::io::{BufRead, BufReader, ErrorKind, Read, Seek};
use sysconf::{sysconf, SysconfVariable}; 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 { pub struct TemperatureSource {
cpu_sensors: Vec<FileSource>, cpu_sensors: Vec<FileSource>,
gpu_sensors: Vec<FileSource>, gpu_sensors: Vec<FileSource>,
@ -117,10 +61,16 @@ impl SensorSource for TemperatureSource {
type Data = Temperatures; type Data = Temperatures;
fn read(&mut self) -> Result<Self::Data> { fn read(&mut self) -> Result<Self::Data> {
Ok(Temperatures { let mut result = 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,
}) };
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 { pub struct CpuTimeSource {
source: BufReader<File>, source: BufReader<File>,
buff: Vec<u8>, 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 { pub struct NetworkSource {
source: File, source: File,
buff: String, buff: String,
@ -326,9 +242,3 @@ impl<'a> Iterator for NetworkStatParser<'a> {
Some(NetworkSource::parse_line(line)) 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::pin_mut;
use futures_util::StreamExt; use futures_util::StreamExt;
use libmdns::Responder; use libmdns::Responder;
use palantir::disk::zfs::arcstats;
use palantir::docker::{get_docker, stat, Container}; 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 palantir::{get_metrics, Sensors};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; 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); 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) Ok(metrics)
} }
@ -86,8 +76,6 @@ async fn main() -> Result<()> {
)); ));
} }
std::thread::spawn(update_gpu_power);
let metrics = warp::path!("metrics") let metrics = warp::path!("metrics")
.and(docker) .and(docker)
.and(sensors) .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)
}