← back to latest

Find out whether a slow command is working or waiting

time du -sh /usr

anatomy

time
In bash, zsh, and ksh this is a reserved word, not a program. The shell starts a clock, runs everything to its right, and asks the kernel how much CPU the children used. Being a keyword is what lets it time a whole pipeline, not only the first command in it. `type time` prints `time is a shell keyword` to confirm.
real
Wall-clock time from start to finish. This is what you felt while you waited, and it includes every moment the process spent blocked on disk, network, locks, or a busy CPU.
user
CPU time spent running the program's own code: parsing, compressing, sorting. Summed across every core and every process in the pipeline.
sys
CPU time the kernel spent working on the program's behalf: system calls, reading directories, copying buffers. A tool that mostly asks the kernel questions, like du or find, shows up here rather than in user.
du -sh /usr
The command being timed. It walks every directory under /usr and stats every file, which makes it a good example of work that is almost entirely kernel calls and, on a cold cache, disk reads.

Sample output

$ time du -sh /usr
858M	/usr

real	0m0.537s
user	0m0.030s
sys	0m0.440s
$ time du -sh /usr
858M	/usr

real	0m0.211s
user	0m0.013s
sys	0m0.198s

Add user and sys and compare the total to real. On the first run the CPU accounts for 0.47 of 0.54 seconds, so roughly 0.07 seconds went to waiting on the disk. On the second run the directory data is already in the page cache, the waiting disappears, and real collapses to almost exactly sys: du is now pure kernel work. When real is far larger than user plus sys, the command is waiting, and a faster CPU will not help.

When you would reach for it

A build, a backup, or a report script takes longer than it should and you want to know where to look before you start optimizing. The three numbers sort the problem into one of three shapes: real much bigger than user plus sys means it is blocked on I/O, the network, or a lock; real roughly equal to user plus sys means it is CPU-bound on one core; user plus sys bigger than real means it is already spreading across several cores. It is also the quickest honest benchmark for comparing two ways of doing the same job.

Gotchas

  • The timing goes to the shell’s stderr, not the command’s, so time du -sh /usr 2> err.txt still prints the numbers on your terminal. To capture them, group the command first: { time du -sh /usr; } 2> timing.txt.
  • zsh, the default shell on macOS, prints a single line instead of three: du -sh /usr 0.03s user 0.44s system 87% cpu 0.537 total. The numbers mean the same thing. Same keyword, different shell, different format, which matters if a script parses the output.
  • /usr/bin/time is a different tool with different flags. time -v in bash fails with -v: command not found, because the keyword only understands -p and treats anything else as the command to run; the GNU program needs its full path, /usr/bin/time -v, and on Debian it is not installed until you apt install time. macOS ships a BSD /usr/bin/time that uses -l instead of -v. In dash, Debian’s /bin/sh, there is no keyword at all, so time in a #!/bin/sh script fails with time: not found unless that package is installed.
  • The second run is almost always faster, because the first one warmed the page cache. Benchmark with a warm cache unless the cold start is the thing you care about, and say which one you measured.

Variants

$ time xz -T0 -c backup.tar | wc -c

Prove a tool is really using all your cores. Here a 50 MB file took 7.1 seconds of real time and 14.2 seconds of user time, so xz kept about two cores busy. The keyword times the whole pipeline, including wc

$ TIMEFORMAT='%R real, %U user, %S sys, %P%% cpu'; time du -sh /usr

bash only. Reformat the output on one line and add %P, the CPU percentage, which is (user + sys) / real. Well under 100 means waiting, near 100 means one busy core, above 100 means parallel work

$ { time ./nightly-report.sh; } 2>> timings.log

Append each run’s timing to a log so you can see when the job started getting slower. The braces make the keyword’s output part of the group’s stderr, so the log also collects anything the script itself writes there

lineage

A time command has been part of Unix since the early editions at Bell Labs, as a separate program that ran its argument and reported elapsed, user, and system time. The trouble with a separate program is that it only sees the one command it was handed: in `time a | b`, the shell builds the pipeline and /usr/bin/time only ever runs `a`. The Korn shell is generally credited with turning time into a reserved word so it could wrap whole pipelines, and bash and zsh followed. That is why there are now two things called time on most systems, and why they disagree about flags and output.