1 // Take a look at the license at the top of the repository in the LICENSE file.
4 CpuRefreshKind
, LoadAvg
, Networks
, Pid
, ProcessExt
, ProcessRefreshKind
, RefreshKind
, SystemExt
,
7 use winapi
::um
::winreg
::HKEY_LOCAL_MACHINE
;
9 use crate::sys
::component
::{self, Component}
;
10 use crate::sys
::cpu
::*;
11 use crate::sys
::disk
::{get_disks, Disk}
;
12 use crate::sys
::process
::{update_memory, Process}
;
13 use crate::sys
::tools
::*;
14 use crate::sys
::users
::get_users
;
15 use crate::sys
::utils
::get_now
;
17 use crate::utils
::into_iter
;
19 use std
::cell
::UnsafeCell
;
20 use std
::collections
::HashMap
;
22 use std
::mem
::{size_of, zeroed}
;
23 use std
::os
::windows
::ffi
::OsStrExt
;
24 use std
::slice
::from_raw_parts
;
25 use std
::time
::SystemTime
;
28 NtQuerySystemInformation
, SystemProcessInformation
, SYSTEM_PROCESS_INFORMATION
,
30 use winapi
::ctypes
::wchar_t
;
31 use winapi
::shared
::minwindef
::{DWORD, FALSE, HKEY, LPBYTE, TRUE}
;
32 use winapi
::shared
::ntdef
::{PVOID, ULONG}
;
33 use winapi
::shared
::ntstatus
::STATUS_INFO_LENGTH_MISMATCH
;
34 use winapi
::shared
::winerror
;
35 use winapi
::um
::minwinbase
::STILL_ACTIVE
;
36 use winapi
::um
::processthreadsapi
::GetExitCodeProcess
;
37 use winapi
::um
::psapi
::{GetPerformanceInfo, PERFORMANCE_INFORMATION}
;
38 use winapi
::um
::sysinfoapi
::{
39 ComputerNamePhysicalDnsHostname
, GetComputerNameExW
, GetTickCount64
, GlobalMemoryStatusEx
,
42 use winapi
::um
::winnt
::{HANDLE, KEY_READ}
;
43 use winapi
::um
::winreg
::{RegOpenKeyExW, RegQueryValueExW}
;
51 #[doc = include_str!("../../md_doc/system.md")]
53 process_list
: HashMap
<Pid
, Process
>,
59 components
: Vec
<Component
>,
67 // Useful for parallel iterations.
70 #[allow(clippy::non_send_fields_in_send_ty)]
71 unsafe impl<T
> Send
for Wrap
<T
> {}
72 unsafe impl<T
> Sync
for Wrap
<T
> {}
74 unsafe fn boot_time() -> u64 {
75 match SystemTime
::now().duration_since(SystemTime
::UNIX_EPOCH
) {
76 Ok(n
) => n
.as_secs().saturating_sub(GetTickCount64()) / 1000,
78 sysinfo_debug
!("Failed to compute boot time: {:?}", _e
);
84 impl SystemExt
for System
{
85 const IS_SUPPORTED
: bool
= true;
86 const SUPPORTED_SIGNALS
: &'
static [Signal
] = supported_signals();
88 #[allow(non_snake_case)]
89 fn new_with_specifics(refreshes
: RefreshKind
) -> System
{
91 process_list
: HashMap
::with_capacity(500),
96 cpus
: CpusWrapper
::new(),
97 components
: Vec
::new(),
98 disks
: Vec
::with_capacity(2),
100 networks
: Networks
::new(),
101 boot_time
: unsafe { boot_time() }
,
104 s
.refresh_specifics(refreshes
);
108 fn refresh_cpu_specifics(&mut self, refresh_kind
: CpuRefreshKind
) {
109 if self.query
.is_none() {
110 self.query
= Query
::new();
111 if let Some(ref mut query
) = self.query
{
113 r
"\Processor(_Total)\% Processor Time".to_string(),
115 get_key_used(self.cpus
.global_cpu_mut()),
118 for (pos
, proc_
) in self.cpus
.iter_mut(refresh_kind
).enumerate() {
120 format
!(r
"\Processor({})\% Processor Time", pos
),
123 format
!("{}_0", pos
),
128 if let Some(ref mut query
) = self.query
{
130 let mut used_time
= None
;
131 if let Some(ref key_used
) = *get_key_used(self.cpus
.global_cpu_mut()) {
134 .get(&key_used
.unique_id
)
135 .expect("global_key_idle disappeared"),
138 if let Some(used_time
) = used_time
{
139 self.cpus
.global_cpu_mut().set_cpu_usage(used_time
);
141 for p
in self.cpus
.iter_mut(refresh_kind
) {
142 let mut used_time
= None
;
143 if let Some(ref key_used
) = *get_key_used(p
) {
146 .get(&key_used
.unique_id
)
147 .expect("key_used disappeared"),
150 if let Some(used_time
) = used_time
{
151 p
.set_cpu_usage(used_time
);
154 if refresh_kind
.frequency() {
155 self.cpus
.get_frequencies();
160 fn refresh_memory(&mut self) {
162 let mut mem_info
: MEMORYSTATUSEX
= zeroed();
163 mem_info
.dwLength
= size_of
::<MEMORYSTATUSEX
>() as u32;
164 GlobalMemoryStatusEx(&mut mem_info
);
165 self.mem_total
= auto_cast
!(mem_info
.ullTotalPhys
, u64) / 1_000;
166 self.mem_available
= auto_cast
!(mem_info
.ullAvailPhys
, u64) / 1_000;
167 let mut perf_info
: PERFORMANCE_INFORMATION
= zeroed();
168 if GetPerformanceInfo(&mut perf_info
, size_of
::<PERFORMANCE_INFORMATION
>() as u32)
171 let swap_total
= perf_info
.PageSize
.saturating_mul(
174 .saturating_sub(perf_info
.PhysicalTotal
),
176 let swap_used
= perf_info
.PageSize
.saturating_mul(
179 .saturating_sub(perf_info
.PhysicalTotal
),
181 self.swap_total
= (swap_total
/ 1000) as u64;
182 self.swap_used
= (swap_used
/ 1000) as u64;
187 fn refresh_components_list(&mut self) {
188 self.components
= component
::get_components();
191 #[allow(clippy::map_entry)]
192 fn refresh_process_specifics(&mut self, pid
: Pid
, refresh_kind
: ProcessRefreshKind
) -> bool
{
193 if self.process_list
.contains_key(&pid
) {
194 return refresh_existing_process(self, pid
, refresh_kind
);
197 if let Some(mut p
) = Process
::new_from_pid(pid
, now
, refresh_kind
) {
198 p
.update(refresh_kind
, self.cpus
.len() as u64, now
);
200 self.process_list
.insert(pid
, p
);
207 #[allow(clippy::cast_ptr_alignment)]
208 fn refresh_processes_specifics(&mut self, refresh_kind
: ProcessRefreshKind
) {
209 // Windows 10 notebook requires at least 512KiB of memory to make it in one go
210 let mut buffer_size
: usize = 512 * 1024;
214 let mut process_information
: Vec
<u8> = Vec
::with_capacity(buffer_size
);
215 let mut cb_needed
= 0;
218 process_information
.set_len(buffer_size
);
219 let ntstatus
= NtQuerySystemInformation(
220 SystemProcessInformation
,
221 process_information
.as_mut_ptr() as PVOID
,
222 buffer_size
as ULONG
,
226 if ntstatus
!= STATUS_INFO_LENGTH_MISMATCH
{
229 "Couldn't get process infos: NtQuerySystemInformation returned {}",
234 // Parse the data block to get process information
235 let mut process_ids
= Vec
::with_capacity(500);
236 let mut process_information_offset
= 0;
238 let p
= process_information
240 .offset(process_information_offset
)
241 as *const SYSTEM_PROCESS_INFORMATION
;
244 process_ids
.push(Wrap(p
));
246 if pi
.NextEntryOffset
== 0 {
250 process_information_offset
+= pi
.NextEntryOffset
as isize;
252 let process_list
= Wrap(UnsafeCell
::new(&mut self.process_list
));
253 let nb_cpus
= if refresh_kind
.cpu() {
254 self.cpus
.len() as u64
259 #[cfg(feature = "multithread")]
260 use rayon
::iter
::ParallelIterator
;
262 // TODO: instead of using parallel iterator only here, would be better to be
263 // able to run it over `process_information` directly!
264 let processes
= into_iter(process_ids
)
267 let pid
= Pid(pi
.UniqueProcessId
as _
);
268 if let Some(proc_
) = (*process_list
.0.get()).get_mut(&pid
) {
269 proc_
.memory
= (pi
.WorkingSetSize
as u64) / 1_000;
270 proc_
.virtual_memory
= (pi
.VirtualSize
as u64) / 1_000;
271 proc_
.update(refresh_kind
, nb_cpus
, now
);
274 let name
= get_process_name(&pi
, pid
);
275 let mut p
= Process
::new_full(
277 if pi
.InheritedFromUniqueProcessId
as usize != 0 {
278 Some(Pid(pi
.InheritedFromUniqueProcessId
as _
))
282 (pi
.WorkingSetSize
as u64) / 1_000,
283 (pi
.VirtualSize
as u64) / 1_000,
288 p
.update(refresh_kind
, nb_cpus
, now
);
291 .collect
::<Vec
<_
>>();
292 for p
in processes
.into_iter() {
293 self.process_list
.insert(p
.pid(), p
);
295 self.process_list
.retain(|_
, v
| {
309 // allocating a few more kilo bytes just in case there are some new process
310 // kicked in since new call to NtQuerySystemInformation
311 buffer_size
= (cb_needed
+ (1024 * 10)) as usize;
316 fn refresh_disks_list(&mut self) {
317 self.disks
= unsafe { get_disks() }
;
320 fn refresh_users_list(&mut self) {
321 self.users
= unsafe { get_users() }
;
324 fn processes(&self) -> &HashMap
<Pid
, Process
> {
328 fn process(&self, pid
: Pid
) -> Option
<&Process
> {
329 self.process_list
.get(&pid
)
332 fn global_cpu_info(&self) -> &Cpu
{
333 self.cpus
.global_cpu()
336 fn cpus(&self) -> &[Cpu
] {
340 fn physical_core_count(&self) -> Option
<usize> {
341 get_physical_core_count()
344 fn total_memory(&self) -> u64 {
348 fn free_memory(&self) -> u64 {
349 // MEMORYSTATUSEX doesn't report free memory
353 fn available_memory(&self) -> u64 {
357 fn used_memory(&self) -> u64 {
358 self.mem_total
- self.mem_available
361 fn total_swap(&self) -> u64 {
365 fn free_swap(&self) -> u64 {
366 self.swap_total
- self.swap_used
369 fn used_swap(&self) -> u64 {
373 fn components(&self) -> &[Component
] {
377 fn components_mut(&mut self) -> &mut [Component
] {
381 fn disks(&self) -> &[Disk
] {
385 fn disks_mut(&mut self) -> &mut [Disk
] {
389 fn users(&self) -> &[User
] {
393 fn networks(&self) -> &Networks
{
397 fn networks_mut(&mut self) -> &mut Networks
{
401 fn uptime(&self) -> u64 {
402 unsafe { GetTickCount64() / 1000 }
405 fn boot_time(&self) -> u64 {
409 fn load_average(&self) -> LoadAvg
{
413 fn name(&self) -> Option
<String
> {
414 Some("Windows".to_owned())
417 fn long_os_version(&self) -> Option
<String
> {
418 get_reg_string_value(
420 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
425 fn host_name(&self) -> Option
<String
> {
429 fn kernel_version(&self) -> Option
<String
> {
430 get_reg_string_value(
432 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
433 "CurrentBuildNumber",
437 fn os_version(&self) -> Option
<String
> {
438 let major
= get_reg_value_u32(
440 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
441 "CurrentMajorVersionNumber",
444 let build_number
= get_reg_string_value(
446 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
447 "CurrentBuildNumber",
452 u32::from_le_bytes(major
.unwrap_or_default()),
453 build_number
.unwrap_or_default()
458 impl Default
for System
{
459 fn default() -> System
{
464 fn is_proc_running(handle
: HANDLE
) -> bool
{
465 let mut exit_code
= 0;
467 let ret
= GetExitCodeProcess(handle
, &mut exit_code
);
468 !(ret
== FALSE
|| exit_code
!= STILL_ACTIVE
)
472 fn refresh_existing_process(s
: &mut System
, pid
: Pid
, refresh_kind
: ProcessRefreshKind
) -> bool
{
473 if let Some(ref mut entry
) = s
.process_list
.get_mut(&pid
) {
474 if let Some(handle
) = entry
.get_handle() {
475 if !is_proc_running(handle
) {
481 update_memory(entry
);
482 entry
.update(refresh_kind
, s
.cpus
.len() as u64, get_now());
483 entry
.updated
= false;
490 #[allow(clippy::size_of_in_element_count)]
491 //^ needed for "name.Length as usize / std::mem::size_of::<u16>()"
492 pub(crate) fn get_process_name(process
: &SYSTEM_PROCESS_INFORMATION
, process_id
: Pid
) -> String
{
493 let name
= &process
.ImageName
;
494 if name
.Buffer
.is_null() {
496 0 => "Idle".to_owned(),
497 4 => "System".to_owned(),
498 _
=> format
!("<no name> Process {}", process_id
),
502 let slice
= std
::slice
::from_raw_parts(
504 // The length is in bytes, not the length of string
505 name
.Length
as usize / std
::mem
::size_of
::<u16>(),
508 String
::from_utf16_lossy(slice
)
513 fn utf16_str
<S
: AsRef
<OsStr
> + ?Sized
>(text
: &S
) -> Vec
<u16> {
516 .chain(Some(0).into_iter())
520 fn get_reg_string_value(hkey
: HKEY
, path
: &str, field_name
: &str) -> Option
<String
> {
521 let c_path
= utf16_str(path
);
522 let c_field_name
= utf16_str(field_name
);
524 let mut new_hkey
: HKEY
= std
::ptr
::null_mut();
526 if RegOpenKeyExW(hkey
, c_path
.as_ptr(), 0, KEY_READ
, &mut new_hkey
) != 0 {
530 let mut buf_len
: DWORD
= 2048;
531 let mut buf_type
: DWORD
= 0;
532 let mut buf
: Vec
<u8> = Vec
::with_capacity(buf_len
as usize);
534 match RegQueryValueExW(
536 c_field_name
.as_ptr(),
537 std
::ptr
::null_mut(),
539 buf
.as_mut_ptr() as LPBYTE
,
544 winerror
::ERROR_MORE_DATA
=> {
545 buf
.reserve(buf_len
as _
);
551 buf
.set_len(buf_len
as _
);
553 let words
= from_raw_parts(buf
.as_ptr() as *const u16, buf
.len() / 2);
554 let mut s
= String
::from_utf16_lossy(words
);
555 while s
.ends_with('
\u{0}'
) {
562 fn get_reg_value_u32(hkey
: HKEY
, path
: &str, field_name
: &str) -> Option
<[u8; 4]> {
563 let c_path
= utf16_str(path
);
564 let c_field_name
= utf16_str(field_name
);
566 let mut new_hkey
: HKEY
= std
::ptr
::null_mut();
568 if RegOpenKeyExW(hkey
, c_path
.as_ptr(), 0, KEY_READ
, &mut new_hkey
) != 0 {
572 let mut buf_len
: DWORD
= 4;
573 let mut buf_type
: DWORD
= 0;
574 let mut buf
= [0u8; 4];
576 match RegQueryValueExW(
578 c_field_name
.as_ptr(),
579 std
::ptr
::null_mut(),
581 buf
.as_mut_ptr() as LPBYTE
,
591 fn get_dns_hostname() -> Option
<String
> {
592 let mut buffer_size
= 0;
593 // Running this first to get the buffer size since the DNS name can be longer than MAX_COMPUTERNAME_LENGTH
594 // setting the `lpBuffer` to null will return the buffer size
595 // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw
598 ComputerNamePhysicalDnsHostname
,
599 std
::ptr
::null_mut(),
603 // Setting the buffer with the new length
604 let mut buffer
= vec
![0_u16; buffer_size
as usize];
606 // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ne-sysinfoapi-computer_name_format
607 if GetComputerNameExW(
608 ComputerNamePhysicalDnsHostname
,
609 buffer
.as_mut_ptr() as *mut wchar_t
,
613 if let Some(pos
) = buffer
.iter().position(|c
| *c
== 0) {
614 buffer
.resize(pos
, 0);
617 return String
::from_utf16(&buffer
).ok();
621 sysinfo_debug
!("Failed to get computer hostname");