initial commit

This commit is contained in:
glyph 2021-12-06 14:21:56 +02:00
commit f7069473ff
7 changed files with 476 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

100
Cargo.lock generated Normal file
View File

@ -0,0 +1,100 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "mini-internal"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "926fbf1cd8695183e4712d1c77cdf2c5f7916f9f73ba103dca78ff2e6755ab47"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "miniserde"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0767e42acf28c5e08fee73cb657bcb23432c3769cbdf3881a8cb69d8df5020df"
dependencies = [
"itoa",
"mini-internal",
"ryu",
]
[[package]]
name = "proc-macro2"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568"
[[package]]
name = "serde"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "vnstat_parse"
version = "0.1.0"
dependencies = [
"miniserde",
"serde",
]

21
Cargo.toml Normal file
View File

@ -0,0 +1,21 @@
[package]
name = "vnstat_parse"
version = "0.1.0"
authors = ["Andrew Reid <glyph@mycelial.technology"]
edition = "2018"
description = "Parse output from `vnstat --oneline` command"
readme = "README.md"
license = "MIT"
[dependencies]
serde = { version = "1.0.130", features = ["derive"], optional = true }
miniserde = { version = "0.1.16", optional = true }
[features]
default = []
# Provide `Serialize` and `Deserialize` traits for the `Vnstat` struct using `miniserde`.
miniserde_support = ["miniserde"]
# Provide `Serialize` and `Deserialize` traits for the `Vnstat` struct using `serde`.
serde_support = ["serde"]

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2021 Andrew 'glyph' Reid
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

62
README.md Normal file
View File

@ -0,0 +1,62 @@
# `vnstat` parse
A Rust library to parse `oneline` data output from [vnstat](https://humdi.net/vnstat/) ("a network and traffic monitor for Linux and BSD").
All fields are parsed, with the exception of the API version information.
Dates are parsed to `String`, data values are parsed to `f32` and data units
are parsed to `String`.
Here is the summary of the `--oneline` option from the `vnstat man` page:
> Show traffic summary for selected interface using one line with a parsable
format. The output contains 15 fields with ; used as field delimiter. The
1st field contains the API version information of the output that will only
be changed in future versions if the field content or structure changes.
The following fields in order 2) interface name, 3) timestamp for today, 4)
rx for today, 5) tx for today, 6) total for today, 7) average traffic rate
for today, 8) timestamp for current month, 9) rx for current month, 10) tx
for current month, 11) total for current month, 12) average traffic rate
for current month, 13) all time total rx, 14) all time total tx, 15) all
time total traffic. An optional mode parameter can be used to force all
fields to output in bytes without the unit itself shown.
**Example output**
```bash
vnstat eno1 --oneline
1;eno1;2021-11-29;6.02 GiB;0.99 GiB;7.00 GiB;738.84 kbit/s;2021-11;6.02 GiB;0.99 GiB;7.00 GiB;24.06 kbit/s;6.02 GiB;0.99 GiB;7.00 GiB
```
## Library Usage
```rust
use vnstat_parse::{Error, Vnstat};
fn main() -> Result<(), Error> {
let vnstat_data = Vnstat::get("eno1")?;
println!("{:?}", vnstat_data);
Ok(())
}
```
**Example output**
```bash
Vnstat { iface: "eno1", today: "2021-11-29", day_rx: 6.02, day_rx_unit: "GiB", day_tx: 0.99, day_tx_unit: "GiB", day_total: 7.0, day_total_unit: "GiB", day_avg_rate: 738.84, day_avg_rate_unit: "kbit/s", month: "2021-11", month_rx: 6.02, month_rx_unit: "GiB", month_tx: 0.99, month_tx_unit: "GiB", month_total: 7.0, month_total_unit: "GiB", month_avg_rate: 24.06, month_avg_rate_unit: "kbit/s", all_time_rx: 6.02, all_time_rx_unit: "GiB", all_time_tx: 0.99, all_time_tx_unit: "GiB", all_time_total: 7.0, all_time_total_unit: "GiB" }
```
## Optional Features
`Serialize` and `Deserialize` can be optionally derived for the `Vnstat` `struct` using either [miniserde](https://crates.io/crates/miniserde) or [serde](https://crates.io/crates/serde). These features are disabled by default to offer a zero dependency parser. `miniserde` offers a lightweight option when compared with `serde` (one less dependency and shorter compile times).
Specify the desired feature in your `Cargo.toml` manifest:
```toml
vnstat_parse = { version = "0.1", features = ["miniserde"] }
```
## License
MIT.

15
examples/vnstat.rs Normal file
View File

@ -0,0 +1,15 @@
use vnstat_parse::{Error, Vnstat};
fn main() -> Result<(), Error> {
// supply the name of the desired interface
let vnstat_data = Vnstat::get("eno1")?;
println!("{:?}", vnstat_data);
println!(
"Total network traffic for today: {} {}",
vnstat_data.day_total, vnstat_data.day_total_unit
);
Ok(())
}

257
src/lib.rs Normal file
View File

@ -0,0 +1,257 @@
#![warn(missing_docs)]
//! A library to parse oneline data output from `vnstat`.
//!
//! All fields are parsed, with the exception of the API version information.
//! Dates are parsed to `String`, data values are parsed to `f32` and data units
//! are parsed to `String`.
//!
//! Here is the summary of the `--oneline` option from the `vnstat man` page:
//!
//! > Show traffic summary for selected interface using one line with a parsable
//! format. The output contains 15 fields with ; used as field delimiter. The
//! 1st field contains the API version information of the output that will only
//! be changed in future versions if the field content or structure changes.
//! The following fields in order 2) interface name, 3) timestamp for today, 4)
//! rx for today, 5) tx for today, 6) total for today, 7) average traffic rate
//! for today, 8) timestamp for current month, 9) rx for current month, 10) tx
//! for current month, 11) total for current month, 12) average traffic rate
//! for current month, 13) all time total rx, 14) all time total tx, 15) all
//! time total traffic. An optional mode parameter can be used to force all
//! fields to output in bytes without the unit itself shown.
//!
//! Example output:
//!
//! `1;eno1;2021-11-29;6.02 GiB;0.99 GiB;7.00 GiB;738.84 kbit/s;2021-11;6.02 GiB;0.99 GiB;7.00 GiB;24.06 kbit/s;6.02 GiB;0.99 GiB;7.00 GiB`
//!
//! ## Library Usage
//!
//! ```rust
//! use vnstat_parse::{Error, Vnstat};
//!
//! fn main() -> Result<(), Error> {
//! let vnstat_data = Vnstat::get("eno1")?;
//!
//! println!("{:?}", vnstat_data);
//!
//! Ok(())
//! }
//! ```
//!
//! **Example output**
//!
//! ```bash
//! Vnstat { iface: "eno1", today: "2021-11-29", day_rx: 6.02, day_rx_unit: "GiB", day_tx: 0.99, day_tx_unit: "GiB", day_total: 7.0, day_total_unit: "GiB", day_avg_rate: 738.84, day_avg_rate_unit: "kbit/s", month: "2021-11", month_rx: 6.02, month_rx_unit: "GiB", month_tx: 0.99, month_tx_unit: "GiB", month_total: 7.0, month_total_unit: "GiB", month_avg_rate: 24.06, month_avg_rate_unit: "kbit/s", all_time_rx: 6.02, all_time_rx_unit: "GiB", all_time_tx: 0.99, all_time_tx_unit: "GiB", all_time_total: 7.0, all_time_total_unit: "GiB" }
//! ```
//!
//! ## Optional Features
//!
//! `Serialize` and `Deserialize` can be optionally derived for the `Vnstat`
//! `struct` using either [miniserde](https://crates.io/crates/miniserde) or
//! [serde](https://crates.io/crates/serde). These features are disabled by
//! default to offer a zero dependency parser. `miniserde` offers a lightweight
//! option when compared with `serde` (one less dependency and shorter compile
//! times).
//!
//! Specify the desired feature in your `Cargo.toml` manifest:
//!
//! ```toml
//! vnstat_parse = { version = "0.1", features = ["miniserde"] }
//! ```
use std::{
io::Error as IoError, num::ParseFloatError, process::Command, result::Result, str::FromStr,
};
#[cfg(feature = "miniserde_support")]
use miniserde::{Deserialize, Serialize};
#[cfg(feature = "serde_support")]
use serde::{Deserialize, Serialize};
/// Custom error type encapsulating all possible errors for this library.
/// `From` implementations are provided for external error types.
#[derive(Debug)]
pub enum Error {
/// Failed to find ' ' (space) while parsing traffic data.
Find,
/// IO error.
Io(IoError),
/// Failed to parse a string slice to `f32`.
ParseFloat(ParseFloatError),
/// Stderr output from `vnstat` command.
StdErr(String),
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
Error::Find => None,
Error::Io(ref err) => Some(err),
Error::ParseFloat(ref err) => Some(err),
Error::StdErr(_) => None,
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
Error::Find => write!(
f,
"Find error: failed to find ' ' character (space) while parsing traffic data and unit"
),
Error::Io(_) => write!(f, "IO error: failed to execute `vnstat` command"),
Error::ParseFloat(_) => write!(f, "Parse error: failed to parse float from string"),
Error::StdErr(ref err) => write!(f, "`vnstat` error: {}", err),
}
}
}
impl From<IoError> for Error {
fn from(err: IoError) -> Self {
Error::Io(err)
}
}
impl From<ParseFloatError> for Error {
fn from(err: ParseFloatError) -> Self {
Error::ParseFloat(err)
}
}
/// Parsed network usage data for a single interface (sourced from the `vnstat` database).
#[derive(Debug)]
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Vnstat {
/// Network interface.
pub iface: String,
/// Timestamp for today (yyyy-mm-dd).
pub today: String,
/// Received data total for today.
pub day_rx: f32,
/// Unit of received data total for today.
pub day_rx_unit: String,
/// Transmitted data total for today.
pub day_tx: f32,
/// Unit of transmitted data total for today.
pub day_tx_unit: String,
/// Combined data total for today.
pub day_total: f32,
/// Unit of combined data total for today.
pub day_total_unit: String,
/// Average traffic rate for today.
pub day_avg_rate: f32,
/// Unit of average traffic rate for today.
pub day_avg_rate_unit: String,
/// Timestamp for current month (yyyy-mm).
pub month: String,
/// Received data for the current month.
pub month_rx: f32,
/// Unit of received data for the current month.
pub month_rx_unit: String,
/// Transmitted data for the current month.
pub month_tx: f32,
/// Unit of transmitted data for the current month.
pub month_tx_unit: String,
/// Combined data total for the current month.
pub month_total: f32,
/// Unit of combined data total for the current month.
pub month_total_unit: String,
/// Average traffic rate for the current month.
pub month_avg_rate: f32,
/// Unit of average traffic rate for the current month.
pub month_avg_rate_unit: String,
/// Received data for all time.
pub all_time_rx: f32,
/// Unit of received data for all time.
pub all_time_rx_unit: String,
/// Transmitted data for all time.
pub all_time_tx: f32,
/// Unit of transmitted data for all time.
pub all_time_tx_unit: String,
/// Combined data total for all time.
pub all_time_total: f32,
/// Unit of combined data total for all time.
pub all_time_total_unit: String,
}
impl Vnstat {
/// Call `vnstat <iface> --online` with the given interface. If the command executes
/// succesfully, parse the `stdout` data. Otherwise, return `stderr`.
pub fn get(iface: &str) -> Result<Vnstat, Error> {
let vnstat_output = Command::new("vnstat")
.arg(&iface)
.arg("--oneline")
.output()?;
match vnstat_output.status.success() {
true => {
let raw_data = String::from_utf8_lossy(&vnstat_output.stdout);
parse_vnstat_data(&raw_data)
}
false => Err(Error::StdErr(
String::from_utf8_lossy(&vnstat_output.stderr).to_string(),
)),
}
}
}
/// Parse the `stdout` data from the `vnstat <iface> --oneline` command.
pub fn parse_vnstat_data(raw_data: &str) -> Result<Vnstat, Error> {
let data_vec: Vec<&str> = raw_data.trim().split(';').collect();
// find the position of the space (' ') in each element (set to `0` if none is found)
let space_vec: Vec<usize> = data_vec
.iter()
.map(|element| element.find(' ').unwrap_or(0))
.collect();
// day data
let (day_rx, day_rx_unit) = data_vec[3].split_at(space_vec[3]);
let (day_tx, day_tx_unit) = data_vec[4].split_at(space_vec[4]);
let (day_total, day_total_unit) = data_vec[5].split_at(space_vec[5]);
let (day_avg_rate, day_avg_rate_unit) = data_vec[6].split_at(space_vec[6]);
// month data
let (month_rx, month_rx_unit) = data_vec[8].split_at(space_vec[8]);
let (month_tx, month_tx_unit) = data_vec[9].split_at(space_vec[9]);
let (month_total, month_total_unit) = data_vec[10].split_at(space_vec[10]);
let (month_avg_rate, month_avg_rate_unit) = data_vec[11].split_at(space_vec[11]);
// all time data
let (all_time_rx, all_time_rx_unit) = data_vec[12].split_at(space_vec[12]);
let (all_time_tx, all_time_tx_unit) = data_vec[13].split_at(space_vec[13]);
let (all_time_total, all_time_total_unit) = data_vec[14].split_at(space_vec[14]);
let parsed_data = Vnstat {
iface: data_vec[1].to_string(),
today: data_vec[2].to_string(),
day_rx: f32::from_str(day_rx)?,
day_rx_unit: day_rx_unit.trim().to_string(),
day_tx: f32::from_str(day_tx)?,
day_tx_unit: day_tx_unit.trim().to_string(),
day_total: f32::from_str(day_total)?,
day_total_unit: day_total_unit.trim().to_string(),
day_avg_rate: f32::from_str(day_avg_rate)?,
day_avg_rate_unit: day_avg_rate_unit.trim().to_string(),
month: data_vec[7].to_string(),
month_rx: f32::from_str(month_rx)?,
month_rx_unit: month_rx_unit.trim().to_string(),
month_tx: f32::from_str(month_tx)?,
month_tx_unit: month_tx_unit.trim().to_string(),
month_total: f32::from_str(month_total)?,
month_total_unit: month_total_unit.trim().to_string(),
month_avg_rate: f32::from_str(month_avg_rate)?,
month_avg_rate_unit: month_avg_rate_unit.trim().to_string(),
all_time_rx: f32::from_str(all_time_rx)?,
all_time_rx_unit: all_time_rx_unit.trim().to_string(),
all_time_tx: f32::from_str(all_time_tx)?,
all_time_tx_unit: all_time_tx_unit.trim().to_string(),
all_time_total: f32::from_str(all_time_total)?,
all_time_total_unit: all_time_total_unit.trim().to_string(),
};
Ok(parsed_data)
}