Skip to content

Commit

Permalink
feat: calculate forward and backward loss (#860) - WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
fujiapple852 committed Nov 23, 2024
1 parent ba3b99f commit 00ea6f9
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 14 deletions.
88 changes: 78 additions & 10 deletions crates/trippy-core/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ pub struct Hop {
total_recv: usize,
/// The total probes that failed for this hop.
total_failed: usize,
/// The total forward loss for this hop.
total_forward_lost: usize,
/// The total backward loss for this hop.
total_backward_lost: usize,
/// The total round trip time for this hop across all rounds.
total_time: Duration,
/// The round trip time for this hop in the current round.
Expand Down Expand Up @@ -238,6 +242,18 @@ impl Hop {
self.total_recv
}

/// The total number of probes with forward loss.
#[must_use]
pub const fn total_forward_loss(&self) -> usize {
self.total_forward_lost
}

/// The total number of probes with backward loss.
#[must_use]
pub const fn total_backward_loss(&self) -> usize {
self.total_backward_lost
}

/// The total number of probes that failed.
#[must_use]
pub const fn total_failed(&self) -> usize {
Expand All @@ -249,7 +265,29 @@ impl Hop {
pub fn loss_pct(&self) -> f64 {
if self.total_sent > 0 {
let lost = self.total_sent - self.total_recv;
lost as f64 / self.total_sent as f64 * 100f64
lost as f64 / self.total_sent as f64 * 100_f64
} else {
0_f64
}
}

/// The % of packets that are lost forward.
#[must_use]
pub fn forward_loss_pct(&self) -> f64 {
if self.total_sent > 0 {
let lost = self.total_forward_lost;
lost as f64 / self.total_sent as f64 * 100_f64
} else {
0_f64
}
}

/// The % of packets that are lost backward.
#[must_use]
pub fn backward_loss_pct(&self) -> f64 {
if self.total_sent > 0 {
let lost = self.total_backward_lost;
lost as f64 / self.total_sent as f64 * 100_f64
} else {
0_f64
}
Expand Down Expand Up @@ -366,6 +404,8 @@ impl Default for Hop {
addrs: IndexMap::default(),
total_sent: 0,
total_recv: 0,
total_forward_lost: 0,
total_backward_lost: 0,
total_failed: 0,
total_time: Duration::default(),
last: None,
Expand Down Expand Up @@ -491,21 +531,24 @@ mod state_updater {
use crate::{NatStatus, ProbeStatus, Round};
use std::time::Duration;

/// Update the `FlowState` from a `Round`.
/// Update the state of a `FlowState` from a `Round`.
pub(super) struct StateUpdater<'a> {
/// The state to update.
state: &'a mut FlowState,
/// The `Round` being processed.
round: &'a Round<'a>,
/// The checksum of the previous hop, if any.
prev_hop_checksum: Option<u16>,
/// Whether any previous hop in the round had forward loss.
forward_loss: bool,
}
impl<'a> StateUpdater<'a> {
pub(super) fn new(state: &'a mut FlowState, round: &'a Round<'_>) -> Self {
Self {
state,
round,
prev_hop_checksum: None,
forward_loss: false,
}
}

Expand Down Expand Up @@ -577,15 +620,29 @@ mod state_updater {
state.update_lowest_ttl(awaited.ttl);
state.update_round(awaited.round);
let index = usize::from(awaited.ttl.0) - 1;
state.hops[index].total_sent += 1;
state.hops[index].ttl = awaited.ttl.0;
state.hops[index].samples.insert(0, Duration::default());
if state.hops[index].samples.len() > state.max_samples {
state.hops[index].samples.pop();
let hop = &mut state.hops[index];
hop.total_sent += 1;
hop.ttl = awaited.ttl.0;
hop.samples.insert(0, Duration::default());
if hop.samples.len() > state.max_samples {
hop.samples.pop();
}
hop.last_src_port = awaited.src_port.0;
hop.last_dest_port = awaited.dest_port.0;
hop.last_sequence = awaited.sequence.0;
if self.forward_loss {
hop.total_backward_lost += 1;
} else {
let remaining = &self.round.probes[index..];
let all_awaited = remaining
.iter()
.skip(1)
.all(|p| matches!(p, ProbeStatus::Awaited(_) | ProbeStatus::Skipped));
if all_awaited {
hop.total_forward_lost += 1;
self.forward_loss = true;
}
}
state.hops[index].last_src_port = awaited.src_port.0;
state.hops[index].last_dest_port = awaited.dest_port.0;
state.hops[index].last_sequence = awaited.sequence.0;
}
ProbeStatus::Failed(failed) => {
state.update_lowest_ttl(failed.ttl);
Expand Down Expand Up @@ -794,6 +851,8 @@ mod tests {
ttl: Option<u8>,
total_sent: Option<usize>,
total_recv: Option<usize>,
total_forward_loss: Option<usize>,
total_backward_loss: Option<usize>,
loss_pct: Option<f64>,
last_ms: Option<f64>,
best_ms: Option<f64>,
Expand Down Expand Up @@ -842,6 +901,7 @@ mod tests {
#[test_case(file!("no_latency.yaml"))]
#[test_case(file!("nat.yaml"))]
#[test_case(file!("minimal.yaml"))]
#[test_case(file!("floss_bloss.yaml"))]
fn test_scenario(scenario: Scenario) {
let mut trace = State::new(StateConfig {
max_flows: 1,
Expand Down Expand Up @@ -876,6 +936,14 @@ mod tests {
assert_eq_opt(&Some(actual.total_sent()), &expected.total_sent);
assert_eq_opt(&Some(actual.total_recv()), &expected.total_recv);
assert_eq_opt_f64(&Some(actual.loss_pct()), &expected.loss_pct);
assert_eq_opt(
&Some(actual.total_forward_loss()),
&expected.total_forward_loss,
);
assert_eq_opt(
&Some(actual.total_backward_loss()),
&expected.total_backward_loss,
);
assert_eq_opt_f64(&actual.last_ms(), &expected.last_ms);
assert_eq_opt_f64(&actual.best_ms(), &expected.best_ms);
assert_eq_opt_f64(&actual.worst_ms(), &expected.worst_ms);
Expand Down
32 changes: 32 additions & 0 deletions crates/trippy-core/tests/resources/state/floss_bloss.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Test forward and backward loss.
largest_ttl: 3
rounds:
- probes:
- 1 C 333 10.1.0.1 0 12340 80 0 0
- 2 C 777 10.1.0.2 1 12340 80 0 0
- 3 C 778 10.1.0.3 2 12340 80 0 0
- probes:
- 1 C 333 10.1.0.1 3 12340 80 0 0
- 2 A 777 10.1.0.2 4 12340 80 0 0
- 3 A 778 10.1.0.3 5 12340 80 0 0
- probes:
- 1 C 333 10.1.0.1 6 12340 80 0 0
- 2 C 777 10.1.0.2 7 12340 80 0 0
- 3 C 778 10.1.0.3 8 12340 80 0 0
expected:
hops:
- ttl: 1
total_sent: 3
total_recv: 3
total_forward_loss: 0
total_backward_loss: 0
- ttl: 2
total_sent: 3
total_recv: 2
total_forward_loss: 1
total_backward_loss: 0
- ttl: 3
total_sent: 3
total_recv: 2
total_forward_loss: 0
total_backward_loss: 1
46 changes: 45 additions & 1 deletion crates/trippy-tui/locales/app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1163,4 +1163,48 @@ column_fail:
sv: "Misslyckades"
ru: "Неуд"
es: "Falló"
de: "Fehlgeschlagen"
de: "Fehlgeschlagen"
column_floss:
en: "Floss"
fr: "Floss"
tr: "Floss"
it: "Floss"
pt: "Floss"
zh: "Floss"
sv: "Floss"
ru: "Floss"
es: "Floss"
de: "Floss"
column_bloss:
en: "Bloss"
fr: "Bloss"
tr: "Bloss"
it: "Bloss"
pt: "Bloss"
zh: "Bloss"
sv: "Bloss"
ru: "Bloss"
es: "Bloss"
de: "Bloss"
column_floss_pct:
en: "Floss%"
fr: "Floss%"
tr: "Floss%"
it: "Floss%"
pt: "Floss%"
zh: "Floss%"
sv: "Floss%"
ru: "Floss%"
es: "Floss%"
de: "Floss%"
column_bloss_pct:
en: "Bloss%"
fr: "Bloss%"
tr: "Bloss%"
it: "Bloss%"
pt: "Bloss%"
zh: "Bloss%"
sv: "Bloss%"
ru: "Bloss%"
es: "Bloss%"
de: "Bloss%"
16 changes: 16 additions & 0 deletions crates/trippy-tui/src/config/columns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ pub enum TuiColumn {
LastNatStatus,
/// The number of probes that failed for a hop.
Failed,
/// The number of probes with forward loss for a hop.
Floss,
/// The number of probes with backward loss for a hop.
Bloss,
/// The forward loss % for a hop.
FlossPct,
/// The backward loss % for a hop.
BlossPct,
}

impl TryFrom<char> for TuiColumn {
Expand Down Expand Up @@ -122,6 +130,10 @@ impl TryFrom<char> for TuiColumn {
'C' => Ok(Self::LastIcmpPacketCode),
'N' => Ok(Self::LastNatStatus),
'f' => Ok(Self::Failed),
'F' => Ok(Self::Floss),
'B' => Ok(Self::Bloss),
'D' => Ok(Self::FlossPct),
'V' => Ok(Self::BlossPct),
c => Err(anyhow!(format!("unknown column code: {c}"))),
}
}
Expand Down Expand Up @@ -152,6 +164,10 @@ impl Display for TuiColumn {
Self::LastIcmpPacketCode => write!(f, "C"),
Self::LastNatStatus => write!(f, "N"),
Self::Failed => write!(f, "f"),
Self::Floss => write!(f, "F"),
Self::Bloss => write!(f, "B"),
Self::FlossPct => write!(f, "D"),
Self::BlossPct => write!(f, "V"),
}
}
}
Expand Down
27 changes: 27 additions & 0 deletions crates/trippy-tui/src/frontend/columns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,14 @@ pub enum ColumnType {
LastNatStatus,
/// The number of probes that failed for a hop.
Failed,
/// The number of probes with forward loss for a hop.
Floss,
/// The number of probes with backward loss for a hop.
Bloss,
/// The forward loss % for a hop.
FlossPct,
/// The backward loss % for a hop.
BlossPct,
}

impl From<ColumnType> for char {
Expand Down Expand Up @@ -216,6 +224,10 @@ impl From<ColumnType> for char {
ColumnType::LastIcmpPacketCode => 'C',
ColumnType::LastNatStatus => 'N',
ColumnType::Failed => 'f',
ColumnType::Floss => 'F',
ColumnType::Bloss => 'B',
ColumnType::FlossPct => 'D',
ColumnType::BlossPct => 'V',
}
}
}
Expand Down Expand Up @@ -245,6 +257,10 @@ impl From<TuiColumn> for Column {
TuiColumn::LastIcmpPacketCode => Self::new_shown(ColumnType::LastIcmpPacketCode),
TuiColumn::LastNatStatus => Self::new_shown(ColumnType::LastNatStatus),
TuiColumn::Failed => Self::new_shown(ColumnType::Failed),
TuiColumn::Floss => Self::new_shown(ColumnType::Floss),
TuiColumn::Bloss => Self::new_shown(ColumnType::Bloss),
TuiColumn::FlossPct => Self::new_shown(ColumnType::FlossPct),
TuiColumn::BlossPct => Self::new_shown(ColumnType::BlossPct),
}
}
}
Expand All @@ -257,6 +273,7 @@ impl Display for ColumnType {

impl ColumnType {
/// The name of the column in the current locale.
#[allow(clippy::cognitive_complexity)]
pub(self) fn name(&self) -> Cow<'_, str> {
match self {
Self::Ttl => Cow::Borrowed("#"),
Expand All @@ -281,6 +298,10 @@ impl ColumnType {
Self::LastIcmpPacketCode => t!("column_code"),
Self::LastNatStatus => t!("column_nat"),
Self::Failed => t!("column_fail"),
Self::Floss => t!("column_floss"),
Self::Bloss => t!("column_bloss"),
Self::FlossPct => t!("column_floss_pct"),
Self::BlossPct => t!("column_bloss_pct"),
}
}

Expand Down Expand Up @@ -319,6 +340,10 @@ impl ColumnType {
Self::LastIcmpPacketCode => ColumnWidth::Fixed(width.max(7)),
Self::LastNatStatus => ColumnWidth::Fixed(width.max(7)),
Self::Failed => ColumnWidth::Fixed(width.max(7)),
Self::Floss => ColumnWidth::Fixed(width.max(7)),
Self::Bloss => ColumnWidth::Fixed(width.max(7)),
Self::FlossPct => ColumnWidth::Fixed(width.max(8)),
Self::BlossPct => ColumnWidth::Fixed(width.max(8)),
}
}
}
Expand Down Expand Up @@ -379,6 +404,8 @@ mod tests {
Column::new_hidden(ColumnType::LastIcmpPacketCode),
Column::new_hidden(ColumnType::LastNatStatus),
Column::new_hidden(ColumnType::Failed),
Column::new_hidden(ColumnType::Floss),
Column::new_hidden(ColumnType::Bloss),
])
);
}
Expand Down
10 changes: 7 additions & 3 deletions crates/trippy-tui/src/frontend/render/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ fn new_cell(
};
host_cell
}
ColumnType::LossPct => render_loss_pct_cell(hop),
ColumnType::LossPct => render_pct_cell(hop.loss_pct()),
ColumnType::Sent => render_usize_cell(hop.total_sent()),
ColumnType::Received => render_usize_cell(hop.total_recv()),
ColumnType::Failed => render_usize_cell(hop.total_failed()),
Expand All @@ -171,6 +171,10 @@ fn new_cell(
ColumnType::LastIcmpPacketType => render_icmp_packet_type_cell(hop.last_icmp_packet_type()),
ColumnType::LastIcmpPacketCode => render_icmp_packet_code_cell(hop.last_icmp_packet_type()),
ColumnType::LastNatStatus => render_nat_cell(hop.last_nat_status()),
ColumnType::Floss => render_usize_cell(hop.total_forward_loss()),
ColumnType::Bloss => render_usize_cell(hop.total_backward_loss()),
ColumnType::FlossPct => render_pct_cell(hop.forward_loss_pct()),
ColumnType::BlossPct => render_pct_cell(hop.backward_loss_pct()),
}
}

Expand All @@ -186,8 +190,8 @@ fn render_nat_cell(value: NatStatus) -> Cell<'static> {
})
}

fn render_loss_pct_cell(hop: &Hop) -> Cell<'static> {
Cell::from(format!("{:.1}%", hop.loss_pct()))
fn render_pct_cell(value: f64) -> Cell<'static> {
Cell::from(format!("{value:.1}%"))
}

fn render_avg_cell(hop: &Hop) -> Cell<'static> {
Expand Down
Loading

0 comments on commit 00ea6f9

Please sign in to comment.