1use crate::sys::cpu::{get_physical_core_count, CpusWrapper};
4use crate::sys::process::{_get_process_data, compute_cpu_usage, refresh_procs, unset_updated};
5use crate::sys::utils::{get_all_data, to_u64};
6use crate::{Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind};
7
8use libc::{self, c_char, sysconf, _SC_CLK_TCK, _SC_HOST_NAME_MAX, _SC_PAGESIZE};
9use std::cmp::min;
10use std::collections::HashMap;
11use std::ffi::CStr;
12use std::fs::File;
13use std::io::Read;
14use std::path::Path;
15use std::str::FromStr;
16use std::sync::atomic::AtomicIsize;
17
18pub(crate) static REMAINING_FILES: once_cell::sync::Lazy<AtomicIsize> =
21 once_cell::sync::Lazy::new(|| {
22 unsafe {
23 let mut limits = libc::rlimit {
24 rlim_cur: 0,
25 rlim_max: 0,
26 };
27 if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 {
28 return AtomicIsize::new(1024 / 2);
30 }
31 let current = limits.rlim_cur;
33
34 limits.rlim_cur = limits.rlim_max;
36 AtomicIsize::new(if libc::setrlimit(libc::RLIMIT_NOFILE, &limits) == 0 {
39 limits.rlim_cur / 2
40 } else {
41 current / 2
42 } as _)
43 }
44 });
45
46pub(crate) fn get_max_nb_fds() -> isize {
47 unsafe {
48 let mut limits = libc::rlimit {
49 rlim_cur: 0,
50 rlim_max: 0,
51 };
52 if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 {
53 1024 / 2
55 } else {
56 limits.rlim_max as isize / 2
57 }
58 }
59}
60
61fn boot_time() -> u64 {
62 if let Ok(buf) = File::open("/proc/stat").and_then(|mut f| {
63 let mut buf = Vec::new();
64 f.read_to_end(&mut buf)?;
65 Ok(buf)
66 }) {
67 let line = buf.split(|c| *c == b'\n').find(|l| l.starts_with(b"btime"));
68
69 if let Some(line) = line {
70 return line
71 .split(|x| *x == b' ')
72 .filter(|s| !s.is_empty())
73 .nth(1)
74 .map(to_u64)
75 .unwrap_or(0);
76 }
77 }
78 unsafe {
80 let mut up: libc::timespec = std::mem::zeroed();
81 if libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut up) == 0 {
82 up.tv_sec as u64
83 } else {
84 sysinfo_debug!("clock_gettime failed: boot time cannot be retrieve...");
85 0
86 }
87 }
88}
89
90pub(crate) struct SystemInfo {
91 pub(crate) page_size_b: u64,
92 pub(crate) clock_cycle: u64,
93 pub(crate) boot_time: u64,
94}
95
96impl SystemInfo {
97 fn new() -> Self {
98 unsafe {
99 Self {
100 page_size_b: sysconf(_SC_PAGESIZE) as _,
101 clock_cycle: sysconf(_SC_CLK_TCK) as _,
102 boot_time: boot_time(),
103 }
104 }
105 }
106}
107
108pub(crate) struct SystemInner {
109 process_list: HashMap<Pid, Process>,
110 mem_total: u64,
111 mem_free: u64,
112 mem_available: u64,
113 mem_buffers: u64,
114 mem_page_cache: u64,
115 mem_shmem: u64,
116 mem_slab_reclaimable: u64,
117 swap_total: u64,
118 swap_free: u64,
119 info: SystemInfo,
120 cpus: CpusWrapper,
121}
122
123impl SystemInner {
124 fn get_max_process_cpu_usage(&self) -> f32 {
130 self.cpus.len() as f32 * 100.
131 }
132
133 fn clear_procs(&mut self, refresh_kind: ProcessRefreshKind) {
134 let (total_time, compute_cpu, max_value) = if refresh_kind.cpu() {
135 self.cpus
136 .refresh_if_needed(true, CpuRefreshKind::new().with_cpu_usage());
137
138 if self.cpus.is_empty() {
139 sysinfo_debug!("cannot compute processes CPU usage: no CPU found...");
140 (0., false, 0.)
141 } else {
142 let (new, old) = self.cpus.get_global_raw_times();
143 let total_time = if old > new { 1 } else { new - old };
144 (
145 total_time as f32 / self.cpus.len() as f32,
146 true,
147 self.get_max_process_cpu_usage(),
148 )
149 }
150 } else {
151 (0., false, 0.)
152 };
153
154 self.process_list.retain(|_, proc_| {
155 let proc_ = &mut proc_.inner;
156 if !proc_.updated {
157 return false;
158 }
159 if compute_cpu {
160 compute_cpu_usage(proc_, total_time, max_value);
161 }
162 unset_updated(proc_);
163 true
164 });
165 }
166
167 fn refresh_cpus(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) {
168 self.cpus.refresh(only_update_global_cpu, refresh_kind);
169 }
170}
171
172impl SystemInner {
173 pub(crate) fn new() -> Self {
174 Self {
175 process_list: HashMap::new(),
176 mem_total: 0,
177 mem_free: 0,
178 mem_available: 0,
179 mem_buffers: 0,
180 mem_page_cache: 0,
181 mem_shmem: 0,
182 mem_slab_reclaimable: 0,
183 swap_total: 0,
184 swap_free: 0,
185 cpus: CpusWrapper::new(),
186 info: SystemInfo::new(),
187 }
188 }
189
190 pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) {
191 if !refresh_kind.ram() && !refresh_kind.swap() {
192 return;
193 }
194 let mut mem_available_found = false;
195 read_table("/proc/meminfo", ':', |key, value_kib| {
196 let field = match key {
197 "MemTotal" => &mut self.mem_total,
198 "MemFree" => &mut self.mem_free,
199 "MemAvailable" => {
200 mem_available_found = true;
201 &mut self.mem_available
202 }
203 "Buffers" => &mut self.mem_buffers,
204 "Cached" => &mut self.mem_page_cache,
205 "Shmem" => &mut self.mem_shmem,
206 "SReclaimable" => &mut self.mem_slab_reclaimable,
207 "SwapTotal" => &mut self.swap_total,
208 "SwapFree" => &mut self.swap_free,
209 _ => return,
210 };
211 *field = value_kib.saturating_mul(1_024);
213 });
214
215 if !mem_available_found {
219 self.mem_available = self
220 .mem_free
221 .saturating_add(self.mem_buffers)
222 .saturating_add(self.mem_page_cache)
223 .saturating_add(self.mem_slab_reclaimable)
224 .saturating_sub(self.mem_shmem);
225 }
226 }
227
228 pub(crate) fn cgroup_limits(&self) -> Option<crate::CGroupLimits> {
229 crate::CGroupLimits::new(self)
230 }
231
232 pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) {
233 self.refresh_cpus(false, refresh_kind);
234 }
235
236 pub(crate) fn refresh_processes_specifics(
237 &mut self,
238 filter: Option<&[Pid]>,
239 refresh_kind: ProcessRefreshKind,
240 ) {
241 let uptime = Self::uptime();
242 refresh_procs(
243 &mut self.process_list,
244 Path::new("/proc"),
245 uptime,
246 &self.info,
247 filter,
248 refresh_kind,
249 );
250 self.clear_procs(refresh_kind);
251 self.cpus.set_need_cpus_update();
252 }
253
254 pub(crate) fn refresh_process_specifics(
255 &mut self,
256 pid: Pid,
257 refresh_kind: ProcessRefreshKind,
258 ) -> bool {
259 let uptime = Self::uptime();
260 match _get_process_data(
261 &Path::new("/proc/").join(pid.to_string()),
262 &mut self.process_list,
263 pid,
264 None,
265 uptime,
266 &self.info,
267 refresh_kind,
268 ) {
269 Ok((Some(p), pid)) => {
270 self.process_list.insert(pid, p);
271 }
272 Ok(_) => {}
273 Err(_e) => {
274 sysinfo_debug!("Cannot get information for PID {:?}: {:?}", pid, _e);
275 return false;
276 }
277 };
278 if refresh_kind.cpu() {
279 self.refresh_cpus(true, CpuRefreshKind::new().with_cpu_usage());
280
281 if self.cpus.is_empty() {
282 eprintln!("Cannot compute process CPU usage: no cpus found...");
283 return true;
284 }
285 let (new, old) = self.cpus.get_global_raw_times();
286 let total_time = (if old >= new { 1 } else { new - old }) as f32;
287 let total_time = total_time / self.cpus.len() as f32;
288
289 let max_cpu_usage = self.get_max_process_cpu_usage();
290 if let Some(p) = self.process_list.get_mut(&pid) {
291 let p = &mut p.inner;
292 compute_cpu_usage(p, total_time, max_cpu_usage);
293 unset_updated(p);
294 }
295 } else if let Some(p) = self.process_list.get_mut(&pid) {
296 unset_updated(&mut p.inner);
297 }
298 true
299 }
300
301 pub(crate) fn processes(&self) -> &HashMap<Pid, Process> {
306 &self.process_list
307 }
308
309 pub(crate) fn process(&self, pid: Pid) -> Option<&Process> {
310 self.process_list.get(&pid)
311 }
312
313 pub(crate) fn global_cpu_info(&self) -> &Cpu {
314 &self.cpus.global_cpu
315 }
316
317 pub(crate) fn cpus(&self) -> &[Cpu] {
318 &self.cpus.cpus
319 }
320
321 pub(crate) fn physical_core_count(&self) -> Option<usize> {
322 get_physical_core_count()
323 }
324
325 pub(crate) fn total_memory(&self) -> u64 {
326 self.mem_total
327 }
328
329 pub(crate) fn free_memory(&self) -> u64 {
330 self.mem_free
331 }
332
333 pub(crate) fn available_memory(&self) -> u64 {
334 self.mem_available
335 }
336
337 pub(crate) fn used_memory(&self) -> u64 {
338 self.mem_total - self.mem_available
339 }
340
341 pub(crate) fn total_swap(&self) -> u64 {
342 self.swap_total
343 }
344
345 pub(crate) fn free_swap(&self) -> u64 {
346 self.swap_free
347 }
348
349 pub(crate) fn used_swap(&self) -> u64 {
351 self.swap_total - self.swap_free
352 }
353
354 pub(crate) fn uptime() -> u64 {
355 let content = get_all_data("/proc/uptime", 50).unwrap_or_default();
356 content
357 .split('.')
358 .next()
359 .and_then(|t| t.parse().ok())
360 .unwrap_or_default()
361 }
362
363 pub(crate) fn boot_time() -> u64 {
364 boot_time()
365 }
366
367 pub(crate) fn load_average() -> LoadAvg {
368 let mut s = String::new();
369 if File::open("/proc/loadavg")
370 .and_then(|mut f| f.read_to_string(&mut s))
371 .is_err()
372 {
373 return LoadAvg::default();
374 }
375 let loads = s
376 .trim()
377 .split(' ')
378 .take(3)
379 .map(|val| val.parse::<f64>().unwrap())
380 .collect::<Vec<f64>>();
381 LoadAvg {
382 one: loads[0],
383 five: loads[1],
384 fifteen: loads[2],
385 }
386 }
387
388 #[cfg(not(target_os = "android"))]
389 pub(crate) fn name() -> Option<String> {
390 get_system_info_linux(
391 InfoType::Name,
392 Path::new("/etc/os-release"),
393 Path::new("/etc/lsb-release"),
394 )
395 }
396
397 #[cfg(target_os = "android")]
398 pub(crate) fn name() -> Option<String> {
399 get_system_info_android(InfoType::Name)
400 }
401
402 pub(crate) fn long_os_version() -> Option<String> {
403 #[cfg(target_os = "android")]
404 let system_name = "Android";
405
406 #[cfg(not(target_os = "android"))]
407 let system_name = "Linux";
408
409 Some(format!(
410 "{} {} {}",
411 system_name,
412 Self::os_version().unwrap_or_default(),
413 Self::name().unwrap_or_default()
414 ))
415 }
416
417 pub(crate) fn host_name() -> Option<String> {
418 unsafe {
419 let hostname_max = sysconf(_SC_HOST_NAME_MAX);
420 let mut buffer = vec![0_u8; hostname_max as usize];
421 if libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len()) == 0 {
422 if let Some(pos) = buffer.iter().position(|x| *x == 0) {
423 buffer.resize(pos, 0);
425 }
426 String::from_utf8(buffer).ok()
427 } else {
428 sysinfo_debug!("gethostname failed: hostname cannot be retrieved...");
429 None
430 }
431 }
432 }
433
434 pub(crate) fn kernel_version() -> Option<String> {
435 let mut raw = std::mem::MaybeUninit::<libc::utsname>::zeroed();
436
437 unsafe {
438 if libc::uname(raw.as_mut_ptr()) == 0 {
439 let info = raw.assume_init();
440
441 let release = info
442 .release
443 .iter()
444 .filter(|c| **c != 0)
445 .map(|c| *c as u8 as char)
446 .collect::<String>();
447
448 Some(release)
449 } else {
450 None
451 }
452 }
453 }
454
455 #[cfg(not(target_os = "android"))]
456 pub(crate) fn os_version() -> Option<String> {
457 get_system_info_linux(
458 InfoType::OsVersion,
459 Path::new("/etc/os-release"),
460 Path::new("/etc/lsb-release"),
461 )
462 }
463
464 #[cfg(target_os = "android")]
465 pub(crate) fn os_version() -> Option<String> {
466 get_system_info_android(InfoType::OsVersion)
467 }
468
469 #[cfg(not(target_os = "android"))]
470 pub(crate) fn distribution_id() -> String {
471 get_system_info_linux(
472 InfoType::DistributionID,
473 Path::new("/etc/os-release"),
474 Path::new(""),
475 )
476 .unwrap_or_else(|| std::env::consts::OS.to_owned())
477 }
478
479 #[cfg(target_os = "android")]
480 pub(crate) fn distribution_id() -> String {
481 get_system_info_android(InfoType::DistributionID)
485 .unwrap_or_else(|| std::env::consts::OS.to_owned())
486 }
487
488 pub(crate) fn cpu_arch() -> Option<String> {
489 let mut raw = std::mem::MaybeUninit::<libc::utsname>::uninit();
490
491 unsafe {
492 if libc::uname(raw.as_mut_ptr()) != 0 {
493 return None;
494 }
495 let info = raw.assume_init();
496 let machine: &[u8] =
498 std::slice::from_raw_parts(info.machine.as_ptr() as *const _, info.machine.len());
499
500 CStr::from_bytes_until_nul(machine)
501 .ok()
502 .and_then(|res| match res.to_str() {
503 Ok(arch) => Some(arch.to_string()),
504 Err(_) => None,
505 })
506 }
507 }
508}
509
510fn read_u64(filename: &str) -> Option<u64> {
511 get_all_data(filename, 16_635)
512 .ok()
513 .and_then(|d| u64::from_str(d.trim()).ok())
514}
515
516fn read_table<F>(filename: &str, colsep: char, mut f: F)
517where
518 F: FnMut(&str, u64),
519{
520 if let Ok(content) = get_all_data(filename, 16_635) {
521 content
522 .split('\n')
523 .flat_map(|line| {
524 let mut split = line.split(colsep);
525 let key = split.next()?;
526 let value = split.next()?;
527 let value0 = value.trim_start().split(' ').next()?;
528 let value0_u64 = u64::from_str(value0).ok()?;
529 Some((key, value0_u64))
530 })
531 .for_each(|(k, v)| f(k, v));
532 }
533}
534
535impl crate::CGroupLimits {
536 fn new(sys: &SystemInner) -> Option<Self> {
537 assert!(
538 sys.mem_total != 0,
539 "You need to call System::refresh_memory before trying to get cgroup limits!",
540 );
541 if let (Some(mem_cur), Some(mem_max)) = (
542 read_u64("/sys/fs/cgroup/memory.current"),
543 read_u64("/sys/fs/cgroup/memory.max"),
544 ) {
545 let mut limits = Self {
548 total_memory: sys.mem_total,
549 free_memory: sys.mem_free,
550 free_swap: sys.swap_free,
551 };
552
553 limits.total_memory = min(mem_max, sys.mem_total);
554 limits.free_memory = limits.total_memory.saturating_sub(mem_cur);
555
556 if let Some(swap_cur) = read_u64("/sys/fs/cgroup/memory.swap.current") {
557 limits.free_swap = sys.swap_total.saturating_sub(swap_cur);
558 }
559
560 Some(limits)
561 } else if let (Some(mem_cur), Some(mem_max)) = (
562 read_u64("/sys/fs/cgroup/memory/memory.usage_in_bytes"),
564 read_u64("/sys/fs/cgroup/memory/memory.limit_in_bytes"),
565 ) {
566 let mut limits = Self {
567 total_memory: sys.mem_total,
568 free_memory: sys.mem_free,
569 free_swap: sys.swap_free,
570 };
571
572 limits.total_memory = min(mem_max, sys.mem_total);
573 limits.free_memory = limits.total_memory.saturating_sub(mem_cur);
574
575 Some(limits)
576 } else {
577 None
578 }
579 }
580}
581
582#[derive(PartialEq, Eq)]
583enum InfoType {
584 Name,
588 OsVersion,
589 DistributionID,
592}
593
594#[cfg(not(target_os = "android"))]
595fn get_system_info_linux(info: InfoType, path: &Path, fallback_path: &Path) -> Option<String> {
596 if let Ok(buf) = File::open(path).and_then(|mut f| {
597 let mut buf = String::new();
598 f.read_to_string(&mut buf)?;
599 Ok(buf)
600 }) {
601 let info_str = match info {
602 InfoType::Name => "NAME=",
603 InfoType::OsVersion => "VERSION_ID=",
604 InfoType::DistributionID => "ID=",
605 };
606
607 for line in buf.lines() {
608 if let Some(stripped) = line.strip_prefix(info_str) {
609 return Some(stripped.replace('"', ""));
610 }
611 }
612 }
613
614 let buf = File::open(fallback_path)
619 .and_then(|mut f| {
620 let mut buf = String::new();
621 f.read_to_string(&mut buf)?;
622 Ok(buf)
623 })
624 .ok()?;
625
626 let info_str = match info {
627 InfoType::OsVersion => "DISTRIB_RELEASE=",
628 InfoType::Name => "DISTRIB_ID=",
629 InfoType::DistributionID => {
630 return None;
632 }
633 };
634 for line in buf.lines() {
635 if let Some(stripped) = line.strip_prefix(info_str) {
636 return Some(stripped.replace('"', ""));
637 }
638 }
639 None
640}
641
642#[cfg(target_os = "android")]
643fn get_system_info_android(info: InfoType) -> Option<String> {
644 let name: &'static [u8] = match info {
646 InfoType::Name => b"ro.product.model\0",
647 InfoType::OsVersion => b"ro.build.version.release\0",
648 InfoType::DistributionID => {
649 return None;
651 }
652 };
653
654 let mut value_buffer = vec![0u8; libc::PROP_VALUE_MAX as usize];
655 unsafe {
656 let len = libc::__system_property_get(
657 name.as_ptr() as *const c_char,
658 value_buffer.as_mut_ptr() as *mut c_char,
659 );
660
661 if len != 0 {
662 if let Some(pos) = value_buffer.iter().position(|c| *c == 0) {
663 value_buffer.resize(pos, 0);
664 }
665 String::from_utf8(value_buffer).ok()
666 } else {
667 None
668 }
669 }
670}
671
672#[cfg(test)]
673mod test {
674 #[cfg(target_os = "android")]
675 use super::get_system_info_android;
676 #[cfg(not(target_os = "android"))]
677 use super::get_system_info_linux;
678 use super::InfoType;
679
680 #[test]
681 #[cfg(target_os = "android")]
682 fn lsb_release_fallback_android() {
683 assert!(get_system_info_android(InfoType::OsVersion).is_some());
684 assert!(get_system_info_android(InfoType::Name).is_some());
685 assert!(get_system_info_android(InfoType::DistributionID).is_none());
686 }
687
688 #[test]
689 #[cfg(not(target_os = "android"))]
690 fn lsb_release_fallback_not_android() {
691 use std::path::Path;
692
693 let dir = tempfile::tempdir().expect("failed to create temporary directory");
694 let tmp1 = dir.path().join("tmp1");
695 let tmp2 = dir.path().join("tmp2");
696
697 std::fs::write(
699 &tmp1,
700 r#"NAME="Ubuntu"
701VERSION="20.10 (Groovy Gorilla)"
702ID=ubuntu
703ID_LIKE=debian
704PRETTY_NAME="Ubuntu 20.10"
705VERSION_ID="20.10"
706VERSION_CODENAME=groovy
707UBUNTU_CODENAME=groovy
708"#,
709 )
710 .expect("Failed to create tmp1");
711
712 std::fs::write(
714 &tmp2,
715 r#"DISTRIB_ID=Ubuntu
716DISTRIB_RELEASE=20.10
717DISTRIB_CODENAME=groovy
718DISTRIB_DESCRIPTION="Ubuntu 20.10"
719"#,
720 )
721 .expect("Failed to create tmp2");
722
723 assert_eq!(
725 get_system_info_linux(InfoType::OsVersion, &tmp1, Path::new("")),
726 Some("20.10".to_owned())
727 );
728 assert_eq!(
729 get_system_info_linux(InfoType::Name, &tmp1, Path::new("")),
730 Some("Ubuntu".to_owned())
731 );
732 assert_eq!(
733 get_system_info_linux(InfoType::DistributionID, &tmp1, Path::new("")),
734 Some("ubuntu".to_owned())
735 );
736
737 assert_eq!(
739 get_system_info_linux(InfoType::OsVersion, Path::new(""), &tmp2),
740 Some("20.10".to_owned())
741 );
742 assert_eq!(
743 get_system_info_linux(InfoType::Name, Path::new(""), &tmp2),
744 Some("Ubuntu".to_owned())
745 );
746 assert_eq!(
747 get_system_info_linux(InfoType::DistributionID, Path::new(""), &tmp2),
748 None
749 );
750 }
751}