1 /* vi: set sw=4 ts=4: */
3 * 'time' utility to display resource usage of processes.
4 * Copyright (C) 1990, 91, 92, 93, 96 Free Software Foundation, Inc.
6 * Licensed under GPLv2, see file LICENSE in this source tree.
8 /* Originally written by David Keppel <pardo@cs.washington.edu>.
9 * Heavily modified by David MacKenzie <djm@gnu.ai.mit.edu>.
10 * Heavily modified for busybox by Erik Andersen <andersen@codepoet.org>
13 //config: bool "time (7 kb)"
16 //config: The time command runs the specified program with the given arguments.
17 //config: When the command finishes, time writes a message to standard output
18 //config: giving timing statistics about this program run.
20 //applet:IF_TIME(APPLET(time, BB_DIR_USR_BIN, BB_SUID_DROP))
22 //kbuild:lib-$(CONFIG_TIME) += time.o
24 //usage:#define time_trivial_usage
25 //usage: "[-vpa] [-o FILE] PROG ARGS"
26 //usage:#define time_full_usage "\n\n"
27 //usage: "Run PROG, display resource usage when it exits\n"
28 //usage: "\n -v Verbose"
29 //usage: "\n -p POSIX output format"
30 //usage: "\n -f FMT Custom format"
31 //usage: "\n -o FILE Write result to FILE"
32 //usage: "\n -a Append (else overwrite)"
35 #include <sys/resource.h> /* getrusage */
37 /* Information on the resources used by a child process. */
41 unsigned elapsed_ms; /* Wallclock time of process. */
44 /* msec = milliseconds = 1/1,000 (1*10e-3) second.
45 usec = microseconds = 1/1,000,000 (1*10e-6) second. */
47 #define UL unsigned long
49 static const char default_format[] ALIGN1 = "real\t%E\nuser\t%u\nsys\t%T";
51 /* The output format for the -p option .*/
52 static const char posix_format[] ALIGN1 = "real %e\nuser %U\nsys %S";
54 /* Format string for printing all statistics verbosely.
55 Keep this output to 24 lines so users on terminals can see it all.*/
56 static const char long_format[] ALIGN1 =
57 "\tCommand being timed: \"%C\"\n"
58 "\tUser time (seconds): %U\n"
59 "\tSystem time (seconds): %S\n"
60 "\tPercent of CPU this job got: %P\n"
61 "\tElapsed (wall clock) time (h:mm:ss or m:ss): %E\n"
62 "\tAverage shared text size (kbytes): %X\n"
63 "\tAverage unshared data size (kbytes): %D\n"
64 "\tAverage stack size (kbytes): %p\n"
65 "\tAverage total size (kbytes): %K\n"
66 "\tMaximum resident set size (kbytes): %M\n"
67 "\tAverage resident set size (kbytes): %t\n"
68 "\tMajor (requiring I/O) page faults: %F\n"
69 "\tMinor (reclaiming a frame) page faults: %R\n"
70 "\tVoluntary context switches: %w\n"
71 "\tInvoluntary context switches: %c\n"
73 "\tFile system inputs: %I\n"
74 "\tFile system outputs: %O\n"
75 "\tSocket messages sent: %s\n"
76 "\tSocket messages received: %r\n"
77 "\tSignals delivered: %k\n"
78 "\tPage size (bytes): %Z\n"
81 /* Wait for and fill in data on child process PID.
82 Return 0 on error, 1 if ok. */
83 /* pid_t is short on BSDI, so don't try to promote it. */
84 static void resuse_end(pid_t pid, resource_t *resp)
88 /* Ignore signals, but don't ignore the children. When wait3
89 * returns the child process, set the time the command finished. */
90 while ((caught = wait3(&resp->waitstatus, 0, &resp->ru)) != pid) {
91 if (caught == -1 && errno != EINTR) {
92 bb_perror_msg("wait");
96 resp->elapsed_ms = monotonic_ms() - resp->elapsed_ms;
99 static void printargv(char *const *argv)
101 const char *fmt = " %s" + 1;
108 /* Return the number of kilobytes corresponding to a number of pages PAGES.
109 (Actually, we use it to convert pages*ticks into kilobytes*ticks.)
111 Try to do arithmetic so that the risk of overflow errors is minimized.
112 This is funky since the pagesize could be less than 1K.
113 Note: Some machines express getrusage statistics in terms of K,
114 others in terms of pages. */
115 static unsigned long ptok(const unsigned pagesize, const unsigned long pages)
120 if (pages > (LONG_MAX / pagesize)) { /* Could overflow. */
121 tmp = pages / 1024; /* Smaller first, */
122 return tmp * pagesize; /* then larger. */
124 /* Could underflow. */
125 tmp = pages * pagesize; /* Larger first, */
126 return tmp / 1024; /* then smaller. */
129 /* summarize: Report on the system use of a command.
131 Print the FMT argument except that '%' sequences
132 have special meaning, and '\n' and '\t' are translated into
133 newline and tab, respectively, and '\\' is translated into '\'.
135 The character following a '%' can be:
136 (* means the tcsh time builtin also recognizes it)
138 C == command name and arguments
139 * D == average unshared data size in K (ru_idrss+ru_isrss)
140 * E == elapsed real (wall clock) time in [hour:]min:sec
141 * F == major page faults (required physical I/O) (ru_majflt)
142 * I == file system inputs (ru_inblock)
143 * K == average total mem usage (ru_idrss+ru_isrss+ru_ixrss)
144 * M == maximum resident set size in K (ru_maxrss)
145 * O == file system outputs (ru_oublock)
146 * P == percent of CPU this job got (total cpu time / elapsed time)
147 * R == minor page faults (reclaims; no physical I/O involved) (ru_minflt)
148 * S == system (kernel) time (seconds) (ru_stime)
149 * T == system time in [hour:]min:sec
150 * U == user time (seconds) (ru_utime)
151 * u == user time in [hour:]min:sec
152 * W == times swapped out (ru_nswap)
153 * X == average amount of shared text in K (ru_ixrss)
155 * c == involuntary context switches (ru_nivcsw)
156 e == elapsed real time in seconds
157 * k == signals delivered (ru_nsignals)
158 p == average unshared stack size in K (ru_isrss)
159 * r == socket messages received (ru_msgrcv)
160 * s == socket messages sent (ru_msgsnd)
161 t == average resident set size in K (ru_idrss)
162 * w == voluntary context switches (ru_nvcsw)
163 x == exit status of command
165 Various memory usages are found by converting from page-seconds
166 to kbytes by multiplying by the page size, dividing by 1024,
167 and dividing by elapsed real time.
169 FMT is the format string, interpreted as described above.
170 COMMAND is the command and args that are being summarized.
171 RESP is resource information on the command. */
173 #ifndef TICKS_PER_SEC
174 #define TICKS_PER_SEC 100
177 static void summarize(const char *fmt, char **command, resource_t *resp)
179 unsigned vv_ms; /* Elapsed virtual (CPU) milliseconds */
180 unsigned cpu_ticks; /* Same, in "CPU ticks" */
181 unsigned pagesize = getpagesize();
183 /* Impossible: we do not use WUNTRACED flag in wait()...
184 if (WIFSTOPPED(resp->waitstatus))
185 printf("Command stopped by signal %u\n",
186 WSTOPSIG(resp->waitstatus));
188 if (WIFSIGNALED(resp->waitstatus))
189 printf("Command terminated by signal %u\n",
190 WTERMSIG(resp->waitstatus));
191 else if (WIFEXITED(resp->waitstatus) && WEXITSTATUS(resp->waitstatus))
192 printf("Command exited with non-zero status %u\n",
193 WEXITSTATUS(resp->waitstatus));
195 vv_ms = (resp->ru.ru_utime.tv_sec + resp->ru.ru_stime.tv_sec) * 1000
196 + (resp->ru.ru_utime.tv_usec + resp->ru.ru_stime.tv_usec) / 1000;
198 #if (1000 / TICKS_PER_SEC) * TICKS_PER_SEC == 1000
199 /* 1000 is exactly divisible by TICKS_PER_SEC (typical) */
200 cpu_ticks = vv_ms / (1000 / TICKS_PER_SEC);
202 cpu_ticks = vv_ms * (unsigned long long)TICKS_PER_SEC / 1000;
204 if (!cpu_ticks) cpu_ticks = 1; /* we divide by it, must be nonzero */
207 /* Handle leading literal part */
208 int n = strcspn(fmt, "%\\");
210 printf("%.*s", n, fmt);
217 /* Handle literal char */
218 /* Usually we optimize for size, but there is a limit
219 * for everything. With this we do a lot of 1-byte writes */
227 #ifdef NOT_NEEDED_YET
228 /* Our format strings do not have these */
229 /* and we do not take format str from user */
238 case 'C': /* The command that got timed. */
241 case 'D': /* Average unshared data size. */
243 (ptok(pagesize, (UL) resp->ru.ru_idrss) +
244 ptok(pagesize, (UL) resp->ru.ru_isrss)) / cpu_ticks);
246 case 'E': { /* Elapsed real (wall clock) time. */
247 unsigned seconds = resp->elapsed_ms / 1000;
248 if (seconds >= 3600) /* One hour -> h:m:s. */
249 printf("%uh %um %02us",
251 (seconds % 3600) / 60,
254 printf("%um %u.%02us", /* -> m:s. */
257 (unsigned)(resp->elapsed_ms / 10) % 100);
260 case 'F': /* Major page faults. */
261 printf("%lu", resp->ru.ru_majflt);
263 case 'I': /* Inputs. */
264 printf("%lu", resp->ru.ru_inblock);
266 case 'K': /* Average mem usage == data+stack+text. */
268 (ptok(pagesize, (UL) resp->ru.ru_idrss) +
269 ptok(pagesize, (UL) resp->ru.ru_isrss) +
270 ptok(pagesize, (UL) resp->ru.ru_ixrss)) / cpu_ticks);
272 case 'M': /* Maximum resident set size. */
273 printf("%lu", ptok(pagesize, (UL) resp->ru.ru_maxrss));
275 case 'O': /* Outputs. */
276 printf("%lu", resp->ru.ru_oublock);
278 case 'P': /* Percent of CPU this job got. */
279 /* % cpu is (total cpu time)/(elapsed time). */
280 if (resp->elapsed_ms > 0)
281 printf("%u%%", (unsigned)(vv_ms * 100 / resp->elapsed_ms));
285 case 'R': /* Minor page faults (reclaims). */
286 printf("%lu", resp->ru.ru_minflt);
288 case 'S': /* System time. */
290 (unsigned)resp->ru.ru_stime.tv_sec,
291 (unsigned)(resp->ru.ru_stime.tv_usec / 10000));
293 case 'T': /* System time. */
294 if (resp->ru.ru_stime.tv_sec >= 3600) /* One hour -> h:m:s. */
295 printf("%uh %um %02us",
296 (unsigned)(resp->ru.ru_stime.tv_sec / 3600),
297 (unsigned)(resp->ru.ru_stime.tv_sec % 3600) / 60,
298 (unsigned)(resp->ru.ru_stime.tv_sec % 60));
300 printf("%um %u.%02us", /* -> m:s. */
301 (unsigned)(resp->ru.ru_stime.tv_sec / 60),
302 (unsigned)(resp->ru.ru_stime.tv_sec % 60),
303 (unsigned)(resp->ru.ru_stime.tv_usec / 10000));
305 case 'U': /* User time. */
307 (unsigned)resp->ru.ru_utime.tv_sec,
308 (unsigned)(resp->ru.ru_utime.tv_usec / 10000));
310 case 'u': /* User time. */
311 if (resp->ru.ru_utime.tv_sec >= 3600) /* One hour -> h:m:s. */
312 printf("%uh %um %02us",
313 (unsigned)(resp->ru.ru_utime.tv_sec / 3600),
314 (unsigned)(resp->ru.ru_utime.tv_sec % 3600) / 60,
315 (unsigned)(resp->ru.ru_utime.tv_sec % 60));
317 printf("%um %u.%02us", /* -> m:s. */
318 (unsigned)(resp->ru.ru_utime.tv_sec / 60),
319 (unsigned)(resp->ru.ru_utime.tv_sec % 60),
320 (unsigned)(resp->ru.ru_utime.tv_usec / 10000));
322 case 'W': /* Times swapped out. */
323 printf("%lu", resp->ru.ru_nswap);
325 case 'X': /* Average shared text size. */
326 printf("%lu", ptok(pagesize, (UL) resp->ru.ru_ixrss) / cpu_ticks);
328 case 'Z': /* Page size. */
329 printf("%u", pagesize);
331 case 'c': /* Involuntary context switches. */
332 printf("%lu", resp->ru.ru_nivcsw);
334 case 'e': /* Elapsed real time in seconds. */
336 (unsigned)resp->elapsed_ms / 1000,
337 (unsigned)(resp->elapsed_ms / 10) % 100);
339 case 'k': /* Signals delivered. */
340 printf("%lu", resp->ru.ru_nsignals);
342 case 'p': /* Average stack segment. */
343 printf("%lu", ptok(pagesize, (UL) resp->ru.ru_isrss) / cpu_ticks);
345 case 'r': /* Incoming socket messages received. */
346 printf("%lu", resp->ru.ru_msgrcv);
348 case 's': /* Outgoing socket messages sent. */
349 printf("%lu", resp->ru.ru_msgsnd);
351 case 't': /* Average resident set size. */
352 printf("%lu", ptok(pagesize, (UL) resp->ru.ru_idrss) / cpu_ticks);
354 case 'w': /* Voluntary context switches. */
355 printf("%lu", resp->ru.ru_nvcsw);
357 case 'x': /* Exit status. */
358 printf("%u", WEXITSTATUS(resp->waitstatus));
363 #ifdef NOT_NEEDED_YET
364 case '\\': /* Format escape. */
389 /* Run command CMD and return statistics on it.
390 Put the statistics in *RESP. */
391 static void run_command(char *const *cmd, resource_t *resp)
394 void (*interrupt_signal)(int);
395 void (*quit_signal)(int);
397 resp->elapsed_ms = monotonic_ms();
401 BB_EXECVP_or_die((char**)cmd);
404 /* Have signals kill the child but not self (if possible). */
405 //TODO: just block all sigs? and re-enable them in the very end in main?
406 interrupt_signal = signal(SIGINT, SIG_IGN);
407 quit_signal = signal(SIGQUIT, SIG_IGN);
409 resuse_end(pid, resp);
411 /* Re-enable signals. */
412 signal(SIGINT, interrupt_signal);
413 signal(SIGQUIT, quit_signal);
416 int time_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
417 int time_main(int argc UNUSED_PARAM, char **argv)
420 /* $TIME has lowest prio (-v,-p,-f FMT overrride it) */
421 const char *output_format = getenv("TIME") ? : default_format;
422 char *output_filename;
434 /* "+": stop on first non-option */
435 opt = getopt32(argv, "^+" "vpao:f:" "\0" "-1"/*at least one arg*/,
436 &output_filename, &output_format
440 output_format = long_format;
442 output_format = posix_format;
443 output_fd = STDERR_FILENO;
448 output_fd = xopen(output_filename,
449 (opt & OPT_a) /* append? */
450 ? (O_CREAT | O_WRONLY | O_CLOEXEC | O_APPEND)
451 : (O_CREAT | O_WRONLY | O_CLOEXEC | O_TRUNC)
454 close_on_exec_on(output_fd);
457 run_command(argv, &res);
459 /* Cheat. printf's are shorter :) */
460 xdup2(output_fd, STDOUT_FILENO);
461 summarize(output_format, argv, &res);
463 ex = WEXITSTATUS(res.waitstatus);
464 /* Impossible: we do not use WUNTRACED flag in wait()...
465 if (WIFSTOPPED(res.waitstatus))
466 ex = WSTOPSIG(res.waitstatus);
468 if (WIFSIGNALED(res.waitstatus))
469 ex = WTERMSIG(res.waitstatus);
471 fflush_stdout_and_exit(ex);