@@ -108,6 +108,146 @@ pub fn get_cpu_count() -> usize {
108108 . unwrap_or ( 1 )
109109}
110110
111+ /// Zswap statistics from debugfs
112+ #[ derive( Debug , Default , Clone ) ]
113+ pub struct ZswapStats {
114+ /// Pages currently stored in zswap pool (RAM)
115+ pub stored_pages : u64 ,
116+ /// Total bytes used by zswap pool in RAM
117+ pub pool_total_size : u64 ,
118+ /// Pages that have been written back to disk swap
119+ pub written_back_pages : u64 ,
120+ /// Pages rejected due to reclaim failure
121+ pub reject_reclaim_fail : u64 ,
122+ /// Same-value filled pages (often zeros)
123+ pub same_filled_pages : u64 ,
124+ /// Pool limit hit count
125+ pub pool_limit_hit : u64 ,
126+ }
127+
128+ const ZSWAP_DEBUG_DIR : & str = "/sys/kernel/debug/zswap" ;
129+
130+ /// Read zswap statistics from debugfs (requires root)
131+ pub fn get_zswap_stats ( ) -> Option < ZswapStats > {
132+ let debug_path = std:: path:: Path :: new ( ZSWAP_DEBUG_DIR ) ;
133+ if !debug_path. is_dir ( ) {
134+ return None ;
135+ }
136+
137+ let read_stat = |name : & str | -> u64 {
138+ std:: fs:: read_to_string ( debug_path. join ( name) )
139+ . ok ( )
140+ . and_then ( |s| s. trim ( ) . parse ( ) . ok ( ) )
141+ . unwrap_or ( 0 )
142+ } ;
143+
144+ Some ( ZswapStats {
145+ stored_pages : read_stat ( "stored_pages" ) ,
146+ pool_total_size : read_stat ( "pool_total_size" ) ,
147+ written_back_pages : read_stat ( "written_back_pages" ) ,
148+ reject_reclaim_fail : read_stat ( "reject_reclaim_fail" ) ,
149+ same_filled_pages : read_stat ( "same_filled_pages" ) ,
150+ pool_limit_hit : read_stat ( "pool_limit_hit" ) ,
151+ } )
152+ }
153+
154+ /// Effective swap usage information accounting for zswap
155+ #[ derive( Debug , Default ) ]
156+ pub struct EffectiveSwapUsage {
157+ /// Total swap space (bytes)
158+ pub swap_total : u64 ,
159+ /// Free swap space as reported by kernel (bytes)
160+ pub swap_free : u64 ,
161+ /// Swap used as reported by kernel (bytes) - includes zswap cached pages
162+ pub swap_used_kernel : u64 ,
163+ /// Bytes stored in zswap RAM pool (not on disk)
164+ pub zswap_pool_bytes : u64 ,
165+ /// Estimated bytes actually written to disk swap
166+ pub swap_used_disk : u64 ,
167+ /// Zswap pool utilization percentage (0-100)
168+ pub zswap_pool_percent : u8 ,
169+ /// Whether zswap is active and has stored pages
170+ pub zswap_active : bool ,
171+ }
172+
173+ /// Get effective swap usage accounting for zswap compression
174+ ///
175+ /// When zswap is active, the kernel reports swap usage based on allocated slots,
176+ /// but most of those pages may still be in zswap's RAM pool and not written to disk.
177+ /// This function calculates the actual disk pressure.
178+ pub fn get_effective_swap_usage ( ) -> Result < EffectiveSwapUsage > {
179+ let stats = get_mem_stats ( & [ "MemTotal" , "SwapTotal" , "SwapFree" ] ) ?;
180+ let swap_total = stats[ "SwapTotal" ] ;
181+ let swap_free = stats[ "SwapFree" ] ;
182+ let swap_used_kernel = swap_total. saturating_sub ( swap_free) ;
183+ let mem_total = stats[ "MemTotal" ] ;
184+
185+ let mut result = EffectiveSwapUsage {
186+ swap_total,
187+ swap_free,
188+ swap_used_kernel,
189+ zswap_pool_bytes : 0 ,
190+ swap_used_disk : swap_used_kernel, // Default: assume all on disk
191+ zswap_pool_percent : 0 ,
192+ zswap_active : false ,
193+ } ;
194+
195+ // Check zswap status
196+ if let Some ( zswap_stats) = get_zswap_stats ( ) {
197+ let page_size = get_page_size ( ) ;
198+ let stored_bytes = zswap_stats. stored_pages * page_size;
199+
200+ if stored_bytes > 0 {
201+ result. zswap_active = true ;
202+ result. zswap_pool_bytes = zswap_stats. pool_total_size ;
203+
204+ // Actual disk usage = kernel reported usage - pages in zswap pool
205+ // Pages in zswap pool are still in RAM, not written to disk
206+ result. swap_used_disk = swap_used_kernel. saturating_sub ( stored_bytes) ;
207+
208+ // Calculate zswap pool utilization
209+ // Read max_pool_percent from sysfs
210+ let max_pool_percent: u64 = std:: fs:: read_to_string (
211+ "/sys/module/zswap/parameters/max_pool_percent"
212+ )
213+ . ok ( )
214+ . and_then ( |s| s. trim ( ) . parse ( ) . ok ( ) )
215+ . unwrap_or ( 20 ) ;
216+
217+ let max_pool_size = mem_total * max_pool_percent / 100 ;
218+ if max_pool_size > 0 {
219+ result. zswap_pool_percent =
220+ ( ( zswap_stats. pool_total_size * 100 ) / max_pool_size) . min ( 100 ) as u8 ;
221+ }
222+ }
223+ }
224+
225+ Ok ( result)
226+ }
227+
228+ /// Get effective free swap percentage accounting for zswap
229+ ///
230+ /// This returns a more accurate picture of swap pressure:
231+ /// - If zswap is inactive, returns normal SwapFree percentage
232+ /// - If zswap is active, considers both pool utilization and disk pressure
233+ pub fn get_effective_free_swap_percent ( ) -> Result < u8 > {
234+ let usage = get_effective_swap_usage ( ) ?;
235+
236+ if !usage. zswap_active || usage. swap_total == 0 {
237+ // No zswap, use traditional calculation
238+ return Ok ( ( ( usage. swap_free * 100 ) / usage. swap_total . max ( 1 ) ) as u8 ) ;
239+ }
240+
241+ // With zswap active, calculate based on actual disk usage
242+ let disk_used_percent = if usage. swap_total > 0 {
243+ ( ( usage. swap_used_disk * 100 ) / usage. swap_total ) as u8
244+ } else {
245+ 0
246+ } ;
247+
248+ Ok ( 100u8 . saturating_sub ( disk_used_percent) )
249+ }
250+
111251#[ cfg( test) ]
112252mod tests {
113253 use super :: * ;
@@ -123,4 +263,10 @@ mod tests {
123263 let percent = get_free_ram_percent ( ) . unwrap ( ) ;
124264 assert ! ( percent <= 100 ) ;
125265 }
266+
267+ #[ test]
268+ fn test_get_effective_swap_usage ( ) {
269+ // This test may not work without swap, but should not panic
270+ let _ = get_effective_swap_usage ( ) ;
271+ }
126272}
0 commit comments