Skip to main content

pagers_core/
output.rs

1use std::sync::atomic::Ordering;
2use std::time::Duration;
3
4use crate::ops::Stats;
5
6pub fn pretty_size(bytes: usize) -> String {
7    const KI: f64 = 1024.0;
8    const MI: f64 = KI * KI;
9    const GI: f64 = KI * MI;
10    const TI: f64 = KI * GI;
11
12    let b = bytes as f64;
13    match b {
14        _ if b < KI => format!("{bytes}"),
15        _ if b < MI => format!("{:.1}K", b / KI),
16        _ if b < GI => format!("{:.1}M", b / MI),
17        _ if b < TI => format!("{:.1}G", b / GI),
18        _ => format!("{:.1}T", b / TI),
19    }
20}
21
22#[derive(Debug, Clone, PartialEq)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub struct Summary {
25    pub total_files: usize,
26    pub total_dirs: usize,
27    pub total_pages: usize,
28    pub total_resident_pages: usize,
29    pub action_pages: usize,
30    pub total_size: usize,
31    pub resident_size: usize,
32    pub action_size: usize,
33    pub resident_pct: f64,
34    pub action_pct: f64,
35    pub elapsed: f64,
36}
37
38impl Summary {
39    pub fn from_stats(stats: &Stats, elapsed: f64, action_sign: isize) -> Self {
40        let page_size = *crate::pagesize::PAGE_SIZE;
41        let total_pages = stats.total_pages.load(Ordering::Relaxed);
42        let action_pages = stats.action_pages.load(Ordering::Relaxed);
43        let initial = stats.initial_pages_in_core.load(Ordering::Relaxed);
44        let signed_action = (action_pages as isize) * action_sign;
45        let total_resident_pages = initial.saturating_add_signed(signed_action);
46        let total_files = stats.total_files.load(Ordering::Relaxed);
47        let total_dirs = stats.total_dirs.load(Ordering::Relaxed);
48
49        let total_size = total_pages * page_size;
50        let resident_size = total_resident_pages * page_size;
51        let action_size = action_pages * page_size;
52        let resident_pct = if total_pages > 0 {
53            100.0 * total_resident_pages as f64 / total_pages as f64
54        } else {
55            0.0
56        };
57        let action_pct = if total_pages > 0 {
58            100.0 * action_pages as f64 / total_pages as f64
59        } else {
60            0.0
61        };
62
63        Self {
64            total_files,
65            total_dirs,
66            total_pages,
67            total_resident_pages,
68            action_pages,
69            total_size,
70            resident_size,
71            action_size,
72            resident_pct,
73            action_pct,
74            elapsed,
75        }
76    }
77}
78
79pub fn pretty_elapsed(secs: f64) -> String {
80    if secs < 60.0 {
81        format!("{secs:.1}s")
82    } else {
83        humantime::format_duration(Duration::from_secs(secs as u64)).to_string()
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn test_pretty_size() {
93        assert_eq!(pretty_size(0), "0");
94        assert_eq!(pretty_size(512), "512");
95        assert_eq!(pretty_size(1024), "1.0K");
96        assert_eq!(pretty_size(1536), "1.5K");
97        assert_eq!(pretty_size(1024 * 1024), "1.0M");
98        assert_eq!(pretty_size(1024 * 1024 * 1024), "1.0G");
99        assert_eq!(pretty_size(2 * 1024 * 1024 * 1024), "2.0G");
100        assert_eq!(pretty_size(1024_usize.pow(4)), "1.0T");
101    }
102
103    #[test]
104    fn test_pretty_elapsed() {
105        assert_eq!(pretty_elapsed(0.5), "0.5s");
106        assert_eq!(pretty_elapsed(1.0), "1.0s");
107        assert_eq!(pretty_elapsed(13.919), "13.9s");
108        assert_eq!(pretty_elapsed(59.9), "59.9s");
109        assert_eq!(pretty_elapsed(60.0), "1m");
110        assert_eq!(pretty_elapsed(61.0), "1m 1s");
111        assert_eq!(pretty_elapsed(1801.0), "30m 1s");
112        assert_eq!(pretty_elapsed(3661.0), "1h 1m 1s");
113    }
114
115    #[test]
116    fn test_summary_from_stats_zero() {
117        let stats = Stats::new();
118        let summary = Summary::from_stats(&stats, 1.0, 0);
119        assert_eq!(summary.total_files, 0);
120        assert_eq!(summary.total_pages, 0);
121        assert!((summary.resident_pct - 0.0).abs() < f64::EPSILON);
122        assert!((summary.action_pct - 0.0).abs() < f64::EPSILON);
123    }
124
125    #[test]
126    fn test_summary_pct() {
127        use std::sync::atomic::Ordering;
128        let stats = Stats::new();
129        stats.total_pages.store(200, Ordering::Relaxed);
130        stats.initial_pages_in_core.store(50, Ordering::Relaxed);
131        stats.action_pages.store(100, Ordering::Relaxed);
132        // resident = initial(50) + action(100) * sign(1) = 150
133        let summary = Summary::from_stats(&stats, 0.5, 1);
134        assert!((summary.resident_pct - 75.0).abs() < 0.001);
135        assert!((summary.action_pct - 50.0).abs() < 0.001);
136    }
137}