Zsh: measure command durations with the time command, TIMEFMT, and REPORTTIME

time is a POSIX utility for timing commands. Most shells build it in, and Zsh’s version has a lot more to offer than the one in Bash.
Put time before a command to time it. For example, to time a Python script:
$ time python example.py
python example.py 1.33s user 1.73s system 74% cpu 4.114 total
When the command finishes, Zsh prints a single line to standard error, which contains:
python example.py: the command, or “job”, that was timed.1.33s user: user CPU time, the time the CPU spent running the program’s own code.1.73s system: system CPU time, the time the CPU spent in the kernel on the program’s behalf.74% cpu: the amount of CPU used in the duration, calculated as (user + system) / elapsed.4.114 total: the elapsed time, also known as “wall clock time”, from start to finish.
This one-line format differs from Bash’s time, which prints three lines, labelled real, user, and sys. But the numbers have the same meanings.
The CPU percentage is a useful clue for what a program is doing:
- Around 100%: the program is CPU-bound, so speeding it up means making it do less work.
- Well under 100%: the program spent time waiting, rather than using the CPU. That may be for disk or network I/O, a lock, a subprocess, or just sleeping.
- Over 100%: the program used multiple CPU cores at once, through threads or child processes.
Pipelines and multiple commands
time applies to a single pipeline: one or more commands joined with |. Zsh reports each command in the pipeline separately:
$ time python example.py | cat
python example.py 1.32s user 1.26s system 70% cpu 3.665 total
cat 0.00s user 0.00s system 0% cpu 3.664 total
To time several commands together, wrap them in a subshell with parentheses:
$ time (python example.py; python example.py)
( python example.py; python example.py; ) 3.34s user 3.03s system 74% cpu 8.539 total
Note that Zsh only times commands that run in a separate process. Unlike Bash, timing a brace group like { ...; } or a shell function prints nothing. Use a subshell for those too.
Customize the output with TIMEFMT
Zsh formats time’s output with the TIMEFMT parameter. The default value is:
TIMEFMT='%J %U user %S system %P cpu %*E total'
That’s the job name, user time, system time, CPU percentage, and elapsed time, matching the output above. The * in %*E formats the time as hh:mm:ss.ttt, where hours and minutes only appear when non-zero, for example if we time a command taking >1 minute:
$ time sleep 61.5
sleep 61.5 0.00s user 0.00s system 0% cpu 1:01.50 total
Swap the * for m or u to get milliseconds or microseconds instead, which can be handy for timing fast commands. For example, to measure Python’s startup time in microseconds:
$ TIMEFMT='%J %uU user %uS system %P cpu %uE total'
$ time python -c pass
python -c pass 19880us user 0us system 80% cpu 24773us total
Beyond times, TIMEFMT can report several resource usage statistics. The most useful is probably %M, the maximum memory the command used. For example:
$ TIMEFMT='%J %M MB max memory'
$ time python example.py
python example.py 392 MB max memory
Unfortunately, %M’s unit depends on your OS. The Zsh documentation says kilobytes, which is what you get on macOS. But on Linux, a long-standing bug means you get megabytes instead (zsh-workers report)—woopsy. So, on macOS, label it KB rather than MB.
There are many more statistics available, like page faults, I/O operations, and context switches. See the TIMEFMT documentation for the full list.
Set TIMEFMT in your ~/.zshrc to use a custom format permanently.
Automatic reports with REPORTTIME
Zsh can also report statistics automatically, without needing time. Set REPORTTIME to a number of seconds, and any command that uses more CPU time than that will get a report:
$ REPORTTIME=1
$ python example.py
python example.py 1.46s user 1.58s system 73% cpu 4.128 total
Note that this threshold applies to CPU time, not elapsed time, so commands that mostly wait, like sleep 10, won’t trigger a report.
Read my book Boost Your Django DX, freshly updated in November 2024.
One summary email a week, no spam, I pinky promise.
Related posts:
- Zsh: set aside a command with “Escape Q” /
push-line - Python: a quick cProfile recipe with pstats
- Zsh: find the largest files with the
(OL)glob qualifier
Tags: zsh