1 // Take a look at the license at the top of the repository in the LICENSE file.
3 use crate::sys
::tools
::KeyHandler
;
4 use crate::{CpuExt, CpuRefreshKind, LoadAvg}
;
6 use std
::collections
::HashMap
;
9 use std
::ops
::DerefMut
;
10 use std
::ptr
::null_mut
;
13 use ntapi
::ntpoapi
::PROCESSOR_POWER_INFORMATION
;
15 use winapi
::shared
::minwindef
::FALSE
;
16 use winapi
::shared
::winerror
::{ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS}
;
17 use winapi
::um
::handleapi
::CloseHandle
;
18 use winapi
::um
::pdh
::{
19 PdhAddEnglishCounterA
, PdhAddEnglishCounterW
, PdhCloseQuery
, PdhCollectQueryData
,
20 PdhCollectQueryDataEx
, PdhGetFormattedCounterValue
, PdhOpenQueryA
, PdhRemoveCounter
,
21 PDH_FMT_COUNTERVALUE
, PDH_FMT_DOUBLE
, PDH_HCOUNTER
, PDH_HQUERY
,
23 use winapi
::um
::powerbase
::CallNtPowerInformation
;
24 use winapi
::um
::synchapi
::CreateEventA
;
25 use winapi
::um
::sysinfoapi
::GetLogicalProcessorInformationEx
;
26 use winapi
::um
::sysinfoapi
::SYSTEM_INFO
;
27 use winapi
::um
::winbase
::{RegisterWaitForSingleObject, INFINITE}
;
28 use winapi
::um
::winnt
::{
29 ProcessorInformation
, RelationAll
, RelationProcessorCore
, BOOLEAN
, HANDLE
,
30 PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX
, PVOID
, WT_EXECUTEDEFAULT
,
33 // This formula comes from Linux's include/linux/sched/loadavg.h
34 // https://github.com/torvalds/linux/blob/345671ea0f9258f410eb057b9ced9cefbbe5dc78/include/linux/sched/loadavg.h#L20-L23
35 #[allow(clippy::excessive_precision)]
36 const LOADAVG_FACTOR_1F
: f64 = 0.9200444146293232478931553241;
37 #[allow(clippy::excessive_precision)]
38 const LOADAVG_FACTOR_5F
: f64 = 0.9834714538216174894737477501;
39 #[allow(clippy::excessive_precision)]
40 const LOADAVG_FACTOR_15F
: f64 = 0.9944598480048967508795473394;
41 // The time interval in seconds between taking load counts, same as Linux
42 const SAMPLING_INTERVAL
: usize = 5;
44 // maybe use a read/write lock instead?
45 static LOAD_AVG
: once_cell
::sync
::Lazy
<Mutex
<Option
<LoadAvg
>>> =
46 once_cell
::sync
::Lazy
::new(|| unsafe { init_load_avg() }
);
48 pub(crate) fn get_load_average() -> LoadAvg
{
49 if let Ok(avg
) = LOAD_AVG
.lock() {
50 if let Some(avg
) = &*avg
{
57 unsafe extern "system" fn load_avg_callback(counter
: PVOID
, _
: BOOLEAN
) {
58 let mut display_value
= mem
::MaybeUninit
::<PDH_FMT_COUNTERVALUE
>::uninit();
60 if PdhGetFormattedCounterValue(
64 display_value
.as_mut_ptr(),
65 ) != ERROR_SUCCESS
as _
69 let display_value
= display_value
.assume_init();
70 if let Ok(mut avg
) = LOAD_AVG
.lock() {
71 if let Some(avg
) = avg
.deref_mut() {
72 let current_load
= display_value
.u
.doubleValue();
74 avg
.one
= avg
.one
* LOADAVG_FACTOR_1F
+ current_load
* (1.0 - LOADAVG_FACTOR_1F
);
75 avg
.five
= avg
.five
* LOADAVG_FACTOR_5F
+ current_load
* (1.0 - LOADAVG_FACTOR_5F
);
77 avg
.fifteen
* LOADAVG_FACTOR_15F
+ current_load
* (1.0 - LOADAVG_FACTOR_15F
);
82 unsafe fn init_load_avg() -> Mutex
<Option
<LoadAvg
>> {
83 // You can see the original implementation here: https://github.com/giampaolo/psutil
84 let mut query
= null_mut();
86 if PdhOpenQueryA(null_mut(), 0, &mut query
) != ERROR_SUCCESS
as _
{
87 sysinfo_debug
!("init_load_avg: PdhOpenQueryA failed");
88 return Mutex
::new(None
);
91 let mut counter
: PDH_HCOUNTER
= mem
::zeroed();
92 if PdhAddEnglishCounterA(
94 b
"\\System\\Cpu Queue Length\0".as_ptr() as _
,
97 ) != ERROR_SUCCESS
as _
100 sysinfo_debug
!("init_load_avg: failed to get CPU queue length");
101 return Mutex
::new(None
);
104 let event
= CreateEventA(null_mut(), FALSE
, FALSE
, b
"LoadUpdateEvent\0".as_ptr() as _
);
106 PdhCloseQuery(query
);
107 sysinfo_debug
!("init_load_avg: failed to create event `LoadUpdateEvent`");
108 return Mutex
::new(None
);
111 if PdhCollectQueryDataEx(query
, SAMPLING_INTERVAL
as _
, event
) != ERROR_SUCCESS
as _
{
112 PdhCloseQuery(query
);
113 sysinfo_debug
!("init_load_avg: PdhCollectQueryDataEx failed");
114 return Mutex
::new(None
);
117 let mut wait_handle
= null_mut();
118 if RegisterWaitForSingleObject(
121 Some(load_avg_callback
),
127 PdhRemoveCounter(counter
);
128 PdhCloseQuery(query
);
129 sysinfo_debug
!("init_load_avg: RegisterWaitForSingleObject failed");
132 Mutex
::new(Some(LoadAvg
::default()))
136 struct InternalQuery
{
139 data
: HashMap
<String
, PDH_HCOUNTER
>,
142 unsafe impl Send
for InternalQuery {}
143 unsafe impl Sync
for InternalQuery {}
145 impl Drop
for InternalQuery
{
148 for (_
, counter
) in self.data
.iter() {
149 PdhRemoveCounter(*counter
);
152 if !self.event
.is_null() {
153 CloseHandle(self.event
);
156 if !self.query
.is_null() {
157 PdhCloseQuery(self.query
);
163 pub(crate) struct Query
{
164 internal
: InternalQuery
,
168 pub fn new() -> Option
<Query
> {
169 let mut query
= null_mut();
171 if PdhOpenQueryA(null_mut(), 0, &mut query
) == ERROR_SUCCESS
as i32 {
172 let q
= InternalQuery
{
175 data
: HashMap
::new(),
177 Some(Query { internal: q }
)
179 sysinfo_debug
!("Query::new: PdhOpenQueryA failed");
185 #[allow(clippy::ptr_arg)]
186 pub fn get(&self, name
: &String
) -> Option
<f32> {
187 if let Some(counter
) = self.internal
.data
.get(name
) {
189 let mut display_value
= mem
::MaybeUninit
::<PDH_FMT_COUNTERVALUE
>::uninit();
190 let counter
: PDH_HCOUNTER
= *counter
;
192 let ret
= PdhGetFormattedCounterValue(
196 display_value
.as_mut_ptr(),
198 let display_value
= display_value
.assume_init();
199 return if ret
== ERROR_SUCCESS
as _
{
200 let data
= *display_value
.u
.doubleValue();
203 sysinfo_debug
!("Query::get: PdhGetFormattedCounterValue failed");
211 #[allow(clippy::ptr_arg)]
212 pub fn add_english_counter(&mut self, name
: &String
, getter
: Vec
<u16>) -> bool
{
213 if self.internal
.data
.contains_key(name
) {
214 sysinfo_debug
!("Query::add_english_counter: doesn't have key `{:?}`", name
);
218 let mut counter
: PDH_HCOUNTER
= std
::mem
::zeroed();
219 let ret
= PdhAddEnglishCounterW(self.internal
.query
, getter
.as_ptr(), 0, &mut counter
);
220 if ret
== ERROR_SUCCESS
as _
{
221 self.internal
.data
.insert(name
.clone(), counter
);
224 "Query::add_english_counter: failed to add counter '{}': {:x}...",
234 pub fn refresh(&self) {
236 if PdhCollectQueryData(self.internal
.query
) != ERROR_SUCCESS
as _
{
237 sysinfo_debug
!("failed to refresh CPU data");
243 pub(crate) struct CpusWrapper
{
246 got_cpu_frequency
: bool
,
250 pub fn new() -> Self {
252 global
: Cpu
::new_with_values("Total CPU".to_owned(), String
::new(), String
::new(), 0),
254 got_cpu_frequency
: false,
258 pub fn global_cpu(&self) -> &Cpu
{
262 pub fn global_cpu_mut(&mut self) -> &mut Cpu
{
266 pub fn cpus(&self) -> &[Cpu
] {
270 fn init_if_needed(&mut self, refresh_kind
: CpuRefreshKind
) {
271 if self.cpus
.is_empty() {
272 let (cpus
, vendor_id
, brand
) = super::tools
::init_cpus(refresh_kind
);
274 self.global
.vendor_id
= vendor_id
;
275 self.global
.brand
= brand
;
276 self.got_cpu_frequency
= refresh_kind
.frequency();
280 pub fn len(&mut self) -> usize {
281 self.init_if_needed(CpuRefreshKind
::new());
285 pub fn iter_mut(&mut self, refresh_kind
: CpuRefreshKind
) -> impl Iterator
<Item
= &mut Cpu
> {
286 self.init_if_needed(refresh_kind
);
290 pub fn get_frequencies(&mut self) {
291 if self.got_cpu_frequency
{
294 let frequencies
= get_frequencies(self.cpus
.len());
296 for (cpu
, frequency
) in self.cpus
.iter_mut().zip(frequencies
) {
297 cpu
.set_frequency(frequency
);
300 .set_frequency(self.cpus
.get(0).map(|cpu
| cpu
.frequency()).unwrap_or(0));
301 self.got_cpu_frequency
= true;
305 #[doc = include_str!("../../md_doc/cpu.md")]
309 key_used
: Option
<KeyHandler
>,
315 impl CpuExt
for Cpu
{
316 fn cpu_usage(&self) -> f32 {
320 fn name(&self) -> &str {
324 fn frequency(&self) -> u64 {
328 fn vendor_id(&self) -> &str {
332 fn brand(&self) -> &str {
338 pub(crate) fn new_with_values(
354 pub(crate) fn set_cpu_usage(&mut self, value
: f32) {
355 self.cpu_usage
= value
;
358 pub(crate) fn set_frequency(&mut self, value
: u64) {
359 self.frequency
= value
;
363 fn get_vendor_id_not_great(info
: &SYSTEM_INFO
) -> String
{
364 use winapi
::um
::winnt
;
365 // https://docs.microsoft.com/fr-fr/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info
367 match info
.u
.s().wProcessorArchitecture
{
368 winnt
::PROCESSOR_ARCHITECTURE_INTEL
=> "Intel x86",
369 winnt
::PROCESSOR_ARCHITECTURE_MIPS
=> "MIPS",
370 winnt
::PROCESSOR_ARCHITECTURE_ALPHA
=> "RISC Alpha",
371 winnt
::PROCESSOR_ARCHITECTURE_PPC
=> "PPC",
372 winnt
::PROCESSOR_ARCHITECTURE_SHX
=> "SHX",
373 winnt
::PROCESSOR_ARCHITECTURE_ARM
=> "ARM",
374 winnt
::PROCESSOR_ARCHITECTURE_IA64
=> "Intel Itanium-based x64",
375 winnt
::PROCESSOR_ARCHITECTURE_ALPHA64
=> "RISC Alpha x64",
376 winnt
::PROCESSOR_ARCHITECTURE_MSIL
=> "MSIL",
377 winnt
::PROCESSOR_ARCHITECTURE_AMD64
=> "(Intel or AMD) x64",
378 winnt
::PROCESSOR_ARCHITECTURE_IA32_ON_WIN64
=> "Intel Itanium-based x86",
379 winnt
::PROCESSOR_ARCHITECTURE_NEUTRAL
=> "unknown",
380 winnt
::PROCESSOR_ARCHITECTURE_ARM64
=> "ARM x64",
381 winnt
::PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64
=> "ARM",
382 winnt
::PROCESSOR_ARCHITECTURE_IA32_ON_ARM64
=> "Intel Itanium-based x86",
389 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
390 pub(crate) fn get_vendor_id_and_brand(info
: &SYSTEM_INFO
) -> (String
, String
) {
391 #[cfg(target_arch = "x86")]
392 use std
::arch
::x86
::__cpuid
;
393 #[cfg(target_arch = "x86_64")]
394 use std
::arch
::x86_64
::__cpuid
;
396 unsafe fn add_u32(v
: &mut Vec
<u8>, i
: u32) {
397 let i
= &i
as *const u32 as *const u8;
399 v
.push(*i
.offset(1));
400 v
.push(*i
.offset(2));
401 v
.push(*i
.offset(3));
405 // First, we try to get the complete name.
406 let res
= __cpuid(0x80000000);
407 let n_ex_ids
= res
.eax
;
408 let brand
= if n_ex_ids
>= 0x80000004 {
409 let mut extdata
= Vec
::with_capacity(5);
411 for i
in 0x80000000..=n_ex_ids
{
412 extdata
.push(__cpuid(i
));
415 // 4 * u32 * nb_entries
416 let mut out
= Vec
::with_capacity(4 * std
::mem
::size_of
::<u32>() * 3);
417 for data
in extdata
.iter().take(5).skip(2) {
418 add_u32(&mut out
, data
.eax
);
419 add_u32(&mut out
, data
.ebx
);
420 add_u32(&mut out
, data
.ecx
);
421 add_u32(&mut out
, data
.edx
);
424 for e
in out
.iter() {
430 match std
::str::from_utf8(&out
[..pos
]) {
431 Ok(s
) => s
.to_owned(),
438 // Failed to get full name, let's retry for the short version!
439 let res
= __cpuid(0);
440 let mut x
= Vec
::with_capacity(3 * std
::mem
::size_of
::<u32>());
441 add_u32(&mut x
, res
.ebx
);
442 add_u32(&mut x
, res
.edx
);
443 add_u32(&mut x
, res
.ecx
);
451 let vendor_id
= match std
::str::from_utf8(&x
[..pos
]) {
452 Ok(s
) => s
.to_owned(),
453 Err(_
) => get_vendor_id_not_great(info
),
459 #[cfg(all(not(target_arch = "x86_64"), not(target_arch = "x86")))]
460 pub(crate) fn get_vendor_id_and_brand(info
: &SYSTEM_INFO
) -> (String
, String
) {
461 (get_vendor_id_not_great(info
), String
::new())
464 pub(crate) fn get_key_used(p
: &mut Cpu
) -> &mut Option
<KeyHandler
> {
468 // From https://stackoverflow.com/a/43813138:
470 // If your PC has 64 or fewer logical cpus installed, the above code will work fine. However,
471 // if your PC has more than 64 logical cpus installed, use GetActiveCpuCount() or
472 // GetLogicalCpuInformation() to determine the total number of logical cpus installed.
473 pub(crate) fn get_frequencies(nb_cpus
: usize) -> Vec
<u64> {
474 let size
= nb_cpus
* mem
::size_of
::<PROCESSOR_POWER_INFORMATION
>();
475 let mut infos
: Vec
<PROCESSOR_POWER_INFORMATION
> = Vec
::with_capacity(nb_cpus
);
478 if CallNtPowerInformation(
479 ProcessorInformation
,
482 infos
.as_mut_ptr() as _
,
486 infos
.set_len(nb_cpus
);
490 .map(|i
| i
.CurrentMhz
as u64)
491 .collect
::<Vec
<_
>>();
494 sysinfo_debug
!("get_frequencies: CallNtPowerInformation failed");
498 pub(crate) fn get_physical_core_count() -> Option
<usize> {
499 // we cannot use the number of cpus here to pre calculate the buf size
500 // GetLogicalCpuInformationEx with RelationProcessorCore passed to it not only returns
501 // the logical cores but also numa nodes
503 // GetLogicalCpuInformationEx: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformationex
505 let mut needed_size
= 0;
507 GetLogicalProcessorInformationEx(RelationAll
, null_mut(), &mut needed_size
);
509 let mut buf
: Vec
<u8> = Vec
::with_capacity(needed_size
as _
);
512 if GetLogicalProcessorInformationEx(
514 buf
.as_mut_ptr() as *mut _
,
518 let e
= Error
::last_os_error();
519 // For some reasons, the function might return a size not big enough...
520 match e
.raw_os_error() {
521 Some(value
) if value
== ERROR_INSUFFICIENT_BUFFER
as _
=> {}
524 "get_physical_core_count: GetLogicalCpuInformationEx failed"
532 buf
.reserve(needed_size
as usize - buf
.capacity());
535 buf
.set_len(needed_size
as _
);
538 let raw_buf
= buf
.as_ptr();
540 while i
< buf
.len() {
541 let p
= &*(raw_buf
.add(i
) as PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX
);
542 i
+= p
.Size
as usize;
543 if p
.Relationship
== RelationProcessorCore
{
544 // Only count the physical cores.