7 Commits

Author SHA1 Message Date
ddidderr 5d5e1b3df3 [release] linkspeed v0.1.4 2026-05-31 11:58:13 +02:00
ddidderr 37d6b04dba [deps] cargo update
Updating indenter  v0.3.3  -> v0.3.4
Updating once_cell v1.21.3 -> v1.21.4
2026-05-31 11:58:10 +02:00
ddidderr fc851e8408 fmt: Cargo.toml with tombi 2026-05-31 11:56:02 +02:00
ddidderr ad0a24201c [release] linkspeed v0.1.3 2025-03-29 20:35:03 +01:00
ddidderr f3f1cb46c4 [deps] cargo update
Updating once_cell v1.21.1 -> v1.21.3
2025-03-29 20:35:02 +01:00
ddidderr c9180033d1 [release] linkspeed v0.1.2 2025-03-14 01:09:28 +01:00
ddidderr a03a679f71 [code] better error handling and show available netdevs on some errors 2025-03-14 01:09:03 +01:00
4 changed files with 92 additions and 22 deletions
Generated
+5 -5
View File
@@ -14,19 +14,19 @@ dependencies = [
[[package]]
name = "indenter"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5"
[[package]]
name = "linkspeed"
version = "0.1.1"
version = "0.1.4"
dependencies = [
"eyre",
]
[[package]]
name = "once_cell"
version = "1.21.1"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+9 -8
View File
@@ -1,34 +1,35 @@
[package]
name = "linkspeed"
version = "0.1.1"
version = "0.1.4"
edition = "2024"
[lints.rust]
unsafe_code = "forbid"
[dependencies]
eyre = "0.6"
[lints.clippy]
redundant_closure_for_method_calls = "allow"
similar_names = "allow"
pedantic = { level = "warn", priority = -1 }
todo = "warn"
unwrap_used = "warn"
[dependencies]
eyre = "0.6"
[lints.rust]
unsafe_code = "forbid"
[profile.release]
debug = true
strip = false
debug-assertions = true
overflow-checks = true
strip = false
lto = false
panic = "unwind"
codegen-units = 1
[profile.release-lto]
inherits = "release"
lto = true
debug = false
strip = true
debug-assertions = false
overflow-checks = false
strip = true
lto = true
+8 -1
View File
@@ -6,6 +6,8 @@ mod sysfs;
use std::{env, thread::sleep};
use sysfs::NetdevError;
use crate::{cfg::Config, measure::LinkSpeed};
fn main() -> eyre::Result<()> {
@@ -19,7 +21,12 @@ fn main() -> eyre::Result<()> {
(Some(_), Some(na)) => na,
(Some(nc), None) => nc,
(None, Some(na)) => na,
(None, None) => eyre::bail!("No network device specified"),
(None, None) => {
eyre::bail!(
"No network device specified. Please provide a network device name as an argument or in the config file.\n\n{}",
NetdevError::available_netdevs_msg()
);
}
};
let link_speed = LinkSpeed::new(&netdev_name)?;
+70 -8
View File
@@ -3,24 +3,73 @@ use std::{
io::{Read as _, Seek as _, SeekFrom},
};
use eyre::Context as _;
pub(crate) struct Netdev {
pub struct Netdev {
rx_bytes: File,
tx_bytes: File,
}
#[derive(Debug)]
pub enum NetdevError {
NotFound { name: String },
IoError { name: String, err: std::io::Error },
}
impl NetdevError {
pub fn available_netdevs_msg() -> String {
let list = Netdev::list()
.unwrap_or_default()
.into_iter()
.map(|dev| format!(" {dev}"))
.collect::<Vec<_>>()
.join("\n");
format!("Available network devices:\n{list}")
}
}
impl std::fmt::Display for NetdevError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NetdevError::NotFound { name } => write!(
f,
"Netdev '{name}' not found.\n\n{}",
Self::available_netdevs_msg()
),
NetdevError::IoError { name, err } => {
write!(f, "I/O Error occurred for netdev '{name}': {err}")
}
}
}
}
impl Netdev {
const SYS_CLASS_NET: &'static str = "/sys/class/net";
pub fn from_name(name: &str) -> eyre::Result<Self> {
let rx_bytes = Self::open_netdev(name, "rx_bytes")?;
let tx_bytes = Self::open_netdev(name, "tx_bytes")?;
let rx_bytes = Self::open_netdev_stat(name, "rx_bytes")?;
let tx_bytes = Self::open_netdev_stat(name, "tx_bytes")?;
Ok(Self { rx_bytes, tx_bytes })
}
fn open_netdev(dev: &str, stat: &str) -> eyre::Result<File> {
let path = format!("/sys/class/net/{dev}/statistics/{stat}");
File::open(&path).wrap_err(format!("Failed to open netdev '{dev}'"))
fn open_netdev_stat(name: &str, stat: &str) -> eyre::Result<File> {
let sys_class_net = Self::SYS_CLASS_NET;
let path = format!("{sys_class_net}/{name}/statistics/{stat}");
match File::open(&path) {
Ok(file) => Ok(file),
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
Err(eyre::eyre!(NetdevError::NotFound {
name: name.to_string(),
}))
} else {
Err(eyre::eyre!(NetdevError::IoError {
name: name.to_string(),
err: e,
}))
}
}
}
}
fn read_bytes(file: &mut File) -> eyre::Result<u64> {
@@ -37,4 +86,17 @@ impl Netdev {
pub fn tx_bytes(&mut self) -> eyre::Result<u64> {
Self::read_bytes(&mut self.tx_bytes)
}
pub fn list() -> eyre::Result<Vec<String>> {
let mut entries = std::fs::read_dir(Self::SYS_CLASS_NET)?
.filter_map(|entry| {
entry
.ok()
.and_then(|entry| entry.file_name().into_string().ok())
})
.collect::<Vec<_>>();
entries.sort();
Ok(entries)
}
}