1 /* vi: set sw=4 ts=4: */
2 /* `time' utility to display resource usage of processes.
3 Copyright (C) 1990, 91, 92, 93, 96 Free Software Foundation, Inc.
5 Licensed under GPL version 2, see file LICENSE in this tarball for details.
7 /* Originally written by David Keppel <pardo@cs.washington.edu>.
8 Heavily modified by David MacKenzie <djm@gnu.ai.mit.edu>.
9 Heavily modified for busybox by Erik Andersen <andersen@codepoet.org>
14 #define TV_MSEC tv_usec / 1000
16 /* Information on the resources used by a child process. */
20 struct timeval start, elapsed; /* Wallclock time of process. */
23 /* msec = milliseconds = 1/1,000 (1*10e-3) second.
24 usec = microseconds = 1/1,000,000 (1*10e-6) second. */
27 #define TICKS_PER_SEC 100
30 /* The number of milliseconds in one `tick' used by the `rusage' structure. */
31 #define MSEC_PER_TICK (1000 / TICKS_PER_SEC)
33 /* Return the number of clock ticks that occur in M milliseconds. */
34 #define MSEC_TO_TICKS(m) ((m) / MSEC_PER_TICK)
36 #define UL unsigned long
38 static const char *const default_format = "real\t%E\nuser\t%u\nsys\t%T";
40 /* The output format for the -p option .*/
41 static const char *const posix_format = "real %e\nuser %U\nsys %S";
44 /* Format string for printing all statistics verbosely.
45 Keep this output to 24 lines so users on terminals can see it all.*/
46 static const char *const long_format =
47 "\tCommand being timed: \"%C\"\n"
48 "\tUser time (seconds): %U\n"
49 "\tSystem time (seconds): %S\n"
50 "\tPercent of CPU this job got: %P\n"
51 "\tElapsed (wall clock) time (h:mm:ss or m:ss): %E\n"
52 "\tAverage shared text size (kbytes): %X\n"
53 "\tAverage unshared data size (kbytes): %D\n"
54 "\tAverage stack size (kbytes): %p\n"
55 "\tAverage total size (kbytes): %K\n"
56 "\tMaximum resident set size (kbytes): %M\n"
57 "\tAverage resident set size (kbytes): %t\n"
58 "\tMajor (requiring I/O) page faults: %F\n"
59 "\tMinor (reclaiming a frame) page faults: %R\n"
60 "\tVoluntary context switches: %w\n"
61 "\tInvoluntary context switches: %c\n"
63 "\tFile system inputs: %I\n"
64 "\tFile system outputs: %O\n"
65 "\tSocket messages sent: %s\n"
66 "\tSocket messages received: %r\n"
67 "\tSignals delivered: %k\n"
68 "\tPage size (bytes): %Z\n"
72 /* Wait for and fill in data on child process PID.
73 Return 0 on error, 1 if ok. */
75 /* pid_t is short on BSDI, so don't try to promote it. */
76 static int resuse_end(pid_t pid, resource_t * resp)
82 /* Ignore signals, but don't ignore the children. When wait3
83 returns the child process, set the time the command finished. */
84 while ((caught = wait3(&status, 0, &resp->ru)) != pid) {
89 gettimeofday(&resp->elapsed, (struct timezone *) 0);
90 resp->elapsed.tv_sec -= resp->start.tv_sec;
91 if (resp->elapsed.tv_usec < resp->start.tv_usec) {
92 /* Manually carry a one from the seconds field. */
93 resp->elapsed.tv_usec += 1000000;
94 --resp->elapsed.tv_sec;
96 resp->elapsed.tv_usec -= resp->start.tv_usec;
98 resp->waitstatus = status;
103 /* Print ARGV, with each entry in ARGV separated by FILLER. */
104 static void printargv(char *const *argv, const char *filler)
106 fputs(*argv, stdout);
108 fputs(filler, stdout);
109 fputs(*argv, stdout);
113 /* Return the number of kilobytes corresponding to a number of pages PAGES.
114 (Actually, we use it to convert pages*ticks into kilobytes*ticks.)
116 Try to do arithmetic so that the risk of overflow errors is minimized.
117 This is funky since the pagesize could be less than 1K.
118 Note: Some machines express getrusage statistics in terms of K,
119 others in terms of pages. */
121 static unsigned long ptok(unsigned long pages)
123 static unsigned long ps;
126 /* Initialization. */
131 if (pages > (LONG_MAX / ps)) { /* Could overflow. */
132 tmp = pages / 1024; /* Smaller first, */
133 return tmp * ps; /* then larger. */
135 /* Could underflow. */
136 tmp = pages * ps; /* Larger first, */
137 return tmp / 1024; /* then smaller. */
140 /* summarize: Report on the system use of a command.
142 Print the FMT argument except that `%' sequences
143 have special meaning, and `\n' and `\t' are translated into
144 newline and tab, respectively, and `\\' is translated into `\'.
146 The character following a `%' can be:
147 (* means the tcsh time builtin also recognizes it)
149 C == command name and arguments
150 * D == average unshared data size in K (ru_idrss+ru_isrss)
151 * E == elapsed real (wall clock) time in [hour:]min:sec
152 * F == major page faults (required physical I/O) (ru_majflt)
153 * I == file system inputs (ru_inblock)
154 * K == average total mem usage (ru_idrss+ru_isrss+ru_ixrss)
155 * M == maximum resident set size in K (ru_maxrss)
156 * O == file system outputs (ru_oublock)
157 * P == percent of CPU this job got (total cpu time / elapsed time)
158 * R == minor page faults (reclaims; no physical I/O involved) (ru_minflt)
159 * S == system (kernel) time (seconds) (ru_stime)
160 * T == system time in [hour:]min:sec
161 * U == user time (seconds) (ru_utime)
162 * u == user time in [hour:]min:sec
163 * W == times swapped out (ru_nswap)
164 * X == average amount of shared text in K (ru_ixrss)
166 * c == involuntary context switches (ru_nivcsw)
167 e == elapsed real time in seconds
168 * k == signals delivered (ru_nsignals)
169 p == average unshared stack size in K (ru_isrss)
170 * r == socket messages received (ru_msgrcv)
171 * s == socket messages sent (ru_msgsnd)
172 t == average resident set size in K (ru_idrss)
173 * w == voluntary context switches (ru_nvcsw)
174 x == exit status of command
176 Various memory usages are found by converting from page-seconds
177 to kbytes by multiplying by the page size, dividing by 1024,
178 and dividing by elapsed real time.
180 FMT is the format string, interpreted as described above.
181 COMMAND is the command and args that are being summarized.
182 RESP is resource information on the command. */
184 static void summarize(const char *fmt, char **command, resource_t * resp)
186 unsigned long r; /* Elapsed real milliseconds. */
187 unsigned long v; /* Elapsed virtual (CPU) milliseconds. */
189 if (WIFSTOPPED(resp->waitstatus))
190 printf("Command stopped by signal %d\n",
191 WSTOPSIG(resp->waitstatus));
192 else if (WIFSIGNALED(resp->waitstatus))
193 printf("Command terminated by signal %d\n",
194 WTERMSIG(resp->waitstatus));
195 else if (WIFEXITED(resp->waitstatus) && WEXITSTATUS(resp->waitstatus))
196 printf("Command exited with non-zero status %d\n",
197 WEXITSTATUS(resp->waitstatus));
199 /* Convert all times to milliseconds. Occasionally, one of these values
200 comes out as zero. Dividing by zero causes problems, so we first
201 check the time value. If it is zero, then we take `evasive action'
202 instead of calculating a value. */
204 r = resp->elapsed.tv_sec * 1000 + resp->elapsed.tv_usec / 1000;
206 v = resp->ru.ru_utime.tv_sec * 1000 + resp->ru.ru_utime.TV_MSEC +
207 resp->ru.ru_stime.tv_sec * 1000 + resp->ru.ru_stime.TV_MSEC;
209 /* putchar() != putc(stdout) in glibc! */
212 /* Handle leading literal part */
213 int n = strcspn(fmt, "%\\");
215 printf("%.*s", n, fmt);
222 /* Handle literal char */
223 /* Usually we optimize for size, but there is a limit
224 * for everything. With this we do a lot of 1-byte writes */
232 #ifdef NOT_NEEDED_YET
233 /* Our format strings do not have these */
234 /* and we do not take format str from user */
243 case 'C': /* The command that got timed. */
244 printargv(command, " ");
246 case 'D': /* Average unshared data size. */
248 MSEC_TO_TICKS(v) == 0 ? 0 :
249 ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v) +
250 ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v));
252 case 'E': /* Elapsed real (wall clock) time. */
253 if (resp->elapsed.tv_sec >= 3600) /* One hour -> h:m:s. */
254 printf("%ldh %ldm %02lds",
255 resp->elapsed.tv_sec / 3600,
256 (resp->elapsed.tv_sec % 3600) / 60,
257 resp->elapsed.tv_sec % 60);
259 printf("%ldm %ld.%02lds", /* -> m:s. */
260 resp->elapsed.tv_sec / 60,
261 resp->elapsed.tv_sec % 60,
262 resp->elapsed.tv_usec / 10000);
264 case 'F': /* Major page faults. */
265 printf("%ld", resp->ru.ru_majflt);
267 case 'I': /* Inputs. */
268 printf("%ld", resp->ru.ru_inblock);
270 case 'K': /* Average mem usage == data+stack+text. */
272 MSEC_TO_TICKS(v) == 0 ? 0 :
273 ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v) +
274 ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v) +
275 ptok((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS(v));
277 case 'M': /* Maximum resident set size. */
278 printf("%lu", ptok((UL) resp->ru.ru_maxrss));
280 case 'O': /* Outputs. */
281 printf("%ld", resp->ru.ru_oublock);
283 case 'P': /* Percent of CPU this job got. */
284 /* % cpu is (total cpu time)/(elapsed time). */
286 printf("%lu%%", (v * 100 / r));
290 case 'R': /* Minor page faults (reclaims). */
291 printf("%ld", resp->ru.ru_minflt);
293 case 'S': /* System time. */
295 resp->ru.ru_stime.tv_sec,
296 resp->ru.ru_stime.TV_MSEC / 10);
298 case 'T': /* System time. */
299 if (resp->ru.ru_stime.tv_sec >= 3600) /* One hour -> h:m:s. */
300 printf("%ldh %ldm %02lds",
301 resp->ru.ru_stime.tv_sec / 3600,
302 (resp->ru.ru_stime.tv_sec % 3600) / 60,
303 resp->ru.ru_stime.tv_sec % 60);
305 printf("%ldm %ld.%02lds", /* -> m:s. */
306 resp->ru.ru_stime.tv_sec / 60,
307 resp->ru.ru_stime.tv_sec % 60,
308 resp->ru.ru_stime.tv_usec / 10000);
310 case 'U': /* User time. */
312 resp->ru.ru_utime.tv_sec,
313 resp->ru.ru_utime.TV_MSEC / 10);
315 case 'u': /* User time. */
316 if (resp->ru.ru_utime.tv_sec >= 3600) /* One hour -> h:m:s. */
317 printf("%ldh %ldm %02lds",
318 resp->ru.ru_utime.tv_sec / 3600,
319 (resp->ru.ru_utime.tv_sec % 3600) / 60,
320 resp->ru.ru_utime.tv_sec % 60);
322 printf("%ldm %ld.%02lds", /* -> m:s. */
323 resp->ru.ru_utime.tv_sec / 60,
324 resp->ru.ru_utime.tv_sec % 60,
325 resp->ru.ru_utime.tv_usec / 10000);
327 case 'W': /* Times swapped out. */
328 printf("%ld", resp->ru.ru_nswap);
330 case 'X': /* Average shared text size. */
332 MSEC_TO_TICKS(v) == 0 ? 0 :
333 ptok((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS(v));
335 case 'Z': /* Page size. */
336 printf("%d", getpagesize());
338 case 'c': /* Involuntary context switches. */
339 printf("%ld", resp->ru.ru_nivcsw);
341 case 'e': /* Elapsed real time in seconds. */
343 resp->elapsed.tv_sec, resp->elapsed.tv_usec / 10000);
345 case 'k': /* Signals delivered. */
346 printf("%ld", resp->ru.ru_nsignals);
348 case 'p': /* Average stack segment. */
350 MSEC_TO_TICKS(v) == 0 ? 0 :
351 ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v));
353 case 'r': /* Incoming socket messages received. */
354 printf("%ld", resp->ru.ru_msgrcv);
356 case 's': /* Outgoing socket messages sent. */
357 printf("%ld", resp->ru.ru_msgsnd);
359 case 't': /* Average resident set size. */
361 MSEC_TO_TICKS(v) == 0 ? 0 :
362 ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v));
364 case 'w': /* Voluntary context switches. */
365 printf("%ld", resp->ru.ru_nvcsw);
367 case 'x': /* Exit status. */
368 printf("%d", WEXITSTATUS(resp->waitstatus));
373 #ifdef NOT_NEEDED_YET
374 case '\\': /* Format escape. */
399 /* Run command CMD and return statistics on it.
400 Put the statistics in *RESP. */
401 static void run_command(char *const *cmd, resource_t * resp)
403 pid_t pid; /* Pid of child. */
404 __sighandler_t interrupt_signal, quit_signal;
406 gettimeofday(&resp->start, (struct timezone *) 0);
407 pid = vfork(); /* Run CMD as child process. */
409 bb_error_msg_and_die("cannot fork");
410 else if (pid == 0) { /* If child. */
411 /* Don't cast execvp arguments; that causes errors on some systems,
412 versus merely warnings if the cast is left off. */
413 BB_EXECVP(cmd[0], cmd);
414 bb_error_msg("cannot run %s", cmd[0]);
415 _exit(errno == ENOENT ? 127 : 126);
418 /* Have signals kill the child but not self (if possible). */
419 interrupt_signal = signal(SIGINT, SIG_IGN);
420 quit_signal = signal(SIGQUIT, SIG_IGN);
422 if (resuse_end(pid, resp) == 0)
423 bb_error_msg("error waiting for child process");
425 /* Re-enable signals. */
426 signal(SIGINT, interrupt_signal);
427 signal(SIGQUIT, quit_signal);
430 int time_main(int argc, char **argv);
431 int time_main(int argc, char **argv)
434 const char *output_format = default_format;
438 /* Parse any options -- don't use getopt() here so we don't
439 * consume the args of our client application... */
440 while (argc > 0 && argv[0][0] == '-') {
441 while ((c = *++*argv)) {
444 output_format = long_format;
447 output_format = posix_format;
460 run_command(argv, &res);
462 /* Cheat. printf's are shorter :) */
464 dup2(2, 1); /* just in case libc does something silly :( */
465 summarize(output_format, argv, &res);
467 if (WIFSTOPPED(res.waitstatus))
468 return WSTOPSIG(res.waitstatus);
469 if (WIFSIGNALED(res.waitstatus))
470 return WTERMSIG(res.waitstatus);
471 if (WIFEXITED(res.waitstatus))
472 return WEXITSTATUS(res.waitstatus);
473 fflush_stdout_and_exit(0);