sysinfo/windows/
system.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use crate::{
4    Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, ProcessRefreshKind, ProcessesToUpdate,
5};
6
7use crate::sys::cpu::*;
8use crate::{Process, ProcessInner};
9
10use std::collections::HashMap;
11use std::ffi::OsStr;
12use std::mem::{size_of, zeroed};
13use std::os::windows::ffi::OsStrExt;
14use std::time::{Duration, SystemTime};
15
16use windows::Win32::Foundation::{self, HANDLE, STILL_ACTIVE};
17use windows::Win32::System::Diagnostics::ToolHelp::{
18    CreateToolhelp32Snapshot, PROCESSENTRY32W, Process32FirstW, Process32NextW, TH32CS_SNAPPROCESS,
19};
20use windows::Win32::System::ProcessStatus::{K32GetPerformanceInfo, PERFORMANCE_INFORMATION};
21use windows::Win32::System::Registry::{
22    HKEY, HKEY_LOCAL_MACHINE, KEY_READ, REG_NONE, RegCloseKey, RegOpenKeyExW, RegQueryValueExW,
23};
24use windows::Win32::System::SystemInformation::{self, GetSystemInfo};
25use windows::Win32::System::SystemInformation::{
26    ComputerNamePhysicalDnsHostname, GetComputerNameExW, GetTickCount64, GlobalMemoryStatusEx,
27    MEMORYSTATUSEX, SYSTEM_INFO,
28};
29use windows::Win32::System::Threading::GetExitCodeProcess;
30use windows::core::{Owned, PCWSTR, PWSTR};
31
32declare_signals! {
33    (),
34    Signal::Kill => (),
35    _ => None,
36}
37
38#[doc = include_str!("../../md_doc/supported_signals.md")]
39pub const SUPPORTED_SIGNALS: &[crate::Signal] = supported_signals();
40#[doc = include_str!("../../md_doc/minimum_cpu_update_interval.md")]
41pub const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(200);
42
43const WINDOWS_ELEVEN_BUILD_NUMBER: u32 = 22000;
44
45impl SystemInner {
46    fn is_windows_eleven() -> bool {
47        WINDOWS_ELEVEN_BUILD_NUMBER
48            <= Self::kernel_version()
49                .unwrap_or_default()
50                .parse()
51                .unwrap_or(0)
52    }
53}
54
55/// Calculates system boot time in seconds with improved precision.
56/// Uses nanoseconds throughout to avoid rounding errors in uptime calculation,
57/// converting to seconds only at the end for stable results. Result is capped
58/// within u64 limits to handle edge cases.
59unsafe fn boot_time() -> u64 {
60    match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
61        Ok(n) => {
62            let system_time_ns = n.as_nanos();
63            // milliseconds to nanoseconds
64            let tick_count_ns = unsafe { GetTickCount64() } as u128 * 1_000_000;
65            // nanoseconds to seconds
66            let boot_time_sec = system_time_ns.saturating_sub(tick_count_ns) / 1_000_000_000;
67            boot_time_sec.try_into().unwrap_or(u64::MAX)
68        }
69        Err(_e) => {
70            sysinfo_debug!("Failed to compute boot time: {:?}", _e);
71            0
72        }
73    }
74}
75
76pub(crate) struct SystemInner {
77    process_list: HashMap<Pid, Process>,
78    mem_total: u64,
79    mem_available: u64,
80    swap_total: u64,
81    swap_used: u64,
82    cpus: CpusWrapper,
83    query: Option<Query>,
84}
85
86impl SystemInner {
87    pub(crate) fn new() -> Self {
88        Self {
89            process_list: HashMap::with_capacity(500),
90            mem_total: 0,
91            mem_available: 0,
92            swap_total: 0,
93            swap_used: 0,
94            cpus: CpusWrapper::new(),
95            query: None,
96        }
97    }
98
99    fn initialize_cpu_counters(&mut self, refresh_kind: CpuRefreshKind) {
100        if let Some(ref mut query) = self.query {
101            add_english_counter(
102                r"\Processor(_Total)\% Idle Time".to_string(),
103                query,
104                &mut self.cpus.global.key_used,
105                "tot_0".to_owned(),
106            );
107            for (pos, proc_) in self.cpus.iter_mut(refresh_kind).enumerate() {
108                add_english_counter(
109                    format!(r"\Processor({pos})\% Idle Time"),
110                    query,
111                    get_key_used(proc_),
112                    format!("{pos}_0"),
113                );
114            }
115        }
116    }
117
118    pub(crate) fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) {
119        if self.query.is_none() {
120            self.query = Query::new(false);
121            self.initialize_cpu_counters(refresh_kind);
122        } else if self.cpus.global.key_used.is_none() {
123            self.query = Query::new(true);
124            self.initialize_cpu_counters(refresh_kind);
125        }
126        if let Some(ref mut query) = self.query {
127            query.refresh();
128            let mut total_idle_time = None;
129            if let Some(ref key_used) = self.cpus.global.key_used {
130                total_idle_time = Some(
131                    query
132                        .get(&key_used.unique_id)
133                        .expect("global_key_idle disappeared"),
134                );
135            }
136            if let Some(total_idle_time) = total_idle_time {
137                self.cpus.global.set_cpu_usage(100.0 - total_idle_time);
138            }
139            for cpu in self.cpus.iter_mut(refresh_kind) {
140                let mut idle_time = None;
141                if let Some(ref key_used) = *get_key_used(cpu) {
142                    idle_time = Some(
143                        query
144                            .get(&key_used.unique_id)
145                            .expect("key_used disappeared"),
146                    );
147                }
148                if let Some(idle_time) = idle_time {
149                    cpu.inner.set_cpu_usage(100.0 - idle_time);
150                }
151            }
152            if refresh_kind.frequency() {
153                self.cpus.get_frequencies();
154            }
155        }
156    }
157
158    pub(crate) fn refresh_cpu_list(&mut self, refresh_kind: CpuRefreshKind) {
159        self.cpus = CpusWrapper::new();
160        self.refresh_cpu_specifics(refresh_kind);
161    }
162
163    pub(crate) fn refresh_memory_specifics(&mut self, refresh_kind: MemoryRefreshKind) {
164        unsafe {
165            if refresh_kind.ram() {
166                let mut mem_info: MEMORYSTATUSEX = zeroed();
167                mem_info.dwLength = size_of::<MEMORYSTATUSEX>() as _;
168                let _err = GlobalMemoryStatusEx(&mut mem_info);
169                self.mem_total = mem_info.ullTotalPhys as _;
170                self.mem_available = mem_info.ullAvailPhys as _;
171            }
172            if refresh_kind.swap() {
173                let mut perf_info: PERFORMANCE_INFORMATION = zeroed();
174                if K32GetPerformanceInfo(&mut perf_info, size_of::<PERFORMANCE_INFORMATION>() as _)
175                    .as_bool()
176                {
177                    let page_size = perf_info.PageSize as u64;
178                    let physical_total = perf_info.PhysicalTotal as u64;
179                    let commit_limit = perf_info.CommitLimit as u64;
180                    let commit_total = perf_info.CommitTotal as u64;
181                    self.swap_total =
182                        page_size.saturating_mul(commit_limit.saturating_sub(physical_total));
183                    self.swap_used =
184                        page_size.saturating_mul(commit_total.saturating_sub(physical_total));
185                }
186            }
187        }
188    }
189
190    pub(crate) fn cgroup_limits(&self) -> Option<crate::CGroupLimits> {
191        None
192    }
193
194    #[allow(clippy::cast_ptr_alignment)]
195    pub(crate) fn refresh_processes_specifics(
196        &mut self,
197        processes_to_update: ProcessesToUpdate<'_>,
198        refresh_kind: ProcessRefreshKind,
199    ) -> usize {
200        #[inline(always)]
201        fn real_filter(e: Pid, filter: &[Pid]) -> bool {
202            filter.contains(&e)
203        }
204
205        #[inline(always)]
206        fn empty_filter(_e: Pid, _filter: &[Pid]) -> bool {
207            true
208        }
209
210        #[allow(clippy::type_complexity)]
211        let (filter_array, filter_callback): (
212            &[Pid],
213            &(dyn Fn(Pid, &[Pid]) -> bool + Sync + Send),
214        ) = match processes_to_update {
215            ProcessesToUpdate::All => (&[], &empty_filter),
216            ProcessesToUpdate::Some(pids) => {
217                if pids.is_empty() {
218                    return 0;
219                }
220                (pids, &real_filter)
221            }
222        };
223
224        let now = get_now();
225
226        let nb_cpus = if refresh_kind.cpu() {
227            self.cpus.len() as u64
228        } else {
229            0
230        };
231
232        // Use the amazing and cool CreateToolhelp32Snapshot function.
233        // Take a snapshot of all running processes. Match the result to an error
234        let snapshot = match unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) } {
235            Ok(handle) => handle,
236            Err(_err) => {
237                sysinfo_debug!(
238                    "Error capturing process snapshot: CreateToolhelp32Snapshot returned {}",
239                    _err
240                );
241                return 0;
242            }
243        };
244
245        // This owns the above handle and makes sure that close will be called when dropped.
246        let snapshot = unsafe { Owned::new(snapshot) };
247
248        // https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/ns-tlhelp32-processentry32w
249        // Microsoft documentation states that for PROCESSENTRY32W, before calling Process32FirstW,
250        // the 'dwSize' field MUST be set to the size of the PROCESSENTRY32W. Otherwise, Process32FirstW fails.
251        let mut process_entry = PROCESSENTRY32W {
252            dwSize: size_of::<PROCESSENTRY32W>() as u32,
253            ..Default::default()
254        };
255
256        let mut num_procs = 0; // keep track of the number of updated processes
257        let process_list = &mut self.process_list;
258
259        // process the first process
260        unsafe {
261            if let Err(_error) = Process32FirstW(*snapshot, &mut process_entry) {
262                sysinfo_debug!("Process32FirstW has failed: {_error:?}");
263                return 0;
264            }
265        }
266
267        // Iterate over processes in the snapshot.
268        // Use Process32NextW to process the next PROCESSENTRY32W in the snapshot
269        loop {
270            let proc_id = Pid::from_u32(process_entry.th32ProcessID);
271
272            if filter_callback(proc_id, filter_array) {
273                // exists already
274                if let Some(p) = process_list.get_mut(&proc_id) {
275                    // Update with the most recent information
276                    let p = &mut p.inner;
277                    p.update(refresh_kind, nb_cpus, now, false);
278
279                    // Update parent process
280                    let parent = if process_entry.th32ParentProcessID == 0 {
281                        None
282                    } else {
283                        Some(Pid::from_u32(process_entry.th32ParentProcessID))
284                    };
285
286                    p.parent = parent;
287                } else {
288                    // Make a new 'ProcessInner' using the Windows PROCESSENTRY32W struct.
289                    let mut p = ProcessInner::from_process_entry(&process_entry, now);
290                    p.update(refresh_kind, nb_cpus, now, false);
291                    process_list.insert(proc_id, Process { inner: p });
292                }
293
294                num_procs += 1;
295            }
296
297            // nothing else to process
298            if unsafe { Process32NextW(*snapshot, &mut process_entry).is_err() } {
299                break;
300            }
301        }
302
303        num_procs
304    }
305
306    pub(crate) fn processes(&self) -> &HashMap<Pid, Process> {
307        &self.process_list
308    }
309
310    pub(crate) fn processes_mut(&mut self) -> &mut HashMap<Pid, Process> {
311        &mut self.process_list
312    }
313
314    pub(crate) fn process(&self, pid: Pid) -> Option<&Process> {
315        self.process_list.get(&pid)
316    }
317
318    pub(crate) fn global_cpu_usage(&self) -> f32 {
319        self.cpus.global_cpu_usage()
320    }
321
322    pub(crate) fn cpus(&self) -> &[Cpu] {
323        self.cpus.cpus()
324    }
325
326    pub(crate) fn total_memory(&self) -> u64 {
327        self.mem_total
328    }
329
330    pub(crate) fn free_memory(&self) -> u64 {
331        // MEMORYSTATUSEX doesn't report free memory
332        self.mem_available
333    }
334
335    pub(crate) fn available_memory(&self) -> u64 {
336        self.mem_available
337    }
338
339    pub(crate) fn used_memory(&self) -> u64 {
340        self.mem_total - self.mem_available
341    }
342
343    pub(crate) fn total_swap(&self) -> u64 {
344        self.swap_total
345    }
346
347    pub(crate) fn free_swap(&self) -> u64 {
348        self.swap_total - self.swap_used
349    }
350
351    pub(crate) fn used_swap(&self) -> u64 {
352        self.swap_used
353    }
354
355    pub(crate) fn uptime() -> u64 {
356        unsafe { GetTickCount64() / 1_000 }
357    }
358
359    pub(crate) fn boot_time() -> u64 {
360        unsafe { boot_time() }
361    }
362
363    pub(crate) fn load_average() -> LoadAvg {
364        get_load_average()
365    }
366
367    pub(crate) fn name() -> Option<String> {
368        Some("Windows".to_owned())
369    }
370
371    pub(crate) fn long_os_version() -> Option<String> {
372        if Self::is_windows_eleven() {
373            return get_reg_string_value(
374                HKEY_LOCAL_MACHINE,
375                r"SOFTWARE\Microsoft\Windows NT\CurrentVersion",
376                "ProductName",
377            )
378            .map(|product_name| product_name.replace("Windows 10 ", "Windows 11 "));
379        }
380        get_reg_string_value(
381            HKEY_LOCAL_MACHINE,
382            r"SOFTWARE\Microsoft\Windows NT\CurrentVersion",
383            "ProductName",
384        )
385    }
386
387    pub(crate) fn host_name() -> Option<String> {
388        get_dns_hostname()
389    }
390
391    pub(crate) fn kernel_version() -> Option<String> {
392        get_reg_string_value(
393            HKEY_LOCAL_MACHINE,
394            r"SOFTWARE\Microsoft\Windows NT\CurrentVersion",
395            "CurrentBuildNumber",
396        )
397    }
398
399    pub(crate) fn os_version() -> Option<String> {
400        let build_number = get_reg_string_value(
401            HKEY_LOCAL_MACHINE,
402            r"SOFTWARE\Microsoft\Windows NT\CurrentVersion",
403            "CurrentBuildNumber",
404        )
405        .unwrap_or_default();
406        let major = if Self::is_windows_eleven() {
407            11u32
408        } else {
409            u32::from_le_bytes(
410                get_reg_value_u32(
411                    HKEY_LOCAL_MACHINE,
412                    r"SOFTWARE\Microsoft\Windows NT\CurrentVersion",
413                    "CurrentMajorVersionNumber",
414                )
415                .unwrap_or_default(),
416            )
417        };
418        Some(format!("{major} ({build_number})"))
419    }
420
421    pub(crate) fn distribution_id() -> String {
422        std::env::consts::OS.to_owned()
423    }
424
425    pub(crate) fn distribution_id_like() -> Vec<String> {
426        Vec::new()
427    }
428
429    pub(crate) fn kernel_name() -> Option<&'static str> {
430        Some("Windows")
431    }
432
433    pub(crate) fn cpu_arch() -> Option<String> {
434        unsafe {
435            // https://docs.microsoft.com/fr-fr/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info
436            let mut info = SYSTEM_INFO::default();
437            GetSystemInfo(&mut info);
438            match info.Anonymous.Anonymous.wProcessorArchitecture {
439                SystemInformation::PROCESSOR_ARCHITECTURE_ALPHA => Some("alpha".to_string()),
440                SystemInformation::PROCESSOR_ARCHITECTURE_ALPHA64 => Some("alpha64".to_string()),
441                SystemInformation::PROCESSOR_ARCHITECTURE_AMD64 => Some("x86_64".to_string()),
442                SystemInformation::PROCESSOR_ARCHITECTURE_ARM => Some("arm".to_string()),
443                SystemInformation::PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64 => Some("arm".to_string()),
444                SystemInformation::PROCESSOR_ARCHITECTURE_ARM64 => Some("arm64".to_string()),
445                SystemInformation::PROCESSOR_ARCHITECTURE_IA32_ON_ARM64
446                | SystemInformation::PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 => {
447                    Some("ia32".to_string())
448                }
449                SystemInformation::PROCESSOR_ARCHITECTURE_IA64 => Some("ia64".to_string()),
450                SystemInformation::PROCESSOR_ARCHITECTURE_INTEL => Some("x86".to_string()),
451                SystemInformation::PROCESSOR_ARCHITECTURE_MIPS => Some("mips".to_string()),
452                SystemInformation::PROCESSOR_ARCHITECTURE_PPC => Some("powerpc".to_string()),
453                _ => None,
454            }
455        }
456    }
457
458    pub(crate) fn physical_core_count() -> Option<usize> {
459        get_physical_core_count()
460    }
461
462    pub(crate) fn open_files_limit() -> Option<usize> {
463        // Apparently when using C run-time libraries, it's limited by _NHANDLE_.
464        // It's a define:
465        //
466        // ```
467        // #define IOINFO_L2E          6
468        // #define IOINFO_ARRAY_ELTS   (1 << IOINFO_L2E)
469        // #define IOINFO_ARRAYS       128
470        // #define _NHANDLE_ (IOINFO_ARRAYS * IOINFO_ARRAY_ELTS)
471        // ```
472        //
473        // So 128 * (1 << 6) = 8192
474        Some(8192)
475    }
476}
477
478pub(crate) fn is_proc_running(handle: HANDLE) -> bool {
479    let mut exit_code = 0;
480    unsafe { GetExitCodeProcess(handle, &mut exit_code) }.is_ok()
481        && exit_code == STILL_ACTIVE.0 as u32
482}
483
484fn get_dns_hostname() -> Option<String> {
485    let mut buffer_size = 0;
486    // Running this first to get the buffer size since the DNS name can be longer than MAX_COMPUTERNAME_LENGTH
487    // setting the `lpBuffer` to null will return the buffer size
488    // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw
489    unsafe {
490        let _err = GetComputerNameExW(ComputerNamePhysicalDnsHostname, None, &mut buffer_size);
491
492        // Setting the buffer with the new length
493        let mut buffer = vec![0_u16; buffer_size as usize];
494
495        // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ne-sysinfoapi-computer_name_format
496        if GetComputerNameExW(
497            ComputerNamePhysicalDnsHostname,
498            Some(PWSTR::from_raw(buffer.as_mut_ptr())),
499            &mut buffer_size,
500        )
501        .is_ok()
502        {
503            if let Some(pos) = buffer.iter().position(|c| *c == 0) {
504                buffer.resize(pos, 0);
505            }
506
507            return String::from_utf16(&buffer).ok();
508        }
509    }
510
511    sysinfo_debug!("Failed to get computer hostname");
512    None
513}
514
515fn add_english_counter(
516    s: String,
517    query: &mut super::cpu::Query,
518    keys: &mut Option<KeyHandler>,
519    counter_name: String,
520) {
521    let mut full = s.encode_utf16().collect::<Vec<_>>();
522    full.push(0);
523    if query.add_english_counter(&counter_name, full) {
524        *keys = Some(KeyHandler::new(counter_name));
525    }
526}
527
528fn get_now() -> u64 {
529    SystemTime::now()
530        .duration_since(SystemTime::UNIX_EPOCH)
531        .map(|n| n.as_secs())
532        .unwrap_or(0)
533}
534
535fn utf16_str<S: AsRef<OsStr> + ?Sized>(text: &S) -> Vec<u16> {
536    OsStr::new(text)
537        .encode_wide()
538        .chain(Some(0))
539        .collect::<Vec<_>>()
540}
541
542struct RegKey(HKEY);
543
544impl RegKey {
545    unsafe fn open(hkey: HKEY, path: &[u16]) -> Option<Self> {
546        let mut new_hkey = Default::default();
547        if unsafe {
548            RegOpenKeyExW(
549                hkey,
550                PCWSTR::from_raw(path.as_ptr()),
551                Some(0),
552                KEY_READ,
553                &mut new_hkey,
554            )
555        }
556        .is_err()
557        {
558            return None;
559        }
560        Some(Self(new_hkey))
561    }
562
563    unsafe fn get_value(
564        &self,
565        field_name: &[u16],
566        buf: &mut [u8],
567        buf_len: &mut u32,
568    ) -> windows::core::Result<()> {
569        let mut buf_type = REG_NONE;
570
571        unsafe {
572            RegQueryValueExW(
573                self.0,
574                PCWSTR::from_raw(field_name.as_ptr()),
575                None,
576                Some(&mut buf_type),
577                Some(buf.as_mut_ptr()),
578                Some(buf_len),
579            )
580        }
581        .ok()
582    }
583}
584
585impl Drop for RegKey {
586    fn drop(&mut self) {
587        let _err = unsafe { RegCloseKey(self.0) };
588    }
589}
590
591pub(crate) fn get_reg_string_value(hkey: HKEY, path: &str, field_name: &str) -> Option<String> {
592    let c_path = utf16_str(path);
593    let c_field_name = utf16_str(field_name);
594
595    unsafe {
596        let new_key = RegKey::open(hkey, &c_path)?;
597        let mut buf_len: u32 = 2048;
598        let mut buf: Vec<u8> = Vec::with_capacity(buf_len as usize);
599
600        loop {
601            match new_key.get_value(&c_field_name, &mut buf, &mut buf_len) {
602                Ok(()) => break,
603                Err(err) if err.code() == Foundation::ERROR_MORE_DATA.to_hresult() => {
604                    // Needs to be updated for `Vec::reserve` to actually add additional capacity.
605                    buf.set_len(buf.capacity());
606                    buf.reserve(buf_len as _);
607                }
608                _ => return None,
609            }
610        }
611
612        buf.set_len(buf_len as _);
613
614        let words = std::slice::from_raw_parts(buf.as_ptr() as *const u16, buf.len() / 2);
615        let mut s = String::from_utf16_lossy(words);
616        while s.ends_with('\u{0}') {
617            s.pop();
618        }
619        Some(s)
620    }
621}
622
623pub(crate) fn get_reg_value_u32(hkey: HKEY, path: &str, field_name: &str) -> Option<[u8; 4]> {
624    let c_path = utf16_str(path);
625    let c_field_name = utf16_str(field_name);
626
627    unsafe {
628        let new_key = RegKey::open(hkey, &c_path)?;
629        let mut buf_len: u32 = 4;
630        let mut buf = [0u8; 4];
631
632        new_key
633            .get_value(&c_field_name, &mut buf, &mut buf_len)
634            .map(|_| buf)
635            .ok()
636    }
637}