1 // 2 // Sysinfo 3 // 4 // Copyright (c) 2015 Guillaume Gomez 5 // 6 7 use crate::sys::component::{self, Component}; 8 use crate::sys::disk; 9 use crate::sys::process::*; 10 use crate::sys::processor::*; 11 use crate::sys::utils::get_all_data; 12 use crate::{Disk, LoadAvg, Networks, Pid, ProcessExt, RefreshKind, SystemExt, User}; 13 14 use libc::{self, c_char, sysconf, _SC_HOST_NAME_MAX, _SC_PAGESIZE}; 15 use std::collections::HashMap; 16 use std::fs::File; 17 use std::io::{BufRead, BufReader, Read}; 18 use std::path::Path; 19 use std::str::FromStr; 20 use std::sync::{Arc, Mutex}; 21 use std::time::SystemTime; 22 23 // This whole thing is to prevent having too many files open at once. It could be problematic 24 // for processes using a lot of files and using sysinfo at the same time. 25 #[allow(clippy::mutex_atomic)] 26 pub(crate) static mut REMAINING_FILES: once_cell::sync::Lazy<Arc<Mutex<isize>>> = 27 once_cell::sync::Lazy::new(|| { 28 unsafe { 29 let mut limits = libc::rlimit { 30 rlim_cur: 0, 31 rlim_max: 0, 32 }; 33 if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 { 34 // Most linux system now defaults to 1024. 35 return Arc::new(Mutex::new(1024 / 2)); 36 } 37 // We save the value in case the update fails. 38 let current = limits.rlim_cur; 39 40 // The set the soft limit to the hard one. 41 limits.rlim_cur = limits.rlim_max; 42 // In this part, we leave minimum 50% of the available file descriptors to the process 43 // using sysinfo. 44 Arc::new(Mutex::new( 45 if libc::setrlimit(libc::RLIMIT_NOFILE, &limits) == 0 { 46 limits.rlim_cur / 2 47 } else { strip_trailoptnull48 current / 2 49 } as _, 50 )) 51 } 52 }); 53 54 pub(crate) fn get_max_nb_fds() -> isize { 55 unsafe { 56 let mut limits = libc::rlimit { 57 rlim_cur: 0, 58 rlim_max: 0, 59 }; 60 if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 { 61 // Most linux system now defaults to 1024. 62 1024 / 2 63 } else { 64 limits.rlim_max as isize / 2 65 } 66 } 67 } 68 69 macro_rules! to_str { 70 ($e:expr) => { 71 unsafe { std::str::from_utf8_unchecked($e) } 72 }; 73 } 74 75 fn boot_time() -> u64 { 76 if let Ok(f) = File::open("/proc/stat") { 77 let buf = BufReader::new(f); 78 let line = buf 79 .split(b'\n') 80 .filter_map(|r| r.ok()) 81 .find(|l| l.starts_with(b"btime")); 82 83 if let Some(line) = line { 84 return line 85 .split(|x| *x == b' ') 86 .filter(|s| !s.is_empty()) 87 .nth(1) 88 .map(to_u64) 89 .unwrap_or(0); 90 } 91 } 92 // Either we didn't find "btime" or "/proc/stat" wasn't available for some reason... 93 let mut up = libc::timespec { 94 tv_sec: 0, 95 tv_nsec: 0, 96 }; 97 if unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut up) } == 0 { 98 up.tv_sec as u64 99 } else { 100 sysinfo_debug!("clock_gettime failed: boot time cannot be retrieve..."); 101 0 102 } 103 } 104 105 /// Structs containing system's information. 106 pub struct System { 107 process_list: Process, 108 mem_total: u64, 109 mem_free: u64, 110 mem_available: u64, 111 mem_buffers: u64, 112 mem_page_cache: u64, 113 mem_slab_reclaimable: u64, 114 swap_total: u64, 115 swap_free: u64, 116 global_processor: Processor, 117 processors: Vec<Processor>, 118 page_size_kb: u64, 119 components: Vec<Component>, 120 disks: Vec<Disk>, 121 networks: Networks, 122 users: Vec<User>, 123 boot_time: u64, 124 } 125 126 impl System { 127 fn clear_procs(&mut self) { 128 if !self.processors.is_empty() { 129 let (new, old) = get_raw_times(&self.global_processor); 130 let total_time = (if old > new { 1 } else { new - old }) as f32; 131 let mut to_delete = Vec::with_capacity(20); 132 133 for (pid, proc_) in &mut self.process_list.tasks { 134 if !has_been_updated(proc_) { 135 to_delete.push(*pid); 136 } else { 137 compute_cpu_usage(proc_, self.processors.len() as u64, total_time); 138 } 139 } 140 for pid in to_delete { 141 self.process_list.tasks.remove(&pid); 142 } 143 } 144 } 145 146 fn refresh_processors(&mut self, limit: Option<u32>) { 147 if let Ok(f) = File::open("/proc/stat") { 148 let buf = BufReader::new(f); 149 let mut i: usize = 0; 150 let first = self.processors.is_empty(); 151 let mut it = buf.split(b'\n'); 152 let mut count = 0; 153 let (vendor_id, brand) = if first { 154 get_vendor_id_and_brand() 155 } else { 156 (String::new(), String::new()) 157 }; 158 159 if let Some(Ok(line)) = it.next() { 160 if &line[..4] != b"cpu " { 161 return; 162 } 163 let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()); 164 if first { 165 self.global_processor.name = to_str!(parts.next().unwrap_or(&[])).to_owned(); 166 } else { 167 parts.next(); 168 } 169 self.global_processor.set( 170 parts.next().map(to_u64).unwrap_or(0), 171 parts.next().map(to_u64).unwrap_or(0), 172 parts.next().map(to_u64).unwrap_or(0), 173 parts.next().map(to_u64).unwrap_or(0), 174 parts.next().map(to_u64).unwrap_or(0), 175 parts.next().map(to_u64).unwrap_or(0), 176 parts.next().map(to_u64).unwrap_or(0), 177 parts.next().map(to_u64).unwrap_or(0), 178 parts.next().map(to_u64).unwrap_or(0), 179 parts.next().map(to_u64).unwrap_or(0), 180 ); 181 count += 1; 182 if let Some(limit) = limit { 183 if count >= limit { 184 return; 185 } 186 } 187 } 188 while let Some(Ok(line)) = it.next() { 189 if &line[..3] != b"cpu" { 190 break; 191 } 192 193 let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()); 194 if first { 195 self.processors.push(Processor::new_with_values( 196 to_str!(parts.next().unwrap_or(&[])), 197 parts.next().map(to_u64).unwrap_or(0), 198 parts.next().map(to_u64).unwrap_or(0), 199 parts.next().map(to_u64).unwrap_or(0), 200 parts.next().map(to_u64).unwrap_or(0), 201 parts.next().map(to_u64).unwrap_or(0), 202 parts.next().map(to_u64).unwrap_or(0), 203 parts.next().map(to_u64).unwrap_or(0), 204 parts.next().map(to_u64).unwrap_or(0), 205 parts.next().map(to_u64).unwrap_or(0), 206 parts.next().map(to_u64).unwrap_or(0), 207 get_cpu_frequency(i), 208 vendor_id.clone(), 209 brand.clone(), 210 )); 211 } else { 212 parts.next(); // we don't want the name again 213 self.processors[i].set( 214 parts.next().map(to_u64).unwrap_or(0), 215 parts.next().map(to_u64).unwrap_or(0), 216 parts.next().map(to_u64).unwrap_or(0), 217 parts.next().map(to_u64).unwrap_or(0), 218 parts.next().map(to_u64).unwrap_or(0), 219 parts.next().map(to_u64).unwrap_or(0), 220 parts.next().map(to_u64).unwrap_or(0), 221 parts.next().map(to_u64).unwrap_or(0), 222 parts.next().map(to_u64).unwrap_or(0), 223 parts.next().map(to_u64).unwrap_or(0), 224 ); 225 self.processors[i].frequency = get_cpu_frequency(i); 226 } 227 228 self.global_processor.frequency = self 229 .processors 230 .iter() 231 .map(|p| p.frequency) 232 .max() 233 .unwrap_or(0); 234 235 i += 1; 236 count += 1; 237 if let Some(limit) = limit { 238 if count >= limit { 239 break; 240 } 241 } 242 } 243 if first { 244 self.global_processor.vendor_id = vendor_id; 245 self.global_processor.brand = brand; 246 } 247 } 248 } 249 } 250 251 impl SystemExt for System { 252 const IS_SUPPORTED: bool = true; 253 254 fn new_with_specifics(refreshes: RefreshKind) -> System { 255 let mut s = System { 256 process_list: Process::new(0, None, 0), 257 mem_total: 0, 258 mem_free: 0, 259 mem_available: 0, 260 mem_buffers: 0, 261 mem_page_cache: 0, 262 mem_slab_reclaimable: 0, 263 swap_total: 0, 264 swap_free: 0, 265 global_processor: Processor::new_with_values( 266 "", 267 0, 268 0, 269 0, 270 0, 271 0, 272 0, 273 0, 274 0, 275 0, 276 0, 277 0, 278 String::new(), 279 String::new(), 280 ), 281 processors: Vec::with_capacity(4), 282 page_size_kb: unsafe { sysconf(_SC_PAGESIZE) as u64 / 1024 }, 283 components: Vec::new(), 284 disks: Vec::with_capacity(2), 285 networks: Networks::new(), 286 users: Vec::new(), 287 boot_time: boot_time(), 288 }; 289 if !refreshes.cpu() { 290 s.refresh_processors(None); // We need the processors to be filled. 291 } 292 s.refresh_specifics(refreshes); 293 s 294 } 295 296 fn refresh_components_list(&mut self) { 297 self.components = component::get_components(); 298 } 299 300 fn refresh_memory(&mut self) { 301 if let Ok(data) = get_all_data("/proc/meminfo", 16_385) { 302 for line in data.split('\n') { 303 let mut iter = line.split(':'); 304 let field = match iter.next() { 305 Some("MemTotal") => &mut self.mem_total, 306 Some("MemFree") => &mut self.mem_free, 307 Some("MemAvailable") => &mut self.mem_available, 308 Some("Buffers") => &mut self.mem_buffers, 309 Some("Cached") => &mut self.mem_page_cache, 310 Some("SReclaimable") => &mut self.mem_slab_reclaimable, 311 Some("SwapTotal") => &mut self.swap_total, 312 Some("SwapFree") => &mut self.swap_free, 313 _ => continue, 314 }; 315 if let Some(val_str) = iter.next().and_then(|s| s.trim_start().split(' ').next()) { 316 if let Ok(value) = u64::from_str(val_str) { 317 // /proc/meminfo reports KiB, though it says "kB". Convert it. 318 *field = value * 128 / 125; 319 } 320 } 321 } 322 } 323 } 324 325 fn refresh_cpu(&mut self) { 326 self.refresh_processors(None); 327 } 328 329 fn refresh_processes(&mut self) { 330 let uptime = self.uptime(); 331 if refresh_procs( 332 &mut self.process_list, 333 Path::new("/proc"), 334 self.page_size_kb, 335 0, 336 uptime, 337 get_secs_since_epoch(), 338 ) { 339 self.clear_procs(); 340 } 341 } 342 343 fn refresh_process(&mut self, pid: Pid) -> bool { 344 let uptime = self.uptime(); 345 let found = match _get_process_data( 346 &Path::new("/proc/").join(pid.to_string()), 347 &mut self.process_list, 348 self.page_size_kb, 349 0, 350 uptime, 351 get_secs_since_epoch(), 352 ) { 353 Ok((Some(p), pid)) => { 354 self.process_list.tasks.insert(pid, p); 355 true 356 } 357 Ok(_) => true, 358 Err(_) => false, 359 }; 360 if found && !self.processors.is_empty() { 361 self.refresh_processors(Some(1)); 362 let (new, old) = get_raw_times(&self.global_processor); 363 let total_time = (if old >= new { 1 } else { new - old }) as f32; 364 365 if let Some(p) = self.process_list.tasks.get_mut(&pid) { 366 compute_cpu_usage(p, self.processors.len() as u64, total_time); 367 } 368 } 369 found 370 } 371 372 fn refresh_disks_list(&mut self) { 373 self.disks = disk::get_all_disks(); 374 } 375 376 fn refresh_users_list(&mut self) { 377 self.users = crate::linux::users::get_users_list(); 378 } 379 380 // COMMON PART 381 // 382 // Need to be moved into a "common" file to avoid duplication. 383 384 fn processes(&self) -> &HashMap<Pid, Process> { 385 &self.process_list.tasks 386 } 387 388 fn process(&self, pid: Pid) -> Option<&Process> { 389 self.process_list.tasks.get(&pid) 390 } 391 392 fn networks(&self) -> &Networks { 393 &self.networks 394 } 395 396 fn networks_mut(&mut self) -> &mut Networks { 397 &mut self.networks 398 } 399 400 fn global_processor_info(&self) -> &Processor { 401 &self.global_processor 402 } 403 404 fn processors(&self) -> &[Processor] { 405 &self.processors 406 } 407 408 fn physical_core_count(&self) -> Option<usize> { 409 get_physical_core_count() 410 } 411 412 fn total_memory(&self) -> u64 { 413 self.mem_total 414 } 415 416 fn free_memory(&self) -> u64 { 417 self.mem_free 418 } 419 420 fn available_memory(&self) -> u64 { 421 self.mem_available 422 } 423 424 fn used_memory(&self) -> u64 { 425 self.mem_total 426 - self.mem_free 427 - self.mem_buffers 428 - self.mem_page_cache 429 - self.mem_slab_reclaimable 430 } 431 432 fn total_swap(&self) -> u64 { 433 self.swap_total 434 } 435 436 fn free_swap(&self) -> u64 { 437 self.swap_free 438 } 439 440 // need to be checked 441 fn used_swap(&self) -> u64 { 442 self.swap_total - self.swap_free 443 } 444 445 fn components(&self) -> &[Component] { 446 &self.components 447 } 448 449 fn components_mut(&mut self) -> &mut [Component] { 450 &mut self.components 451 } 452 453 fn disks(&self) -> &[Disk] { 454 &self.disks 455 } 456 457 fn disks_mut(&mut self) -> &mut [Disk] { 458 &mut self.disks 459 } 460 461 fn uptime(&self) -> u64 { 462 let content = get_all_data("/proc/uptime", 50).unwrap_or_default(); 463 content 464 .split('.') 465 .next() 466 .and_then(|t| t.parse().ok()) 467 .unwrap_or_default() 468 } 469 470 fn boot_time(&self) -> u64 { 471 self.boot_time 472 } 473 474 fn load_average(&self) -> LoadAvg { 475 let mut s = String::new(); 476 if File::open("/proc/loadavg") 477 .and_then(|mut f| f.read_to_string(&mut s)) 478 .is_err() 479 { 480 return LoadAvg::default(); 481 } 482 let loads = s 483 .trim() 484 .split(' ') 485 .take(3) 486 .map(|val| val.parse::<f64>().unwrap()) 487 .collect::<Vec<f64>>(); 488 LoadAvg { 489 one: loads[0], 490 five: loads[1], 491 fifteen: loads[2], 492 } 493 } 494 495 fn users(&self) -> &[User] { 496 &self.users 497 } 498 499 #[cfg(not(target_os = "android"))] 500 fn name(&self) -> Option<String> { 501 get_system_info_linux( 502 InfoType::Name, 503 Path::new("/etc/os-release"), 504 Path::new("/etc/lsb-release"), 505 ) 506 } 507 508 #[cfg(target_os = "android")] 509 fn name(&self) -> Option<String> { 510 get_system_info_android(InfoType::Name) 511 } 512 513 fn long_os_version(&self) -> Option<String> { 514 #[cfg(target_os = "android")] 515 let system_name = "Android"; 516 517 #[cfg(not(target_os = "android"))] 518 let system_name = "Linux"; 519 520 Some(format!( 521 "{} {} {}", 522 system_name, 523 self.os_version().unwrap_or_default(), 524 self.name().unwrap_or_default() 525 )) 526 } 527 528 fn host_name(&self) -> Option<String> { 529 let hostname_max = unsafe { sysconf(_SC_HOST_NAME_MAX) }; 530 let mut buffer = vec![0_u8; hostname_max as usize]; 531 if unsafe { libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len()) } == 0 { 532 if let Some(pos) = buffer.iter().position(|x| *x == 0) { 533 // Shrink buffer to terminate the null bytes 534 buffer.resize(pos, 0); 535 } 536 String::from_utf8(buffer).ok() 537 } else { 538 sysinfo_debug!("gethostname failed: hostname cannot be retrieved..."); 539 None 540 } 541 } 542 543 fn kernel_version(&self) -> Option<String> { 544 let mut raw = std::mem::MaybeUninit::<libc::utsname>::zeroed(); 545 546 if unsafe { libc::uname(raw.as_mut_ptr()) } == 0 { 547 let info = unsafe { raw.assume_init() }; 548 549 let release = info 550 .release 551 .iter() 552 .filter(|c| **c != 0) 553 .map(|c| *c as u8 as char) 554 .collect::<String>(); 555 556 Some(release) 557 } else { 558 None 559 } 560 } 561 562 #[cfg(not(target_os = "android"))] 563 fn os_version(&self) -> Option<String> { 564 get_system_info_linux( 565 InfoType::OsVersion, 566 Path::new("/etc/os-release"), 567 Path::new("/etc/lsb-release"), 568 ) 569 } 570 571 #[cfg(target_os = "android")] 572 fn os_version(&self) -> Option<String> { 573 get_system_info_android(InfoType::OsVersion) 574 } 575 } 576 577 impl Default for System { 578 fn default() -> System { 579 System::new() 580 } 581 } 582 583 fn to_u64(v: &[u8]) -> u64 { 584 let mut x = 0; 585 586 for c in v { 587 x *= 10; 588 x += u64::from(c - b'0'); 589 } 590 x 591 } 592 593 #[derive(PartialEq)] 594 enum InfoType { 595 /// The end-user friendly name of: 596 /// - Android: The device model 597 /// - Linux: The distributions name 598 Name, 599 OsVersion, 600 } 601 602 #[cfg(not(target_os = "android"))] 603 fn get_system_info_linux(info: InfoType, path: &Path, fallback_path: &Path) -> Option<String> { 604 if let Ok(f) = File::open(path) { 605 let reader = BufReader::new(f); 606 607 let info_str = match info { 608 InfoType::Name => "NAME=", 609 InfoType::OsVersion => "VERSION_ID=", 610 }; 611 612 for line in reader.lines().flatten() { 613 if let Some(stripped) = line.strip_prefix(info_str) { 614 return Some(stripped.replace("\"", "")); 615 } 616 } 617 } 618 619 // Fallback to `/etc/lsb-release` file for systems where VERSION_ID is not included. 620 // VERSION_ID is not required in the `/etc/os-release` file 621 // per https://www.linux.org/docs/man5/os-release.html 622 // If this fails for some reason, fallback to None 623 let reader = BufReader::new(File::open(fallback_path).ok()?); 624 625 let info_str = match info { 626 InfoType::OsVersion => "DISTRIB_RELEASE=", 627 InfoType::Name => "DISTRIB_ID=", 628 }; 629 for line in reader.lines().flatten() { 630 if let Some(stripped) = line.strip_prefix(info_str) { 631 return Some(stripped.replace("\"", "")); 632 } 633 } 634 None 635 } 636 637 #[cfg(target_os = "android")] 638 fn get_system_info_android(info: InfoType) -> Option<String> { 639 use libc::c_int; 640 641 // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/Build.java#58 642 let name: &'static [u8] = match info { 643 InfoType::Name => b"ro.product.model\0", 644 InfoType::OsVersion => b"ro.build.version.release\0", 645 }; 646 647 let mut value_buffer = vec![0u8; libc::PROP_VALUE_MAX as usize]; 648 let len = unsafe { 649 libc::__system_property_get( 650 name.as_ptr() as *const c_char, 651 value_buffer.as_mut_ptr() as *mut c_char, 652 ) 653 }; 654 655 if len != 0 { 656 if let Some(pos) = value_buffer.iter().position(|c| *c == 0) { 657 value_buffer.resize(pos, 0); 658 } 659 String::from_utf8(value_buffer).ok() 660 } else { 661 None 662 } 663 } 664 665 fn get_secs_since_epoch() -> u64 { 666 match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { 667 Ok(n) => n.as_secs(), 668 _ => panic!("SystemTime before UNIX EPOCH!"), 669 } 670 } 671 672 #[cfg(test)] 673 mod 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 } 686 687 #[test] 688 #[cfg(not(target_os = "android"))] 689 fn lsb_release_fallback_not_android() { 690 use std::path::Path; 691 692 let dir = tempfile::tempdir().expect("failed to create temporary directory"); 693 let tmp1 = dir.path().join("tmp1"); 694 let tmp2 = dir.path().join("tmp2"); 695 696 // /etc/os-release 697 std::fs::write( 698 &tmp1, 699 r#"NAME="Ubuntu" 700 VERSION="20.10 (Groovy Gorilla)" 701 ID=ubuntu 702 ID_LIKE=debian 703 PRETTY_NAME="Ubuntu 20.10" 704 VERSION_ID="20.10" 705 VERSION_CODENAME=groovy 706 UBUNTU_CODENAME=groovy 707 "#, 708 ) 709 .expect("Failed to create tmp1"); 710 711 // /etc/lsb-release 712 std::fs::write( 713 &tmp2, 714 r#"DISTRIB_ID=Ubuntu 715 DISTRIB_RELEASE=20.10 716 DISTRIB_CODENAME=groovy 717 DISTRIB_DESCRIPTION="Ubuntu 20.10" 718 "#, 719 ) 720 .expect("Failed to create tmp2"); 721 722 // Check for the "normal" path: "/etc/os-release" 723 assert_eq!( 724 get_system_info_linux(InfoType::OsVersion, &tmp1, Path::new("")), 725 Some("20.10".to_owned()) 726 ); 727 assert_eq!( 728 get_system_info_linux(InfoType::Name, &tmp1, Path::new("")), 729 Some("Ubuntu".to_owned()) 730 ); 731 732 // Check for the "fallback" path: "/etc/lsb-release" 733 assert_eq!( 734 get_system_info_linux(InfoType::OsVersion, Path::new(""), &tmp2), 735 Some("20.10".to_owned()) 736 ); 737 assert_eq!( 738 get_system_info_linux(InfoType::Name, Path::new(""), &tmp2), 739 Some("Ubuntu".to_owned()) 740 ); 741 } 742 } 743