Skip to content

Commit

Permalink
feat: calculate and display jitter (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
trkelly23 authored and fujiapple852 committed Jan 21, 2024
1 parent 0d3a8a3 commit 7c3da55
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 42 deletions.
42 changes: 42 additions & 0 deletions src/backend/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ pub struct Hop {
best: Option<Duration>,
/// The worst round trip time for this hop across all rounds.
worst: Option<Duration>,
/// The current jitter i.e. round-trip difference with the last round-trip.
jitter: Option<Duration>,
/// The average jitter time for all probes at this hop.
javg: f64,
/// The worst round-trip jitter time for all probes at this hop.
jmax: Option<Duration>,
/// The smoothed jitter value for all probes at this hop.
jinta: f64,
/// The history of round trip times across the last N rounds.
samples: Vec<Duration>,
/// The ICMP extensions for this hop.
Expand Down Expand Up @@ -219,6 +227,25 @@ impl Hop {
}
}

/// The duration of the jitter probe observed.
pub fn jitter_ms(&self) -> Option<f64> {
self.jitter.map(|j| j.as_secs_f64() * 1000_f64)
}

/// The duration of the jworst probe observed.
pub fn jmax_ms(&self) -> Option<f64> {
self.jmax.map(|x| x.as_secs_f64() * 1000_f64)
}

/// The jitter average duration of all probes.
pub fn javg_ms(&self) -> f64 {
self.javg
}
/// The jitter interval of all probes.
pub fn jinta(&self) -> f64 {
self.jinta
}

/// The last N samples.
pub fn samples(&self) -> &[Duration] {
&self.samples
Expand All @@ -240,6 +267,10 @@ impl Default for Hop {
last: None,
best: None,
worst: None,
jitter: None,
javg: 0f64,
jmax: None,
jinta: 0f64,
mean: 0f64,
m2: 0f64,
samples: Vec::default(),
Expand Down Expand Up @@ -336,6 +367,17 @@ 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 = hop.last.and(Some(jitter_dur));
hop.javg += (jitter_ms - hop.javg) / hop.total_recv as f64;
// algorithm is from rfc1889, A.8 or rfc3550
hop.jinta += jitter_ms - ((hop.jinta + 8.0) / 16.0);
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)));
Expand Down
18 changes: 17 additions & 1 deletion src/config/columns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ pub enum TuiColumn {
StdDev,
/// The status of a hop.
Status,
/// The current jitter i.e. round-trip difference with the last round-trip.
Jitter,
/// The average jitter time for all probes at this hop.
Javg,
/// The worst round-trip jitter time for all probes at this hop.
Jmax,
/// The smoothed jitter value for all probes at this hop.
Jinta,
}

impl TryFrom<char> for TuiColumn {
Expand All @@ -89,6 +97,10 @@ impl TryFrom<char> 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}"))),
}
}
Expand All @@ -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"),
}
}
}
Expand All @@ -134,7 +150,7 @@ mod tests {
}

///Negative test for invalid characters
#[test_case('x' ; "invalid x")]
#[test_case('k' ; "invalid k")]
#[test_case('z' ; "invalid z")]
fn test_try_invalid_char_for_tui_column(c: char) {
// Negative test for an unknown character
Expand Down
24 changes: 24 additions & 0 deletions src/frontend/columns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ pub enum Column {
StdDev,
/// The status of a hop.
Status,
/// The current jitter i.e. round-trip difference with the last round-trip.
Jitter,
/// The average jitter time for all probes at this hop.
Javg,
/// The worst round-trip jitter time for all probes at this hop.
Jmax,
/// The smoothed jitter value for all probes at this hop.
Jinta,
}

impl From<Column> for char {
Expand All @@ -95,6 +103,10 @@ impl From<Column> for char {
Column::Worst => 'w',
Column::StdDev => 'd',
Column::Status => 't',
Column::Jitter => 'j',
Column::Javg => 'g',
Column::Jmax => 'x',
Column::Jinta => 'i',
}
}
}
Expand All @@ -113,6 +125,10 @@ impl From<TuiColumn> 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,
}
}
}
Expand All @@ -131,6 +147,10 @@ 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"),
}
}
}
Expand All @@ -151,6 +171,10 @@ impl Column {
Self::Worst => ColumnWidth::Fixed(7),
Self::StdDev => ColumnWidth::Fixed(8),
Self::Status => ColumnWidth::Fixed(7),
Self::Jitter => ColumnWidth::Fixed(7),
Self::Javg => ColumnWidth::Fixed(7),
Self::Jmax => ColumnWidth::Fixed(7),
Self::Jinta => ColumnWidth::Fixed(8),
}
}
}
Expand Down
66 changes: 25 additions & 41 deletions src/frontend/render/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use trippy::tracing::{Extension, Extensions, MplsLabelStackMember, UnknownExtens

/// Render the table of data about the hops.
///
/// For each hop, we show:
/// For each hop, we show by default:
///
/// - The time-to-live (indexed from 1) at this hop (`#`)
/// - The host(s) reported at this hop (`Host`)
Expand All @@ -30,6 +30,13 @@ use trippy::tracing::{Extension, Extensions, MplsLabelStackMember, UnknownExtens
/// - The worst round-trip time for all probes at this hop (`Wrst`)
/// - The standard deviation round-trip time for all probes at this hop (`StDev`)
/// - The status of this hop (`Sts`)
///
/// Optional columns that can be added:
///
/// - The current jitter i.e. round-trip difference with the last round-trip ('Jttr')
/// - The average jitter time for all probes at this hop ('Javg')
/// - The worst round-trip jitter time for all probes at this hop ('Jmax')
/// - The smoothed jitter value for all probes at this hop ('Jinta')
pub fn render(f: &mut Frame<'_>, app: &mut TuiApp, rect: Rect) {
let config = &app.tui_config;
let widths = config.tui_columns.constraints(rect);
Expand Down Expand Up @@ -132,7 +139,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)
Expand All @@ -142,32 +149,29 @@ 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(Some(hop.javg_ms()), 1),
Column::Jmax => render_float_cell(hop.jmax_ms(), 1),
Column::Jinta => render_float_cell(Some(hop.jinta()), 1),
}
}
fn render_ttl_cell(hop: &Hop) -> Cell<'static> {
Cell::from(format!("{}", hop.ttl()))

fn render_usize_cell(value: usize) -> Cell<'static> {
Cell::from(format!("{value}"))
}

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_avg_cell(hop: &Hop) -> Cell<'static> {
Cell::from(if hop.total_recv() > 0 {
format!("{:.1}", hop.avg_ms())
Expand All @@ -176,30 +180,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())
Expand All @@ -208,6 +188,10 @@ fn render_stddev_cell(hop: &Hop) -> Cell<'static> {
})
}

fn render_float_cell(value: Option<f64>, places: usize) -> Cell<'static> {
Cell::from(value.map(|v| format!("{v:.places$}")).unwrap_or_default())
}

fn render_status_cell(hop: &Hop, is_target: bool) -> Cell<'static> {
let lost = hop.total_sent() - hop.total_recv();
Cell::from(match (lost, is_target) {
Expand Down
12 changes: 12 additions & 0 deletions src/report/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<R: Resolver> From<(&backend::trace::Hop, &R)> for Hop {
Expand All @@ -53,6 +61,10 @@ impl<R: Resolver> 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(),
jmax: value.jmax_ms().unwrap_or_default(),
jinta: value.jinta(),
}
}
}
Expand Down

0 comments on commit 7c3da55

Please sign in to comment.