real, user and sys in time output: wall-clock time vs CPU time
What real, user and sys in time output count according to POSIX, why user + sys can exceed real, and how the shell's time differs from /usr/bin/time on Linux.
Run a command under time and you get three numbers: real, user and sys. Judging speed from real alone, or adding all three together, are both easy ways to misread them. POSIX defines what each one counts.
time -p ./build.sh
What the three numbers are
In POSIX, real is the elapsed time between invoking the utility and its termination, usually called wall-clock time. user is user CPU time, defined as the sum of tms_utime and tms_cutime from times(). sys is system CPU time, the sum of tms_stime and tms_cstime. With -p, the output is real, user and sys on separate lines, in seconds.
tms_utime is CPU time spent running the process’s own instructions; tms_stime is CPU time the system spent working on the process’s behalf. Linux’s getrusage(2) page describes the same split as time in user mode and time in kernel mode. Work the kernel does inside system calls lands in sys.
Children count only after they are waited for
tms_cutime and tms_cstime belong to child processes. POSIX says a terminated child’s times are added to the parent’s when wait(), waitid() or waitpid() returns that child’s process ID. If a child never waited for its own children, their times are not included. That is why time works on a script that starts many processes. If the timed utility does not wait for a process, that process’s CPU time is not included in that time result.
Comparing user + sys with real
On Linux, getrusage() with RUSAGE_SELF sums usage across every thread in the calling process, but it does not include child processes. Waited-for children are counted separately by RUSAGE_CHILDREN, corresponding to the child-time fields used by time. Parallel threads, and separately the waited-for child processes included by time, accumulate CPU time independently. That can make user + sys larger than real. GNU time’s %P is computed as (%U + %S) / %E, which is how it ends up above 100%.
If real is much larger than user + sys, the job used less than one CPU on average over the interval. It may have been waiting for disk, network, a timer, or CPU scheduling, but these three values cannot identify the cause. With concurrent threads or child processes, real - (user + sys) is not a direct measure of waiting time.
The shell’s time and /usr/bin/time
In bash, time is a reserved word. It can time a whole pipeline or a shell function, and TIMEFORMAT controls its output. On systems where GNU time is installed at /usr/bin/time, that path selects the separate program; /usr/bin/time --version identifies the implementation. GNU time can also report major and minor page faults (%F, %R) and voluntary and involuntary context switches (%w, %c), with -v or a format string. Its manual warns that some values may read zero on some systems, and Linux itself zeroes the rusage fields it does not maintain.
Where the standard ends
POSIX requires only two members of struct rusage: ru_utime and ru_stime. Fault and context-switch counts are extras that implementations like Linux fill in, so on another OS check its own documentation. Inside a program on Linux, clock_gettime(2) with CLOCK_PROCESS_CPUTIME_ID gives the process’s CPU time, and CLOCK_MONOTONIC gives monotonically increasing elapsed time. On Linux, CLOCK_MONOTONIC excludes time spent suspended; use CLOCK_BOOTTIME when suspension must count. CLOCK_REALTIME can jump when the system clock is changed, so it is generally unsuitable for measuring durations.
Low CPU time does not tell you what the process waited for. Why disk space can stay occupied after deleting a file is one example of an OS behavior outside these three counters. For system clocks and timestamps, see Unix time and the 2038 problem.