Repository: qdrant/qdrant
File: src/common/metrics.rs (Rust)

Code before:
    fn child_processes_recursive(pid: Pid, pids: &mut AHashSet<Pid>) -> Result<(), ProcError> {
        for child_pid in child_processes(pid)? {
            let is_new = pids.insert(child_pid);

            // If PID is new we recurse into it
            if is_new {
                child_processes_recursive(child_pid, pids)?;
            }
        }

        Ok(())
    }

    /// Returns all new child processes of a parent process, specified by `parent_pid`.
    fn child_processes(parent_pid: Pid) -> Result<AHashSet<Pid>, ProcError> {
        let process = match Process::new(parent_pid) {
            Ok(proc) => proc,
            // Ignore children that don't exist anymore.
            Err(ProcError::NotFound(_)) => return Ok(AHashSet::default()),
            Err(err) => return Err(err),
        };

        process
            .tasks()?
            .filter_map(|task| {
                let children = task.and_then(|task| task.children());
                match children {
                    Ok(children) => Some(Ok(children)),
                    // Filter out tasks/children that might not exist anymore.
                    Err(ProcError::NotFound(_)) => None,
                    Err(err) => Some(Err(err)),
                }
            })
            .flatten_ok()
            .map_ok(|i| i as Pid)
            .collect()
    }

    #[test]
    fn test_child_processes() {
        let my_pid = procfs::process::Process::myself().unwrap().pid;

        // We have no children now
        assert_eq!(child_processes_helper(my_pid).unwrap(), AHashSet::new(),);

        // Spawn two child processes
        let mut child_1 = std::process::Command::new("sleep")
            .arg("10s")
            .spawn()
            .unwrap();
        let mut child_2 = std::process::Command::new("sleep")

Review comment (suggestion):
TIL we can get a list of children PIDs directly via `/proc/*/task/*/children`.

The classic approach was to read `/proc/*/stat` for every process and rebuild the process tree by looking at parent PIDs.

The `/children` approach requires kernel with `CONFIG_PROC_CHILDREN`. I think it should be enabled practically everywhere on servers, but not sure.

Code after:
            }
        }

        Ok(())
    }

    /// Returns all new child processes of a parent process, specified by `parent_pid`.
    fn child_processes(parent_pid: Pid) -> Box<dyn Iterator<Item = Result<Pid, ProcError>>> {
        let process = match Process::new(parent_pid) {
            Ok(proc) => proc,
            // Ignore children that don't exist anymore.
            Err(ProcError::NotFound(_)) => return Box::new(std::iter::empty()),
            Err(err) => return Box::new(std::iter::once(Err(err))),
        };

        let tasks = match process.tasks() {
            Ok(tasks) => tasks,
            Err(err) => return Box::new(std::iter::once(Err(err))),
        };

        let pids = tasks
            .filter_map(|task| {
                let children = task.and_then(|task| task.children());
                match children {
                    Ok(children) => Some(Ok(children)),
                    // Filter out tasks/children that might not exist anymore.
                    Err(ProcError::NotFound(_)) => None,
                    Err(err) => Some(Err(err)),
                }
            })
            .flatten_ok()
            .map_ok(|i| i as Pid);

        Box::new(pids)
    }

    #[test]
    fn test_child_processes() {
        use nix::sys::wait::waitpid;
        use nix::unistd::{ForkResult, fork};

        let my_pid = procfs::process::Process::myself().unwrap().pid;

        // We have no children now
        assert_eq!(child_processes_helper(my_pid).unwrap(), AHashSet::new(),);

        // Spawn two child processes
        let mut child_1 = std::process::Command::new("sleep")
            .arg("3s")
            .spawn()
            .unwrap();