Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat display jitter values #935

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 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,20 @@ 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));
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);
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 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
65 changes: 38 additions & 27 deletions src/frontend/columns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@ pub struct Columns(pub Vec<Column>);

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<Constraint> {
let total_fixed_width = self
.0
Expand All @@ -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()
}
Expand Down Expand Up @@ -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<Column> for char {
Expand All @@ -95,6 +90,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 +112,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,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 {
Expand All @@ -150,20 +166,15 @@ 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::*;
Expand Down Expand Up @@ -229,7 +240,7 @@ mod tests {
assert_eq!(
vec![
Min(4),
Min(11),
Min(14),
Min(8),
Min(7),
Min(7),
Expand All @@ -238,7 +249,7 @@ mod tests {
Min(7),
Min(7),
Min(8),
Min(7)
Min(4)
],
constraints
);
Expand Down
69 changes: 26 additions & 43 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
/// - 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 best round-trip jitter tim 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 @@ -59,8 +66,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 +138,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 +148,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(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_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 +179,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 +187,10 @@ fn render_stddev_cell(hop: &Hop) -> Cell<'static> {
})
}

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();
Cell::from(match (lost, is_target) {
Expand Down
Loading
Loading