Skip to content

Tracking Child Processes (#2568)#4259

Open
fbriol wants to merge 7 commits into
dask:mainfrom
fbriol:master
Open

Tracking Child Processes (#2568)#4259
fbriol wants to merge 7 commits into
dask:mainfrom
fbriol:master

Conversation

@fbriol

@fbriol fbriol commented Nov 20, 2020

Copy link
Copy Markdown

Modification allowing to take into account the child processes created from the workers.

Comment thread distributed/system_monitor.py Outdated
with self.proc.oneshot():
cpu = self.proc.cpu_percent()
memory = self.proc.memory_info().rss
children = set(self.proc.children(True))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm, it looks like it takes a few milliseconds in order to compute this. This may be too expensive to have on by default. We run this fairly frequently.

In [1]: import psutil

In [2]: proc = psutil.Process()

In [3]: %timeit proc.children()
4.87 ms ± 161 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

@fbriol fbriol Nov 22, 2020

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The time-consuming step is the reading of the process table. First line of the psutil.Process.children function.

In [1]: import psutil

In [2]: proc = psutil.Process()

In [3]: %timeit proc.children()
8.79 ms ± 36.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [4]: %timeit psutil._psplatform.ppid_map()
8.59 ms ± 101 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

One of the possible solutions, maybe if you have a better idea, is to delegate the calculation to a thread. For example,

import threading
import psutil
import time


class TrackChildren(threading.Thread):
    def __init__(self, proc, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self._proc = proc
        self._cpu = 0
        self._mem = 0
        self._children = set()
        self._lock = threading.Lock()
        self._event = threading.Event()

    def stop(self):
        self._event.set()

    def values(self):
        with self._lock:
            return self._cpu, self._mem

    def run(self):
        while not self._event.is_set():
            cpu = 0
            mem = 0
            children = set(self._proc.children(True))
            # "self.children" tracks the subprocesses to calculate the CPU
            # usage correctly. Otherwise, the computed CPU usage would always
            # be 0 as no time interval would exist for the calculation
            # (cf. psutil.Process.cpu_percent).
            if children:
                new_children = children - self._children
                if new_children:
                    self._children.update(new_children)
                for child in list(self._children):
                    # The inspected process may die during its introspection.
                    try:
                        with child.oneshot():
                            cpu += child.cpu_percent()
                            mem += child.memory_info().rss
                    except (psutil.NoSuchProcess, psutil.ZombieProcess):
                        self._children.discard(child)
            with self._lock:
                self._cpu, self._mem = cpu, mem
            time.sleep(0.1)

In this case, the reading of the numbers is much faster.

t = TrackChildren(psutil.Process())
t.start()
%timeit t.values()
298 ns ± 4.68 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Base automatically changed from master to main March 8, 2021 19:04
@fbriol fbriol requested a review from fjetter as a code owner January 23, 2024 10:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants