network sensor

This commit is contained in:
Robin Appelman 2023-05-03 21:56:40 +02:00
commit f06b8ab698
5 changed files with 495 additions and 423 deletions

749
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -34,7 +34,7 @@
# `nix develop` # `nix develop`
devShell = pkgs.mkShell { devShell = pkgs.mkShell {
nativeBuildInputs = with pkgs; [rustc cargo bacon]; nativeBuildInputs = with pkgs; [cargo bacon];
}; };
}) })
// { // {

View file

@ -1,8 +1,9 @@
use crate::{Error, Result}; use crate::{Error, Result, SensorData};
use ahash::{AHashSet, AHasher}; use ahash::{AHashSet, AHasher};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use std::ffi::CString; use std::ffi::CString;
use std::fmt::Write;
use std::fs::File; use std::fs::File;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
@ -17,6 +18,25 @@ pub struct IoStats {
pub bytes_received: u64, pub bytes_received: u64,
} }
impl SensorData for IoStats {
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();
}
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DiskUsage { pub struct DiskUsage {
pub name: String, pub name: String,

View file

@ -53,8 +53,9 @@ pub fn get_metrics() -> Result<String> {
let memory = memory()?; let memory = memory()?;
let mut temp_source = TemperatureSource::new()?; let mut temp_source = TemperatureSource::new()?;
let temperatures = temp_source.read()?; let temperatures = temp_source.read()?;
let mut net_source = NetworkSource::new()?;
let networks = net_source.read()?;
let pools = pools(); let pools = pools();
let networks = network_stats()?;
let mut result = String::with_capacity(256); let mut result = String::with_capacity(256);
cpu.write(&mut result, &hostname); cpu.write(&mut result, &hostname);
@ -75,19 +76,8 @@ pub fn get_metrics() -> Result<String> {
.ok(); .ok();
} }
for network in networks { for network in networks {
if network.bytes_received > 0 || network.bytes_sent > 0 { if let Ok(network) = network {
writeln!( network.write(&mut result, &hostname);
&mut result,
"net_sent{{host=\"{}\", network=\"{}\"}} {}",
hostname, network.interface, network.bytes_sent
)
.ok();
writeln!(
&mut result,
"net_received{{host=\"{}\", network=\"{}\"}} {}",
hostname, network.interface, network.bytes_received
)
.ok();
} }
} }
for disk in disks { for disk in disks {
@ -146,3 +136,12 @@ pub trait SensorSource {
fn read(&mut self) -> Result<Self::Data>; fn read(&mut self) -> Result<Self::Data>;
} }
pub trait MultiSensorSource {
type Data: SensorData;
type Iter<'a>: Iterator<Item = Result<Self::Data>>
where
Self: 'a;
fn read(&mut self) -> Result<Self::Iter<'_>>;
}

View file

@ -1,11 +1,11 @@
use crate::disk::IoStats; use crate::disk::IoStats;
use crate::hwmon::{Device, FileSource}; use crate::hwmon::{Device, FileSource};
use crate::{Error, Result, SensorData, SensorSource}; use crate::{Error, MultiSensorSource, Result, SensorData, SensorSource};
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, ErrorKind, Seek}; use std::io::{BufRead, BufReader, ErrorKind, Read, Seek};
use sysconf::{sysconf, SysconfVariable}; use sysconf::{sysconf, SysconfVariable};
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
@ -201,16 +201,20 @@ impl SensorSource for CpuTimeSource {
} }
} }
pub fn network_stats() -> Result<impl Iterator<Item = IoStats>> { pub struct NetworkSource {
let stat = BufReader::new(File::open("/proc/net/dev")?); source: File,
Ok(stat buff: String,
.lines() }
.filter_map(Result::ok)
.filter(|line: &String| { impl NetworkSource {
let trimmed = line.trim_start(); pub fn new() -> Result<NetworkSource> {
trimmed.starts_with("en") || trimmed.starts_with("eth") Ok(NetworkSource {
source: File::open("/proc/net/dev")?,
buff: String::new(),
}) })
.filter_map(|line: String| { }
fn parse_line(line: &str) -> Result<IoStats> {
let mut parts = line.trim_start().split_ascii_whitespace(); let mut parts = line.trim_start().split_ascii_whitespace();
if let ( if let (
Some(interface), Some(interface),
@ -235,15 +239,51 @@ pub fn network_stats() -> Result<impl Iterator<Item = IoStats>> {
parts.next(), parts.next(),
parts.next(), parts.next(),
) { ) {
Some(IoStats { Ok(IoStats {
interface: interface.trim_end_matches(':').into(), interface: interface.trim_end_matches(':').into(),
bytes_sent: bytes_sent.parse().ok()?, bytes_sent: bytes_sent.parse()?,
bytes_received: bytes_received.parse().ok()?, bytes_received: bytes_received.parse()?,
}) })
} else { } else {
None Err(Error::Io(ErrorKind::InvalidData.into()))
}
}
}
impl MultiSensorSource for NetworkSource {
type Data = IoStats;
type Iter<'a> = NetworkStatParser<'a>;
fn read(&mut self) -> Result<Self::Iter<'_>> {
self.buff.clear();
self.source.rewind()?;
self.source.read_to_string(&mut self.buff)?;
Ok(NetworkStatParser {
lines: self.buff.lines(),
})
}
}
pub struct NetworkStatParser<'a> {
lines: std::str::Lines<'a>,
}
impl<'a> Iterator for NetworkStatParser<'a> {
type Item = Result<IoStats>;
fn next(&mut self) -> Option<Self::Item> {
let line = loop {
let line = self.lines.next()?;
let trimmed = line.trim_start();
if trimmed.starts_with("en") || trimmed.starts_with("eth") || trimmed.starts_with("wlp")
{
break trimmed;
}
};
Some(NetworkSource::parse_line(line))
} }
}))
} }
pub fn hostname() -> Result<String> { pub fn hostname() -> Result<String> {