From d0266697ccff4bd6ce83c038b008fd2ac03de06d Mon Sep 17 00:00:00 2001 From: trkelly23 <69165022+trkelly23@users.noreply.github.com> Date: Fri, 22 Dec 2023 10:54:20 -0500 Subject: [PATCH] feat display jitter Merge in of #925 improvement on column display. feat display jitter feat display jitter(WIP) Includes possible test approach using JSON to mimic trace data feat(tui): improve column width layout logic (WIP) --- Cargo.lock | 31 +++- Cargo.toml | 1 + src/backend/trace.rs | 289 +++++++++++++++++++++++++++++++++ src/config/columns.rs | 18 +- src/frontend/columns.rs | 77 +++++---- src/frontend/render/table.rs | 58 ++----- src/report/types.rs | 13 +- src/tracing.rs | 4 +- tests/data/base_line.json | 27 +++ tests/data/jinta_1hop.json | 53 ++++++ tests/expected/jinta_1hop.json | 13 ++ 11 files changed, 508 insertions(+), 76 deletions(-) create mode 100644 tests/data/base_line.json create mode 100644 tests/data/jinta_1hop.json create mode 100644 tests/expected/jinta_1hop.json diff --git a/Cargo.lock b/Cargo.lock index eb5869aa..2d2e24c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bitflags" @@ -625,6 +625,15 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe739944a5406424e080edccb6add95685130b9f160d5407c639c7df0c5836b0" +dependencies = [ + "typenum", +] + [[package]] name = "getrandom" version = "0.2.11" @@ -863,6 +872,17 @@ dependencies = [ "serde", ] +[[package]] +name = "iso8601-timestamp" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daa57edc7ad71119e3d501cec542b6c3552881a421ebd61a78c7bf25ebc2eb73" +dependencies = [ + "generic-array", + "serde", + "time", +] + [[package]] name = "itertools" version = "0.12.0" @@ -1840,6 +1860,7 @@ dependencies = [ "humantime", "indexmap 2.1.0", "ipnetwork", + "iso8601-timestamp", "itertools", "maxminddb", "nix", @@ -1885,6 +1906,12 @@ dependencies = [ "wintun", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-bidi" version = "0.3.14" diff --git a/Cargo.toml b/Cargo.toml index 636e2a82..b8faf0ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ tracing-subscriber = { version = "0.3.18", default-features = false, features = tracing-chrome = "0.7.1" petgraph = "0.6.4" csv = "1.3.0" +iso8601-timestamp = "0.2.16" serde_with = "3.4.0" # Library dependencies (Linux) diff --git a/src/backend/trace.rs b/src/backend/trace.rs index 619ffbaf..e569fe93 100644 --- a/src/backend/trace.rs +++ b/src/backend/trace.rs @@ -144,6 +144,14 @@ pub struct Hop { extensions: Option, mean: f64, m2: f64, + /// The ABS(RTTx - RTTx-n) + jitter: Option, + /// The Sequential jitter average calculated for each + javg: Option, + /// The worst jitter reading recorded + jmax: Option, + /// The interval calculation i.e smooth + jinta: f64, } impl Hop { @@ -219,6 +227,31 @@ impl Hop { } } + /// The duration of the jitter probe observed. + pub fn jitter_ms(&self) -> Option { + self.jitter.map(|j| j.as_secs_f64() * 1000_f64) + } + /// The duration of the jworst probe observed. + pub fn jmax_ms(&self) -> Option { + self.jmax.map(|x| x.as_secs_f64() * 1000_f64) + } + /// The jitter average duration of all probes. + pub fn javg_ms(&self) -> Option { + if self.total_recv() > 0 { + self.javg + } else { + None + } + } + /// The jitter interval of all probes. + pub fn jinta(&self) -> Option { + if self.total_recv() > 0 { + Some(self.jinta) + } else { + None + } + } + /// The last N samples. pub fn samples(&self) -> &[Duration] { &self.samples @@ -244,6 +277,10 @@ impl Default for Hop { m2: 0f64, samples: Vec::default(), extensions: None, + jitter: None, + javg: None, + jmax: None, + jinta: 0f64, } } } @@ -336,6 +373,21 @@ impl TraceData { let dur = probe.duration(); let dur_ms = dur.as_secs_f64() * 1000_f64; hop.total_time += dur; + //Before last is set use it to calc jitter + let last_ms = hop.last_ms().unwrap_or_default(); + let jitter_ms = (last_ms - dur_ms).abs(); + let jitter_dur = Duration::from_secs_f64(jitter_ms / 1000_f64); + hop.jitter = Some(jitter_dur); + let mut javg_ms = hop.javg_ms().unwrap_or_default(); + //Welfords online algorithm avg without dataset values. + javg_ms += (jitter_ms - javg_ms) / hop.total_recv as f64; + hop.javg = Some(javg_ms); + // algorithm is from rfc1889, A.8 or rfc3550 + hop.jinta += jitter_ms - ((hop.jinta + 8.0) / 16.0); + //Max following logic of hop.worst + hop.jmax = hop + .jmax + .map_or(Some(jitter_dur), |d| Some(d.max(jitter_dur))); hop.last = Some(dur); hop.samples.insert(0, dur); hop.best = hop.best.map_or(Some(dur), |d| Some(d.min(dur))); @@ -381,3 +433,240 @@ impl TraceData { } } } +#[cfg(test)] +mod tests { + use super::*; + use crate::backend::trace::{Hop, TraceData}; + use iso8601_timestamp::Timestamp; + use serde::{Deserialize, Serialize}; + use std::fs::File; + use std::io::Read; + use std::str::FromStr; + use std::time::SystemTime; + use test_case::test_case; + use trippy::tracing::tracer::CompletionReason; + use trippy::tracing::types::{Port, Round, Sequence, TimeToLive, TraceId}; + use trippy::tracing::{IcmpPacketType, Probe}; + + #[derive(Serialize, Deserialize, Debug)] + struct ImportProbe { + sequence: u16, + identifier: u16, + src_port: u16, + dest_port: u16, + ttl: u8, + round: usize, + sent: Option, + status: String, + host: Option, + received: Option, + icmp_packet_type: Option, + extensions: Option, + } + impl ImportProbe { + fn sequence(&self) -> Sequence { + Sequence(self.sequence) + } + fn identifier(&self) -> TraceId { + TraceId(self.identifier) + } + fn src_port(&self) -> Port { + Port(self.src_port) + } + fn dest_port(&self) -> Port { + Port(self.dest_port) + } + fn ttl(&self) -> TimeToLive { + TimeToLive(self.ttl) + } + fn round(&self) -> Round { + Round(self.round) + } + fn sent(&self) -> SystemTime { + match SystemTime::try_from(self.sent.unwrap()) { + Ok(st) => st, + Err(_d) => SystemTime::now(), + } + } + fn received(&self) -> Option { + self.received.map(|r| SystemTime::try_from(r).ok().unwrap()) + } + fn status(&self) -> ProbeStatus { + match self.status.as_str() { + "Complete" => ProbeStatus::Complete, + "NotSent" => ProbeStatus::NotSent, + "Awaited" => ProbeStatus::Awaited, + _ => ProbeStatus::Skipped, + } + } + fn host(&self) -> Option { + self.host + .as_ref() + .map(|h| IpAddr::V4(Ipv4Addr::from_str(h).unwrap())) + } + } + impl From for Probe { + fn from(value: ImportProbe) -> Self { + Self { + sequence: value.sequence(), + identifier: value.identifier(), + src_port: value.src_port(), + dest_port: value.dest_port(), + ttl: value.ttl(), + round: value.round(), + sent: Some(value.sent()), + status: value.status(), + host: value.host(), + received: value.received(), + icmp_packet_type: Some(IcmpPacketType::NotApplicable), + extensions: None, + } + } + } + #[derive(Deserialize,Serialize,Debug,Clone)] + struct HopResults { + /// The total probes sent for this hop. + total_sent: usize, + /// The total probes received for this hop. + total_recv: usize, + /// The round trip time for this hop in the current round. + last: Option, + /// The best round trip time for this hop across all rounds. + best: Option, + /// The worst round trip time for this hop across all rounds. + worst: Option, + /// The history of round trip times across the last N rounds. + mean: f64, + m2: f64, + /// The ABS(RTTx - RTTx-n) + jitter: Option, + /// The Sequential jitter average calculated for each + javg: Option, + /// The worst jitter reading recorded + jmax: Option, + /// The interval calculation i.e smooth + jinta: f64, + } + impl HopResults { + #[allow(clippy::too_many_arguments)] + fn new( + total_sent: usize, + total_recv: usize, + last: u64, + best: u64, + worst: u64, + mean: f64, + m2: f64, + jitter: u64, + javg: f64, + jmax: u64, + jinta: f64, + ) -> Self { + Self {total_sent, + total_recv, + last: Some(Duration::from_millis(last)), + best: Some(Duration::from_millis(best)), + worst: Some(Duration::from_millis(worst)), + mean, + m2, + jitter: Some(Duration::from_millis(jitter)), + javg: Some(javg), + jmax: Some(Duration::from_millis(jmax)), + jinta, + } + } + //TODO: Create combined struct ImportProbe Vec & HopResults. + //JSON will define the test and expected results. + } + impl PartialEq<&Hop> for HopResults { + fn eq(&self, other: &&Hop) -> bool { + self.last == other.last + && self.jitter == other.jitter + && self.jmax == other.jmax + && self.javg == other.javg + && format!("{:.2}",self.jinta) == format!("{:.2}", other.jinta) + } + } + #[test_case("base_line.json", &HopResults::new(2,2,700,300,700,0.0,0.0,400,350.0,400,680.28))] + fn test_probe_file(json_file: &str, expected: &HopResults) { + let mut json = String::new(); + let mut dir_json_file = "./tests/data/".to_owned(); + dir_json_file.push_str(json_file); + let mut file = File::open(dir_json_file).expect("Failed to open input file"); + // Read the network inputs into a string + file.read_to_string(&mut json).expect("Failed to read file"); + + let mut trace = Trace::new(100); + let import_probes: Vec = serde_json::from_str(&json).unwrap(); + let probes: Vec = import_probes.into_iter().map(Probe::from).collect(); + let round = TracerRound::new(&probes, TimeToLive(1), CompletionReason::TargetFound); + trace.update_from_round(&round); + println!("Hop = {:#?}", trace.hops(Trace::default_flow_id())[0]); + let hop: &Hop = &trace.hops(Trace::default_flow_id())[0]; + ///Check if expected matches results + assert_eq!(expected, &hop ); + } + #[test] + //#[ignore = "WIP"] + fn test_probe_raw_list() { + let json = r#"[{ + "sequence": 1, + "identifier": 1, + "src_port": 80, + "dest_port": 80, + "ttl": 1, + "round": 1, + "sent": "2023-01-01T12:01:55.100", + "status": "Complete", + "host": "10.1.0.2", + "received": "2023-01-01T12:01:55.400", + "icmp_packet_type": null, + "extensions": null + },{ + "sequence": 2, + "identifier": 1, + "src_port": 80, + "dest_port": 80, + "ttl": 1, + "round": 1, + "sent": "2023-01-01T12:01:56.100", + "status": "Complete", + "host": "10.1.0.2", + "received": "2023-01-01T12:01:56.800", + "icmp_packet_type": null, + "extensions": null + }]"#; + let mut trace = Trace::new(100); + let import_probes: Vec = serde_json::from_str(json).unwrap(); + let probes: Vec = import_probes.into_iter().map(Probe::from).collect(); + let round = TracerRound::new(&probes, TimeToLive(1), CompletionReason::TargetFound); + trace.update_from_round(&round); + println!("Hop = {:#?}", trace.hops(Trace::default_flow_id())[0]); + assert_eq!(2, trace.hops(Trace::default_flow_id())[0].total_recv); + } + + #[test] + fn test_probe_raw_single() { + let json = r#"{ + "sequence": 2, + "identifier": 2, + "src_port": 80, + "dest_port": 80, + "ttl": 63, + "round": 2, + "sent": "2022-01-01T12:02:00Z", + "status": "Complete", + "host": "10.1.0.1", + "received": "2022-01-01T12:02:01Z", + "icmp_packet_type": null, + "extensions": null + }"#; + + let import_probe: ImportProbe = serde_json::from_str(json).expect("Failed to deserialize JSON"); + let probe = Probe::from(import_probe); + let mut td = TraceData::new(2); + td.update_from_probe(&probe); + assert_eq!(probe.sequence.0, 2); + println!("Host: {:?}, Age: {:?}", probe.host, probe.sequence); + } +} diff --git a/src/config/columns.rs b/src/config/columns.rs index ac69c9a3..84cacf7d 100644 --- a/src/config/columns.rs +++ b/src/config/columns.rs @@ -71,6 +71,14 @@ pub enum TuiColumn { StdDev, /// The status of a hop. Status, + /// The jitter abs(RTTx-RTTx-1) + Jitter, + /// The jitter total average + Javg, + /// The worst or max jitter recorded. + Jmax, + /// The smoothed jitter reading + Jinta, } impl TryFrom for TuiColumn { @@ -89,6 +97,10 @@ impl TryFrom for TuiColumn { 'w' => Ok(Self::Worst), 'd' => Ok(Self::StdDev), 't' => Ok(Self::Status), + 'j' => Ok(Self::Jitter), + 'g' => Ok(Self::Javg), + 'x' => Ok(Self::Jmax), + 'i' => Ok(Self::Jinta), c => Err(anyhow!(format!("unknown column code: {c}"))), } } @@ -108,6 +120,10 @@ impl Display for TuiColumn { Self::Worst => write!(f, "w"), Self::StdDev => write!(f, "d"), Self::Status => write!(f, "t"), + Self::Jitter => write!(f, "j"), + Self::Javg => write!(f, "g"), + Self::Jmax => write!(f, "x"), + Self::Jinta => write!(f, "i"), } } } @@ -134,7 +150,7 @@ mod tests { } ///Negative test for invalid characters - #[test_case('x' ; "invalid x")] + #[test_case('k' ; "invalid x")] #[test_case('z' ; "invalid z")] fn test_try_invalid_char_for_tui_column(c: char) { // Negative test for an unknown character diff --git a/src/frontend/columns.rs b/src/frontend/columns.rs index ea1e81e3..a8c4af96 100644 --- a/src/frontend/columns.rs +++ b/src/frontend/columns.rs @@ -8,13 +8,6 @@ pub struct Columns(pub Vec); impl Columns { /// Column width constraints. - /// - /// All columns are returned as `Constraint::Min(width)`. - /// - /// For `Fixed(n)` columns the width is as specified in `n`. - /// For `Variable` columns the width is calculated by subtracting the total - /// size of all `Fixed` columns from the width of the containing `Rect` and - /// dividing by the number of `Variable` columns. pub fn constraints(&self, rect: Rect) -> Vec { let total_fixed_width = self .0 @@ -24,18 +17,12 @@ impl Columns { ColumnWidth::Variable => 0, }) .sum(); - let variable_width_count = self - .0 - .iter() - .filter(|c| matches!(c.width(), ColumnWidth::Variable)) - .count() as u16; - let variable_width = - rect.width.saturating_sub(total_fixed_width) / variable_width_count.max(1); + let total_variable_width = rect.width.saturating_sub(total_fixed_width); self.0 .iter() .map(|c| match c.width() { ColumnWidth::Fixed(width) => Constraint::Min(width), - ColumnWidth::Variable => Constraint::Min(variable_width), + ColumnWidth::Variable => Constraint::Min(total_variable_width), }) .collect() } @@ -79,6 +66,14 @@ pub enum Column { StdDev, /// The status of a hop. Status, + /// The jitter of a hop(RTTx-RTTx-1). + Jitter, + /// The Average Jitter + Javg, + /// The worst or max jitter hop RTT + Jmax, + /// The smoothed jitter reading for a hop + Jinta, } impl From for char { @@ -95,6 +90,10 @@ impl From for char { Column::Worst => 'w', Column::StdDev => 'd', Column::Status => 't', + Column::Jitter => 'j', + Column::Javg => 'g', + Column::Jmax => 'x', + Column::Jinta => 'i', } } } @@ -113,6 +112,10 @@ impl From for Column { TuiColumn::Worst => Self::Worst, TuiColumn::StdDev => Self::StdDev, TuiColumn::Status => Self::Status, + TuiColumn::Jitter => Self::Jitter, + TuiColumn::Javg => Self::Javg, + TuiColumn::Jmax => Self::Jmax, + TuiColumn::Jinta => Self::Jinta, } } } @@ -131,10 +134,23 @@ impl Display for Column { Self::Worst => write!(f, "Wrst"), Self::StdDev => write!(f, "StDev"), Self::Status => write!(f, "Sts"), + Self::Jitter => write!(f, "Jttr"), + Self::Javg => write!(f, "Javg"), + Self::Jmax => write!(f, "Jmax"), + Self::Jinta => write!(f, "Jint"), } } } +/// Table column layout constraints. +#[derive(Debug, PartialEq)] +enum ColumnWidth { + /// A fixed size column. + Fixed(u16), + /// A column that will use the remaining space. + Variable, +} + impl Column { /// The width of the column. pub(self) fn width(self) -> ColumnWidth { @@ -150,26 +166,27 @@ impl Column { Self::Best => ColumnWidth::Fixed(7), Self::Worst => ColumnWidth::Fixed(7), Self::StdDev => ColumnWidth::Fixed(8), - Self::Status => ColumnWidth::Fixed(7), + Self::Status => ColumnWidth::Fixed(4), + Self::Jitter => ColumnWidth::Fixed(7), + Self::Javg => ColumnWidth::Fixed(7), + Self::Jmax => ColumnWidth::Fixed(7), + Self::Jinta => ColumnWidth::Fixed(7), } } } -/// Table column layout constraints. -#[derive(Debug, PartialEq)] -enum ColumnWidth { - /// A fixed size column. - Fixed(u16), - /// A column that will use the remaining space. - Variable, -} - #[cfg(test)] mod tests { use super::*; use ratatui::layout::Constraint::Min; use test_case::test_case; + use crate::frontend::columns::ColumnWidth; + use crate::{ + config::{TuiColumn, TuiColumns}, + frontend::columns::{Column, Columns}, + }; + #[test] fn test_columns_conversion_from_tui_columns() { let tui_columns = TuiColumns(vec![ @@ -229,7 +246,7 @@ mod tests { assert_eq!( vec![ Min(4), - Min(11), + Min(14), Min(8), Min(7), Min(7), @@ -238,7 +255,7 @@ mod tests { Min(7), Min(7), Min(8), - Min(7) + Min(4) ], constraints ); @@ -271,8 +288,12 @@ mod tests { Column::Worst, Column::StdDev, Column::Status, + Column::Jitter, + Column::Javg, + Column::Jmax, + Column::Jinta, ]); - assert_eq!("holsravbwdt", format!("{cols}")); + assert_eq!("holsravbwdtjgxi", format!("{cols}")); } /// Reverse subset test for subset of columns. diff --git a/src/frontend/render/table.rs b/src/frontend/render/table.rs index 425e87bc..3d701472 100644 --- a/src/frontend/render/table.rs +++ b/src/frontend/render/table.rs @@ -59,8 +59,7 @@ pub fn render(f: &mut Frame<'_>, app: &mut TuiApp, rect: Rect) { .bg(app.tui_config.theme.bg_color) .fg(app.tui_config.theme.text_color), ) - .highlight_style(selected_style) - .column_spacing(1); + .highlight_style(selected_style); f.render_stateful_widget(table, rect, &mut app.table_state); } @@ -132,7 +131,7 @@ fn new_cell( ) -> Cell<'static> { let is_target = app.tracer_data().is_target(hop, app.selected_flow); match column { - Column::Ttl => render_ttl_cell(hop), + Column::Ttl => render_usize_cell(hop.ttl().into()), Column::Host => { let (host_cell, _) = if is_selected_hop && app.show_hop_details { render_hostname_with_details(app, hop, dns, geoip_lookup, config) @@ -142,30 +141,26 @@ fn new_cell( host_cell } Column::LossPct => render_loss_pct_cell(hop), - Column::Sent => render_total_sent_cell(hop), - Column::Received => render_total_recv_cell(hop), - Column::Last => render_last_cell(hop), + Column::Sent => render_usize_cell(hop.total_sent()), + Column::Received => render_usize_cell(hop.total_recv()), + Column::Last => render_float_cell(hop.last_ms(), 1), Column::Average => render_avg_cell(hop), - Column::Best => render_best_cell(hop), - Column::Worst => render_worst_cell(hop), + Column::Best => render_float_cell(hop.best_ms(), 1), + Column::Worst => render_float_cell(hop.worst_ms(), 1), Column::StdDev => render_stddev_cell(hop), Column::Status => render_status_cell(hop, is_target), + Column::Jitter => render_float_cell(hop.jitter_ms(), 1), + Column::Javg => render_float_cell(hop.javg_ms(), 1), + Column::Jmax => render_float_cell(hop.jmax_ms(), 1), + Column::Jinta => render_float_cell(hop.jinta(), 1), } } -fn render_ttl_cell(hop: &Hop) -> Cell<'static> { - Cell::from(format!("{}", hop.ttl())) -} - fn render_loss_pct_cell(hop: &Hop) -> Cell<'static> { Cell::from(format!("{:.1}%", hop.loss_pct())) } -fn render_total_sent_cell(hop: &Hop) -> Cell<'static> { - Cell::from(format!("{}", hop.total_sent())) -} - -fn render_total_recv_cell(hop: &Hop) -> Cell<'static> { - Cell::from(format!("{}", hop.total_recv())) +fn render_usize_cell(value: usize) -> Cell<'static> { + Cell::from(format!("{value}")) } fn render_avg_cell(hop: &Hop) -> Cell<'static> { @@ -176,30 +171,6 @@ fn render_avg_cell(hop: &Hop) -> Cell<'static> { }) } -fn render_last_cell(hop: &Hop) -> Cell<'static> { - Cell::from( - hop.last_ms() - .map(|last| format!("{last:.1}")) - .unwrap_or_default(), - ) -} - -fn render_best_cell(hop: &Hop) -> Cell<'static> { - Cell::from( - hop.best_ms() - .map(|best| format!("{best:.1}")) - .unwrap_or_default(), - ) -} - -fn render_worst_cell(hop: &Hop) -> Cell<'static> { - Cell::from( - hop.worst_ms() - .map(|worst| format!("{worst:.1}")) - .unwrap_or_default(), - ) -} - fn render_stddev_cell(hop: &Hop) -> Cell<'static> { Cell::from(if hop.total_recv() > 1 { format!("{:.1}", hop.stddev_ms()) @@ -207,6 +178,9 @@ fn render_stddev_cell(hop: &Hop) -> Cell<'static> { String::default() }) } +fn render_float_cell(value: Option, places: usize) -> Cell<'static> { + Cell::from(value.map_or(String::new(), |v| format!("{v:.places$}"))) +} fn render_status_cell(hop: &Hop, is_target: bool) -> Cell<'static> { let lost = hop.total_sent() - hop.total_recv(); diff --git a/src/report/types.rs b/src/report/types.rs index 7cde0733..9f7921d1 100644 --- a/src/report/types.rs +++ b/src/report/types.rs @@ -35,6 +35,14 @@ pub struct Hop { pub worst: f64, #[serde(serialize_with = "fixed_width")] pub stddev: f64, + #[serde(serialize_with = "fixed_width")] + pub jitter: f64, + #[serde(serialize_with = "fixed_width")] + pub javg: f64, + #[serde(serialize_with = "fixed_width")] + pub jmax: f64, + #[serde(serialize_with = "fixed_width")] + pub jinta: f64, } impl From<(&backend::trace::Hop, &R)> for Hop { @@ -53,6 +61,10 @@ impl From<(&backend::trace::Hop, &R)> for Hop { best: value.best_ms().unwrap_or_default(), worst: value.worst_ms().unwrap_or_default(), stddev: value.stddev_ms(), + jitter: value.jitter_ms().unwrap_or_default(), + javg: value.javg_ms().unwrap_or_default(), + jmax: value.jmax_ms().unwrap_or_default(), + jinta: value.jinta().unwrap_or_default(), } } } @@ -72,7 +84,6 @@ impl<'a, R: Resolver, I: Iterator> From<(I, &R)> for Hosts { ) } } - impl Display for Hosts { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0.iter().format(", ")) diff --git a/src/tracing.rs b/src/tracing.rs index bfcdff28..3a6481df 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -4,8 +4,8 @@ mod constants; mod error; mod net; mod probe; -mod tracer; -mod types; +pub mod tracer; +pub mod types; /// Packet wire formats. pub mod packet; diff --git a/tests/data/base_line.json b/tests/data/base_line.json new file mode 100644 index 00000000..6c025c46 --- /dev/null +++ b/tests/data/base_line.json @@ -0,0 +1,27 @@ +[{ + "sequence": 1, + "identifier": 1, + "src_port": 80, + "dest_port": 80, + "ttl": 1, + "round": 1, + "sent": "2023-01-01T12:01:55.100", + "status": "Complete", + "host": "10.1.0.2", + "received": "2023-01-01T12:01:55.400", + "icmp_packet_type": null, + "extensions": null + },{ + "sequence": 2, + "identifier": 1, + "src_port": 80, + "dest_port": 80, + "ttl": 1, + "round": 1, + "sent": "2023-01-01T12:01:56.100", + "status": "Complete", + "host": "10.1.0.2", + "received": "2023-01-01T12:01:56.800", + "icmp_packet_type": null, + "extensions": null + }] \ No newline at end of file diff --git a/tests/data/jinta_1hop.json b/tests/data/jinta_1hop.json new file mode 100644 index 00000000..2e3efb62 --- /dev/null +++ b/tests/data/jinta_1hop.json @@ -0,0 +1,53 @@ +[{ + "sequence": 1, + "identifier": 1, + "src_port": 80, + "dest_port": 80, + "ttl": 1, + "round": 1, + "sent": "2023-01-01T12:01:55.100", + "status": "Complete", + "host": "10.1.0.2", + "received": "2023-01-01T12:01:55.300", + "icmp_packet_type": null, + "extensions": null + },{ + "sequence": 2, + "identifier": 1, + "src_port": 80, + "dest_port": 80, + "ttl": 1, + "round": 1, + "sent": "2023-01-01T12:01:56.100", + "status": "Complete", + "host": "10.1.0.2", + "received": "2023-01-01T12:01:56.200", + "icmp_packet_type": null, + "extensions": null + },{ + "sequence": 3, + "identifier": 1, + "src_port": 80, + "dest_port": 80, + "ttl": 1, + "round": 1, + "sent": "2023-01-01T12:01:57.100", + "status": "Complete", + "host": "10.1.0.2", + "received": "2023-01-01T12:01:57.250", + "icmp_packet_type": null, + "extensions": null + },{ + "sequence": 4, + "identifier": 1, + "src_port": 80, + "dest_port": 80, + "ttl": 1, + "round": 1, + "sent": "2023-01-01T12:01:58.000", + "status": "Complete", + "host": "10.1.0.2", + "received": "2023-01-01T12:01:59.001", + "icmp_packet_type": null, + "extensions": null + }] \ No newline at end of file diff --git a/tests/expected/jinta_1hop.json b/tests/expected/jinta_1hop.json new file mode 100644 index 00000000..3f6e0733 --- /dev/null +++ b/tests/expected/jinta_1hop.json @@ -0,0 +1,13 @@ +{ + "total_sent": 4, + "total_recv": 4, + "last": 1001, + "best": 300, + "worst": 700, + "mean": 0.0, + "m2": 0.0, + "jitter": 851, + "javg": 300.25, + "jmax": 851, + "jinta": 1148.74 +} \ No newline at end of file