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" "\tExit status: %x";
71 /* Wait for and fill in data on child process PID.
72 Return 0 on error, 1 if ok. */
74 /* pid_t is short on BSDI, so don't try to promote it. */
75 static int resuse_end(pid_t pid, resource_t * resp)
81 /* Ignore signals, but don't ignore the children. When wait3
82 returns the child process, set the time the command finished. */
83 while ((caught = wait3(&status, 0, &resp->ru)) != pid) {
88 gettimeofday(&resp->elapsed, (struct timezone *) 0);
89 resp->elapsed.tv_sec -= resp->start.tv_sec;
90 if (resp->elapsed.tv_usec < resp->start.tv_usec) {
91 /* Manually carry a one from the seconds field. */
92 resp->elapsed.tv_usec += 1000000;
93 --resp->elapsed.tv_sec;
95 resp->elapsed.tv_usec -= resp->start.tv_usec;
97 resp->waitstatus = status;
102 /* Print ARGV to FP, with each entry in ARGV separated by FILLER. */
103 static void fprintargv(FILE * fp, char *const *argv, const char *filler)
114 bb_error_msg_and_die(bb_msg_write_error);
117 /* Return the number of kilobytes corresponding to a number of pages PAGES.
118 (Actually, we use it to convert pages*ticks into kilobytes*ticks.)
120 Try to do arithmetic so that the risk of overflow errors is minimized.
121 This is funky since the pagesize could be less than 1K.
122 Note: Some machines express getrusage statistics in terms of K,
123 others in terms of pages. */
125 static unsigned long ptok(unsigned long pages)
127 static unsigned long ps = 0;
129 static long size = LONG_MAX;
131 /* Initialization. */
133 ps = (long) getpagesize();
136 if (pages > (LONG_MAX / ps)) { /* Could overflow. */
137 tmp = pages / 1024; /* Smaller first, */
138 size = tmp * ps; /* then larger. */
139 } else { /* Could underflow. */
140 tmp = pages * ps; /* Larger first, */
141 size = tmp / 1024; /* then smaller. */
146 /* summarize: Report on the system use of a command.
148 Copy the FMT argument to FP except that `%' sequences
149 have special meaning, and `\n' and `\t' are translated into
150 newline and tab, respectively, and `\\' is translated into `\'.
152 The character following a `%' can be:
153 (* means the tcsh time builtin also recognizes it)
155 C == command name and arguments
156 * D == average unshared data size in K (ru_idrss+ru_isrss)
157 * E == elapsed real (wall clock) time in [hour:]min:sec
158 * F == major page faults (required physical I/O) (ru_majflt)
159 * I == file system inputs (ru_inblock)
160 * K == average total mem usage (ru_idrss+ru_isrss+ru_ixrss)
161 * M == maximum resident set size in K (ru_maxrss)
162 * O == file system outputs (ru_oublock)
163 * P == percent of CPU this job got (total cpu time / elapsed time)
164 * R == minor page faults (reclaims; no physical I/O involved) (ru_minflt)
165 * S == system (kernel) time (seconds) (ru_stime)
166 * T == system time in [hour:]min:sec
167 * U == user time (seconds) (ru_utime)
168 * u == user time in [hour:]min:sec
169 * W == times swapped out (ru_nswap)
170 * X == average amount of shared text in K (ru_ixrss)
172 * c == involuntary context switches (ru_nivcsw)
173 e == elapsed real time in seconds
174 * k == signals delivered (ru_nsignals)
175 p == average unshared stack size in K (ru_isrss)
176 * r == socket messages received (ru_msgrcv)
177 * s == socket messages sent (ru_msgsnd)
178 t == average resident set size in K (ru_idrss)
179 * w == voluntary context switches (ru_nvcsw)
180 x == exit status of command
182 Various memory usages are found by converting from page-seconds
183 to kbytes by multiplying by the page size, dividing by 1024,
184 and dividing by elapsed real time.
186 FP is the stream to print to.
187 FMT is the format string, interpreted as described above.
188 COMMAND is the command and args that are being summarized.
189 RESP is resource information on the command. */
191 static void summarize(FILE * fp, const char *fmt, char **command,
194 unsigned long r; /* Elapsed real milliseconds. */
195 unsigned long v; /* Elapsed virtual (CPU) milliseconds. */
197 if (WIFSTOPPED(resp->waitstatus))
198 fprintf(fp, "Command stopped by signal %d\n",
199 WSTOPSIG(resp->waitstatus));
200 else if (WIFSIGNALED(resp->waitstatus))
201 fprintf(fp, "Command terminated by signal %d\n",
202 WTERMSIG(resp->waitstatus));
203 else if (WIFEXITED(resp->waitstatus) && WEXITSTATUS(resp->waitstatus))
204 fprintf(fp, "Command exited with non-zero status %d\n",
205 WEXITSTATUS(resp->waitstatus));
207 /* Convert all times to milliseconds. Occasionally, one of these values
208 comes out as zero. Dividing by zero causes problems, so we first
209 check the time value. If it is zero, then we take `evasive action'
210 instead of calculating a value. */
212 r = resp->elapsed.tv_sec * 1000 + resp->elapsed.tv_usec / 1000;
214 v = resp->ru.ru_utime.tv_sec * 1000 + resp->ru.ru_utime.TV_MSEC +
215 resp->ru.ru_stime.tv_sec * 1000 + resp->ru.ru_stime.TV_MSEC;
221 case '%': /* Literal '%'. */
224 case 'C': /* The command that got timed. */
225 fprintargv(fp, command, " ");
227 case 'D': /* Average unshared data size. */
229 MSEC_TO_TICKS(v) == 0 ? 0 :
230 ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v) +
231 ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v));
233 case 'E': /* Elapsed real (wall clock) time. */
234 if (resp->elapsed.tv_sec >= 3600) /* One hour -> h:m:s. */
235 fprintf(fp, "%ldh %ldm %02lds",
236 resp->elapsed.tv_sec / 3600,
237 (resp->elapsed.tv_sec % 3600) / 60,
238 resp->elapsed.tv_sec % 60);
240 fprintf(fp, "%ldm %ld.%02lds", /* -> m:s. */
241 resp->elapsed.tv_sec / 60,
242 resp->elapsed.tv_sec % 60,
243 resp->elapsed.tv_usec / 10000);
245 case 'F': /* Major page faults. */
246 fprintf(fp, "%ld", resp->ru.ru_majflt);
248 case 'I': /* Inputs. */
249 fprintf(fp, "%ld", resp->ru.ru_inblock);
251 case 'K': /* Average mem usage == data+stack+text. */
253 MSEC_TO_TICKS(v) == 0 ? 0 :
254 ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v) +
255 ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v) +
256 ptok((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS(v));
258 case 'M': /* Maximum resident set size. */
259 fprintf(fp, "%lu", ptok((UL) resp->ru.ru_maxrss));
261 case 'O': /* Outputs. */
262 fprintf(fp, "%ld", resp->ru.ru_oublock);
264 case 'P': /* Percent of CPU this job got. */
265 /* % cpu is (total cpu time)/(elapsed time). */
267 fprintf(fp, "%lu%%", (v * 100 / r));
271 case 'R': /* Minor page faults (reclaims). */
272 fprintf(fp, "%ld", resp->ru.ru_minflt);
274 case 'S': /* System time. */
275 fprintf(fp, "%ld.%02ld",
276 resp->ru.ru_stime.tv_sec,
277 resp->ru.ru_stime.TV_MSEC / 10);
279 case 'T': /* System time. */
280 if (resp->ru.ru_stime.tv_sec >= 3600) /* One hour -> h:m:s. */
281 fprintf(fp, "%ldh %ldm %02lds",
282 resp->ru.ru_stime.tv_sec / 3600,
283 (resp->ru.ru_stime.tv_sec % 3600) / 60,
284 resp->ru.ru_stime.tv_sec % 60);
286 fprintf(fp, "%ldm %ld.%02lds", /* -> m:s. */
287 resp->ru.ru_stime.tv_sec / 60,
288 resp->ru.ru_stime.tv_sec % 60,
289 resp->ru.ru_stime.tv_usec / 10000);
291 case 'U': /* User time. */
292 fprintf(fp, "%ld.%02ld",
293 resp->ru.ru_utime.tv_sec,
294 resp->ru.ru_utime.TV_MSEC / 10);
296 case 'u': /* User time. */
297 if (resp->ru.ru_utime.tv_sec >= 3600) /* One hour -> h:m:s. */
298 fprintf(fp, "%ldh %ldm %02lds",
299 resp->ru.ru_utime.tv_sec / 3600,
300 (resp->ru.ru_utime.tv_sec % 3600) / 60,
301 resp->ru.ru_utime.tv_sec % 60);
303 fprintf(fp, "%ldm %ld.%02lds", /* -> m:s. */
304 resp->ru.ru_utime.tv_sec / 60,
305 resp->ru.ru_utime.tv_sec % 60,
306 resp->ru.ru_utime.tv_usec / 10000);
308 case 'W': /* Times swapped out. */
309 fprintf(fp, "%ld", resp->ru.ru_nswap);
311 case 'X': /* Average shared text size. */
313 MSEC_TO_TICKS(v) == 0 ? 0 :
314 ptok((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS(v));
316 case 'Z': /* Page size. */
317 fprintf(fp, "%d", getpagesize());
319 case 'c': /* Involuntary context switches. */
320 fprintf(fp, "%ld", resp->ru.ru_nivcsw);
322 case 'e': /* Elapsed real time in seconds. */
323 fprintf(fp, "%ld.%02ld",
324 resp->elapsed.tv_sec, resp->elapsed.tv_usec / 10000);
326 case 'k': /* Signals delivered. */
327 fprintf(fp, "%ld", resp->ru.ru_nsignals);
329 case 'p': /* Average stack segment. */
331 MSEC_TO_TICKS(v) == 0 ? 0 :
332 ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v));
334 case 'r': /* Incoming socket messages received. */
335 fprintf(fp, "%ld", resp->ru.ru_msgrcv);
337 case 's': /* Outgoing socket messages sent. */
338 fprintf(fp, "%ld", resp->ru.ru_msgsnd);
340 case 't': /* Average resident set size. */
342 MSEC_TO_TICKS(v) == 0 ? 0 :
343 ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v));
345 case 'w': /* Voluntary context switches. */
346 fprintf(fp, "%ld", resp->ru.ru_nvcsw);
348 case 'x': /* Exit status. */
349 fprintf(fp, "%d", WEXITSTATUS(resp->waitstatus));
361 case '\\': /* Format escape. */
385 bb_error_msg_and_die(bb_msg_write_error);
390 bb_error_msg_and_die(bb_msg_write_error);
393 /* Run command CMD and return statistics on it.
394 Put the statistics in *RESP. */
395 static void run_command(char *const *cmd, resource_t * resp)
397 pid_t pid; /* Pid of child. */
398 __sighandler_t interrupt_signal, quit_signal;
400 gettimeofday(&resp->start, (struct timezone *) 0);
401 pid = vfork(); /* Run CMD as child process. */
403 bb_error_msg_and_die("cannot fork");
404 else if (pid == 0) { /* If child. */
405 /* Don't cast execvp arguments; that causes errors on some systems,
406 versus merely warnings if the cast is left off. */
408 bb_error_msg("cannot run %s", cmd[0]);
409 _exit(errno == ENOENT ? 127 : 126);
412 /* Have signals kill the child but not self (if possible). */
413 interrupt_signal = signal(SIGINT, SIG_IGN);
414 quit_signal = signal(SIGQUIT, SIG_IGN);
416 if (resuse_end(pid, resp) == 0)
417 bb_error_msg("error waiting for child process");
419 /* Re-enable signals. */
420 signal(SIGINT, interrupt_signal);
421 signal(SIGQUIT, quit_signal);
424 int time_main(int argc, char **argv)
428 const char *output_format = default_format;
432 /* Parse any options -- don't use getopt() here so we don't
433 * consume the args of our client application... */
434 while (argc > 0 && **argv == '-') {
436 while (gotone == 0 && *++(*argv)) {
439 output_format = long_format;
442 output_format = posix_format;
453 if (argv == NULL || *argv == NULL)
456 run_command(argv, &res);
457 summarize(stderr, output_format, argv, &res);
460 if (WIFSTOPPED(res.waitstatus))
461 exit(WSTOPSIG(res.waitstatus));
462 else if (WIFSIGNALED(res.waitstatus))
463 exit(WTERMSIG(res.waitstatus));
464 else if (WIFEXITED(res.waitstatus))
465 exit(WEXITSTATUS(res.waitstatus));