88
99use std:: error:: Error ;
1010use std:: fmt;
11- use std:: num:: IntErrorKind ;
11+ #[ cfg( target_os = "linux" ) ]
12+ use std:: io:: BufRead ;
13+ use std:: num:: { IntErrorKind , ParseIntError } ;
1214
1315use crate :: display:: Quotable ;
1416
17+ /// Error arising from trying to compute system memory.
18+ enum SystemError {
19+ IOError ,
20+ ParseError ,
21+ NotFound ,
22+ }
23+
24+ impl From < std:: io:: Error > for SystemError {
25+ fn from ( _: std:: io:: Error ) -> Self {
26+ Self :: IOError
27+ }
28+ }
29+
30+ impl From < ParseIntError > for SystemError {
31+ fn from ( _: ParseIntError ) -> Self {
32+ Self :: ParseError
33+ }
34+ }
35+
36+ /// Get the total number of bytes of physical memory.
37+ ///
38+ /// The information is read from the `/proc/meminfo` file.
39+ ///
40+ /// # Errors
41+ ///
42+ /// If there is a problem reading the file or finding the appropriate
43+ /// entry in the file.
44+ #[ cfg( target_os = "linux" ) ]
45+ fn total_physical_memory ( ) -> Result < u128 , SystemError > {
46+ // On Linux, the `/proc/meminfo` file has a table with information
47+ // about memory usage. For example,
48+ //
49+ // MemTotal: 7811500 kB
50+ // MemFree: 1487876 kB
51+ // MemAvailable: 3857232 kB
52+ // ...
53+ //
54+ // We just need to extract the number of `MemTotal`
55+ let table = std:: fs:: read ( "/proc/meminfo" ) ?;
56+ for line in table. lines ( ) {
57+ let line = line?;
58+ if line. starts_with ( "MemTotal:" ) && line. ends_with ( "kB" ) {
59+ let num_kilobytes: u128 = line[ 9 ..line. len ( ) - 2 ] . trim ( ) . parse ( ) ?;
60+ let num_bytes = 1024 * num_kilobytes;
61+ return Ok ( num_bytes) ;
62+ }
63+ }
64+ Err ( SystemError :: NotFound )
65+ }
66+
67+ /// Get the total number of bytes of physical memory.
68+ ///
69+ /// TODO Implement this for non-Linux systems.
70+ #[ cfg( not( target_os = "linux" ) ) ]
71+ fn total_physical_memory ( ) -> Result < u128 , SystemError > {
72+ Err ( SystemError :: NotFound )
73+ }
74+
1575/// Parser for sizes in SI or IEC units (multiples of 1000 or 1024 bytes).
1676///
1777/// The [`Parser::parse`] function performs the parse.
@@ -133,6 +193,16 @@ impl<'parser> Parser<'parser> {
133193 }
134194 }
135195
196+ // Special case: for percentage, just compute the given fraction
197+ // of the total physical memory on the machine, if possible.
198+ if unit == "%" {
199+ let number: u128 = Self :: parse_number ( & numeric_string, 10 , size) ?;
200+ return match total_physical_memory ( ) {
201+ Ok ( total) => Ok ( ( number / 100 ) * total) ,
202+ Err ( _) => Err ( ParseSizeError :: PhysicalMem ( size. to_string ( ) ) ) ,
203+ } ;
204+ }
205+
136206 // Compute the factor the unit represents.
137207 // empty string means the factor is 1.
138208 //
@@ -320,6 +390,9 @@ pub enum ParseSizeError {
320390
321391 /// Overflow
322392 SizeTooBig ( String ) ,
393+
394+ /// Could not determine total physical memory size.
395+ PhysicalMem ( String ) ,
323396}
324397
325398impl Error for ParseSizeError {
@@ -328,14 +401,18 @@ impl Error for ParseSizeError {
328401 Self :: InvalidSuffix ( ref s) => s,
329402 Self :: ParseFailure ( ref s) => s,
330403 Self :: SizeTooBig ( ref s) => s,
404+ Self :: PhysicalMem ( ref s) => s,
331405 }
332406 }
333407}
334408
335409impl fmt:: Display for ParseSizeError {
336410 fn fmt ( & self , f : & mut fmt:: Formatter ) -> Result < ( ) , fmt:: Error > {
337411 let s = match self {
338- Self :: InvalidSuffix ( s) | Self :: ParseFailure ( s) | Self :: SizeTooBig ( s) => s,
412+ Self :: InvalidSuffix ( s)
413+ | Self :: ParseFailure ( s)
414+ | Self :: SizeTooBig ( s)
415+ | Self :: PhysicalMem ( s) => s,
339416 } ;
340417 write ! ( f, "{s}" )
341418 }
@@ -674,4 +751,16 @@ mod tests {
674751 assert_eq ! ( Ok ( 94722 ) , parse_size_u64( "0x17202" ) ) ;
675752 assert_eq ! ( Ok ( 44251 * 1024 ) , parse_size_u128( "0xACDBK" ) ) ;
676753 }
754+
755+ #[ test]
756+ #[ cfg( target_os = "linux" ) ]
757+ fn parse_percent ( ) {
758+ assert ! ( parse_size_u64( "0%" ) . is_ok( ) ) ;
759+ assert ! ( parse_size_u64( "50%" ) . is_ok( ) ) ;
760+ assert ! ( parse_size_u64( "100%" ) . is_ok( ) ) ;
761+ assert ! ( parse_size_u64( "100000%" ) . is_ok( ) ) ;
762+ assert ! ( parse_size_u64( "-1%" ) . is_err( ) ) ;
763+ assert ! ( parse_size_u64( "1.0%" ) . is_err( ) ) ;
764+ assert ! ( parse_size_u64( "0x1%" ) . is_err( ) ) ;
765+ }
677766}
0 commit comments