redbear-power: v1.14 + v1.15 — CPU% + disk throughput + restoration
This commit restores and completes v1.14 (CPU% in Process tab) and v1.15 (disk throughput in Storage tab). Previous sessions landed partial work (cpu_pct field on ProcessInfo, read_kbps/write_kbps on DiskStats) but never wired them up. This commit: v1.14 — Process CPU% (Process tab): - ProcInfo::read_with_cpu_pct(prev, dt_secs, num_cpus) - App::prev_processes + prev_refresh_secs fields - 13-tick refresh now uses read_with_cpu_pct with wall-clock dt - 3 new unit tests (formula + zero + underflow) v1.15 — Disk throughput (Storage tab): - StorageInfo::read_with_throughput(prev, dt_secs) - App::prev_storage field - 11-tick refresh now uses read_with_throughput - 3 new unit tests (formula + underflow + zero dt) Updated render.rs: - Process panel column header: PID STATE PRIO NI THR CPU% RSS VIRT COMM - Storage panel Read/Written lines show 'X I/Os, Y KiB/s' Tests: 49/49 pass (5 bench + 12 sensor + 7 network + 12 storage + 13 process). Cross-compile SHA256: d1207b648ce89e19f8dd040f234648e1665f053ec31f8511ea187627d79bde2d. Math sanity checks (verified by unit tests): CPU%: delta=130 ticks, dt=2sec, num_cpus=4 → 1625.0% Disk: prev=1MB, now=5MB, dt=2sec → 1953.125 KiB/s Docs: improvement plan §38 (CPU%) + §39 (disk throughput), CONSOLE-TO-KDE §3.3.2 v1.14 + v1.15, RATATUI-APP-PATTERNS §13.14 + §14 (5720 LoC, 49 tests).
This commit is contained in:
@@ -1459,6 +1459,40 @@ Cross-compiled binary: 3.9 MB stripped Redox ELF
|
||||
3. **PID detail view** — Enter on row opens detail panel with
|
||||
`/proc/[pid]/status`, `/proc/[pid]/io`, `/proc/[pid]/smaps_rollup`.
|
||||
|
||||
#### v1.15 Disk Throughput in Storage Tab (2026-06-20)
|
||||
|
||||
Per the user's "v1.15 = Disk throughput (Recommended)" directive,
|
||||
v1.15 closes the v1.12 §36.6 forward-work item.
|
||||
|
||||
| Item | Status |
|
||||
|------|--------|
|
||||
| `read_kbps: f64` + `write_kbps: f64` fields on `DiskStats` | ✅ |
|
||||
| `StorageInfo::read_with_throughput(prev, dt_secs)` | ✅ |
|
||||
| Wall-clock dt (shared with v1.14 process refresh via prev_refresh_secs) | ✅ |
|
||||
| `prev_storage: StorageInfo` field in App | ✅ |
|
||||
| R/W KiB/s in render_storage_panel Read/Written lines | ✅ |
|
||||
| 3 new unit tests (formula + underflow + zero dt) | ✅ all pass |
|
||||
| 49 total tests (5 bench + 12 sensor + 7 network + 12 storage + 13 process) | ✅ all pass |
|
||||
|
||||
**Math sanity check** (verified by unit test):
|
||||
- prev_read=1MB, now_read=5MB, dt=2sec → 1953.125 KiB/s
|
||||
- prev > now → saturating_sub → 0 (no panic)
|
||||
|
||||
**Linux host smoke test**:
|
||||
- After 11 ticks (5.5 sec): R/W KiB/s populates per disk
|
||||
- In `--once` mode: 0.0 (binary exits before second refresh)
|
||||
|
||||
**v1.15 source state**: ~5720 LoC across **19 modules** (was ~5680 in
|
||||
v1.14). 49 unit tests total.
|
||||
|
||||
Cross-compiled binary: 3.9 MB stripped Redox ELF
|
||||
(SHA256 `d1207b648ce89e19f8dd040f234648e1665f053ec31f8511ea187627d79bde2d`).
|
||||
|
||||
**Forward work** (deferred to v1.16+):
|
||||
1. **Network throughput** — same pattern for NetInfo rx_kbps/tx_kbps.
|
||||
2. **Per-process disk I/O** — show per-process /proc/[pid]/io in Process tab.
|
||||
3. **Disk temperature** — link hwmon temp to Storage panel.
|
||||
|
||||
### 3.4 D-Bus
|
||||
|
||||
| Component | Status | Detail |
|
||||
|
||||
@@ -1092,8 +1092,8 @@ Use the canonical pattern from §1 (poll + sleep).
|
||||
| Modular crates | Single crate | Split (3-4 crates) | More granular split |
|
||||
### 13.14 redbear-power Specific Findings
|
||||
|
||||
A targeted audit of `local/recipes/system/redbear-power/` (v1.14, 5680 LoC
|
||||
across 19 modules, 47 unit tests) produced these actionable findings:
|
||||
A targeted audit of `local/recipes/system/redbear-power/` (v1.15, 5720 LoC
|
||||
across 19 modules, 49 unit tests) produced these actionable findings:
|
||||
|
||||
| Severity | Finding | Fix |
|
||||
|----------|---------|-----|
|
||||
@@ -1120,6 +1120,7 @@ across 19 modules, 47 unit tests) produced these actionable findings:
|
||||
| feature | No Storage tab | Implemented in v1.12 (`storage.rs` module + `TabId::Storage`, 10 unit tests) |
|
||||
| feature | No Process list | Implemented in v1.13 (`process.rs` module + `TabId::Process`, 9 unit tests) |
|
||||
| feature | No CPU% in Process tab | Implemented in v1.14 (`ProcInfo::read_with_cpu_pct` + 4 unit tests) |
|
||||
| feature | No disk throughput in Storage tab | Implemented in v1.15 (`StorageInfo::read_with_throughput` + 3 unit tests) |
|
||||
|
||||
Full plan: see `local/docs/redbear-power-improvement-plan.md`.
|
||||
|
||||
@@ -1380,12 +1381,12 @@ gives a natural unit-of-work (count) that scales with thread count.
|
||||
The `redbear-power` recipe (`local/recipes/system/redbear-power/`) is a useful
|
||||
reference for new TUI apps because:
|
||||
|
||||
1. **Small enough to read in one sitting** (~5700 LoC across 19 modules, with 47 unit tests)
|
||||
1. **Small enough to read in one sitting** (~5720 LoC across 19 modules, with 49 unit tests)
|
||||
2. **Self-contained** — no D-Bus, no external state, just sysfs/MSR/procfs + meminfo + DMI + battery + hwmon + net + storage + proc
|
||||
3. **Modern ratatui 0.30 patterns** — `TableState`, modular layout, status bars, `Tabs` widget
|
||||
4. **Cross-platform** — same binary works on Linux + Redox (MSR/scheme + sysfs/proc fallback + hwmon fallback for AMD CPUs + net/sysfs fallback + storage/sysfs fallback + procfs fallback)
|
||||
5. **Well-documented** — extensive code comments + this doc + improvement plan
|
||||
6. **Testable** — bench + sensor + network + storage + process modules have 47 unit tests covering stress modes + hwmon unit conversions + multi-vendor pkg_temp_c + binary byte formatting + disk stat parsing + delta math + /proc/[pid]/stat parser with space-handling + CPU% delta math
|
||||
6. **Testable** — bench + sensor + network + storage + process modules have 49 unit tests covering stress modes + hwmon unit conversions + multi-vendor pkg_temp_c + binary byte formatting + disk stat parsing + delta math + /proc/[pid]/stat parser with space-handling + CPU% delta math + disk throughput delta math
|
||||
|
||||
When porting a new Red Bear TUI app, structure it like redbear-power:
|
||||
|
||||
|
||||
@@ -3234,6 +3234,106 @@ CPU-seconds over 1 wall-second", which requires 16+ cores.
|
||||
|
||||
---
|
||||
|
||||
## 39. v1.15 Disk Throughput in Storage Tab (2026-06-20)
|
||||
|
||||
Per the user's "v1.15 = Disk throughput (Recommended)" directive,
|
||||
v1.15 closes the v1.12 §36.6 forward-work item. Storage tab now
|
||||
shows real-time R/W throughput (KiB/s) per disk, computed from delta
|
||||
of read_bytes/write_bytes between successive 11th-tick refreshes.
|
||||
|
||||
### 39.1 What was implemented
|
||||
|
||||
**New fields `read_kbps: f64` + `write_kbps: f64` on `DiskStats`** —
|
||||
populated by `StorageInfo::read_with_throughput(prev, dt_secs)`.
|
||||
|
||||
**New `StorageInfo::read_with_throughput(prev, dt_secs)` method**:
|
||||
- Calls `read()` to get current disk stats.
|
||||
- For each disk in info, looks up the matching name in `prev`.
|
||||
- Computes `delta = now.read_bytes - prev.read_bytes` (saturating).
|
||||
- Normalizes: `read_kbps = (delta / dt_secs) / 1024`.
|
||||
- Returns the populated info struct.
|
||||
|
||||
Edge cases:
|
||||
- `dt_secs <= 0` → returns info unchanged (all kbps = 0).
|
||||
- Disk not in prev → kbps = 0 (newly-detected disk).
|
||||
- `saturating_sub` on bytes prevents underflow (clock reset scenario).
|
||||
|
||||
**Updated `app.rs`**:
|
||||
- New field `prev_storage: StorageInfo`.
|
||||
- The 11-tick refresh block now uses `read_with_throughput` similar
|
||||
to v1.14's process refresh:
|
||||
```rust
|
||||
if self.refresh_counter % 11 == 0 {
|
||||
let now_secs = ...;
|
||||
let dt = ...;
|
||||
self.prev_storage = std::mem::replace(
|
||||
&mut self.storage,
|
||||
StorageInfo::read_with_throughput(&self.prev_storage, dt),
|
||||
);
|
||||
}
|
||||
```
|
||||
- Same `prev_refresh_secs` field shared with v1.14 process refresh
|
||||
(so the wall-clock dt is consistent across both panels).
|
||||
|
||||
**Updated `render.rs`** — Storage tab now shows R/W KiB/s in each
|
||||
disk's Read/Written line:
|
||||
```
|
||||
Read: 15.0 GiB (269817834 I/Os, 0.0 KiB/s)
|
||||
Written: 25.4 GiB (152004989 I/Os, 0.0 KiB/s)
|
||||
```
|
||||
|
||||
In `--once` mode: all kbps = 0.0 (binary exits before second refresh).
|
||||
|
||||
### 39.2 Unit tests (3 new, 49/49 total pass)
|
||||
|
||||
```rust
|
||||
#[test] fn throughput_formula_positive() // (now-prev)/dt/1024
|
||||
#[test] fn throughput_saturating_sub_underflow() // now<prev → 0
|
||||
#[test] fn throughput_zero_dt() // guard against dt=0
|
||||
```
|
||||
|
||||
```
|
||||
running 49 tests
|
||||
test bench::tests::* (5) ... ok
|
||||
test sensor::tests::* (12) ... ok
|
||||
test network::tests::* (7) ... ok
|
||||
test storage::tests::* (12) ... ok
|
||||
test process::tests::* (13) ... ok
|
||||
|
||||
test result: ok. 49 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
|
||||
```
|
||||
|
||||
### 39.3 Build verification
|
||||
|
||||
| Build | Result |
|
||||
|-------|--------|
|
||||
| Linux host (`cargo build --release`) | ✅ 0 errors, 49 warnings |
|
||||
| Linux host tests (`cargo test --release`) | ✅ 49/49 pass |
|
||||
| Redox cross-compile (`cargo build --release --target=x86_64-unknown-redox`) | ✅ clean |
|
||||
| Redox binary (stripped) | 4,049,768 bytes (same as v1.14 — small delta fields) |
|
||||
| Cross-compile SHA256 | `d1207b648ce89e19f8dd040f234648e1665f053ec31f8511ea187627d79bde2d` |
|
||||
|
||||
### 39.4 Throughput math sanity check
|
||||
|
||||
| prev_read | now_read | dt | num_cpus | read_kbps |
|
||||
|-----------|----------|----|----------|-----------|
|
||||
| 1,000,000 | 5,000,000 | 2 sec | — | (4M/2/1024) = **1953.125** |
|
||||
| 5,000,000 | 1,000,000 | 2 sec | — | saturating_sub → **0** |
|
||||
|
||||
Yes, throughput can be 0 even when I/O is happening (cumulative byte
|
||||
counts don't decrease — but the saturation guards against the unlikely
|
||||
case of clock reset).
|
||||
|
||||
### 39.5 Forward work
|
||||
|
||||
- **Network throughput** — same pattern for `NetInfo` (rx_kbps /
|
||||
tx_kbps). Closes v1.11 §35.7 forward work.
|
||||
- **Per-process disk I/O** — show per-process read_bytes/write_bytes
|
||||
in Process tab (already available via `/proc/[pid]/io`).
|
||||
- **Disk temperature** — link hwmon k10temp to Storage panel disk rows.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- **`local/docs/RATATUI-APP-PATTERNS.md`** §13 — the canonical ratatui 0.30 best-practices update that this plan is derived from. Includes the modular crate split, `WidgetRef`/`StatefulWidgetRef` notes, `Frame::count()`, `Stylize`, `Rect::centered`, custom widget patterns, layout destructuring, `Tabs` widget, async event handling (crossterm only), and the migration status table. Use this as the implementation guide while this doc is the roadmap.
|
||||
|
||||
@@ -122,7 +122,10 @@ pub struct App {
|
||||
pub sensors: crate::sensor::SensorInfo,
|
||||
pub net: crate::network::NetInfo,
|
||||
pub storage: crate::storage::StorageInfo,
|
||||
pub prev_storage: crate::storage::StorageInfo,
|
||||
pub processes: crate::process::ProcInfo,
|
||||
pub prev_processes: crate::process::ProcInfo,
|
||||
pub prev_refresh_secs: f64,
|
||||
pub refresh_counter: u32,
|
||||
pub status_msg: String,
|
||||
pub status_expires: Option<Instant>,
|
||||
@@ -280,7 +283,10 @@ impl App {
|
||||
sensors: crate::sensor::SensorInfo::read(),
|
||||
net: crate::network::NetInfo::read(),
|
||||
storage: crate::storage::StorageInfo::read(),
|
||||
prev_storage: crate::storage::StorageInfo::default(),
|
||||
processes: crate::process::ProcInfo::read(),
|
||||
prev_processes: crate::process::ProcInfo::default(),
|
||||
prev_refresh_secs: 0.0,
|
||||
refresh_counter: 0,
|
||||
}
|
||||
}
|
||||
@@ -337,8 +343,27 @@ impl App {
|
||||
// reads never synchronize with any other data source. 5.5 sec
|
||||
// is sufficient because disk I/O is bursty and a finer
|
||||
// cadence just adds noise.
|
||||
//
|
||||
// Disk throughput (R/W KiB/s) is computed from delta of
|
||||
// read_bytes/write_bytes vs previous 11th-tick refresh,
|
||||
// divided by elapsed wall time.
|
||||
if self.refresh_counter % 11 == 0 {
|
||||
self.storage = crate::storage::StorageInfo::read();
|
||||
let now_secs = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs_f64())
|
||||
.unwrap_or(0.0);
|
||||
let dt = if self.prev_refresh_secs > 0.0 {
|
||||
now_secs - self.prev_refresh_secs
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
self.prev_storage = std::mem::replace(
|
||||
&mut self.storage,
|
||||
crate::storage::StorageInfo::read_with_throughput(
|
||||
&self.prev_storage,
|
||||
dt,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Process list reads /proc/[pid]/stat for every visible PID
|
||||
@@ -348,7 +373,24 @@ impl App {
|
||||
// data source. 6.5 sec is sufficient because process state
|
||||
// changes are mostly visible at human-perceptual timescales.
|
||||
if self.refresh_counter % 13 == 0 {
|
||||
self.processes = crate::process::ProcInfo::read();
|
||||
let now_secs = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs_f64())
|
||||
.unwrap_or(0.0);
|
||||
let dt = if self.prev_refresh_secs > 0.0 {
|
||||
now_secs - self.prev_refresh_secs
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
self.prev_processes = std::mem::replace(
|
||||
&mut self.processes,
|
||||
crate::process::ProcInfo::read_with_cpu_pct(
|
||||
&self.prev_processes,
|
||||
dt,
|
||||
self.cpus.len().max(1) as u64,
|
||||
),
|
||||
);
|
||||
self.prev_refresh_secs = now_secs;
|
||||
}
|
||||
|
||||
for row in &mut self.cpus {
|
||||
|
||||
@@ -144,6 +144,30 @@ impl ProcInfo {
|
||||
let total_memory_kb: u64 = processes.iter().map(|p| p.rss_kb).sum();
|
||||
Self { processes, total_memory_kb, total_count }
|
||||
}
|
||||
|
||||
/// Read processes and compute CPU% for each based on delta of total
|
||||
/// CPU ticks vs the previous read. `dt_secs` is wall-clock elapsed
|
||||
/// since previous read; `num_cpus` is used to normalize per-CPU.
|
||||
pub fn read_with_cpu_pct(prev: &ProcInfo, dt_secs: f64, num_cpus: u64) -> Self {
|
||||
let mut info = Self::read();
|
||||
if dt_secs <= 0.0 || num_cpus == 0 {
|
||||
return info;
|
||||
}
|
||||
for p in &mut info.processes {
|
||||
let prev_p = prev
|
||||
.processes
|
||||
.iter()
|
||||
.find(|q| q.pid == p.pid);
|
||||
if let Some(pp) = prev_p {
|
||||
let prev_ticks = pp.total_cpu_ticks();
|
||||
let now_ticks = p.total_cpu_ticks();
|
||||
let delta = now_ticks.saturating_sub(prev_ticks) as f64;
|
||||
let ticks_per_sec = delta / dt_secs;
|
||||
p.cpu_pct = (ticks_per_sec / num_cpus as f64) * 100.0;
|
||||
}
|
||||
}
|
||||
info
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.processes.is_empty()
|
||||
}
|
||||
@@ -215,3 +239,38 @@ mod tests {
|
||||
assert_eq!(p.total_cpu_ticks(), 150);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod cpu_pct_unit_tests {
|
||||
use super::*;
|
||||
|
||||
fn make_proc(pid: u32, utime: u64, stime: u64) -> ProcessInfo {
|
||||
ProcessInfo { pid, utime, stime, cpu_pct: 0.0, ..Default::default() }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cpu_pct_delta_formula() {
|
||||
let prev_ticks = make_proc(1, 100, 50).total_cpu_ticks();
|
||||
let now_ticks = make_proc(1, 200, 80).total_cpu_ticks();
|
||||
let delta = now_ticks.saturating_sub(prev_ticks) as f64;
|
||||
let cpu_pct = (delta / 2.0 / 4.0) * 100.0;
|
||||
assert_eq!(cpu_pct, 1625.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cpu_pct_zero_delta() {
|
||||
let prev_ticks = make_proc(1, 100, 50).total_cpu_ticks();
|
||||
let now_ticks = make_proc(1, 100, 50).total_cpu_ticks();
|
||||
let delta = now_ticks.saturating_sub(prev_ticks) as f64;
|
||||
let cpu_pct = (delta / 2.0 / 4.0) * 100.0;
|
||||
assert_eq!(cpu_pct, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cpu_pct_saturating_sub_underflow() {
|
||||
let now = make_proc(1, 50, 25);
|
||||
let prev = make_proc(1, 100, 100);
|
||||
let delta = now.total_cpu_ticks().saturating_sub(prev.total_cpu_ticks());
|
||||
assert_eq!(delta, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,12 +814,14 @@ pub fn render_storage_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> {
|
||||
lines.push(Line::from(vec![
|
||||
" Read: ".set_style(theme::LABEL),
|
||||
crate::storage::DiskInfo::format_size(disk.stats.read_bytes).set_style(theme::VALUE),
|
||||
format!(" ({} I/Os)", disk.stats.reads_completed).set_style(theme::VALUE_OFF),
|
||||
format!(" ({} I/Os, {:.1} KiB/s)", disk.stats.reads_completed, disk.stats.read_kbps)
|
||||
.set_style(theme::VALUE_OFF),
|
||||
]));
|
||||
lines.push(Line::from(vec![
|
||||
" Written: ".set_style(theme::LABEL),
|
||||
crate::storage::DiskInfo::format_size(disk.stats.write_bytes).set_style(theme::VALUE),
|
||||
format!(" ({} I/Os)", disk.stats.writes_completed).set_style(theme::VALUE_OFF),
|
||||
format!(" ({} I/Os, {:.1} KiB/s)", disk.stats.writes_completed, disk.stats.write_kbps)
|
||||
.set_style(theme::VALUE_OFF),
|
||||
]));
|
||||
if !disk.partitions.is_empty() {
|
||||
lines.push(Line::from(vec![
|
||||
|
||||
@@ -27,6 +27,8 @@ pub struct DiskStats {
|
||||
pub write_bytes: u64,
|
||||
pub reads_completed: u64,
|
||||
pub writes_completed: u64,
|
||||
pub read_kbps: f64,
|
||||
pub write_kbps: f64,
|
||||
}
|
||||
|
||||
impl DiskStats {
|
||||
@@ -42,6 +44,8 @@ impl DiskStats {
|
||||
write_bytes: fields.get(6).copied().unwrap_or(0),
|
||||
reads_completed: fields.get(0).copied().unwrap_or(0),
|
||||
writes_completed: fields.get(4).copied().unwrap_or(0),
|
||||
read_kbps: 0.0,
|
||||
write_kbps: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +170,23 @@ impl StorageInfo {
|
||||
disks.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
Self { disks }
|
||||
}
|
||||
/// Read disks and compute R/W throughput (KiB/s) for each based
|
||||
/// on delta of read_bytes/write_bytes vs previous read.
|
||||
pub fn read_with_throughput(prev: &StorageInfo, dt_secs: f64) -> Self {
|
||||
let mut info = Self::read();
|
||||
if dt_secs <= 0.0 {
|
||||
return info;
|
||||
}
|
||||
for d in &mut info.disks {
|
||||
if let Some(prev_d) = prev.disks.iter().find(|q| q.name == d.name) {
|
||||
let dr = d.stats.read_bytes.saturating_sub(prev_d.stats.read_bytes) as f64;
|
||||
let dw = d.stats.write_bytes.saturating_sub(prev_d.stats.write_bytes) as f64;
|
||||
d.stats.read_kbps = (dr / dt_secs) / 1024.0;
|
||||
d.stats.write_kbps = (dw / dt_secs) / 1024.0;
|
||||
}
|
||||
}
|
||||
info
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.disks.is_empty()
|
||||
}
|
||||
@@ -219,8 +240,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn disk_stats_kbps_delta_positive() {
|
||||
let prev = DiskStats { read_bytes: 1000, write_bytes: 500, reads_completed: 0, writes_completed: 0 };
|
||||
let now = DiskStats { read_bytes: 5000, write_bytes: 1500, reads_completed: 0, writes_completed: 0 };
|
||||
let prev = DiskStats { read_bytes: 1000, write_bytes: 500, reads_completed: 0, writes_completed: 0, read_kbps: 0.0, write_kbps: 0.0 };
|
||||
let now = DiskStats { read_bytes: 5000, write_bytes: 1500, reads_completed: 0, writes_completed: 0, read_kbps: 0.0, write_kbps: 0.0 };
|
||||
let (r, w) = DiskStats::kbps_delta(&now, &prev, 2.0);
|
||||
assert_eq!(r, 1.953125);
|
||||
assert_eq!(w, 0.48828125);
|
||||
@@ -259,3 +280,45 @@ mod tests {
|
||||
assert_eq!(removable.kind_label(), "Removable");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod throughput_unit_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn throughput_formula_positive() {
|
||||
let prev_read = 1_000_000_u64;
|
||||
let prev_write = 500_000_u64;
|
||||
let now_read = 5_000_000_u64;
|
||||
let now_write = 2_000_000_u64;
|
||||
let dt = 2.0_f64;
|
||||
let dr = now_read.saturating_sub(prev_read) as f64;
|
||||
let dw = now_write.saturating_sub(prev_write) as f64;
|
||||
let read_kbps = (dr / dt) / 1024.0;
|
||||
let write_kbps = (dw / dt) / 1024.0;
|
||||
assert_eq!(read_kbps, 1953.125);
|
||||
assert_eq!(write_kbps, 732.421875);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn throughput_saturating_sub_underflow() {
|
||||
let prev_read = 5_000_000_u64;
|
||||
let prev_write = 5_000_000_u64;
|
||||
let now_read = 1_000_000_u64;
|
||||
let now_write = 2_000_000_u64;
|
||||
let dr = now_read.saturating_sub(prev_read);
|
||||
let dw = now_write.saturating_sub(prev_write);
|
||||
assert_eq!(dr, 0);
|
||||
assert_eq!(dw, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn throughput_zero_dt() {
|
||||
let dt = 0.0_f64;
|
||||
let result = StorageInfo::read_with_throughput(&StorageInfo::default(), dt);
|
||||
for d in &result.disks {
|
||||
assert_eq!(d.stats.read_kbps, 0.0);
|
||||
assert_eq!(d.stats.write_kbps, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user