Skip to content

Commit

Permalink
feat display jitter(WIP)
Browse files Browse the repository at this point in the history
feat display jitter(WIP)

feat display jitter(WIP)
  • Loading branch information
trkelly23 committed Jan 1, 2024
1 parent 947c4ce commit 0d75c2e
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 46 deletions.
71 changes: 71 additions & 0 deletions src/backend/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ pub struct Hop {
extensions: Option<Extensions>,
mean: f64,
m2: f64,
/// The ABS(RTTx - RTTx-n)
jitter: Option<Duration>,
/// The Sequential jitter average calculated for each
javg: Option<f64>,
/// The worst jitter reading recorded
jmax: Option<Duration>,
/// The interval calculation i.e smooth
jinta: f64,
}

impl Hop {
Expand Down Expand Up @@ -219,6 +227,31 @@ 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) -> Option<f64> {
if self.total_recv() > 0 {
self.javg
} else {
None
}
}
/// The jitter interval of all probes.
pub fn jinta(&self) -> Option<f64> {
if self.total_recv() > 0 {
Some(self.jinta)
} else {
None
}
}

/// The last N samples.
pub fn samples(&self) -> &[Duration] {
&self.samples
Expand All @@ -244,6 +277,10 @@ impl Default for Hop {
m2: 0f64,
samples: Vec::default(),
extensions: None,
jitter: None,
javg: None,
jmax: None,
jinta: 0f64,
}
}
}
Expand Down Expand Up @@ -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)));
Expand Down Expand Up @@ -381,3 +433,22 @@ impl TraceData {
}
}
}
#[cfg(test)]
mod tests {
//use chrono::Duration;
use crate::backend::trace::{Hop, TraceData};
use trippy::tracing::Probe;

#[test]
#[ignore = "Possible integration test between TraceData,Hop & Probe."]
fn test_jitter() {
//let hop = Hop::default();
let probe = Probe::default();
let mut td = TraceData::new(2);
td.hops.push(Hop::default());
td.hops.push(Hop::default());
//
td.update_from_probe(&probe);
assert_eq!(td.hops[0].jitter, None);
}
}
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 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<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 x")]
#[test_case('z' ; "invalid z")]
fn test_try_invalid_char_for_tui_column(c: char) {
// Negative test for an unknown character
Expand Down
50 changes: 49 additions & 1 deletion src/frontend/columns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,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,
}

//Output a char for each column type
Expand All @@ -61,6 +69,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 @@ -79,6 +91,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 @@ -97,6 +113,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 @@ -108,6 +128,26 @@ impl Column {
Self::Ttl => 3,
Self::Host => 42,
Self::LossPct => 5,
Self::Sent => 4,
Self::Received => 4,
Self::Last => 4,
Self::Average => 4,
Self::Best => 4,
Self::Worst => 4,
Self::StdDev => 4,
Self::Status => 4,
Self::Jitter => 4,
Self::Javg => 4,
Self::Jmax => 4,
Self::Jinta => 4,
}
}
pub fn width_min(self) -> u16 {
#[allow(clippy::match_same_arms)]
match self {
Self::Ttl => 4,
Self::Host => 42,
Self::LossPct => 5,
Self::Sent => 5,
Self::Received => 5,
Self::Last => 5,
Expand All @@ -116,6 +156,10 @@ impl Column {
Self::Worst => 5,
Self::StdDev => 5,
Self::Status => 5,
Self::Jitter => 5,
Self::Javg => 5,
Self::Jmax => 5,
Self::Jinta => 5,
}
}
}
Expand Down Expand Up @@ -208,8 +252,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 colummns
Expand Down
60 changes: 17 additions & 43 deletions src/frontend/render/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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)
Expand All @@ -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> {
Expand All @@ -176,37 +171,16 @@ 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())
} else {
String::default()
})
}
fn render_float_cell(value: Option<f64>, 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();
Expand Down Expand Up @@ -612,6 +586,6 @@ fn get_column_widths(columns: &Columns) -> Vec<Constraint> {
columns
.0
.iter()
.map(|c| Constraint::Percentage(c.width_pct()))
.map(|c| Constraint::Min(c.width_min()))
.collect()
}
13 changes: 12 additions & 1 deletion 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().unwrap_or_default(),
jmax: value.jmax_ms().unwrap_or_default(),
jinta: value.jinta().unwrap_or_default(),
}
}
}
Expand All @@ -72,7 +84,6 @@ impl<'a, R: Resolver, I: Iterator<Item = &'a IpAddr>> From<(I, &R)> for Hosts {
)
}
}

impl Display for Hosts {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.iter().format(", "))
Expand Down

0 comments on commit 0d75c2e

Please sign in to comment.