FAQ

This section answers common questions and pitfalls when using psutil.

General

Why should I avoid positional unpacking of named tuples?

Most psutil functions return named tuples. It is tempting to unpack them positionally, but field order may change across major releases (as happened in 8.0 with cpu_times() and Process.memory_info()). Always use attribute access instead:

# bad
rss, vms = p.memory_info()

# good
m = p.memory_info()
print(m.rss, m.vms)

See the migration guide for the full list of field-order changes in 8.0.

Exceptions

Why do I get AccessDenied?

AccessDenied is raised when the OS refuses to return information about a process because the calling user does not have sufficient privileges. This is expected behavior and is not a bug. It typically happens when:

You have two options to deal with it.

Why do I get NoSuchProcess?

NoSuchProcess is raised when a process no longer exists. The most common cause is a TOCTOU (time-of-check / time-of-use) race condition: a process can die between the moment its PID is obtained and the moment it is queried. The following two naive patterns are racy:

import psutil

for pid in psutil.pids():
    p = psutil.Process(pid)  # may raise NoSuchProcess
    print(p.name())  # may raise NoSuchProcess
import psutil

if psutil.pid_exists(pid):
    p = psutil.Process(pid)  # may raise NoSuchProcess
    print(p.name())  # may raise NoSuchProcess

The correct approach is to use process_iter(), which handles NoSuchProcess internally and skips processes that disappear during iteration:

import psutil

for p in psutil.process_iter(["name"]):
    print(p.name())

If you have a specific PID (e.g. a known child process), wrap the call in a try/except:

An even simpler pattern is to catch Error, which implies both AccessDenied and NoSuchProcess:

import psutil

try:
    p = psutil.Process(pid)
    print(p.name(), p.status())
except psutil.Error:
    pass

Processes

PID reuse

Operating systems recycle PIDs. A Process object obtained now may later refer to a different process if the original one terminated and a new one was assigned the same PID.

How psutil handles this:

Process.is_running() is the recommended way to verify whether a Process instance still refers to the same process. It compares PID and creation time, and returns False if the PID was reused. Prefer it over pid_exists().

What is a zombie process?

A zombie process is a process that has finished execution but whose entry remains in the process table until the parent calls wait(). When psutil encounters a zombie process it raises ZombieProcess, a subclass of NoSuchProcess.

What you can and cannot do with a zombie:

How to create a zombie:

import os, time

pid = os.fork()  # the zombie
if pid == 0:
    os._exit(0)  # child exits immediately
else:
    time.sleep(1000)  # parent does NOT call wait()

How to detect zombies:

import psutil

for p in psutil.process_iter(["status"]):
    if p.status() == psutil.STATUS_ZOMBIE:
        print(f"zombie: pid={p.pid}")

How to get rid of a zombie:

The only way is to have its parent process call wait() (or waitpid()). If the parent never does this, killing the parent will cause the zombie to be re-parented to init / systemd, which will reap it automatically.

Why does open_files() not return all files on Windows?

Process.open_files() on Windows is not guaranteed to enumerate all regular file handles. The underlying Windows API may hang when retrieving certain handle names, so psutil spawns a thread to query each handle and kills it if it doesn’t respond within 100 ms. This means some entries can be missed. This is a known OS-level limitation shared by tools like Process Hacker (see issue 597).

What is the difference between pid_exists() and Process.is_running()?

pid_exists() checks whether a PID is present in the process list. Process.is_running() does the same, but also detects PID reuse by comparing the process creation time. Use pid_exists() when you have a bare PID and don’t need to guard against reuse (it’s faster). Use Process.is_running() when you hold a Process object and want to confirm it still refers to the same process.

CPU

Why does cpu_percent() return 0.0 on first call?

cpu_percent() (and Process.cpu_percent()) measures CPU usage between two calls. The very first call has no prior sample to compare against, so it always returns 0.0. The fix is to call it once to initialize the baseline, discard the result, then call it again after a short sleep:

import time
import psutil

psutil.cpu_percent()          # discard first call
time.sleep(0.5)
print(psutil.cpu_percent())   # meaningful value

Alternatively, pass interval to make it block internally:

print(psutil.cpu_percent(interval=0.5))

The same applies to Process.cpu_percent():

p = psutil.Process()
p.cpu_percent()               # discard
time.sleep(0.5)
print(p.cpu_percent())        # meaningful value

Can Process.cpu_percent() return a value higher than 100%?

Yes. On a multi-core system a process can run threads on several CPUs at the same time. The maximum value is psutil.cpu_count() * 100. For example, on a 4-core machine a fully-loaded process can reach 400%. The system-wide cpu_percent() (without a Process) always stays in the 0–100% range because it averages across all cores.

The returned value is explicitly not split evenly between all available CPUs. This is consistent with the top UNIX utility: a busy loop on a system with 2 logical CPUs is reported as 100%, not 50%. Note that Windows taskmgr.exe behaves differently (it would report 50%). To emulate that: p.cpu_percent() / psutil.cpu_count().

What is the difference between psutil, os, and multiprocessing cpu_count()?

Memory

What is the difference between virtual_memory() available and free?

virtual_memory() returns both free and available, but they measure different things:

  • free: memory that is not being used at all.

  • available: how much memory can be given to processes without swapping. This includes reclaimable caches and buffers that the OS can reclaim under pressure.

In practice, available is almost always the metric you want when monitoring memory. free can be misleadingly low on systems where the OS aggressively uses RAM for caches (which is normal and healthy). On Windows, free and available are the same value.

What is the difference between RSS and VMS?

rss is the go-to metric for answering “how much RAM is this process using?”. Note that it includes shared memory, so it may overestimate actual usage when compared across processes. vms is generally larger and can be misleadingly high, as it includes memory that is not resident in physical RAM. Both values are portable across platforms and are returned by Process.memory_info().

When should I use memory_footprint() vs memory_info()?

Process.memory_info() returns rss (Resident Set Size), which includes shared libraries counted in every process that uses them. For example, if libc uses 2 MB and 100 processes map it, each process includes those 2 MB in its rss.

Process.memory_footprint() returns uss (Unique Set Size), i.e. private memory of the process. It represents the amount of memory that would be freed if the process were terminated right now. It is more accurate than RSS, but substantially slower and requires higher privileges. On Linux it also returns pss (Proportional Set Size) and swap.

Why does virtual_memory() used + free != total?

Because some memory (like page cache and buffers) is reclaimable and accounted separately:

>>> import psutil
>>> m = psutil.virtual_memory()
>>> m.used + m.free == m.total
False

The available field already includes this reclaimable memory and is the best indicator of memory pressure. See What is the difference between virtual_memory() available and free?.