88
99use std:: error:: Error ;
1010use std:: fmt;
11- use std:: num:: { IntErrorKind , ParseIntError } ;
11+ use std:: num:: IntErrorKind ;
1212
1313use crate :: display:: Quotable ;
14- #[ cfg( target_os = "linux" ) ]
15- use procfs:: { Current , Meminfo } ;
1614
1715/// Error arising from trying to compute system memory.
1816enum SystemError {
@@ -28,10 +26,48 @@ impl From<std::io::Error> for SystemError {
2826 }
2927}
3028
31- impl From < ParseIntError > for SystemError {
32- fn from ( _: ParseIntError ) -> Self {
33- Self :: ParseError
29+ #[ cfg( target_os = "linux" ) ]
30+ fn parse_meminfo_kb ( line : & str , key : & str ) -> Option < u128 > {
31+ line. strip_prefix ( key) ?
32+ . strip_prefix ( ':' ) ?
33+ . trim ( )
34+ . trim_end_matches ( " kB" )
35+ . trim ( )
36+ . parse :: < u128 > ( )
37+ . ok ( )
38+ . map ( |kb| kb. saturating_mul ( 1024 ) )
39+ }
40+
41+ #[ cfg( target_os = "linux" ) ]
42+ fn read_meminfo ( ) -> Result < ( u128 , u128 ) , SystemError > {
43+ let contents = std:: fs:: read_to_string ( "/proc/meminfo" ) ?;
44+ let mut total = None ;
45+ let mut available = None ;
46+ let mut free = 0u128 ;
47+ let mut buffers = 0u128 ;
48+ let mut cached = 0u128 ;
49+ for line in contents. lines ( ) {
50+ if let Some ( v) = parse_meminfo_kb ( line, "MemTotal" ) {
51+ total = Some ( v) ;
52+ } else if let Some ( v) = parse_meminfo_kb ( line, "MemAvailable" ) {
53+ available = Some ( v) ;
54+ } else if let Some ( v) = parse_meminfo_kb ( line, "MemFree" ) {
55+ free = v;
56+ } else if let Some ( v) = parse_meminfo_kb ( line, "Buffers" ) {
57+ buffers = v;
58+ } else if let Some ( v) = parse_meminfo_kb ( line, "Cached" ) {
59+ cached = v;
60+ }
3461 }
62+ let total = total. ok_or ( SystemError :: ParseError ) ?;
63+ let available = available
64+ . filter ( |& v| v > 0 )
65+ . or_else ( || {
66+ let fallback = free. saturating_add ( buffers) . saturating_add ( cached) ;
67+ ( fallback > 0 ) . then_some ( fallback)
68+ } )
69+ . unwrap_or ( total) ;
70+ Ok ( ( total, available) )
3571}
3672
3773/// Get the total number of bytes of physical memory.
@@ -44,31 +80,13 @@ impl From<ParseIntError> for SystemError {
4480/// entry in the file.
4581#[ cfg( target_os = "linux" ) ]
4682fn total_physical_memory ( ) -> Result < u128 , SystemError > {
47- let info = Meminfo :: current ( ) . map_err ( |_| SystemError :: IOError ) ?;
48- Ok ( ( info. mem_total as u128 ) . saturating_mul ( 1024 ) )
83+ read_meminfo ( ) . map ( |( total, _) | total)
4984}
5085
5186/// Return the number of bytes of memory that appear to be currently available.
5287#[ cfg( target_os = "linux" ) ]
5388pub fn available_memory_bytes ( ) -> Option < u128 > {
54- let info = Meminfo :: current ( ) . ok ( ) ?;
55-
56- if let Some ( available_kib) = info. mem_available {
57- let available_bytes = ( available_kib as u128 ) . saturating_mul ( 1024 ) ;
58- if available_bytes > 0 {
59- return Some ( available_bytes) ;
60- }
61- }
62-
63- let fallback_kib = ( info. mem_free as u128 )
64- . saturating_add ( info. buffers as u128 )
65- . saturating_add ( info. cached as u128 ) ;
66-
67- if fallback_kib > 0 {
68- Some ( fallback_kib. saturating_mul ( 1024 ) )
69- } else {
70- total_physical_memory ( ) . ok ( )
71- }
89+ read_meminfo ( ) . ok ( ) . map ( |( _, available) | available)
7290}
7391
7492/// Return `None` when the platform does not expose Linux-like `/proc/meminfo`.
@@ -219,7 +237,7 @@ impl<'parser> Parser<'parser> {
219237 if unit == "%" {
220238 let number: u128 = Self :: parse_number ( & numeric_string, 10 , size) ?;
221239 return match total_physical_memory ( ) {
222- Ok ( total) => Ok ( ( number / 100 ) * total ) ,
240+ Ok ( total) => Ok ( total * number / 100 ) ,
223241 Err ( _) => Err ( ParseSizeError :: PhysicalMem ( size. to_string ( ) ) ) ,
224242 } ;
225243 }
@@ -815,4 +833,18 @@ mod tests {
815833 assert ! ( parse_size_u64( "1.0%" ) . is_err( ) ) ;
816834 assert ! ( parse_size_u64( "0x1%" ) . is_err( ) ) ;
817835 }
836+
837+ #[ test]
838+ #[ cfg( target_os = "linux" ) ]
839+ fn parse_percent_values ( ) {
840+ let total = total_physical_memory ( ) . ok ( ) . unwrap ( ) ;
841+
842+ // 100% should equal total physical memory, not 1024x too large
843+ assert_eq ! ( Ok ( total) , parse_size_u128( "100%" ) ) ;
844+
845+ // 50% should be half of total, not 0 from integer division truncation
846+ assert_eq ! ( Ok ( total / 2 ) , parse_size_u128( "50%" ) ) ;
847+
848+ assert_eq ! ( Ok ( 0 ) , parse_size_u128( "0%" ) ) ;
849+ }
818850}
0 commit comments