mrkeyoor.com_
Thu 06 Aug 00:58 UTC
PyPIInfraupdated 05 Aug 2026

psutil

psutil is the standard Python library for reading what the operating system knows: running processes, CPU load, memory, disks, network counters, sensors, and logged-in users. It reimplements what tools like ps, top, free, lsof, and netstat show, behind one Python API that works the same on Linux, Windows, macOS, and the BSDs. Under the hood it is C extensions talking to each platform's native interfaces (procfs, sysctl, Windows APIs), so calls are fast and do not shell out. Monitoring agents, task runners, and test frameworks use it to watch, limit, and kill processes.

Verdict

If Python code needs to know about processes or system resources, psutil is the answer and has been for fifteen years; roughly 91M weekly downloads agree. Just read the platform notes in the docs before trusting any number inside a container.

API stability4/5Core functions have kept their names and shapes for over a decade; majors do remove long-deprecated pieces (7.0 dropped Python 2.7 support and old aliases) but migrations are small and documented in a detailed changelog.
Docs5/5psutil.io has a full API reference with per-platform availability notes, a shell-equivalents table mapping ps/top/lsof to calls, and runnable scripts for common tasks. The platform caveats are documented rather than hidden.
Maintenance4/5Actively developed by the original author since 2009 with a push on August 5, 2026 and 271 open issues and PRs, but it is essentially a one-maintainer project funded by sponsorships, so bus factor is the main risk.
Ecosystem5/5Among the most-downloaded packages on PyPI at about 91M per week, with the project citing 780k+ dependent GitHub repositories; monitoring stacks and orchestration tools treat it as a given.

Use it if

  • You are building a monitoring agent, health endpoint, or autoscaler that needs CPU, memory, disk, and network numbers without parsing top output
  • You manage child processes and need to find, inspect, terminate, or wait on whole process trees reliably across platforms
  • You need per-process detail (open files, connections, memory maps, CPU affinity) that subprocess and os in the stdlib simply do not expose
  • You ship one codebase to Linux, Windows, and macOS and want the same call to mean roughly the same thing on each
Skip it if

Setup reality

pip install psutil is usually one wheel and done, since prebuilt wheels cover Linux, macOS, and Windows on common architectures. On Alpine, unusual CPU targets, or brand-new Python versions you fall back to compiling the C extension, which means gcc plus python3-dev headers or the install fails with a compiler error. The API has sharp edges you learn the hard way: cpu_percent(interval=None) returns a meaningless 0.0 on first call, many process methods raise NoSuchProcess or AccessDenied mid-iteration, and version 7 dropped Python 2 leftovers and some deprecated aliases, so old snippets from Stack Overflow may not run as pasted.

Patterns

Measure CPU utilization correctlycpu-percent

import psutil

# Blocking sample over 1 second, per core:
psutil.cpu_percent(interval=1, percpu=True)  # [4.0, 6.9, 3.7, 9.2]

# Non-blocking: compares against the previous call
psutil.cpu_percent(interval=None)

The very first interval=None call returns a meaningless 0.0 because there is no previous sample; call it once, wait, then read.

Read system memory and swapmemory-usage

import psutil

mem = psutil.virtual_memory()
print(mem.percent, mem.available)

swap = psutil.swap_memory()
print(swap.percent)

Use mem.available (not mem.free) to judge headroom; free excludes cache and buffers and looks alarmingly low on healthy Linux boxes.

List partitions and check disk spacedisk-usage

import psutil

for part in psutil.disk_partitions():
    usage = psutil.disk_usage(part.mountpoint)
    print(part.device, part.mountpoint, f"{usage.percent}%")

disk_partitions(all=False) skips pseudo filesystems like proc and tmpfs; disk_usage on an unmounted or permission-blocked path raises OSError.

Read network I/O per interfacenetwork-counters

import psutil

counters = psutil.net_io_counters(pernic=True)
for nic, io in counters.items():
    print(nic, io.bytes_sent, io.bytes_recv)

These are cumulative since boot; to get a rate, sample twice and divide the delta by the elapsed time yourself.

List open TCP connectionslist-connections

import psutil

for conn in psutil.net_connections(kind="tcp"):
    if conn.status == psutil.CONN_LISTEN:
        print(conn.laddr, conn.pid)

On macOS this requires root and on Linux you may get pid=None for other users' sockets; expect AccessDenied in unprivileged code.

Inspect one process efficientlyinspect-process

import psutil

p = psutil.Process(7055)
with p.oneshot():
    print(p.name())
    print(p.exe())
    print(p.memory_info().rss)
    print(p.cpu_percent(interval=1.0))

oneshot() caches process data so grouped reads hit the OS once; per-process cpu_percent also needs an interval or a prior call to mean anything.

Iterate all processes without racingiterate-processes

import psutil

for proc in psutil.process_iter(attrs=["pid", "name", "username"]):
    info = proc.info
    if info["name"] == "python3":
        print(info["pid"], info["username"])

Passing attrs makes process_iter swallow NoSuchProcess and AccessDenied per process and hand you a plain dict, which is what you want in a loop.

Terminate a process and its childrenkill-process-tree

import psutil

def kill_tree(pid, timeout=3):
    parent = psutil.Process(pid)
    procs = parent.children(recursive=True) + [parent]
    for p in procs:
        p.terminate()
    gone, alive = psutil.wait_procs(procs, timeout=timeout)
    for p in alive:
        p.kill()

terminate() sends SIGTERM for a graceful stop and kill() sends SIGKILL; wait_procs gives stragglers a chance before you escalate.

Handle vanished and protected processeshandle-process-errors

import psutil

try:
    p = psutil.Process(pid)
    cmdline = p.cmdline()
except psutil.NoSuchProcess:
    cmdline = None  # exited between lookup and call
except psutil.AccessDenied:
    cmdline = "<protected>"
except psutil.ZombieProcess:
    cmdline = "<zombie>"

Every Process method can raise these at call time, not construction time, because the process can die at any moment.

Read temperatures and batterysensors-battery

import psutil

if hasattr(psutil, "sensors_temperatures"):
    temps = psutil.sensors_temperatures()
    for name, entries in temps.items():
        for e in entries:
            print(name, e.label, e.current)

batt = psutil.sensors_battery()
if batt:
    print(batt.percent, batt.power_plugged)

sensors_temperatures is mostly Linux-only and does not exist on some platforms, hence the hasattr guard; sensors_battery returns None on desktops.

Get boot time and logged-in usersboot-time-users

import datetime
import psutil

boot = datetime.datetime.fromtimestamp(psutil.boot_time())
print("up since", boot)

for user in psutil.users():
    print(user.name, user.host, user.started)

boot_time() is a plain epoch float and does not change across calls; cache it rather than re-reading in a loop.

Spawn a subprocess you can monitorspawn-and-monitor

import psutil

p = psutil.Popen(["python3", "worker.py"])
print(p.pid, p.status())
print(p.cpu_times())
returncode = p.wait(timeout=30)

psutil.Popen wraps subprocess.Popen and adds all Process methods; wait(timeout=...) raises TimeoutExpired instead of blocking forever.

Alternatives

PackageRegistryPick it when
distroPyPIYou only need to identify the Linux distribution and version, not live system metrics
py-cpuinfoPyPIYou need CPU model, flags, and identification details rather than utilization numbers
nvidia-ml-pyPyPIYou need GPU utilization and memory from NVIDIA cards, which psutil does not cover