microfetch_lib/
system.rs

1use std::{ffi::CStr, fmt::Write as _, io, mem::MaybeUninit};
2
3use crate::{UtsName, colors::COLORS, syscall::read_file_fast};
4
5#[must_use]
6#[cfg_attr(feature = "hotpath", hotpath::measure)]
7pub fn get_username_and_hostname(utsname: &UtsName) -> String {
8  let username = unsafe {
9    let ptr = libc::getenv(c"USER".as_ptr());
10    if ptr.is_null() {
11      "unknown_user"
12    } else {
13      CStr::from_ptr(ptr).to_str().unwrap_or("unknown_user")
14    }
15  };
16  let hostname = utsname.nodename().to_str().unwrap_or("unknown_host");
17
18  let capacity = COLORS.yellow.len()
19    + username.len()
20    + COLORS.red.len()
21    + 1
22    + COLORS.green.len()
23    + hostname.len()
24    + COLORS.reset.len();
25  let mut result = String::with_capacity(capacity);
26
27  result.push_str(COLORS.yellow);
28  result.push_str(username);
29  result.push_str(COLORS.red);
30  result.push('@');
31  result.push_str(COLORS.green);
32  result.push_str(hostname);
33  result.push_str(COLORS.reset);
34
35  result
36}
37
38#[must_use]
39#[cfg_attr(feature = "hotpath", hotpath::measure)]
40pub fn get_shell() -> String {
41  unsafe {
42    let ptr = libc::getenv(c"SHELL".as_ptr());
43    if ptr.is_null() {
44      return "unknown_shell".into();
45    }
46
47    let bytes = CStr::from_ptr(ptr).to_bytes();
48    let start = bytes.iter().rposition(|&b| b == b'/').map_or(0, |i| i + 1);
49    let name = std::str::from_utf8_unchecked(&bytes[start..]);
50    name.into()
51  }
52}
53
54/// Gets the root disk usage information.
55///
56/// # Errors
57///
58/// Returns an error if the filesystem information cannot be retrieved.
59#[cfg_attr(feature = "hotpath", hotpath::measure)]
60#[allow(clippy::cast_precision_loss)]
61pub fn get_root_disk_usage() -> Result<String, io::Error> {
62  let mut vfs = MaybeUninit::uninit();
63  let path = b"/\0";
64
65  if unsafe { libc::statvfs(path.as_ptr().cast(), vfs.as_mut_ptr()) } != 0 {
66    return Err(io::Error::last_os_error());
67  }
68
69  let vfs = unsafe { vfs.assume_init() };
70  let block_size = vfs.f_bsize;
71  let total_blocks = vfs.f_blocks;
72  let available_blocks = vfs.f_bavail;
73
74  let total_size = block_size * total_blocks;
75  let used_size = total_size - (block_size * available_blocks);
76
77  let total_size = total_size as f64 / (1024.0 * 1024.0 * 1024.0);
78  let used_size = used_size as f64 / (1024.0 * 1024.0 * 1024.0);
79  let usage = (used_size / total_size) * 100.0;
80
81  let mut result = String::with_capacity(64);
82  write!(
83    result,
84    "{used_size:.2} GiB / {total_size:.2} GiB ({cyan}{usage:.0}%{reset})",
85    cyan = COLORS.cyan,
86    reset = COLORS.reset,
87  )
88  .unwrap();
89
90  Ok(result)
91}
92
93/// Fast integer parsing without stdlib overhead
94#[inline]
95fn parse_u64_fast(s: &[u8]) -> u64 {
96  let mut result = 0u64;
97  for &byte in s {
98    if byte.is_ascii_digit() {
99      result = result * 10 + u64::from(byte - b'0');
100    } else {
101      break;
102    }
103  }
104  result
105}
106
107/// Gets the system memory usage information.
108///
109/// # Errors
110///
111/// Returns an error if `/proc/meminfo` cannot be read.
112#[cfg_attr(feature = "hotpath", hotpath::measure)]
113pub fn get_memory_usage() -> Result<String, io::Error> {
114  #[cfg_attr(feature = "hotpath", hotpath::measure)]
115  fn parse_memory_info() -> Result<(f64, f64), io::Error> {
116    let mut total_memory_kb = 0u64;
117    let mut available_memory_kb = 0u64;
118    let mut buffer = [0u8; 1024];
119
120    // Use fast syscall-based file reading
121    let bytes_read = read_file_fast("/proc/meminfo", &mut buffer)?;
122    let meminfo = &buffer[..bytes_read];
123
124    // Fast scanning for MemTotal and MemAvailable
125    let mut offset = 0;
126    let mut found_total = false;
127    let mut found_available = false;
128
129    while offset < meminfo.len() && (!found_total || !found_available) {
130      let remaining = &meminfo[offset..];
131
132      // Find newline or end
133      let line_end = remaining
134        .iter()
135        .position(|&b| b == b'\n')
136        .unwrap_or(remaining.len());
137      let line = &remaining[..line_end];
138
139      if line.starts_with(b"MemTotal:") {
140        // Skip "MemTotal:" and whitespace
141        let mut pos = 9;
142        while pos < line.len() && line[pos].is_ascii_whitespace() {
143          pos += 1;
144        }
145        total_memory_kb = parse_u64_fast(&line[pos..]);
146        found_total = true;
147      } else if line.starts_with(b"MemAvailable:") {
148        // Skip "MemAvailable:" and whitespace
149        let mut pos = 13;
150        while pos < line.len() && line[pos].is_ascii_whitespace() {
151          pos += 1;
152        }
153        available_memory_kb = parse_u64_fast(&line[pos..]);
154        found_available = true;
155      }
156
157      offset += line_end + 1;
158    }
159
160    #[allow(clippy::cast_precision_loss)]
161    let total_memory_gb = total_memory_kb as f64 / 1024.0 / 1024.0;
162    #[allow(clippy::cast_precision_loss)]
163    let available_memory_gb = available_memory_kb as f64 / 1024.0 / 1024.0;
164    let used_memory_gb = total_memory_gb - available_memory_gb;
165
166    Ok((used_memory_gb, total_memory_gb))
167  }
168
169  let (used_memory, total_memory) = parse_memory_info()?;
170  #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
171  let percentage_used = (used_memory / total_memory * 100.0).round() as u64;
172
173  let mut result = String::with_capacity(64);
174  write!(
175    result,
176    "{used_memory:.2} GiB / {total_memory:.2} GiB \
177     ({cyan}{percentage_used}%{reset})",
178    cyan = COLORS.cyan,
179    reset = COLORS.reset,
180  )
181  .unwrap();
182
183  Ok(result)
184}