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 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}