The pidstat command reports CPU, memory, and disk I/O usage broken down by individual process, sampled at a fixed interval. pidstat -u 2 prints per-process CPU every two seconds; pidstat -r 2 does the same for memory; pidstat -d 2 for disk I/O. It ships in the sysstat package alongside sar, iostat, and mpstat, and it is the tool I reach for when top can show me that the box is busy but not which process is causing it over time.
The reason pidstat beats eyeballing top is the interval sampling. Each line is an average over the sampling window, not a single instantaneous reading, so a process that spikes for 200ms every few seconds shows up cleanly instead of flickering past. Point it at one PID and let it run, and you get a per-second (or per-N-second) trace of exactly what that process is doing to the machine.
Install it: the sysstat package
pidstat is not always present on a minimal install. It comes from sysstat:
# Debian / Ubuntu
sudo apt install sysstat
# RHEL / Rocky / Alma / Fedora
sudo dnf install sysstat
# Alpine
sudo apk add sysstatOnce installed, pidstat with no arguments prints one block: the CPU usage of every active task since system boot. That is rarely what you want. The interval is what makes the tool.
Per-process CPU: pidstat -u
pidstat -u 2-u reports CPU utilization. The 2 is the interval in seconds, so this samples every two seconds and prints a new block each time. Output looks like this:
07:14:32 UID PID %usr %system %guest %wait %CPU CPU Command
07:14:34 1000 4821 38.50 2.00 0.00 0.50 40.50 3 node
07:14:34 0 912 0.50 1.50 0.00 0.00 2.00 1 containerd
%usr is time in user space, %system is time in the kernel on this process's behalf, %CPU is the total. The CPU column is which logical core the process was last seen on. %wait is time the task spent runnable but waiting for a core, which is your signal that the box is CPU-saturated rather than the process being slow on its own.
To bound the run, add a count after the interval:
pidstat -u 2 5That samples every two seconds, five times, then exits and prints an Average: line summarizing the whole run. Without the count it runs until you press Ctrl+C.
Per-process memory and page faults: pidstat -r
pidstat -r 2-r reports memory utilization and page faults:
07:16:10 UID PID minflt/s majflt/s VSZ RSS %MEM Command
07:16:12 1000 4821 142.00 0.00 1284560 398204 4.91 node
RSS (resident set size, in kB) is the real memory the process holds in RAM and is usually the number you care about. VSZ is the virtual size, which is almost always larger and far less meaningful. The interesting columns are the fault rates: majflt/s is major faults per second, meaning the kernel had to hit disk to satisfy the page. A nonzero majflt/s that stays up is a process thrashing against a memory shortfall, and it correlates with the box swapping.
Per-process disk I/O: pidstat -d
pidstat -d 2-d reports I/O statistics (kernels 2.6.20 and later):
07:18:44 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
07:18:46 0 338 0.00 1840.00 0.00 7 jbd2/sda1-8
07:18:46 1000 4821 0.00 512.00 0.00 2 node
kB_rd/s and kB_wr/s are read and write throughput per second. iodelay is the time the task was blocked waiting on I/O, measured in clock ticks, and it is the column that tells you whether a process is bottlenecked on disk versus merely doing a lot of I/O comfortably. This is the per-process answer that iostat cannot give you: iostat shows the device is busy, pidstat -d shows who is making it busy. For the device-level and whole-system view, see the iotop command, which sorts processes live by I/O the way top sorts by CPU.
Watch one process: -p
The flag that makes pidstat genuinely useful for debugging is -p, which scopes the report to specific PIDs:
pidstat -u -r -d -p 4821 1That traces CPU (-u), memory (-r), and I/O (-d) for PID 4821, once per second, until you stop it. Combining the activity flags prints them as separate columns on the same lines, so you get a unified per-second view of one process across all three resources. This is what I leave running in a second terminal while I reproduce a performance problem.
-p takes a comma-separated list (-p 4821,4822), the keyword ALL for every task the system manages, or SELF for the pidstat process itself. -p ALL is noisy on a busy host; pair it with -C to filter by command name:
pidstat -u -C nginx 2-C keeps only tasks whose command name matches the string, which is treated as a regular expression. pidstat -u -C 'nginx|php-fpm' 2 watches both web tiers at once.
Threads, not just processes: -t
By default pidstat aggregates a process across its threads. When a single multithreaded process is the suspect and you need to know which thread is hot, add -t:
pidstat -t -u -p 4821 1-t adds TGID (the thread group, i.e. the process) and TID (the individual thread) columns and reports each thread separately. A row with a TGID and a dash for TID is the process total; rows with a TID are its threads. This is how you find the one runaway thread inside an otherwise healthy-looking process.
Useful flags at a glance
| Flag | What it reports |
|---|---|
-u | CPU utilization (%usr, %system, %wait, %CPU) |
-r | Memory and page faults (RSS, VSZ, minflt/s, majflt/s) |
-d | Disk I/O (kB_rd/s, kB_wr/s, iodelay) |
-w | Context-switch rate (voluntary and involuntary) |
-p PID | Scope to a PID list, or ALL / SELF |
-t | Break processes down into their threads |
-C comm | Filter to tasks whose command name matches comm (regex) |
-l | Show the full command line, not just the process name |
-h | One line per sample, no Average: block (parser-friendly) |
-h is worth calling out: it prints everything horizontally with no averages, which is the form to pipe into awk or a log when you want to chart pidstat output later.
When pidstat is not the right tool
pidstat is a sampler, so it is excellent for "watch this process over the next minute" and poor for "what happened five minutes ago". For after-the-fact analysis you want sar (also from sysstat), which records to disk on a schedule and lets you replay any window. For a live, sorted, interactive view, top and htop are still better for at-a-glance triage; pidstat wins once you know which process you care about and want a clean numeric trace of it.
It is also strictly a per-process tool. If the question is about network throughput rather than CPU, memory, or disk, pidstat has nothing to say. For watching interface bandwidth in real time, reach for the nload command instead. And for whole-device I/O saturation independent of any one process, iostat -x is the companion to pidstat -d.
One distro caveat: on Debian and Ubuntu the sysstat collector service (sar history) is disabled by default after install. That does not affect pidstat interactive sampling, which works the moment the package is installed, but it does mean sar has no history to show until you enable it in /etc/default/sysstat.
See also
- iotop: find which process is hammering the disk: the live, sorted,
top-style view of per-process disk I/O, the natural companion topidstat -d - nload: watch network bandwidth in the terminal: for the one resource
pidstatdoes not cover, real-time interface throughput - External: pidstat(1) man page, the sysstat project on GitHub.
FAQ
Sources
Authoritative references this article was fact-checked against.





