io error context

This commit is contained in:
Robin Appelman 2024-09-29 19:46:04 +02:00
commit 8f3d2beb87
6 changed files with 65 additions and 33 deletions

View file

@ -21,8 +21,8 @@ pub use win::{get_metrics, Sensors};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
#[error(transparent)] #[error("{1}: {0}")]
Io(#[from] std::io::Error), Io(std::io::Error, &'static str),
#[error("{1}: {0}")] #[error("{1}: {0}")]
Os(std::io::Error, &'static str), Os(std::io::Error, &'static str),
#[error("{0}")] #[error("{0}")]
@ -55,6 +55,10 @@ impl Error {
let err = std::io::Error::last_os_error(); let err = std::io::Error::last_os_error();
Error::Os(err, context) Error::Os(err, context)
} }
pub fn io(context: &'static str, err: std::io::Error) -> Error {
Error::Io(err, context)
}
} }
impl From<FromUtf8Error> for Error { impl From<FromUtf8Error> for Error {
@ -86,7 +90,18 @@ pub trait MultiSensorSource {
} }
pub fn hostname() -> Result<String> { pub fn hostname() -> Result<String> {
hostname::get()? hostname::get()
.context("error getting hostname")?
.into_string() .into_string()
.map_err(|_| Error::InvalidHostName) .map_err(|_| Error::InvalidHostName)
} }
pub trait IoResultExt<T> {
fn context(self, context: &'static str) -> Result<T, Error>;
}
impl<T> IoResultExt<T> for Result<T, std::io::Error> {
fn context(self, context: &'static str) -> Result<T, Error> {
self.map_err(|e| Error::io(context, e))
}
}

View file

@ -1,5 +1,5 @@
use crate::data::{DiskStats, DiskUsage}; use crate::data::{DiskStats, DiskUsage};
use crate::{Error, MultiSensorSource, Result}; use crate::{Error, IoResultExt, MultiSensorSource, Result};
use ahash::{AHashSet, AHasher}; use ahash::{AHashSet, AHasher};
use regex::Regex; use regex::Regex;
use std::ffi::CString; use std::ffi::CString;
@ -20,7 +20,7 @@ pub struct DiskStatSource {
impl DiskStatSource { impl DiskStatSource {
pub fn new() -> Result<DiskStatSource> { pub fn new() -> Result<DiskStatSource> {
Ok(DiskStatSource { Ok(DiskStatSource {
source: File::open("/proc/diskstats")?, source: File::open("/proc/diskstats").context("error getting disk stats")?,
buff: String::new(), buff: String::new(),
regex: Regex::new(r" ([sv]d[a-z]+|nvme[0-9]n[0-9]|mmcblk[0-9]) ").unwrap(), regex: Regex::new(r" ([sv]d[a-z]+|nvme[0-9]n[0-9]|mmcblk[0-9]) ").unwrap(),
}) })
@ -33,8 +33,10 @@ impl MultiSensorSource for DiskStatSource {
fn read(&mut self) -> Result<Self::Iter<'_>> { fn read(&mut self) -> Result<Self::Iter<'_>> {
self.buff.clear(); self.buff.clear();
self.source.rewind()?; self.source.rewind().context("error rewinding disk stats")?;
self.source.read_to_string(&mut self.buff)?; self.source
.read_to_string(&mut self.buff)
.context("error reading disk stats")?;
Ok(DiskStatParser { Ok(DiskStatParser {
lines: self.buff.lines(), lines: self.buff.lines(),
@ -83,7 +85,7 @@ pub struct DiskUsageSource {
impl DiskUsageSource { impl DiskUsageSource {
pub fn new() -> Result<DiskUsageSource> { pub fn new() -> Result<DiskUsageSource> {
Ok(DiskUsageSource { Ok(DiskUsageSource {
source: File::open("/proc/mounts")?, source: File::open("/proc/mounts").context("error opening mounts")?,
buff: String::new(), buff: String::new(),
}) })
} }
@ -95,8 +97,10 @@ impl MultiSensorSource for DiskUsageSource {
fn read(&mut self) -> Result<Self::Iter<'_>> { fn read(&mut self) -> Result<Self::Iter<'_>> {
self.buff.clear(); self.buff.clear();
self.source.rewind()?; self.source.rewind().context("error rewinding mounts")?;
self.source.read_to_string(&mut self.buff)?; self.source
.read_to_string(&mut self.buff)
.context("error reading mounts")?;
Ok(DiskUsageParser { Ok(DiskUsageParser {
lines: self.buff.lines(), lines: self.buff.lines(),

View file

@ -1,5 +1,5 @@
use crate::linux::disk::DiskUsage; use crate::linux::disk::DiskUsage;
use crate::Result; use crate::{IoResultExt, 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;
@ -23,7 +23,7 @@ pub fn pools() -> impl Iterator<Item = DiskUsage> {
fn zpool_command() -> Result<String> { fn zpool_command() -> Result<String> {
let mut z = Command::new("zpool"); let mut z = Command::new("zpool");
z.args(["list", "-p", "-H", "-o", "name,size,free"]); z.args(["list", "-p", "-H", "-o", "name,size,free"]);
let out = z.output()?; let out = z.output().context("error getting zpool list")?;
if out.status.success() { if out.status.success() {
Ok(String::from_utf8(out.stdout)?) Ok(String::from_utf8(out.stdout)?)
} else { } else {
@ -111,10 +111,10 @@ impl ArcStats {
} }
} }
pub fn arcstats() -> Result<Option<ArcStats>> { pub fn arcstats() -> Option<ArcStats> {
let content = match read_to_string("/proc/spl/kstat/zfs/arcstats") { let content = match read_to_string("/proc/spl/kstat/zfs/arcstats") {
Ok(c) => c, Ok(c) => c,
Err(_) => return Ok(None), Err(_) => return None,
}; };
let mut stats = ArcStats::default(); let mut stats = ArcStats::default();
@ -150,5 +150,5 @@ pub fn arcstats() -> Result<Option<ArcStats>> {
} }
} }
Ok(Some(stats)) Some(stats)
} }

View file

@ -112,7 +112,7 @@ pub fn get_metrics(sensors: &Sensors) -> Result<String> {
cpu_power.write(&mut result, &sensors.hostname); cpu_power.write(&mut result, &sensors.hostname);
gpu_power.write(&mut result, &sensors.hostname); gpu_power.write(&mut result, &sensors.hostname);
if let Some(arc) = arcstats()? { if let Some(arc) = arcstats() {
arc.write(&mut result, &sensors.hostname); arc.write(&mut result, &sensors.hostname);
} }
if let Some(memory) = gpu::memory() { if let Some(memory) = gpu::memory() {

View file

@ -1,7 +1,7 @@
use crate::data::{CpuPowerUsage, GpuPowerUsage}; use crate::data::{CpuPowerUsage, GpuPowerUsage};
use crate::linux::gpu::gpu_power; use crate::linux::gpu::gpu_power;
use crate::linux::hwmon::FileSource; use crate::linux::hwmon::FileSource;
use crate::{Result, SensorSource}; use crate::{IoResultExt, Result, SensorSource};
use std::fs::read_dir; use std::fs::read_dir;
#[derive(Default)] #[derive(Default)]
@ -11,7 +11,8 @@ pub struct CpuPowerSource {
impl CpuPowerSource { impl CpuPowerSource {
pub fn new() -> Result<CpuPowerSource> { pub fn new() -> Result<CpuPowerSource> {
let sources: Vec<_> = read_dir("/sys/devices/virtual/powercap/intel-rapl")? let sources: Vec<_> = read_dir("/sys/devices/virtual/powercap/intel-rapl")
.context("error listing power devices")?
.flatten() .flatten()
.filter(|path| { .filter(|path| {
path.file_name() path.file_name()
@ -37,7 +38,7 @@ impl SensorSource for CpuPowerSource {
fn read(&mut self) -> Result<Self::Data> { fn read(&mut self) -> Result<Self::Data> {
let mut usage = CpuPowerUsage::default(); let mut usage = CpuPowerUsage::default();
for source in self.sources.iter_mut() { for source in self.sources.iter_mut() {
let package_usage = source.read()?; let package_usage = source.read().context("error reading power source")?;
usage.cpu_uj += package_usage; usage.cpu_uj += package_usage;
usage.cpu_packages_uj.push(package_usage); usage.cpu_packages_uj.push(package_usage);
} }

View file

@ -1,6 +1,6 @@
use crate::data::{CpuTime, Memory, NetStats, Temperatures}; use crate::data::{CpuTime, Memory, NetStats, Temperatures};
use crate::linux::hwmon::{Device, FileSource}; use crate::linux::hwmon::{Device, FileSource};
use crate::{Error, MultiSensorSource, Result, SensorSource}; use crate::{Error, IoResultExt, MultiSensorSource, Result, SensorSource};
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};
@ -23,7 +23,7 @@ impl TemperatureSource {
{ {
for sensor in device.sensors().flatten() { for sensor in device.sensors().flatten() {
if sensor.name() == "Tdie" || sensor.name().starts_with("Core ") { if sensor.name() == "Tdie" || sensor.name().starts_with("Core ") {
cpu_sensors.push(sensor.reader()?); cpu_sensors.push(sensor.reader().context("error opening cpu temp sensor")?);
} }
} }
} }
@ -31,7 +31,7 @@ impl TemperatureSource {
if device.name() == "amdgpu" || device.name() == "gpu_thermal" { if device.name() == "amdgpu" || device.name() == "gpu_thermal" {
for sensor in device.sensors().flatten() { for sensor in device.sensors().flatten() {
if sensor.name() == "edge" { if sensor.name() == "edge" {
gpu_sensors.push(sensor.reader()?); gpu_sensors.push(sensor.reader().context("error opening gpu temp sensor")?);
} }
} }
} }
@ -85,7 +85,7 @@ pub struct MemorySource {
impl MemorySource { impl MemorySource {
pub fn new() -> Result<MemorySource> { pub fn new() -> Result<MemorySource> {
Ok(MemorySource { Ok(MemorySource {
source: File::open("/proc/meminfo")?, source: File::open("/proc/meminfo").context("error opening meminfo")?,
buff: String::new(), buff: String::new(),
}) })
} }
@ -96,8 +96,10 @@ impl SensorSource for MemorySource {
fn read(&mut self) -> Result<Self::Data> { fn read(&mut self) -> Result<Self::Data> {
self.buff.clear(); self.buff.clear();
self.source.rewind()?; self.source.rewind().context("error rewdinging meminfo")?;
self.source.read_to_string(&mut self.buff)?; self.source
.read_to_string(&mut self.buff)
.context("error reading meminfo")?;
let mut mem = Memory::default(); let mut mem = Memory::default();
for line in self.buff.lines() { for line in self.buff.lines() {
@ -127,7 +129,7 @@ pub struct CpuTimeSource {
impl CpuTimeSource { impl CpuTimeSource {
pub fn new() -> Result<CpuTimeSource> { pub fn new() -> Result<CpuTimeSource> {
Ok(CpuTimeSource { Ok(CpuTimeSource {
source: BufReader::new(File::open("/proc/stat")?), source: BufReader::new(File::open("/proc/stat").context("error opening proc stats")?),
buff: Vec::new(), buff: Vec::new(),
cpu_count: sysconf(SysconfVariable::ScNprocessorsOnln)? as f32, cpu_count: sysconf(SysconfVariable::ScNprocessorsOnln)? as f32,
}) })
@ -139,9 +141,11 @@ impl SensorSource for CpuTimeSource {
fn read(&mut self) -> Result<Self::Data> { fn read(&mut self) -> Result<Self::Data> {
self.buff.clear(); self.buff.clear();
self.source.rewind()?; self.source.rewind().context("error rewinding proc")?;
self.source.read_until(b'\n', &mut self.buff)?; self.source
.read_until(b'\n', &mut self.buff)
.context("error reading proc")?;
let line = std::str::from_utf8(&self.buff)?; let line = std::str::from_utf8(&self.buff)?;
@ -156,7 +160,10 @@ impl SensorSource for CpuTimeSource {
(user + system) / (clock_ticks as f32) / self.cpu_count, (user + system) / (clock_ticks as f32) / self.cpu_count,
)) ))
} else { } else {
Err(io::Error::from(ErrorKind::InvalidData).into()) Err(Error::io(
"invalid proc data",
io::Error::from(ErrorKind::InvalidData),
))
} }
} }
} }
@ -169,7 +176,7 @@ pub struct NetworkSource {
impl NetworkSource { impl NetworkSource {
pub fn new() -> Result<NetworkSource> { pub fn new() -> Result<NetworkSource> {
Ok(NetworkSource { Ok(NetworkSource {
source: File::open("/proc/net/dev")?, source: File::open("/proc/net/dev").context("error opening netdev")?,
buff: String::new(), buff: String::new(),
}) })
} }
@ -205,7 +212,10 @@ impl NetworkSource {
bytes_received: bytes_received.parse()?, bytes_received: bytes_received.parse()?,
}) })
} else { } else {
Err(Error::Io(ErrorKind::InvalidData.into())) Err(Error::io(
"error reading netdev",
ErrorKind::InvalidData.into(),
))
} }
} }
} }
@ -216,8 +226,10 @@ impl MultiSensorSource for NetworkSource {
fn read(&mut self) -> Result<Self::Iter<'_>> { fn read(&mut self) -> Result<Self::Iter<'_>> {
self.buff.clear(); self.buff.clear();
self.source.rewind()?; self.source.rewind().context("error rewinding netdev")?;
self.source.read_to_string(&mut self.buff)?; self.source
.read_to_string(&mut self.buff)
.context("error reading netdev")?;
Ok(NetworkStatParser { Ok(NetworkStatParser {
lines: self.buff.lines(), lines: self.buff.lines(),