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 GPLv2 or later, 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>
21 #include <sys/types.h> /* For pid_t. */
23 #include <sys/param.h> /* For getpagesize, maybe. */
25 #define TV_MSEC tv_usec / 1000
26 #include <sys/resource.h>
28 /* Information on the resources used by a child process. */
32 struct timeval start, elapsed; /* Wallclock time of process. */
35 /* msec = milliseconds = 1/1,000 (1*10e-3) second.
36 usec = microseconds = 1/1,000,000 (1*10e-6) second. */
39 #define TICKS_PER_SEC 100
42 /* The number of milliseconds in one `tick' used by the `rusage' structure. */
43 #define MSEC_PER_TICK (1000 / TICKS_PER_SEC)
45 /* Return the number of clock ticks that occur in M milliseconds. */
46 #define MSEC_TO_TICKS(m) ((m) / MSEC_PER_TICK)
48 #define UL unsigned long
50 static const char *const default_format = "real\t%E\nuser\t%u\nsys\t%T";
52 /* The output format for the -p option .*/
53 static const char *const posix_format = "real %e\nuser %U\nsys %S";
56 /* Format string for printing all statistics verbosely.
57 Keep this output to 24 lines so users on terminals can see it all.*/
58 static const char *const long_format =
59 "\tCommand being timed: \"%C\"\n"
60 "\tUser time (seconds): %U\n"
61 "\tSystem time (seconds): %S\n"
62 "\tPercent of CPU this job got: %P\n"
63 "\tElapsed (wall clock) time (h:mm:ss or m:ss): %E\n"
64 "\tAverage shared text size (kbytes): %X\n"
65 "\tAverage unshared data size (kbytes): %D\n"
66 "\tAverage stack size (kbytes): %p\n"
67 "\tAverage total size (kbytes): %K\n"
68 "\tMaximum resident set size (kbytes): %M\n"
69 "\tAverage resident set size (kbytes): %t\n"
70 "\tMajor (requiring I/O) page faults: %F\n"
71 "\tMinor (reclaiming a frame) page faults: %R\n"
72 "\tVoluntary context switches: %w\n"
73 "\tInvoluntary context switches: %c\n"
75 "\tFile system inputs: %I\n"
76 "\tFile system outputs: %O\n"
77 "\tSocket messages sent: %s\n"
78 "\tSocket messages received: %r\n"
79 "\tSignals delivered: %k\n"
80 "\tPage size (bytes): %Z\n" "\tExit status: %x";
83 /* Wait for and fill in data on child process PID.
84 Return 0 on error, 1 if ok. */
86 /* pid_t is short on BSDI, so don't try to promote it. */
87 static int resuse_end(pid_t pid, resource_t * resp)
93 /* Ignore signals, but don't ignore the children. When wait3
94 returns the child process, set the time the command finished. */
95 while ((caught = wait3(&status, 0, &resp->ru)) != pid) {
100 gettimeofday(&resp->elapsed, (struct timezone *) 0);
101 resp->elapsed.tv_sec -= resp->start.tv_sec;
102 if (resp->elapsed.tv_usec < resp->start.tv_usec) {
103 /* Manually carry a one from the seconds field. */
104 resp->elapsed.tv_usec += 1000000;
105 --resp->elapsed.tv_sec;
107 resp->elapsed.tv_usec -= resp->start.tv_usec;
109 resp->waitstatus = status;
114 /* Print ARGV to FP, with each entry in ARGV separated by FILLER. */
115 static void fprintargv(FILE * fp, char *const *argv, const char *filler)
126 bb_error_msg_and_die(bb_msg_write_error);
129 /* Return the number of kilobytes corresponding to a number of pages PAGES.
130 (Actually, we use it to convert pages*ticks into kilobytes*ticks.)
132 Try to do arithmetic so that the risk of overflow errors is minimized.
133 This is funky since the pagesize could be less than 1K.
134 Note: Some machines express getrusage statistics in terms of K,
135 others in terms of pages. */
137 static unsigned long ptok(unsigned long pages)
139 static unsigned long ps = 0;
141 static long size = LONG_MAX;
143 /* Initialization. */
145 ps = (long) getpagesize();
148 if (pages > (LONG_MAX / ps)) { /* Could overflow. */
149 tmp = pages / 1024; /* Smaller first, */
150 size = tmp * ps; /* then larger. */
151 } else { /* Could underflow. */
152 tmp = pages * ps; /* Larger first, */
153 size = tmp / 1024; /* then smaller. */
158 /* summarize: Report on the system use of a command.
160 Copy the FMT argument to FP except that `%' sequences
161 have special meaning, and `\n' and `\t' are translated into
162 newline and tab, respectively, and `\\' is translated into `\'.
164 The character following a `%' can be:
165 (* means the tcsh time builtin also recognizes it)
167 C == command name and arguments
168 * D == average unshared data size in K (ru_idrss+ru_isrss)
169 * E == elapsed real (wall clock) time in [hour:]min:sec
170 * F == major page faults (required physical I/O) (ru_majflt)
171 * I == file system inputs (ru_inblock)
172 * K == average total mem usage (ru_idrss+ru_isrss+ru_ixrss)
173 * M == maximum resident set size in K (ru_maxrss)
174 * O == file system outputs (ru_oublock)
175 * P == percent of CPU this job got (total cpu time / elapsed time)
176 * R == minor page faults (reclaims; no physical I/O involved) (ru_minflt)
177 * S == system (kernel) time (seconds) (ru_stime)
178 * T == system time in [hour:]min:sec
179 * U == user time (seconds) (ru_utime)
180 * u == user time in [hour:]min:sec
181 * W == times swapped out (ru_nswap)
182 * X == average amount of shared text in K (ru_ixrss)
184 * c == involuntary context switches (ru_nivcsw)
185 e == elapsed real time in seconds
186 * k == signals delivered (ru_nsignals)
187 p == average unshared stack size in K (ru_isrss)
188 * r == socket messages received (ru_msgrcv)
189 * s == socket messages sent (ru_msgsnd)
190 t == average resident set size in K (ru_idrss)
191 * w == voluntary context switches (ru_nvcsw)
192 x == exit status of command
194 Various memory usages are found by converting from page-seconds
195 to kbytes by multiplying by the page size, dividing by 1024,
196 and dividing by elapsed real time.
198 FP is the stream to print to.
199 FMT is the format string, interpreted as described above.
200 COMMAND is the command and args that are being summarized.
201 RESP is resource information on the command. */
203 static void summarize(FILE * fp, const char *fmt, char **command,
206 unsigned long r; /* Elapsed real milliseconds. */
207 unsigned long v; /* Elapsed virtual (CPU) milliseconds. */
209 if (WIFSTOPPED(resp->waitstatus))
210 fprintf(fp, "Command stopped by signal %d\n",
211 WSTOPSIG(resp->waitstatus));
212 else if (WIFSIGNALED(resp->waitstatus))
213 fprintf(fp, "Command terminated by signal %d\n",
214 WTERMSIG(resp->waitstatus));
215 else if (WIFEXITED(resp->waitstatus) && WEXITSTATUS(resp->waitstatus))
216 fprintf(fp, "Command exited with non-zero status %d\n",
217 WEXITSTATUS(resp->waitstatus));
219 /* Convert all times to milliseconds. Occasionally, one of these values
220 comes out as zero. Dividing by zero causes problems, so we first
221 check the time value. If it is zero, then we take `evasive action'
222 instead of calculating a value. */
224 r = resp->elapsed.tv_sec * 1000 + resp->elapsed.tv_usec / 1000;
226 v = resp->ru.ru_utime.tv_sec * 1000 + resp->ru.ru_utime.TV_MSEC +
227 resp->ru.ru_stime.tv_sec * 1000 + resp->ru.ru_stime.TV_MSEC;
233 case '%': /* Literal '%'. */
236 case 'C': /* The command that got timed. */
237 fprintargv(fp, command, " ");
239 case 'D': /* Average unshared data size. */
241 MSEC_TO_TICKS(v) == 0 ? 0 :
242 ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v) +
243 ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v));
245 case 'E': /* Elapsed real (wall clock) time. */
246 if (resp->elapsed.tv_sec >= 3600) /* One hour -> h:m:s. */
247 fprintf(fp, "%ldh %ldm %02lds",
248 resp->elapsed.tv_sec / 3600,
249 (resp->elapsed.tv_sec % 3600) / 60,
250 resp->elapsed.tv_sec % 60);
252 fprintf(fp, "%ldm %ld.%02lds", /* -> m:s. */
253 resp->elapsed.tv_sec / 60,
254 resp->elapsed.tv_sec % 60,
255 resp->elapsed.tv_usec / 10000);
257 case 'F': /* Major page faults. */
258 fprintf(fp, "%ld", resp->ru.ru_majflt);
260 case 'I': /* Inputs. */
261 fprintf(fp, "%ld", resp->ru.ru_inblock);
263 case 'K': /* Average mem usage == data+stack+text. */
265 MSEC_TO_TICKS(v) == 0 ? 0 :
266 ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v) +
267 ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v) +
268 ptok((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS(v));
270 case 'M': /* Maximum resident set size. */
271 fprintf(fp, "%lu", ptok((UL) resp->ru.ru_maxrss));
273 case 'O': /* Outputs. */
274 fprintf(fp, "%ld", resp->ru.ru_oublock);
276 case 'P': /* Percent of CPU this job got. */
277 /* % cpu is (total cpu time)/(elapsed time). */
279 fprintf(fp, "%lu%%", (v * 100 / r));
283 case 'R': /* Minor page faults (reclaims). */
284 fprintf(fp, "%ld", resp->ru.ru_minflt);
286 case 'S': /* System time. */
287 fprintf(fp, "%ld.%02ld",
288 resp->ru.ru_stime.tv_sec,
289 resp->ru.ru_stime.TV_MSEC / 10);
291 case 'T': /* System time. */
292 if (resp->ru.ru_stime.tv_sec >= 3600) /* One hour -> h:m:s. */
293 fprintf(fp, "%ldh %ldm %02lds",
294 resp->ru.ru_stime.tv_sec / 3600,
295 (resp->ru.ru_stime.tv_sec % 3600) / 60,
296 resp->ru.ru_stime.tv_sec % 60);
298 fprintf(fp, "%ldm %ld.%02lds", /* -> m:s. */
299 resp->ru.ru_stime.tv_sec / 60,
300 resp->ru.ru_stime.tv_sec % 60,
301 resp->ru.ru_stime.tv_usec / 10000);
303 case 'U': /* User time. */
304 fprintf(fp, "%ld.%02ld",
305 resp->ru.ru_utime.tv_sec,
306 resp->ru.ru_utime.TV_MSEC / 10);
308 case 'u': /* User time. */
309 if (resp->ru.ru_utime.tv_sec >= 3600) /* One hour -> h:m:s. */
310 fprintf(fp, "%ldh %ldm %02lds",
311 resp->ru.ru_utime.tv_sec / 3600,
312 (resp->ru.ru_utime.tv_sec % 3600) / 60,
313 resp->ru.ru_utime.tv_sec % 60);
315 fprintf(fp, "%ldm %ld.%02lds", /* -> m:s. */
316 resp->ru.ru_utime.tv_sec / 60,
317 resp->ru.ru_utime.tv_sec % 60,
318 resp->ru.ru_utime.tv_usec / 10000);
320 case 'W': /* Times swapped out. */
321 fprintf(fp, "%ld", resp->ru.ru_nswap);
323 case 'X': /* Average shared text size. */
325 MSEC_TO_TICKS(v) == 0 ? 0 :
326 ptok((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS(v));
328 case 'Z': /* Page size. */
329 fprintf(fp, "%d", getpagesize());
331 case 'c': /* Involuntary context switches. */
332 fprintf(fp, "%ld", resp->ru.ru_nivcsw);
334 case 'e': /* Elapsed real time in seconds. */
335 fprintf(fp, "%ld.%02ld",
336 resp->elapsed.tv_sec, resp->elapsed.tv_usec / 10000);
338 case 'k': /* Signals delivered. */
339 fprintf(fp, "%ld", resp->ru.ru_nsignals);
341 case 'p': /* Average stack segment. */
343 MSEC_TO_TICKS(v) == 0 ? 0 :
344 ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v));
346 case 'r': /* Incoming socket messages received. */
347 fprintf(fp, "%ld", resp->ru.ru_msgrcv);
349 case 's': /* Outgoing socket messages sent. */
350 fprintf(fp, "%ld", resp->ru.ru_msgsnd);
352 case 't': /* Average resident set size. */
354 MSEC_TO_TICKS(v) == 0 ? 0 :
355 ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v));
357 case 'w': /* Voluntary context switches. */
358 fprintf(fp, "%ld", resp->ru.ru_nvcsw);
360 case 'x': /* Exit status. */
361 fprintf(fp, "%d", WEXITSTATUS(resp->waitstatus));
373 case '\\': /* Format escape. */
397 bb_error_msg_and_die(bb_msg_write_error);
402 bb_error_msg_and_die(bb_msg_write_error);
405 /* Run command CMD and return statistics on it.
406 Put the statistics in *RESP. */
407 static void run_command(char *const *cmd, resource_t * resp)
409 pid_t pid; /* Pid of child. */
410 __sighandler_t interrupt_signal, quit_signal;
412 gettimeofday(&resp->start, (struct timezone *) 0);
413 pid = fork(); /* Run CMD as child process. */
415 bb_error_msg_and_die("cannot fork");
416 else if (pid == 0) { /* If child. */
417 /* Don't cast execvp arguments; that causes errors on some systems,
418 versus merely warnings if the cast is left off. */
420 bb_error_msg("cannot run %s", cmd[0]);
421 _exit(errno == ENOENT ? 127 : 126);
424 /* Have signals kill the child but not self (if possible). */
425 interrupt_signal = signal(SIGINT, SIG_IGN);
426 quit_signal = signal(SIGQUIT, SIG_IGN);
428 if (resuse_end(pid, resp) == 0)
429 bb_error_msg("error waiting for child process");
431 /* Re-enable signals. */
432 signal(SIGINT, interrupt_signal);
433 signal(SIGQUIT, quit_signal);
436 int time_main(int argc, char **argv)
440 const char *output_format = default_format;
444 /* Parse any options -- don't use getopt() here so we don't
445 * consume the args of our client application... */
446 while (argc > 0 && **argv == '-') {
448 while (gotone == 0 && *++(*argv)) {
451 output_format = long_format;
454 output_format = posix_format;
465 if (argv == NULL || *argv == NULL)
468 run_command(argv, &res);
469 summarize(stderr, output_format, argv, &res);
472 if (WIFSTOPPED(res.waitstatus))
473 exit(WSTOPSIG(res.waitstatus));
474 else if (WIFSIGNALED(res.waitstatus))
475 exit(WTERMSIG(res.waitstatus));
476 else if (WIFEXITED(res.waitstatus))
477 exit(WEXITSTATUS(res.waitstatus));