giampaolo / psutil

Cross-platform lib for process and system monitoring in Python
BSD 3-Clause "New" or "Revised" License
10.32k stars 1.39k forks source link

[Ubuntu] Child process cpu_percent() returns 0 #2351

Closed dorinclisu closed 10 months ago

dorinclisu commented 10 months ago

Summary

Description

Replicable code:

import subprocess
import time

import psutil  # type: ignore

process = psutil.Process()
interval = 5

# Main process test
process.cpu_percent(interval=None)
print(f'Main {process} {process.cpu_times()}')

t = time.time()
while time.time() - t < interval:
    pass

cpu_usage = process.cpu_percent(interval=None)
print(f'Main {process} {process.cpu_times()}')
print(f'Main CPU (%): {cpu_usage} (expecting ~100)')

# Child process test
cmd = f'''
keep_cpu_busy() {{
    end=$((SECONDS+{interval}))
    while [ $SECONDS -lt $end ]; do
        :
    done
}}
keep_cpu_busy
'''
p = subprocess.Popen(['bash', '-c', cmd])

for child in process.children(recursive=True):
    child.cpu_percent(interval=None)
    print(f'Child {child} {child.cpu_times()}')

cpu_usage = 0
time.sleep(interval - 1)

for child in process.children(recursive=True):
    cpu_usage += child.cpu_percent(interval=None)
    print(f'Child {child} {child.cpu_times()}')

print(f'Child CPU (%): {cpu_usage} (expecting ~100)')
p.wait()

Basically, cpu_usage works as expected for the main process, but not for the child spawned with subprocess.Popen, expecting ~100 but getting 0.0 instead.

giampaolo commented 10 months ago

This part:

for child in process.children(recursive=True):
    cpu_usage += child.cpu_percent(interval=None)

...creates a set of brand new Process instances, upon which you immediately call cpu_percent(). They have no knowledge of what happened before, nor they have a big enough time window to calculate a meaningful percent value, hence they return 0. From the doc:

the first time this cpu_percent() is called with interval = 0.0 or None it will return a meaningless 0.0 value which you are supposed to ignore

dorinclisu commented 10 months ago

I was assuming there is a state stored outside the psutil.Process instance, since the pcputimes.user value is consistent with the actual child that exists before its psutil twin instantiation. And I could argue the documentation is confusing, because child.cpu_percent() is being called a first time (so as to ignore its meaningless 0.0 value), according to the PID, but not according to the psutil instance.

giampaolo commented 10 months ago

And I could argue the documentation is confusing, because child.cpu_percent() is being called a first time (so as to ignore its meaningless 0.0 value), according to the PID, but not according to the psutil instance.

I read it twice but I don't understand what you mean here. If you have suggestions on how to reword the doc please go for it.