Repository: GuillaumeGomez/sysinfo
Pull request #408 (2021-01-13 21:48:22)

Add get_physical_core_numbers
fixes: #401

I've put some extra checks on the Linux implementation to make sure we count the number of cores over all the CPUs but as i don't have a server with 2 CPUs i cannot test if the extra code is necessary.  

GuillaumeGomez: Thanks for the PR! If you don't mind, I'd prefer to get this information on demand (just like "host name" for example) instead of storing it. It's an information which doesn't change in time (normally) so no need to store it on our end.
mjarkk: I've changed the code to what you suggested
mjarkk: Fixed your suggestions 
mjarkk: Fixed your sugestions
GuillaumeGomez: Looks all good, thanks!

Patch:
diff --git a/src/apple/system.rs b/src/apple/system.rs
index 9e48fc8..84fb886 100644
--- a/src/apple/system.rs
+++ b/src/apple/system.rs
@@ -6,6 +6,7 @@
 
 #[cfg(target_os = "macos")]
 use core_foundation_sys::base::{kCFAllocatorDefault, CFRelease};
+use libc::c_char;
 use sys::component::Component;
 use sys::disk::*;
 use sys::ffi;
@@ -346,6 +347,22 @@ impl SystemExt for System {
         &self.processors
     }
 
+    fn get_physical_core_count(&self) -> usize {
+        let mut physical_core_count = 0;
+
+        if unsafe {
+            get_sys_value_by_name(
+                b"hw.physicalcpu\0",
+                mem::size_of::<u32>(),
+                &mut physical_core_count as *mut usize as *mut c_void,
+            )
+        } {
+            physical_core_count
+        } else {
+            0
+        }
+    }
+
     fn get_networks(&self) -> &Networks {
         &self.networks
     }
@@ -521,6 +538,16 @@ pub(crate) unsafe fn get_sys_value(
     ) == 0
 }
 
+unsafe fn get_sys_value_by_name(name: &[u8], mut len: usize, value: *mut libc::c_void) -> bool {
+    libc::sysctlbyname(
+        name.as_ptr() as *const c_char,
+        value,
+        &mut len,
+        ::std::ptr::null_mut(),
+        0,
+    ) == 0
+}
+
 fn get_system_info(value: c_int, default: Option<&str>) -> Option<String> {
     let len = 256;
     let mut mib: [c_int; 2] = [libc::CTL_KERN, value];
diff --git a/src/linux/processor.rs b/src/linux/processor.rs
index dc670bf..b869e57 100644
--- a/src/linux/processor.rs
+++ b/src/linux/processor.rs
@@ -6,6 +6,7 @@
 
 #![allow(clippy::too_many_arguments)]
 
+use std::collections::HashSet;
 use std::fs::File;
 use std::io::Read;
 
@@ -260,6 +261,42 @@ pub fn get_cpu_frequency(cpu_core_index: usize) -> u64 {
         .unwrap_or_default()
 }
 
+pub fn get_physical_core_count() -> usize {
+    let mut s = String::new();
+    if File::open("/proc/cpuinfo")
+        .and_then(|mut f| f.read_to_string(&mut s))
+        .is_err()
+    {
+        return 0;
+    }
+
+    let mut core_ids_and_physical_ids: HashSet<String> = HashSet::new();
+    let mut core_id = "";
+    let mut physical_id = "";
+    for line in s.lines() {
+        if line.starts_with("core id") {
+            core_id = line
+                .splitn(2, ':')
+                .last()
+                .map(|x| x.trim())
+                .unwrap_or_default();
+        } else if line.starts_with("physical id") {
+            physical_id = line
+                .splitn(2, ':')
+                .last()
+                .map(|x| x.trim())
+                .unwrap_or_default();
+        }
+        if !core_id.is_empty() && !physical_id.is_empty() {
+            core_ids_and_physical_ids.insert(format!("{} {}", core_id, physical_id));
+            core_id = "";
+            physical_id = "";
+        }
+    }
+
+    core_ids_and_physical_ids.len()
+}
+
 /// Returns the brand/vendor string for the first CPU (which should be the same for all CPUs).
 pub fn get_vendor_id_and_brand() -> (String, String) {
     let mut s = String::new();
diff --git a/src/linux/system.rs b/src/linux/system.rs
index 508ce16..37d1ed7 100644
--- a/src/linux/system.rs
+++ b/src/linux/system.rs
@@ -421,6 +421,10 @@ impl SystemExt for System {
         &self.processors
     }
 
+    fn get_physical_core_count(&self) -> usize {
+        get_physical_core_count()
+    }
+
     fn get_total_memory(&self) -> u64 {
         self.mem_total
     }
diff --git a/src/traits.rs b/src/traits.rs
index 2995297..a912bbd 100644
--- a/src/traits.rs
+++ b/src/traits.rs
@@ -733,6 +733,20 @@ pub trait SystemExt: Sized + Debug + Default {
     /// ```
     fn get_processors(&self) -> &[Processor];
 
+    /// Returns the number of physical cores on the processor.
+    ///
+    /// In case there are multiple CPUs, it will combine the physical core count of all the CPUs.
+    ///
+    /// **Important**: this information is computed every time this function is called.
+    ///
+    /// ```no_run
+    /// use sysinfo::{ProcessorExt, System, SystemExt};
+    ///
+    /// let s = System::new();
+    /// println!("{}", s.get_physical_core_count());
+    /// ```
+    fn get_physical_core_count(&self) -> usize;
+
     /// Returns the RAM size in kB.
     ///
     /// ```no_run
diff --git a/src/unknown/system.rs b/src/unknown/system.rs
index e7f7725..9b9b12a 100644
--- a/src/unknown/system.rs
+++ b/src/unknown/system.rs
@@ -76,6 +76,10 @@ impl SystemExt for System {
         &[]
     }
 
+    fn get_physical_core_count(&self) -> usize {
+        0
+    }
+
     fn get_total_memory(&self) -> u64 {
         0
     }
diff --git a/src/windows/processor.rs b/src/windows/processor.rs
index 2259e81..f8726a2 100644
--- a/src/windows/processor.rs
+++ b/src/windows/processor.rs
@@ -26,9 +26,13 @@ use winapi::um::pdh::{
 };
 use winapi::um::powerbase::CallNtPowerInformation;
 use winapi::um::synchapi::CreateEventA;
+use winapi::um::sysinfoapi::GetLogicalProcessorInformationEx;
 use winapi::um::sysinfoapi::SYSTEM_INFO;
 use winapi::um::winbase::{RegisterWaitForSingleObject, INFINITE};
-use winapi::um::winnt::{ProcessorInformation, BOOLEAN, HANDLE, PVOID, WT_EXECUTEDEFAULT};
+use winapi::um::winnt::{
+    ProcessorInformation, BOOLEAN, HANDLE, PVOID, SYSTEM_LOGICAL_PROCESSOR_INFORMATION,
+    WT_EXECUTEDEFAULT,
+};
 
 // This formula comes from linux's include/linux/sched/loadavg.h
 // https://github.com/torvalds/linux/blob/345671ea0f9258f410eb057b9ced9cefbbe5dc78/include/linux/sched/loadavg.h#L20-L23
@@ -410,3 +414,32 @@ pub fn get_frequencies(nb_processors: usize) -> Vec<u64> {
         vec![0; nb_processors]
     }
 }
+
+pub fn get_physical_core_count() -> usize {
+    // we cannot use the number of processors here to pre calculate the buf size
+    // GetLogicalProcessorInformationEx with RelationProcessorCore passed to it not only returns the logical cores but also numa nodes
+    //
+    // GetLogicalProcessorInformationEx: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformationex
+
+    let mut needed_size = 0;
+    unsafe { GetLogicalProcessorInformationEx(0, null_mut(), &mut needed_size) };
+    let size = mem::size_of::<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>() as u32;
+    if needed_size == 0 || needed_size < size || needed_size % size != 0 {
+        return 0;
+    }
+
+    let count = needed_size / size;
+    let mut buf = Vec::with_capacity(count as usize);
+
+    let result = unsafe { GetLogicalProcessorInformationEx(0, buf.as_mut_ptr(), &mut needed_size) };
+    if result == 0 {
+        return 0;
+    }
+
+    unsafe {
+        buf.set_len(count as usize);
+    }
+    buf.iter()
+        .filter(|proc_info| proc_info.Relationship == 0) // Only get the physical cores
+        .count()
+}
diff --git a/src/windows/system.rs b/src/windows/system.rs
index c64d032..9a56593 100644
--- a/src/windows/system.rs
+++ b/src/windows/system.rs
@@ -326,6 +326,10 @@ impl SystemExt for System {
         &self.processors
     }
 
+    fn get_physical_core_count(&self) -> usize {
+        get_physical_core_count()
+    }
+
     fn get_total_memory(&self) -> u64 {
         self.mem_total
     }


Test patch:
diff --git a/tests/processor.rs b/tests/processor.rs
index 7bfa253..f551c89 100644
--- a/tests/processor.rs
+++ b/tests/processor.rs
@@ -18,3 +18,13 @@ fn test_processor() {
     let s = sysinfo::System::new_all();
     assert!(!s.get_processors().is_empty());
 }
+
+#[test]
+fn test_physical_core_numbers() {
+    use sysinfo::SystemExt;
+
+    let s = sysinfo::System::new();
+    assert_ne!(s.get_physical_core_count(), 0);
+    let s = sysinfo::System::new_all();
+    assert_ne!(s.get_physical_core_count(), 0);
+}