Repository: fujiapple852/trippy (language: rust)

Issue:
Sample history and frequency charts ignore sub-millisecond samples
The sample history and frequency charts ignore sub-millisecond samples.  This leads to "gaps" in the sample history chart and missing frequency buckets in the frequency chart.

## Sample History Chart

For the sample history, the logic is:

```rust
if s.as_secs_f64() > 0_f64 {
    Some((s.as_secs_f64() * 1000_f64) as u64)
} else {
    None
}
```

If the sample duration is < 1ms (1000us) then the above evaluated to 0.

Examples:

```
micros: 1000, millis: 1, nanos: 1000000, secs: 0.001,    (secs * 1000_f64) as u64: 1
micros: 999,  millis: 0, nanos: 999000,  secs: 0.000999, (secs * 1000_f64) as u64: 0
micros: 1,    millis: 0, nanos: 1000,    secs: 0.000001, (secs * 1000_f64) as u64: 0
```

For this chart we do not care about the absolute value of the sample, only the relative value (vs all other values).  Therefore, to support a resolution down to 1us, we can use `s.as_micros()` instead.

We should also replace `s.as_secs_f64() > 0_f64` with `!s.is_zero()`.

## Frequency Chart

For the frequency chart the logic is:

```rust
if sample.as_millis() > 0 {
    ... snip ...
}
```

If the sample duration is < 1ms (1000us) then the above check evaluates to false.

For this chart it is ok for a bucket to exist for zero (samples of duration between 0us and 999us), therefore we should replace `sample.as_millis() > 0` with `!s.is_zero()`.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 37399c8d..cc7a9eed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
   `--tui-preserve-screen` ([#1375](https://github.com/fujiapple852/trippy/issues/1375))
 - Config item `tui-address-mode` does not accept `ip` ([#1327](https://github.com/fujiapple852/trippy/issues/1327))
 - Icmp extension mode not shown in Tui settings ([#1289](https://github.com/fujiapple852/trippy/issues/1289))
+- Sample history and frequency charts ignore sub-millisecond
+  samples ([#1398](https://github.com/fujiapple852/trippy/issues/1398))
 
 ## [0.11.0] - 2024-08-11
 
diff --git a/RELEASES.md b/RELEASES.md
index 21568bde..d48597b4 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -319,6 +319,9 @@ The previous release of Trippy introduced a bug ([#1290](https://github.com/fuji
 caused reverse DNS lookups to be enqueued multiple times when the `dns-ttl` expired, potentially leading to the hostname
 being displayed as `Timeout: xxx` for a brief period.
 
+A long standing bug ([#1398](https://github.com/fujiapple852/trippy/issues/1398)) which caused the TUI sample history
+and frequency charts to ignore sub-millisecond samples has been fixed.
+
 This release fixes a bug ([#1287](https://github.com/fujiapple852/trippy/issues/1287)) that caused a tracer panic when
 parsing certain ICMP extensions with malformed lengths.
 
diff --git a/crates/trippy-tui/src/frontend/render/histogram.rs b/crates/trippy-tui/src/frontend/render/histogram.rs
index 43203818..ccbc1047 100644
--- a/crates/trippy-tui/src/frontend/render/histogram.rs
+++ b/crates/trippy-tui/src/frontend/render/histogram.rs
@@ -43,7 +43,7 @@ fn sample_frequency(samples: &[Duration]) -> Vec<(String, u64)> {
     let sample_count = samples.len();
     let mut count_by_duration: BTreeMap<u128, u64> = BTreeMap::new();
     for sample in samples {
-        if sample.as_millis() > 0 {
+        if !sample.is_zero() {
             *count_by_duration.entry(sample.as_millis()).or_default() += 1;
         }
     }
diff --git a/crates/trippy-tui/src/frontend/render/history.rs b/crates/trippy-tui/src/frontend/render/history.rs
index a8620f25..8534a8f9 100644
--- a/crates/trippy-tui/src/frontend/render/history.rs
+++ b/crates/trippy-tui/src/frontend/render/history.rs
@@ -13,10 +13,10 @@ pub fn render(f: &mut Frame<'_>, app: &TuiApp, rect: Rect) {
         .iter()
         .take(rect.width as usize)
         .map(|s| {
-            if s.as_secs_f64() > 0_f64 {
-                Some((s.as_secs_f64() * 1000_f64) as u64)
-            } else {
+            if s.is_zero() {
                 None
+            } else {
+                Some(s.as_micros() as u64)
             }
         })
         .collect::<Vec<_>>();


Test patch: