Reading Linux PSI: some, full, avg and total in /proc/pressure

What some and full, avg10/avg60/avg300 and total in /proc/pressure measure, how PSI differs from iowait, and the limits on pressure triggers, based on the kernel documentation.

LinuxPSIperformance monitoringcgroup

CPU utilization and memory usage tell you how much of a resource is in use. They don’t tell you how long work was actually held up waiting for that resource. Linux PSI (Pressure Stall Information) exports that stalled time separately for CPU, memory and I/O. This post goes through what each number means and where its interpretation stops, following the kernel PSI documentation.

The file format

System-wide pressure is exported in three files under /proc/pressure/: cpu, memory and io. The format is:

some avg10=0.00 avg60=0.00 avg300=0.00 total=0
full avg10=0.00 avg60=0.00 avg300=0.00 total=0
Field Meaning
some line Share of time in which at least some tasks were stalled on the resource
full line Share of time in which all non-idle tasks were stalled on the resource at the same time
avg10, avg60, avg300 Recent trends over 10, 60 and 300 second windows, in percent
total Cumulative absolute stall time, in microseconds (µs)

full is a subset of some. The kernel documentation describes the full state as one where actual CPU cycles go to waste, and says a workload that spends extended time there is considered to be thrashing. If only some is elevated, some tasks are waiting but the CPU may still be doing productive work. That distinction is why the two lines are tracked separately.

full in /proc/pressure/cpu reads zero

According to the documentation, CPU full is undefined at the system level. The line has been reported since 5.13 and is set to zero for backward compatibility. So a zero full in /proc/pressure/cpu says nothing about spare CPU capacity; for CPU, read the some line. The documentation states this zero rule for the system level.

Using total for your own interval

The avg values use fixed 10, 60 and 300 second windows. The documentation gives two reasons for also exporting total: to catch latency spikes that wouldn’t necessarily make a dent in the time averages, and to average over custom time frames.

Since total is a cumulative count in microseconds, the difference between two reads divided by the elapsed time gives the stall share for that interval.

cat /proc/pressure/io
sleep 5
cat /proc/pressure/io

For example (made-up values for illustration), if total on the some line went from 1,200,000 to 1,950,000 over 5 seconds:

(1,950,000 − 1,200,000) µs ÷ 5,000,000 µs = 0.15 → 15%

During those 5 seconds, at least some tasks were stalled on I/O for about 15% of the time.

How this differs from iowait, procs_blocked and disk stats

The CPU lines in /proc/stat include an iowait column. The kernel /proc documentation lists several problems with it:

  • The CPU does not wait for I/O to complete; a task does. When a CPU goes idle because a task is waiting on I/O, another task can be scheduled on that CPU.
  • On a multi-core CPU, the task waiting for I/O is not running on any CPU, so per-CPU iowait is difficult to calculate.
  • The iowait value in /proc/stat can decrease under certain conditions.

The documentation’s conclusion is that iowait read from /proc/stat is not reliable. The procs_blocked line in the same file is the number of processes currently blocked waiting for I/O to complete. It is a point-in-time count and says nothing about how much time has accumulated.

Field 10 of the per-disk statistics (milliseconds spent doing I/O; in /proc/diskstats, the tenth value after the major and minor numbers and device name) takes the device’s point of view. Per the iostats documentation, the field increases while I/Os are in progress; since 5.0 it counts jiffies in which at least one request was started or completed, and if a request runs longer than 2 jiffies some I/O time may go unaccounted when requests are concurrent. Whether the device was busy and whether work was stalled on I/O are different questions, and the PSI io file answers the second one.

Load average has its own post. For reading memory usage numbers, see the post on free’s available column. High usage and work stalling for lack of memory are separate facts, and /proc/pressure/memory is the file for the latter.

Threshold triggers: getting woken up by poll()

Instead of reading the files periodically, you can register a trigger that wakes you when stall time crosses a threshold. Open the relevant /proc/pressure/ file, write a string in the following format, and wait on that file descriptor with select(), poll() or epoll().

<some|full> <stall amount in µs> <time window in µs>

The documentation’s examples:

  • some 150000 1000000 written to /proc/pressure/memory: notify when partial memory stall exceeds 150ms within a 1s window
  • full 50000 1000000 written to /proc/pressure/io: notify when full I/O stall exceeds 50ms within a 1s window

The documented constraints:

  • Window sizes from 500ms to 10s are accepted. Stall growth is checked 10 times per window, so the update interval ranges from 50ms to 1s.
  • Unprivileged users can create triggers, but the window size must be a multiple of 2s.
  • Each trigger needs its own file descriptor. Even for the same file, call open() separately per trigger; writing to a descriptor that already has a trigger fails with EBUSY.
  • A monitor activates when the metric enters a stall state and, once active, stays active for at least one window. Notifications are rate-limited to one per window.
  • The trigger is de-registered when the file descriptor used to define it is closed.

Because of that last rule, echo "some 150000 1000000" > /proc/pressure/memory in a shell closes the descriptor as soon as the redirection finishes, so no trigger is left to watch. You need a program that keeps the descriptor open while it waits. Trimmed down, the C example from the documentation looks like this:

const char trig[] = "some 150000 1000000";
struct pollfd fds;

fds.fd = open("/proc/pressure/memory", O_RDWR | O_NONBLOCK);
fds.events = POLLPRI;
write(fds.fd, trig, strlen(trig) + 1);

while (1) {
    poll(&fds, 1, -1);
    if (fds.revents & POLLERR) break;      /* event source is gone */
    if (fds.revents & POLLPRI) printf("event triggered!\n");
}

The original includes error handling for open(), write() and poll(); use the original as the reference for real code.

PSI in cgroup v2

On a kernel with CONFIG_CGROUPS=y and the cgroup2 filesystem mounted, PSI is also tracked per cgroup. Each cgroup directory contains cpu.pressure, memory.pressure and io.pressure in the same format as /proc/pressure/. Triggers can be registered the same way as for the system-wide files.

The cgroup v2 documentation adds two more files:

  • irq.pressure: listed as showing pressure stall information for IRQ/SOFTIRQ. The version of the PSI document checked for this post has no separate section on IRQ.
  • cgroup.pressure: defaults to 1; writing 0 disables PSI accounting for that cgroup. It is not hierarchical, so it doesn’t affect descendants. It exists because PSI accounts stalls per cgroup and aggregates them at each level of the hierarchy, which can add non-negligible overhead for some workloads in deep hierarchies; the file lets you turn accounting off in non-leaf cgroups.

Limits of interpretation

  • PSI is stall time aggregated per resource. These files alone don’t tell you which process caused it. Comparing per-cgroup files can narrow it down to the group where stalls occurred.
  • The kernel documentation gives no percentage at which pressure becomes a problem. This post doesn’t give one either.
  • The avg values are recent trends, so short spikes can disappear in them. That is why the documentation exports total separately.
  • This post is based on the docs.kernel.org documentation (7.3.0-rc6 build) checked on 2026-10-11. The documentation cited here states two versions explicitly: CPU full has been reported since 5.13, and since 5.0 diskstats field 10 has counted jiffies in which at least one request was started or completed. Older kernels may differ in output format or supported files.

Back to the blog