deno_runtime/
sys_info.rs

1// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2#[cfg(target_family = "windows")]
3use std::sync::Once;
4
5type LoadAvg = (f64, f64, f64);
6const DEFAULT_LOADAVG: LoadAvg = (0.0, 0.0, 0.0);
7
8pub fn loadavg() -> LoadAvg {
9  #[cfg(any(target_os = "android", target_os = "linux"))]
10  {
11    use libc::SI_LOAD_SHIFT;
12
13    let mut info = std::mem::MaybeUninit::uninit();
14    // SAFETY: `info` is a valid pointer to a `libc::sysinfo` struct.
15    let res = unsafe { libc::sysinfo(info.as_mut_ptr()) };
16    if res == 0 {
17      // SAFETY: `sysinfo` returns 0 on success, and `info` is initialized.
18      let info = unsafe { info.assume_init() };
19      (
20        info.loads[0] as f64 / (1 << SI_LOAD_SHIFT) as f64,
21        info.loads[1] as f64 / (1 << SI_LOAD_SHIFT) as f64,
22        info.loads[2] as f64 / (1 << SI_LOAD_SHIFT) as f64,
23      )
24    } else {
25      DEFAULT_LOADAVG
26    }
27  }
28  #[cfg(any(
29    target_vendor = "apple",
30    target_os = "freebsd",
31    target_os = "openbsd"
32  ))]
33  {
34    let mut l: [f64; 3] = [0.; 3];
35    // SAFETY: `&mut l` is a valid pointer to an array of 3 doubles
36    if unsafe { libc::getloadavg(&mut l as *mut f64, l.len() as _) } < 3 {
37      DEFAULT_LOADAVG
38    } else {
39      (l[0], l[1], l[2])
40    }
41  }
42  #[cfg(target_os = "windows")]
43  {
44    DEFAULT_LOADAVG
45  }
46}
47
48pub fn os_release() -> String {
49  #[cfg(target_os = "linux")]
50  {
51    #[allow(clippy::disallowed_methods)]
52    match std::fs::read_to_string("/proc/sys/kernel/osrelease") {
53      Ok(mut s) => {
54        s.pop(); // pop '\n'
55        s
56      }
57      _ => String::from(""),
58    }
59  }
60  #[cfg(target_os = "android")]
61  {
62    let mut info = std::mem::MaybeUninit::uninit();
63    // SAFETY: `info` is a valid pointer to a `libc::utsname` struct.
64    let res = unsafe { libc::uname(info.as_mut_ptr()) };
65    if res != 0 {
66      return String::from("");
67    }
68    // SAFETY: `uname` returns 0 on success, and `info` is initialized.
69    let mut info = unsafe { info.assume_init() };
70    let len = info.release.len();
71    info.release[len - 1] = 0;
72    // SAFETY: `info.release` is a valid pointer and NUL-terminated.
73    let c_str = unsafe { std::ffi::CStr::from_ptr(info.release.as_ptr()) };
74    c_str.to_string_lossy().into_owned()
75  }
76  #[cfg(any(
77    target_vendor = "apple",
78    target_os = "freebsd",
79    target_os = "openbsd"
80  ))]
81  {
82    let mut s = [0u8; 256];
83    let mut mib = [libc::CTL_KERN, libc::KERN_OSRELEASE];
84    // 256 is enough.
85    let mut len = s.len();
86    // SAFETY: `sysctl` is thread-safe.
87    // `s` is only accessed if sysctl() succeeds and agrees with the `len` set
88    // by sysctl().
89    if unsafe {
90      libc::sysctl(
91        mib.as_mut_ptr(),
92        mib.len() as _,
93        s.as_mut_ptr() as _,
94        &mut len,
95        std::ptr::null_mut(),
96        0,
97      )
98    } == -1
99    {
100      return String::from("Unknown");
101    }
102
103    // without the NUL terminator
104    return String::from_utf8_lossy(&s[..len - 1]).to_string();
105  }
106  #[cfg(target_family = "windows")]
107  {
108    use ntapi::ntrtl::RtlGetVersion;
109    use winapi::shared::ntdef::NT_SUCCESS;
110    use winapi::um::winnt::RTL_OSVERSIONINFOEXW;
111
112    let mut version_info =
113      std::mem::MaybeUninit::<RTL_OSVERSIONINFOEXW>::uninit();
114    // SAFETY: we need to initialize dwOSVersionInfoSize.
115    unsafe {
116      (*version_info.as_mut_ptr()).dwOSVersionInfoSize =
117        std::mem::size_of::<RTL_OSVERSIONINFOEXW>() as u32;
118    }
119    // SAFETY: `version_info` is pointer to a valid `RTL_OSVERSIONINFOEXW` struct and
120    // dwOSVersionInfoSize  is set to the size of RTL_OSVERSIONINFOEXW.
121    if !NT_SUCCESS(unsafe {
122      RtlGetVersion(version_info.as_mut_ptr() as *mut _)
123    }) {
124      String::from("")
125    } else {
126      // SAFETY: we assume that RtlGetVersion() initializes the fields.
127      let version_info = unsafe { version_info.assume_init() };
128      format!(
129        "{}.{}.{}",
130        version_info.dwMajorVersion,
131        version_info.dwMinorVersion,
132        version_info.dwBuildNumber
133      )
134    }
135  }
136}
137
138#[cfg(target_family = "windows")]
139static WINSOCKET_INIT: Once = Once::new();
140
141pub fn hostname() -> String {
142  #[cfg(target_family = "unix")]
143  // SAFETY: `sysconf` returns a system constant.
144  unsafe {
145    let buf_size = libc::sysconf(libc::_SC_HOST_NAME_MAX) as usize;
146    let mut buf = vec![0u8; buf_size + 1];
147    let len = buf.len();
148    if libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, len) < 0 {
149      return String::from("");
150    }
151    // ensure NUL termination
152    buf[len - 1] = 0;
153    std::ffi::CStr::from_ptr(buf.as_ptr() as *const libc::c_char)
154      .to_string_lossy()
155      .to_string()
156  }
157  #[cfg(target_family = "windows")]
158  {
159    use std::ffi::OsString;
160    use std::mem;
161    use std::os::windows::ffi::OsStringExt;
162    use winapi::shared::minwindef::MAKEWORD;
163    use winapi::um::winsock2::GetHostNameW;
164    use winapi::um::winsock2::WSAStartup;
165
166    let namelen = 256;
167    let mut name: Vec<u16> = vec![0u16; namelen];
168    // Start winsock to make `GetHostNameW` work correctly
169    // https://github.com/retep998/winapi-rs/issues/296
170    // SAFETY: winapi call
171    WINSOCKET_INIT.call_once(|| unsafe {
172      let mut data = mem::zeroed();
173      let wsa_startup_result = WSAStartup(MAKEWORD(2, 2), &mut data);
174      if wsa_startup_result != 0 {
175        panic!("Failed to start winsocket");
176      }
177    });
178    let err =
179      // SAFETY: length of wide string is 256 chars or less.
180      // https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-gethostnamew
181      unsafe { GetHostNameW(name.as_mut_ptr(), namelen as libc::c_int) };
182
183    if err == 0 {
184      // TODO(@littledivy): Probably not the most efficient way.
185      let len = name.iter().take_while(|&&c| c != 0).count();
186      OsString::from_wide(&name[..len])
187        .to_string_lossy()
188        .into_owned()
189    } else {
190      String::from("")
191    }
192  }
193}
194
195#[derive(serde::Serialize)]
196#[serde(rename_all = "camelCase")]
197pub struct MemInfo {
198  pub total: u64,
199  pub free: u64,
200  pub available: u64,
201  pub buffers: u64,
202  pub cached: u64,
203  pub swap_total: u64,
204  pub swap_free: u64,
205}
206
207pub fn mem_info() -> Option<MemInfo> {
208  let mut mem_info = MemInfo {
209    total: 0,
210    free: 0,
211    available: 0,
212    buffers: 0,
213    cached: 0,
214    swap_total: 0,
215    swap_free: 0,
216  };
217  #[cfg(any(target_os = "android", target_os = "linux"))]
218  {
219    let mut info = std::mem::MaybeUninit::uninit();
220    // SAFETY: `info` is a valid pointer to a `libc::sysinfo` struct.
221    let res = unsafe { libc::sysinfo(info.as_mut_ptr()) };
222    if res == 0 {
223      // SAFETY: `sysinfo` initializes the struct.
224      let info = unsafe { info.assume_init() };
225      let mem_unit = info.mem_unit as u64;
226      mem_info.swap_total = info.totalswap * mem_unit;
227      mem_info.swap_free = info.freeswap * mem_unit;
228      mem_info.total = info.totalram * mem_unit;
229      mem_info.free = info.freeram * mem_unit;
230      mem_info.available = mem_info.free;
231      mem_info.buffers = info.bufferram * mem_unit;
232    }
233
234    // Gets the available memory from /proc/meminfo in linux for compatibility
235    #[allow(clippy::disallowed_methods)]
236    if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
237      let line = meminfo.lines().find(|l| l.starts_with("MemAvailable:"));
238      if let Some(line) = line {
239        let mem = line.split_whitespace().nth(1);
240        let mem = mem.and_then(|v| v.parse::<u64>().ok());
241        mem_info.available = mem.unwrap_or(0) * 1024;
242      }
243    }
244  }
245  #[cfg(target_vendor = "apple")]
246  {
247    let mut mib: [i32; 2] = [0, 0];
248    mib[0] = libc::CTL_HW;
249    mib[1] = libc::HW_MEMSIZE;
250    // SAFETY:
251    //  - We assume that `mach_host_self` always returns a valid value.
252    //  - sysconf returns a system constant.
253    unsafe {
254      let mut size = std::mem::size_of::<u64>();
255      libc::sysctl(
256        mib.as_mut_ptr(),
257        mib.len() as _,
258        &mut mem_info.total as *mut _ as *mut libc::c_void,
259        &mut size,
260        std::ptr::null_mut(),
261        0,
262      );
263
264      let mut xs: libc::xsw_usage = std::mem::zeroed::<libc::xsw_usage>();
265      mib[0] = libc::CTL_VM;
266      mib[1] = libc::VM_SWAPUSAGE;
267
268      let mut size = std::mem::size_of::<libc::xsw_usage>();
269      libc::sysctl(
270        mib.as_mut_ptr(),
271        mib.len() as _,
272        &mut xs as *mut _ as *mut libc::c_void,
273        &mut size,
274        std::ptr::null_mut(),
275        0,
276      );
277
278      mem_info.swap_total = xs.xsu_total;
279      mem_info.swap_free = xs.xsu_avail;
280
281      let mut count: u32 = libc::HOST_VM_INFO64_COUNT as _;
282      let mut stat = std::mem::zeroed::<libc::vm_statistics64>();
283      if libc::host_statistics64(
284        // TODO(@littledivy): Put this in a once_cell.
285        libc::mach_host_self(),
286        libc::HOST_VM_INFO64,
287        &mut stat as *mut libc::vm_statistics64 as *mut _,
288        &mut count,
289      ) == libc::KERN_SUCCESS
290      {
291        // TODO(@littledivy): Put this in a once_cell
292        let page_size = libc::sysconf(libc::_SC_PAGESIZE) as u64;
293        mem_info.available =
294          (stat.free_count as u64 + stat.inactive_count as u64) * page_size
295            / 1024;
296        mem_info.free =
297          (stat.free_count as u64 - stat.speculative_count as u64) * page_size
298            / 1024;
299      }
300    }
301  }
302  #[cfg(target_family = "windows")]
303  // SAFETY:
304  //   - `mem_status` is a valid pointer to a `libc::MEMORYSTATUSEX` struct.
305  //   - `dwLength` is set to the size of the struct.
306  unsafe {
307    use std::mem;
308    use winapi::shared::minwindef;
309    use winapi::um::psapi::GetPerformanceInfo;
310    use winapi::um::psapi::PERFORMANCE_INFORMATION;
311    use winapi::um::sysinfoapi;
312
313    let mut mem_status =
314      mem::MaybeUninit::<sysinfoapi::MEMORYSTATUSEX>::uninit();
315    let length =
316      mem::size_of::<sysinfoapi::MEMORYSTATUSEX>() as minwindef::DWORD;
317    (*mem_status.as_mut_ptr()).dwLength = length;
318
319    let result = sysinfoapi::GlobalMemoryStatusEx(mem_status.as_mut_ptr());
320    if result != 0 {
321      let stat = mem_status.assume_init();
322      mem_info.total = stat.ullTotalPhys;
323      mem_info.available = 0;
324      mem_info.free = stat.ullAvailPhys;
325      mem_info.cached = 0;
326      mem_info.buffers = 0;
327
328      // `stat.ullTotalPageFile` is reliable only from GetPerformanceInfo()
329      //
330      // See https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex
331      // and https://github.com/GuillaumeGomez/sysinfo/issues/534
332
333      let mut perf_info = mem::MaybeUninit::<PERFORMANCE_INFORMATION>::uninit();
334      let result = GetPerformanceInfo(
335        perf_info.as_mut_ptr(),
336        mem::size_of::<PERFORMANCE_INFORMATION>() as minwindef::DWORD,
337      );
338      if result == minwindef::TRUE {
339        let perf_info = perf_info.assume_init();
340        let swap_total = perf_info.PageSize
341          * perf_info
342            .CommitLimit
343            .saturating_sub(perf_info.PhysicalTotal);
344        let swap_free = perf_info.PageSize
345          * perf_info
346            .CommitLimit
347            .saturating_sub(perf_info.PhysicalTotal)
348            .saturating_sub(perf_info.PhysicalAvailable);
349        mem_info.swap_total = (swap_total / 1000) as u64;
350        mem_info.swap_free = (swap_free / 1000) as u64;
351      }
352    }
353  }
354
355  Some(mem_info)
356}
357
358pub fn os_uptime() -> u64 {
359  let uptime: u64;
360
361  #[cfg(any(target_os = "android", target_os = "linux"))]
362  {
363    let mut info = std::mem::MaybeUninit::uninit();
364    // SAFETY: `info` is a valid pointer to a `libc::sysinfo` struct.
365    let res = unsafe { libc::sysinfo(info.as_mut_ptr()) };
366    uptime = if res == 0 {
367      // SAFETY: `sysinfo` initializes the struct.
368      let info = unsafe { info.assume_init() };
369      info.uptime as u64
370    } else {
371      0
372    }
373  }
374
375  #[cfg(any(
376    target_vendor = "apple",
377    target_os = "freebsd",
378    target_os = "openbsd"
379  ))]
380  {
381    use std::mem;
382    use std::time::Duration;
383    use std::time::SystemTime;
384    let mut request = [libc::CTL_KERN, libc::KERN_BOOTTIME];
385    // SAFETY: `boottime` is only accessed if sysctl() succeeds
386    // and agrees with the `size` set by sysctl().
387    let mut boottime: libc::timeval = unsafe { mem::zeroed() };
388    let mut size: libc::size_t = mem::size_of_val(&boottime) as libc::size_t;
389    // SAFETY: `sysctl` is thread-safe.
390    let res = unsafe {
391      libc::sysctl(
392        &mut request[0],
393        2,
394        &mut boottime as *mut libc::timeval as *mut libc::c_void,
395        &mut size,
396        std::ptr::null_mut(),
397        0,
398      )
399    };
400    uptime = if res == 0 {
401      SystemTime::now()
402        .duration_since(SystemTime::UNIX_EPOCH)
403        .map(|d| {
404          (d - Duration::new(
405            boottime.tv_sec as u64,
406            boottime.tv_usec as u32 * 1000,
407          ))
408          .as_secs()
409        })
410        .unwrap_or_default()
411    } else {
412      0
413    }
414  }
415
416  #[cfg(target_family = "windows")]
417  // SAFETY: windows API usage
418  unsafe {
419    // Windows is the only one that returns `uptime` in millisecond precision,
420    // so we need to get the seconds out of it to be in sync with other envs.
421    uptime = winapi::um::sysinfoapi::GetTickCount64() / 1000;
422  }
423
424  uptime
425}