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 Oct 27, 2024
1 parent 56e32c8 commit 2f9cd43
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 3 deletions.
61 changes: 59 additions & 2 deletions crates/trippy-core/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,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 @@ -239,6 +243,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 @@ -256,6 +272,17 @@ impl Hop {
}
}

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

/// The duration of the last probe.
#[must_use]
pub fn last_ms(&self) -> Option<f64> {
Expand Down Expand Up @@ -367,6 +394,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 @@ -471,12 +500,24 @@ impl FlowState {
self.highest_ttl = std::cmp::max(self.highest_ttl, round.largest_ttl.0);
self.highest_ttl_for_round = round.largest_ttl.0;
let mut prev_hop_checksum = None;
let mut prev_hop_carry = false;
for probe in round.probes {
self.update_from_probe(probe, &mut prev_hop_checksum);
self.update_from_probe(
round.probes,
probe,
&mut prev_hop_checksum,
&mut prev_hop_carry,
);
}
}

fn update_from_probe(&mut self, probe: &ProbeStatus, prev_hop_checksum: &mut Option<u16>) {
fn update_from_probe(
&mut self,
probes: &[ProbeStatus],
probe: &ProbeStatus,
prev_hop_checksum: &mut Option<u16>,
prev_hop_carry: &mut bool,
) {
match probe {
ProbeStatus::Complete(complete) => {
self.update_lowest_ttl(complete.ttl);
Expand Down Expand Up @@ -541,6 +582,22 @@ impl FlowState {
self.hops[index].last_src_port = awaited.src_port.0;
self.hops[index].last_dest_port = awaited.dest_port.0;
self.hops[index].last_sequence = awaited.sequence.0;
if *prev_hop_carry {
self.hops[index].total_backward_lost += 1;
} else if awaited.ttl.0 <= self.highest_ttl_for_round {
// TODO panicked: range end index 17 out of range for slice of length 5
let remaining = &probes[index..usize::from(self.highest_ttl_for_round)];
if remaining.len() > 1 {
let all_awaited = remaining
.iter()
.skip(1)
.all(|p| matches!(p, ProbeStatus::Awaited(_)));
if all_awaited {
self.hops[index].total_forward_lost += 1;
*prev_hop_carry = true;
}
}
}
}
ProbeStatus::Failed(failed) => {
self.update_lowest_ttl(failed.ttl);
Expand Down
37 changes: 37 additions & 0 deletions crates/trippy-core/tests/resources/simulation/ipv4_icmp_loss.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: IPv4/ICMP with 9 hops, 2 of which do not respond
target: 10.0.0.109
protocol: Icmp
icmp_identifier: 3
hops:
- ttl: 1
resp: !SingleHost
addr: 10.0.0.101
rtt_ms: 10
- ttl: 2
resp: NoResponse
- ttl: 3
resp: !SingleHost
addr: 10.0.0.103
rtt_ms: 20
- ttl: 4
resp: !SingleHost
addr: 10.0.0.104
rtt_ms: 20
- ttl: 5
resp: !SingleHost
addr: 10.0.0.105
rtt_ms: 20
- ttl: 6
resp: !SingleHost
addr: 10.0.0.106
rtt_ms: 20
- ttl: 7
resp: !SingleHost
addr: 10.0.0.107
rtt_ms: 20
- ttl: 8
resp: NoResponse
- ttl: 9
resp: !SingleHost
addr: 10.0.0.109
rtt_ms: 20
23 changes: 22 additions & 1 deletion crates/trippy-tui/locales/app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -732,4 +732,25 @@ column_fail:
tr: "Başarısız"
it: "Falliti"
pt: "Falha"
zh: "失败"
zh: "失败"
column_floss:
en: "Floss"
fr: "Floss"
tr: "Floss"
it: "Floss"
pt: "Floss"
zh: "Floss"
column_bloss:
en: "Bloss"
fr: "Bloss"
tr: "Bloss"
it: "Bloss"
pt: "Bloss"
zh: "Bloss"
column_aloss_pct:
en: "Aloss%"
fr: "Aloss%"
tr: "Aloss%"
it: "Aloss%"
pt: "Aloss%"
zh: "Aloss%"
12 changes: 12 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,12 @@ pub enum TuiColumn {
LastNatStatus,
/// The number of probes that failed for a hop.
Failed,
/// The number of probes with forward loss.
Floss,
/// The number of probes with backward loss.
Bloss,
/// The loss percentage adjusted for back and forward loss.
AdjustedLossPct,
}

impl TryFrom<char> for TuiColumn {
Expand Down Expand Up @@ -122,6 +128,9 @@ 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),
'A' => Ok(Self::AdjustedLossPct),
c => Err(anyhow!(format!("unknown column code: {c}"))),
}
}
Expand Down Expand Up @@ -152,6 +161,9 @@ 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::AdjustedLossPct => write!(f, "A"),
}
}
}
Expand Down
22 changes: 22 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,12 @@ pub enum ColumnType {
LastNatStatus,
/// The number of probes that failed for a hop.
Failed,
/// The number of probes with forward loss.
Floss,
/// The number of probes with backward loss.
Bloss,
/// The loss percentage adjusted for back and forward loss.
AdjustedLossPct,
}

impl From<ColumnType> for char {
Expand Down Expand Up @@ -216,6 +222,9 @@ impl From<ColumnType> for char {
ColumnType::LastIcmpPacketCode => 'C',
ColumnType::LastNatStatus => 'N',
ColumnType::Failed => 'f',
ColumnType::Floss => 'F',
ColumnType::Bloss => 'B',
ColumnType::AdjustedLossPct => 'A',
}
}
}
Expand Down Expand Up @@ -245,6 +254,9 @@ 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::AdjustedLossPct => Self::new_shown(ColumnType::AdjustedLossPct),
}
}
}
Expand All @@ -257,6 +269,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 +294,9 @@ 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::AdjustedLossPct => t!("column_aloss_pct"),
}
}

Expand Down Expand Up @@ -319,6 +335,9 @@ 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::AdjustedLossPct => ColumnWidth::Fixed(width.max(8)),
}
}
}
Expand Down Expand Up @@ -379,6 +398,9 @@ 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),
Column::new_hidden(ColumnType::AdjustedLossPct),
])
);
}
Expand Down
7 changes: 7 additions & 0 deletions crates/trippy-tui/src/frontend/render/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ 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::AdjustedLossPct => render_adjusted_loss_pct_cell(hop),
}
}

Expand All @@ -190,6 +193,10 @@ fn render_loss_pct_cell(hop: &Hop) -> Cell<'static> {
Cell::from(format!("{:.1}%", hop.loss_pct()))
}

fn render_adjusted_loss_pct_cell(hop: &Hop) -> Cell<'static> {
Cell::from(format!("{:.1}%", hop.adjusted_loss_pct()))
}

fn render_avg_cell(hop: &Hop) -> Cell<'static> {
Cell::from(if hop.total_recv() > 0 {
format!("{:.1}", hop.avg_ms())
Expand Down
2 changes: 2 additions & 0 deletions trippy-config-sample.toml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ tui-as-mode = "asn"
# C - Last icmp packet code
# N - Last NAT status
# f - Probes failed
# F = Probe forward loss
# B = Probe backward loss
#
# The columns will be shown in the order specified.
tui-custom-columns = "holsravbwdt"
Expand Down

0 comments on commit 2f9cd43

Please sign in to comment.