microfetch_lib/
uptime.rs

1use std::{io, mem::MaybeUninit};
2
3/// Faster integer to string conversion without the formatting overhead.
4#[inline]
5fn itoa(mut n: u64, buf: &mut [u8]) -> &str {
6  if n == 0 {
7    return "0";
8  }
9
10  let mut i = buf.len();
11  while n > 0 {
12    i -= 1;
13    buf[i] = b'0' + (n % 10) as u8;
14    n /= 10;
15  }
16
17  unsafe { std::str::from_utf8_unchecked(&buf[i..]) }
18}
19
20/// Direct `sysinfo` syscall using inline assembly
21///
22/// # Safety
23///
24/// This function uses inline assembly to make a direct syscall.
25/// The caller must ensure the sysinfo pointer is valid.
26#[inline]
27unsafe fn sys_sysinfo(info: *mut libc::sysinfo) -> i64 {
28  #[cfg(target_arch = "x86_64")]
29  {
30    let ret: i64;
31    unsafe {
32      std::arch::asm!(
33        "syscall",
34        in("rax") 99_i64, // __NR_sysinfo
35        in("rdi") info,
36        out("rcx") _,
37        out("r11") _,
38        lateout("rax") ret,
39        options(nostack)
40      );
41    }
42    ret
43  }
44
45  #[cfg(target_arch = "aarch64")]
46  {
47    let ret: i64;
48    unsafe {
49      std::arch::asm!(
50        "svc #0",
51        in("x8") 179_i64, // __NR_sysinfo
52        in("x0") info,
53        lateout("x0") ret,
54        options(nostack)
55      );
56    }
57    ret
58  }
59
60  #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
61  {
62    unsafe { libc::sysinfo(info) as i64 }
63  }
64}
65
66/// Gets the current system uptime.
67///
68/// # Errors
69///
70/// Returns an error if the system uptime cannot be retrieved.
71#[cfg_attr(feature = "hotpath", hotpath::measure)]
72pub fn get_current() -> Result<String, io::Error> {
73  let uptime_seconds = {
74    let mut info = MaybeUninit::uninit();
75    if unsafe { sys_sysinfo(info.as_mut_ptr()) } != 0 {
76      return Err(io::Error::last_os_error());
77    }
78    #[allow(clippy::cast_sign_loss)]
79    unsafe {
80      info.assume_init().uptime as u64
81    }
82  };
83
84  let days = uptime_seconds / 86400;
85  let hours = (uptime_seconds / 3600) % 24;
86  let minutes = (uptime_seconds / 60) % 60;
87
88  let mut result = String::with_capacity(32);
89  let mut buf = [0u8; 20]; // Enough for u64::MAX
90
91  if days > 0 {
92    result.push_str(itoa(days, &mut buf));
93    result.push_str(if days == 1 { " day" } else { " days" });
94  }
95  if hours > 0 {
96    if !result.is_empty() {
97      result.push_str(", ");
98    }
99    result.push_str(itoa(hours, &mut buf));
100    result.push_str(if hours == 1 { " hour" } else { " hours" });
101  }
102  if minutes > 0 {
103    if !result.is_empty() {
104      result.push_str(", ");
105    }
106    result.push_str(itoa(minutes, &mut buf));
107    result.push_str(if minutes == 1 { " minute" } else { " minutes" });
108  }
109  if result.is_empty() {
110    result.push_str("less than a minute");
111  }
112
113  Ok(result)
114}