Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions etc/mtree/BSD.tests.dist
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,8 @@
..
tar
..
time
..
touch
..
tr
Expand Down
5 changes: 5 additions & 0 deletions usr.bin/time/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# @(#)Makefile 8.1 (Berkeley) 6/6/93

.include <src.opts.mk>

PROG= time

HAS_TESTS=
SUBDIR.${MK_TESTS}+= tests

.include <bsd.prog.mk>
4 changes: 4 additions & 0 deletions usr.bin/time/tests/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
PACKAGE= tests
ATF_TESTS_SH= time_test

.include <bsd.test.mk>
171 changes: 171 additions & 0 deletions usr.bin/time/tests/time_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#
# Copyright (c) 2026 Lucas Holt
#
# SPDX-License-Identifier: BSD-2-Clause
#

TIME=/usr/bin/time

atf_test_case format_literal
format_literal_head() {
atf_set descr "-f copies literal text and appends a newline"
}
format_literal_body() {
atf_check -s exit:0 -e inline:"hello world\n" \
${TIME} -f "hello world" true
}

atf_test_case format_percent
format_percent_head() {
atf_set descr "-f prints %% as a single percent sign"
}
format_percent_body() {
atf_check -s exit:0 -e inline:"100%\n" \
${TIME} -f "100%%" true
}

atf_test_case format_backslash
format_backslash_head() {
atf_set descr "-f expands \\t, \\n and \\\\"
}
format_backslash_body() {
printf 'a\tb\nc\\d\n' >expected
atf_check -s exit:0 -e file:expected \
${TIME} -f 'a\tb\nc\\d' true
}

atf_test_case format_unknown
format_unknown_head() {
atf_set descr "-f prints unknown conversions as ?c"
}
format_unknown_body() {
atf_check -s exit:0 -e inline:"?q\n" \
${TIME} -f "%q" true
}

atf_test_case format_trailing
format_trailing_head() {
atf_set descr "-f keeps a trailing % or backslash literal"
}
format_trailing_body() {
atf_check -s exit:0 -e inline:"x%\n" ${TIME} -f "x%" true
atf_check -s exit:0 -e inline:"x\\\\\n" ${TIME} -f 'x\' true
}

atf_test_case format_command
format_command_head() {
atf_set descr "%C prints the command and its arguments"
}
format_command_body() {
atf_check -s exit:0 -e inline:"sh -c exit 0\n" \
${TIME} -f "%C" sh -c "exit 0"
}

atf_test_case format_exit_status
format_exit_status_head() {
atf_set descr "%x prints the exit status of the command"
}
format_exit_status_body() {
atf_check -s exit:0 -e inline:"0\n" ${TIME} -f "%x" true
atf_check -s exit:3 -e inline:"3\n" ${TIME} -f "%x" sh -c "exit 3"
}

atf_test_case format_signal
format_signal_head() {
atf_set descr "%x prints the signal number when the command is killed"
}
format_signal_body() {
atf_check -s signal:15 -e match:"^15$" \
${TIME} -f "%x" sh -c 'kill -TERM $$'
}

atf_test_case format_elapsed
format_elapsed_head() {
atf_set descr "%e and %E report the elapsed time"
}
format_elapsed_body() {
atf_check -s exit:0 -e match:"^[1-9][.,][0-9][0-9]$" \
${TIME} -f "%e" sleep 1
atf_check -s exit:0 -e match:"^0:0[1-9][.,][0-9][0-9]$" \
${TIME} -f "%E" sleep 1
atf_check -s exit:0 -e match:"^0[.,][0-9][0-9]$" \
${TIME} -f "%e" true
}

atf_test_case format_numeric
format_numeric_head() {
atf_set descr "numeric conversions produce numbers"
}
format_numeric_body() {
atf_check -s exit:0 \
-e match:"^U=[0-9]+[.,][0-9]{2} S=[0-9]+[.,][0-9]{2} P=[0-9?]+%$" \
${TIME} -f "U=%U S=%S P=%P" true
atf_check -s exit:0 \
-e match:"^M=[0-9]+ t=[0-9]+ K=[0-9]+ D=[0-9]+ p=[0-9]+ X=[0-9]+$" \
${TIME} -f "M=%M t=%t K=%K D=%D p=%p X=%X" true
atf_check -s exit:0 \
-e match:"^F=[0-9]+ R=[0-9]+ W=[0-9]+ c=[0-9]+ w=[0-9]+$" \
${TIME} -f "F=%F R=%R W=%W c=%c w=%w" true
atf_check -s exit:0 \
-e match:"^I=[0-9]+ O=[0-9]+ r=[0-9]+ s=[0-9]+ k=[0-9]+$" \
${TIME} -f "I=%I O=%O r=%r s=%s k=%k" true
}

atf_test_case format_pagesize
format_pagesize_head() {
atf_set descr "%Z matches the system page size"
}
format_pagesize_body() {
pagesize=$(sysctl -n hw.pagesize)
atf_check -s exit:0 -e inline:"${pagesize}\n" ${TIME} -f "%Z" true
}

atf_test_case format_overrides
format_overrides_head() {
atf_set descr "-f takes precedence over -p and -h, -l still appends"
}
format_overrides_body() {
atf_check -s exit:0 -e inline:"fmt\n" ${TIME} -p -f "fmt" true
atf_check -s exit:0 -e inline:"fmt\n" ${TIME} -h -f "fmt" true
atf_check -s exit:0 -e match:"^fmt$" \
-e match:"maximum resident set size" \
${TIME} -l -f "fmt" true
}

atf_test_case format_output_file
format_output_file_head() {
atf_set descr "-f output honours -o and -a"
}
format_output_file_body() {
atf_check -s exit:0 ${TIME} -o out.txt -f "first %x" true
atf_check -o inline:"first 0\n" cat out.txt
atf_check -s exit:0 ${TIME} -a -o out.txt -f "second %x" true
atf_check -o inline:"first 0\nsecond 0\n" cat out.txt
atf_check -s exit:0 ${TIME} -o out.txt -f "third %x" true
atf_check -o inline:"third 0\n" cat out.txt
}

atf_test_case format_missing_arg
format_missing_arg_head() {
atf_set descr "-f without an argument is a usage error"
}
format_missing_arg_body() {
atf_check -s exit:1 -e match:"usage:" ${TIME} -f
}

atf_init_test_cases() {
atf_add_test_case format_literal
atf_add_test_case format_percent
atf_add_test_case format_backslash
atf_add_test_case format_unknown
atf_add_test_case format_trailing
atf_add_test_case format_command
atf_add_test_case format_exit_status
atf_add_test_case format_signal
atf_add_test_case format_elapsed
atf_add_test_case format_numeric
atf_add_test_case format_pagesize
atf_add_test_case format_overrides
atf_add_test_case format_output_file
atf_add_test_case format_missing_arg
}
115 changes: 112 additions & 3 deletions usr.bin/time/time.1
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
.\"
.\" @(#)time.1 8.1 (Berkeley) 6/6/93
.\"
.Dd July 7, 2020
.Dd September 8, 2026
.Dt TIME 1
.Os
.Sh NAME
Expand All @@ -36,7 +36,7 @@
.Sh SYNOPSIS
.Nm
.Op Fl al
.Op Fl h | Fl p
.Op Fl f Ar format | Fl h | Fl p
.Op Fl o Ar file
.Ar utility Op Ar argument ...
.Sh DESCRIPTION
Expand Down Expand Up @@ -65,6 +65,107 @@ If the
flag is used, append to the specified file rather than overwriting
it.
Otherwise, this option has no effect.
.It Fl f Ar format
Print the resource usage using
.Ar format
instead of the default output.
The format string is copied to the output verbatim, with the following
exceptions:
a newline is appended,
the sequences
.Ql \et ,
.Ql \en
and
.Ql \e\e
produce a tab, a newline and a backslash respectively,
and each
.Ql %
introduces a conversion described below.
An unknown conversion is printed as a
.Ql \&?
followed by the offending character.
The conversions are compatible with those of GNU
.Nm ,
so scripts written for it can be used unchanged.
.Pp
Time:
.Bl -tag -width indent -compact
.It Cm \&%E
Elapsed real time, as
.Ar minutes:seconds.hundredths ,
or
.Ar hours:minutes:seconds
if the command ran for an hour or more.
.It Cm \&%e
Elapsed real time in seconds.
.It Cm \&%S
Total CPU seconds spent in kernel mode.
.It Cm \&%U
Total CPU seconds spent in user mode.
.It Cm \&%P
Percentage of the CPU this job got, computed as
.Cm ( \&%U No + Cm \&%S ) No / Cm \&%e .
.El
.Pp
Memory (all sizes are in kilobytes):
.Bl -tag -width indent -compact
.It Cm \&%M
Maximum resident set size.
.It Cm \&%t
Average resident set size (unshared data).
.It Cm \&%K
Average total memory use (data, stack and text).
.It Cm \&%D
Average size of the unshared data and stack areas.
.It Cm \&%p
Average size of the unshared stack area.
.It Cm \&%X
Average size of the shared text area.
.It Cm \&%Z
The system page size, in bytes.
.It Cm \&%F
Number of major page faults.
.It Cm \&%R
Number of minor page faults (page reclaims).
.It Cm \&%W
Number of times the process was swapped out.
.It Cm \&%c
Number of involuntary context switches.
.It Cm \&%w
Number of voluntary context switches.
.El
.Pp
I/O and miscellaneous:
.Bl -tag -width indent -compact
.It Cm \&%I
Number of block input operations.
.It Cm \&%O
Number of block output operations.
.It Cm \&%r
Number of socket messages received.
.It Cm \&%s
Number of socket messages sent.
.It Cm \&%k
Number of signals delivered to the process.
.It Cm \&%C
The name and arguments of the command being timed.
.It Cm \&%x
The exit status of the command, or the number of the signal that
terminated it.
.It Cm \&%%
A literal
.Ql % .
.El
.Pp
The
.Fl f
option takes precedence over
.Fl h
and
.Fl p .
The
.Fl l
output, if requested, follows the formatted line.
.It Fl h
Print times in a human friendly format.
Times are printed in minutes, hours,
Expand Down Expand Up @@ -102,7 +203,7 @@ receives a
(see the status argument for
.Xr stty 1 )
signal, the current time the given command is running will be written to the
standard output.
standard output in the default format.
.Sh ENVIRONMENT
The
.Ev PATH
Expand Down Expand Up @@ -184,6 +285,14 @@ sys 0.00
3 voluntary context switches
0 involuntary context switches
.Ed
.Pp
Print only the elapsed time and the peak memory use of a
.Xr sort 1
run, using a custom format:
.Bd -literal -offset indent
$ /usr/bin/time -f "%e seconds, %M KB max RSS, exit %x" sort -o /dev/null words
0.12 seconds, 6740 KB max RSS, exit 0
.Ed
.Sh SEE ALSO
.Xr builtin 1 ,
.Xr csh 1 ,
Expand Down
Loading
Loading