Repository: GuillaumeGomez/sysinfo (language: rust)

Issue:
Linux: filter out threads
Since version 0.30 the `system` also returns a `process` class for each Linux thread (instead of one per process).
Please either add a flag to only retrieve actual processes or add a member function to `process` to check whether it refers to a thread.



Discussion / hints:
It's something I was wondering if it'd be ever needed but I guess it is. Adding a method to query this information on a `Processs` then.

Fix patch:
diff --git a/src/common.rs b/src/common.rs
index bc82704a7..798ab1a93 100644
--- a/src/common.rs
+++ b/src/common.rs
@@ -1278,7 +1278,7 @@ impl Process {
 
     /// Tasks run by this process. If there are none, returns `None`.
     ///
-    /// ⚠️ This method always returns `None` on other plantforms than Linux.
+    /// ⚠️ This method always returns `None` on other platforms than Linux.
     ///
     /// ```no_run
     /// use sysinfo::{Pid, System};
@@ -1308,6 +1308,35 @@ impl Process {
             }
         }
     }
+
+    /// If the process is a thread, it'll return `Some` with the kind of thread it is. Returns
+    /// `None` otherwise.
+    ///
+    /// ⚠️ This method always returns `None` on other platforms than Linux.
+    ///
+    /// ```no_run
+    /// use sysinfo::System;
+    ///
+    /// let s = System::new_all();
+    ///
+    /// for (_, process) in s.processes() {
+    ///     if let Some(thread_kind) = process.thread_kind() {
+    ///         println!("Process {:?} is a {thread_kind:?} thread", process.pid());
+    ///     }
+    /// }
+    /// ```
+    pub fn thread_kind(&self) -> Option<ThreadKind> {
+        cfg_if::cfg_if! {
+            if #[cfg(all(
+                any(target_os = "linux", target_os = "android"),
+                not(feature = "unknown-ci")
+            ))] {
+                self.inner.thread_kind()
+            } else {
+                None
+            }
+        }
+    }
 }
 
 macro_rules! pid_decl {
@@ -3305,6 +3334,15 @@ pub enum ProcessStatus {
     Unknown(u32),
 }
 
+/// Enum describing the different kind of threads.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum ThreadKind {
+    /// Kernel thread.
+    Kernel,
+    /// User thread.
+    Userland,
+}
+
 /// Returns the pid for the current process.
 ///
 /// `Err` is returned in case the platform isn't supported.
diff --git a/src/lib.rs b/src/lib.rs
index b16919b28..602a3e2f3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -53,8 +53,8 @@ cfg_if::cfg_if! {
 pub use crate::common::{
     get_current_pid, CGroupLimits, Component, Components, Cpu, CpuRefreshKind, Disk, DiskKind,
     DiskUsage, Disks, Gid, Group, LoadAvg, MacAddr, MemoryRefreshKind, NetworkData, Networks, Pid,
-    Process, ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, System, Uid, UpdateKind, User,
-    Users,
+    Process, ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, System, ThreadKind, Uid,
+    UpdateKind, User, Users,
 };
 
 pub(crate) use crate::sys::{
diff --git a/src/serde.rs b/src/serde.rs
index 5ddc3b302..1b273b43c 100644
--- a/src/serde.rs
+++ b/src/serde.rs
@@ -186,6 +186,20 @@ impl Serialize for crate::Users {
     }
 }
 
+impl Serialize for crate::ThreadKind {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let (index, variant) = match *self {
+            Self::Kernel => (0, "Kernel"),
+            Self::Userland => (1, "Userland"),
+        };
+
+        serializer.serialize_unit_variant("ThreadKind", index, variant)
+    }
+}
+
 impl Serialize for crate::Signal {
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
diff --git a/src/unix/linux/process.rs b/src/unix/linux/process.rs
index d67b928e4..9c1d55834 100644
--- a/src/unix/linux/process.rs
+++ b/src/unix/linux/process.rs
@@ -9,13 +9,15 @@ use std::io::Read;
 use std::path::{Path, PathBuf};
 use std::str::FromStr;
 
-use libc::{gid_t, kill, uid_t};
+use libc::{c_ulong, gid_t, kill, uid_t};
 
 use crate::sys::system::SystemInfo;
 use crate::sys::utils::{
     get_all_data, get_all_data_from_file, realpath, FileCounter, PathHandler, PathPush,
 };
-use crate::{DiskUsage, Gid, Pid, Process, ProcessRefreshKind, ProcessStatus, Signal, Uid};
+use crate::{
+    DiskUsage, Gid, Pid, Process, ProcessRefreshKind, ProcessStatus, Signal, ThreadKind, Uid,
+};
 
 #[doc(hidden)]
 impl From<char> for ProcessStatus {
@@ -86,6 +88,9 @@ enum ProcIndex {
     // More exist but we only use the listed ones. For more, take a look at `man proc`.
 }
 
+/* Not exposed yet. Defined at include/linux/sched.h */
+const PF_KTHREAD: c_ulong = 0x00200000;
+
 pub(crate) struct ProcessInner {
     pub(crate) name: String,
     pub(crate) cmd: Vec<String>,
@@ -117,10 +122,12 @@ pub(crate) struct ProcessInner {
     old_written_bytes: u64,
     read_bytes: u64,
     written_bytes: u64,
+    thread_kind: Option<ThreadKind>,
+    proc_path: PathBuf,
 }
 
 impl ProcessInner {
-    pub(crate) fn new(pid: Pid) -> Self {
+    pub(crate) fn new(pid: Pid, proc_path: PathBuf) -> Self {
         Self {
             name: String::new(),
             pid,
@@ -152,6 +159,8 @@ impl ProcessInner {
             old_written_bytes: 0,
             read_bytes: 0,
             written_bytes: 0,
+            thread_kind: None,
+            proc_path,
         }
     }
 
@@ -265,6 +274,10 @@ impl ProcessInner {
             }
         }
     }
+
+    pub(crate) fn thread_kind(&self) -> Option<ThreadKind> {
+        self.thread_kind
+    }
 }
 
 pub(crate) fn compute_cpu_usage(p: &mut ProcessInner, total_time: f32, max_value: f32) {
@@ -429,7 +442,7 @@ fn retrieve_all_new_process_info(
     refresh_kind: ProcessRefreshKind,
     uptime: u64,
 ) -> Process {
-    let mut p = ProcessInner::new(pid);
+    let mut p = ProcessInner::new(pid, path.to_owned());
     let mut proc_path = PathHandler::new(path);
     let name = parts[ProcIndex::ShortExe as usize];
 
@@ -447,6 +460,14 @@ fn retrieve_all_new_process_info(
         .saturating_add(info.boot_time);
 
     p.name = name.into();
+    if c_ulong::from_str(parts[ProcIndex::Flags as usize])
+        .map(|flags| flags & PF_KTHREAD != 0)
+        .unwrap_or(false)
+    {
+        p.thread_kind = Some(ThreadKind::Kernel);
+    } else if parent_pid.is_some() {
+        p.thread_kind = Some(ThreadKind::Userland);
+    }
 
     update_proc_info(&mut p, refresh_kind, &mut proc_path, parts, uptime, info);
 
@@ -475,7 +496,7 @@ pub(crate) fn _get_process_data(
                 Err(_) => {
                     // It's possible that the file descriptor is no longer valid in case the
                     // original process was terminated and another one took its place.
-                    _get_stat_data(path, &mut entry.stat_file)?
+                    _get_stat_data(&entry.proc_path, &mut entry.stat_file)?
                 }
             }
         } else {
@@ -607,12 +628,14 @@ fn update_time_and_memory(
 
 struct ProcAndTasks {
     pid: Pid,
+    parent_pid: Option<Pid>,
     path: PathBuf,
     tasks: Option<HashSet<Pid>>,
 }
 
 fn get_all_pid_entries(
     parent: Option<&OsStr>,
+    parent_pid: Option<Pid>,
     entry: DirEntry,
     data: &mut Vec<ProcAndTasks>,
 ) -> Option<Pid> {
@@ -635,7 +658,7 @@ fn get_all_pid_entries(
         if let Ok(entries) = fs::read_dir(tasks_dir) {
             for task in entries
                 .into_iter()
-                .filter_map(|entry| get_all_pid_entries(Some(name), entry.ok()?, data))
+                .filter_map(|entry| get_all_pid_entries(Some(name), Some(pid), entry.ok()?, data))
             {
                 tasks.insert(task);
             }
@@ -646,6 +669,7 @@ fn get_all_pid_entries(
     };
     data.push(ProcAndTasks {
         pid,
+        parent_pid,
         path: entry,
         tasks,
     });
@@ -714,7 +738,7 @@ pub(crate) fn refresh_procs(
             .map(|entry| {
                 let Ok(entry) = entry else { return Vec::new() };
                 let mut entries = Vec::new();
-                get_all_pid_entries(None, entry, &mut entries);
+                get_all_pid_entries(None, None, entry, &mut entries);
                 entries
             })
             .flatten()
@@ -724,7 +748,7 @@ pub(crate) fn refresh_procs(
                     e.path.as_path(),
                     proc_list.get(),
                     e.pid,
-                    None,
+                    e.parent_pid,
                     uptime,
                     info,
                     refresh_kind,


Test patch: