blob: 9d0d6c05f0eec44a7575ead0b8194b54275354dc [file] [log] [blame]
Eric Andersenc3657422001-11-30 07:54:32 +00001/* `time' utility to display resource usage of processes.
2 Copyright (C) 1990, 91, 92, 93, 96 Free Software Foundation, Inc.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
7 any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 02111-1307, USA. */
18
19/* Originally written by David Keppel <pardo@cs.washington.edu>.
Eric Andersenc7bda1c2004-03-15 08:29:22 +000020 Heavily modified by David MacKenzie <djm@gnu.ai.mit.edu>.
Eric Andersenc3657422001-11-30 07:54:32 +000021 Heavily modified for busybox by Erik Andersen <andersen@codepoet.org>
22 */
23
Bernhard Reutner-Fischerc89982d2006-06-03 19:49:21 +000024#include "busybox.h"
Eric Andersenc3657422001-11-30 07:54:32 +000025#include <stdlib.h>
26#include <stdio.h>
27#include <signal.h>
28#include <errno.h>
29#include <getopt.h>
30#include <string.h>
31#include <limits.h>
32#include <unistd.h>
Eric Andersenc3657422001-11-30 07:54:32 +000033#include <sys/types.h> /* For pid_t. */
34#include <sys/wait.h>
35#include <sys/param.h> /* For getpagesize, maybe. */
36
37#define TV_MSEC tv_usec / 1000
38#include <sys/resource.h>
Eric Andersenc3657422001-11-30 07:54:32 +000039
40/* Information on the resources used by a child process. */
41typedef struct
42{
43 int waitstatus;
44 struct rusage ru;
45 struct timeval start, elapsed; /* Wallclock time of process. */
46} resource_t;
47
48/* msec = milliseconds = 1/1,000 (1*10e-3) second.
49 usec = microseconds = 1/1,000,000 (1*10e-6) second. */
50
51#ifndef TICKS_PER_SEC
52#define TICKS_PER_SEC 100
53#endif
54
55/* The number of milliseconds in one `tick' used by the `rusage' structure. */
56#define MSEC_PER_TICK (1000 / TICKS_PER_SEC)
57
58/* Return the number of clock ticks that occur in M milliseconds. */
59#define MSEC_TO_TICKS(m) ((m) / MSEC_PER_TICK)
60
61#define UL unsigned long
62
63static const char *const default_format = "real\t%E\nuser\t%u\nsys\t%T";
64
65/* The output format for the -p option .*/
66static const char *const posix_format = "real %e\nuser %U\nsys %S";
67
68
69/* Format string for printing all statistics verbosely.
70 Keep this output to 24 lines so users on terminals can see it all.*/
71static const char *const long_format =
72 "\tCommand being timed: \"%C\"\n"
73 "\tUser time (seconds): %U\n"
74 "\tSystem time (seconds): %S\n"
75 "\tPercent of CPU this job got: %P\n"
76 "\tElapsed (wall clock) time (h:mm:ss or m:ss): %E\n"
77 "\tAverage shared text size (kbytes): %X\n"
78 "\tAverage unshared data size (kbytes): %D\n"
79 "\tAverage stack size (kbytes): %p\n"
80 "\tAverage total size (kbytes): %K\n"
81 "\tMaximum resident set size (kbytes): %M\n"
82 "\tAverage resident set size (kbytes): %t\n"
83 "\tMajor (requiring I/O) page faults: %F\n"
84 "\tMinor (reclaiming a frame) page faults: %R\n"
85 "\tVoluntary context switches: %w\n"
86 "\tInvoluntary context switches: %c\n"
87 "\tSwaps: %W\n"
88 "\tFile system inputs: %I\n"
89 "\tFile system outputs: %O\n"
90 "\tSocket messages sent: %s\n"
91 "\tSocket messages received: %r\n"
92 "\tSignals delivered: %k\n"
93 "\tPage size (bytes): %Z\n"
94 "\tExit status: %x";
95
96
97 /* Wait for and fill in data on child process PID.
98 Return 0 on error, 1 if ok. */
99
100/* pid_t is short on BSDI, so don't try to promote it. */
101static int resuse_end (pid_t pid, resource_t *resp)
102{
103 int status;
104
105 pid_t caught;
106
107 /* Ignore signals, but don't ignore the children. When wait3
108 returns the child process, set the time the command finished. */
109 while ((caught = wait3 (&status, 0, &resp->ru)) != pid)
110 {
111 if (caught == -1)
112 return 0;
113 }
114
115 gettimeofday (&resp->elapsed, (struct timezone *) 0);
116 resp->elapsed.tv_sec -= resp->start.tv_sec;
117 if (resp->elapsed.tv_usec < resp->start.tv_usec)
118 {
119 /* Manually carry a one from the seconds field. */
120 resp->elapsed.tv_usec += 1000000;
121 --resp->elapsed.tv_sec;
122 }
123 resp->elapsed.tv_usec -= resp->start.tv_usec;
124
125 resp->waitstatus = status;
126
127 return 1;
128}
129
130/* Print ARGV to FP, with each entry in ARGV separated by FILLER. */
131static void fprintargv (FILE *fp, char *const *argv, const char *filler)
132{
133 char *const *av;
134
135 av = argv;
136 fputs (*av, fp);
137 while (*++av)
138 {
139 fputs (filler, fp);
140 fputs (*av, fp);
141 }
142 if (ferror (fp))
Manuel Novoa III cad53642003-03-19 09:13:01 +0000143 bb_error_msg_and_die("write error");
Eric Andersenc3657422001-11-30 07:54:32 +0000144}
145
146/* Return the number of kilobytes corresponding to a number of pages PAGES.
147 (Actually, we use it to convert pages*ticks into kilobytes*ticks.)
148
149 Try to do arithmetic so that the risk of overflow errors is minimized.
150 This is funky since the pagesize could be less than 1K.
151 Note: Some machines express getrusage statistics in terms of K,
152 others in terms of pages. */
153
154static unsigned long ptok (unsigned long pages)
155{
156 static unsigned long ps = 0;
157 unsigned long tmp;
158 static long size = LONG_MAX;
159
160 /* Initialization. */
161 if (ps == 0)
162 ps = (long) getpagesize ();
163
164 /* Conversion. */
165 if (pages > (LONG_MAX / ps))
166 { /* Could overflow. */
167 tmp = pages / 1024; /* Smaller first, */
168 size = tmp * ps; /* then larger. */
169 }
170 else
171 { /* Could underflow. */
172 tmp = pages * ps; /* Larger first, */
173 size = tmp / 1024; /* then smaller. */
174 }
175 return size;
176}
177
178/* summarize: Report on the system use of a command.
179
180 Copy the FMT argument to FP except that `%' sequences
181 have special meaning, and `\n' and `\t' are translated into
182 newline and tab, respectively, and `\\' is translated into `\'.
183
184 The character following a `%' can be:
185 (* means the tcsh time builtin also recognizes it)
186 % == a literal `%'
187 C == command name and arguments
188* D == average unshared data size in K (ru_idrss+ru_isrss)
189* E == elapsed real (wall clock) time in [hour:]min:sec
190* F == major page faults (required physical I/O) (ru_majflt)
191* I == file system inputs (ru_inblock)
192* K == average total mem usage (ru_idrss+ru_isrss+ru_ixrss)
193* M == maximum resident set size in K (ru_maxrss)
194* O == file system outputs (ru_oublock)
195* P == percent of CPU this job got (total cpu time / elapsed time)
196* R == minor page faults (reclaims; no physical I/O involved) (ru_minflt)
197* S == system (kernel) time (seconds) (ru_stime)
198* T == system time in [hour:]min:sec
199* U == user time (seconds) (ru_utime)
200* u == user time in [hour:]min:sec
201* W == times swapped out (ru_nswap)
202* X == average amount of shared text in K (ru_ixrss)
203 Z == page size
204* c == involuntary context switches (ru_nivcsw)
205 e == elapsed real time in seconds
206* k == signals delivered (ru_nsignals)
207 p == average unshared stack size in K (ru_isrss)
208* r == socket messages received (ru_msgrcv)
209* s == socket messages sent (ru_msgsnd)
210 t == average resident set size in K (ru_idrss)
211* w == voluntary context switches (ru_nvcsw)
212 x == exit status of command
213
214 Various memory usages are found by converting from page-seconds
215 to kbytes by multiplying by the page size, dividing by 1024,
216 and dividing by elapsed real time.
217
218 FP is the stream to print to.
219 FMT is the format string, interpreted as described above.
220 COMMAND is the command and args that are being summarized.
221 RESP is resource information on the command. */
222
223static void summarize (FILE *fp, const char *fmt, char **command, resource_t *resp)
224{
225 unsigned long r; /* Elapsed real milliseconds. */
226 unsigned long v; /* Elapsed virtual (CPU) milliseconds. */
227
228 if (WIFSTOPPED (resp->waitstatus))
229 fprintf (fp, "Command stopped by signal %d\n", WSTOPSIG (resp->waitstatus));
230 else if (WIFSIGNALED (resp->waitstatus))
231 fprintf (fp, "Command terminated by signal %d\n", WTERMSIG (resp->waitstatus));
232 else if (WIFEXITED (resp->waitstatus) && WEXITSTATUS (resp->waitstatus))
233 fprintf (fp, "Command exited with non-zero status %d\n", WEXITSTATUS (resp->waitstatus));
234
235 /* Convert all times to milliseconds. Occasionally, one of these values
236 comes out as zero. Dividing by zero causes problems, so we first
237 check the time value. If it is zero, then we take `evasive action'
238 instead of calculating a value. */
239
240 r = resp->elapsed.tv_sec * 1000 + resp->elapsed.tv_usec / 1000;
241
242 v = resp->ru.ru_utime.tv_sec * 1000 + resp->ru.ru_utime.TV_MSEC +
243 resp->ru.ru_stime.tv_sec * 1000 + resp->ru.ru_stime.TV_MSEC;
244
245 while (*fmt)
246 {
247 switch (*fmt)
248 {
249 case '%':
250 switch (*++fmt)
251 {
252 case '%': /* Literal '%'. */
253 putc ('%', fp);
254 break;
255 case 'C': /* The command that got timed. */
256 fprintargv (fp, command, " ");
257 break;
258 case 'D': /* Average unshared data size. */
259 fprintf (fp, "%lu",
260 MSEC_TO_TICKS (v) == 0 ? 0 :
261 ptok ((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS (v) +
262 ptok ((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS (v));
263 break;
264 case 'E': /* Elapsed real (wall clock) time. */
265 if (resp->elapsed.tv_sec >= 3600) /* One hour -> h:m:s. */
266 fprintf (fp, "%ldh %ldm %02lds",
267 resp->elapsed.tv_sec / 3600,
268 (resp->elapsed.tv_sec % 3600) / 60,
269 resp->elapsed.tv_sec % 60);
270 else
271 fprintf (fp, "%ldm %ld.%02lds", /* -> m:s. */
272 resp->elapsed.tv_sec / 60,
273 resp->elapsed.tv_sec % 60,
274 resp->elapsed.tv_usec / 10000);
275 break;
276 case 'F': /* Major page faults. */
277 fprintf (fp, "%ld", resp->ru.ru_majflt);
278 break;
279 case 'I': /* Inputs. */
280 fprintf (fp, "%ld", resp->ru.ru_inblock);
281 break;
282 case 'K': /* Average mem usage == data+stack+text. */
283 fprintf (fp, "%lu",
284 MSEC_TO_TICKS (v) == 0 ? 0 :
285 ptok ((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS (v) +
286 ptok ((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS (v) +
287 ptok ((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS (v));
288 break;
289 case 'M': /* Maximum resident set size. */
290 fprintf (fp, "%lu", ptok ((UL) resp->ru.ru_maxrss));
291 break;
292 case 'O': /* Outputs. */
293 fprintf (fp, "%ld", resp->ru.ru_oublock);
294 break;
295 case 'P': /* Percent of CPU this job got. */
296 /* % cpu is (total cpu time)/(elapsed time). */
297 if (r > 0)
298 fprintf (fp, "%lu%%", (v * 100 / r));
299 else
300 fprintf (fp, "?%%");
301 break;
302 case 'R': /* Minor page faults (reclaims). */
303 fprintf (fp, "%ld", resp->ru.ru_minflt);
304 break;
305 case 'S': /* System time. */
306 fprintf (fp, "%ld.%02ld",
307 resp->ru.ru_stime.tv_sec,
308 resp->ru.ru_stime.TV_MSEC / 10);
309 break;
310 case 'T': /* System time. */
311 if (resp->ru.ru_stime.tv_sec >= 3600) /* One hour -> h:m:s. */
312 fprintf (fp, "%ldh %ldm %02lds",
313 resp->ru.ru_stime.tv_sec / 3600,
314 (resp->ru.ru_stime.tv_sec % 3600) / 60,
Eric Andersene4d2a432002-09-30 19:37:48 +0000315 resp->ru.ru_stime.tv_sec % 60);
Eric Andersenc3657422001-11-30 07:54:32 +0000316 else
317 fprintf (fp, "%ldm %ld.%02lds", /* -> m:s. */
318 resp->ru.ru_stime.tv_sec / 60,
319 resp->ru.ru_stime.tv_sec % 60,
Eric Andersen92052e32002-09-27 23:34:53 +0000320 resp->ru.ru_stime.tv_usec / 10000);
Eric Andersenc3657422001-11-30 07:54:32 +0000321 break;
322 case 'U': /* User time. */
323 fprintf (fp, "%ld.%02ld",
324 resp->ru.ru_utime.tv_sec,
325 resp->ru.ru_utime.TV_MSEC / 10);
326 break;
327 case 'u': /* User time. */
328 if (resp->ru.ru_utime.tv_sec >= 3600) /* One hour -> h:m:s. */
329 fprintf (fp, "%ldh %ldm %02lds",
330 resp->ru.ru_utime.tv_sec / 3600,
331 (resp->ru.ru_utime.tv_sec % 3600) / 60,
Eric Andersene4d2a432002-09-30 19:37:48 +0000332 resp->ru.ru_utime.tv_sec % 60);
Eric Andersenc3657422001-11-30 07:54:32 +0000333 else
334 fprintf (fp, "%ldm %ld.%02lds", /* -> m:s. */
335 resp->ru.ru_utime.tv_sec / 60,
336 resp->ru.ru_utime.tv_sec % 60,
Eric Andersen92052e32002-09-27 23:34:53 +0000337 resp->ru.ru_utime.tv_usec / 10000);
Eric Andersenc3657422001-11-30 07:54:32 +0000338 break;
339 case 'W': /* Times swapped out. */
340 fprintf (fp, "%ld", resp->ru.ru_nswap);
341 break;
342 case 'X': /* Average shared text size. */
343 fprintf (fp, "%lu",
344 MSEC_TO_TICKS (v) == 0 ? 0 :
345 ptok ((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS (v));
346 break;
347 case 'Z': /* Page size. */
348 fprintf (fp, "%d", getpagesize ());
349 break;
350 case 'c': /* Involuntary context switches. */
351 fprintf (fp, "%ld", resp->ru.ru_nivcsw);
352 break;
353 case 'e': /* Elapsed real time in seconds. */
354 fprintf (fp, "%ld.%02ld",
355 resp->elapsed.tv_sec,
356 resp->elapsed.tv_usec / 10000);
357 break;
358 case 'k': /* Signals delivered. */
359 fprintf (fp, "%ld", resp->ru.ru_nsignals);
360 break;
361 case 'p': /* Average stack segment. */
362 fprintf (fp, "%lu",
363 MSEC_TO_TICKS (v) == 0 ? 0 :
364 ptok ((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS (v));
365 break;
366 case 'r': /* Incoming socket messages received. */
367 fprintf (fp, "%ld", resp->ru.ru_msgrcv);
368 break;
369 case 's': /* Outgoing socket messages sent. */
370 fprintf (fp, "%ld", resp->ru.ru_msgsnd);
371 break;
372 case 't': /* Average resident set size. */
373 fprintf (fp, "%lu",
374 MSEC_TO_TICKS (v) == 0 ? 0 :
375 ptok ((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS (v));
376 break;
377 case 'w': /* Voluntary context switches. */
378 fprintf (fp, "%ld", resp->ru.ru_nvcsw);
379 break;
380 case 'x': /* Exit status. */
381 fprintf (fp, "%d", WEXITSTATUS (resp->waitstatus));
382 break;
383 case '\0':
384 putc ('?', fp);
385 return;
386 default:
387 putc ('?', fp);
388 putc (*fmt, fp);
389 }
390 ++fmt;
391 break;
392
393 case '\\': /* Format escape. */
394 switch (*++fmt)
395 {
396 case 't':
397 putc ('\t', fp);
398 break;
399 case 'n':
400 putc ('\n', fp);
401 break;
402 case '\\':
403 putc ('\\', fp);
404 break;
405 default:
406 putc ('?', fp);
407 putc ('\\', fp);
408 putc (*fmt, fp);
409 }
410 ++fmt;
411 break;
412
413 default:
414 putc (*fmt++, fp);
415 }
416
417 if (ferror (fp))
Manuel Novoa III cad53642003-03-19 09:13:01 +0000418 bb_error_msg_and_die("write error");
Eric Andersenc3657422001-11-30 07:54:32 +0000419 }
420 putc ('\n', fp);
421
422 if (ferror (fp))
Manuel Novoa III cad53642003-03-19 09:13:01 +0000423 bb_error_msg_and_die("write error");
Eric Andersenc3657422001-11-30 07:54:32 +0000424}
425
426/* Run command CMD and return statistics on it.
427 Put the statistics in *RESP. */
428static void run_command (char *const *cmd, resource_t *resp)
429{
430 pid_t pid; /* Pid of child. */
431 __sighandler_t interrupt_signal, quit_signal;
432
433 gettimeofday (&resp->start, (struct timezone *) 0);
434 pid = fork (); /* Run CMD as child process. */
435 if (pid < 0)
Manuel Novoa III cad53642003-03-19 09:13:01 +0000436 bb_error_msg_and_die("cannot fork");
Eric Andersenc3657422001-11-30 07:54:32 +0000437 else if (pid == 0)
438 { /* If child. */
439 /* Don't cast execvp arguments; that causes errors on some systems,
440 versus merely warnings if the cast is left off. */
441 execvp (cmd[0], cmd);
Manuel Novoa III cad53642003-03-19 09:13:01 +0000442 bb_error_msg("cannot run %s", cmd[0]);
Eric Andersenc3657422001-11-30 07:54:32 +0000443 _exit (errno == ENOENT ? 127 : 126);
444 }
445
446 /* Have signals kill the child but not self (if possible). */
447 interrupt_signal = signal (SIGINT, SIG_IGN);
448 quit_signal = signal (SIGQUIT, SIG_IGN);
449
450 if (resuse_end (pid, resp) == 0)
Manuel Novoa III cad53642003-03-19 09:13:01 +0000451 bb_error_msg("error waiting for child process");
Eric Andersenc3657422001-11-30 07:54:32 +0000452
453 /* Re-enable signals. */
454 signal (SIGINT, interrupt_signal);
455 signal (SIGQUIT, quit_signal);
456}
457
Rob Landleydfba7412006-03-06 20:47:33 +0000458int time_main (int argc, char **argv)
Eric Andersenc3657422001-11-30 07:54:32 +0000459{
460 int gotone;
461 resource_t res;
462 const char *output_format = default_format;
463
464 argc--;
465 argv++;
466 /* Parse any options -- don't use getopt() here so we don't
467 * consume the args of our client application... */
468 while (argc > 0 && **argv == '-') {
469 gotone = 0;
470 while (gotone==0 && *++(*argv)) {
471 switch (**argv) {
472 case 'v':
473 output_format = long_format;
474 break;
475 case 'p':
476 output_format = posix_format;
477 break;
478 default:
Manuel Novoa III cad53642003-03-19 09:13:01 +0000479 bb_show_usage();
Eric Andersenc3657422001-11-30 07:54:32 +0000480 }
481 argc--;
482 argv++;
483 gotone = 1;
484 }
485 }
486
487 if (argv == NULL || *argv == NULL)
Manuel Novoa III cad53642003-03-19 09:13:01 +0000488 bb_show_usage();
Eric Andersenc3657422001-11-30 07:54:32 +0000489
490 run_command (argv, &res);
Eric Andersen28492092003-11-07 21:39:14 +0000491 summarize (stderr, output_format, argv, &res);
492 fflush (stderr);
Eric Andersenc3657422001-11-30 07:54:32 +0000493
494 if (WIFSTOPPED (res.waitstatus))
495 exit (WSTOPSIG (res.waitstatus));
496 else if (WIFSIGNALED (res.waitstatus))
497 exit (WTERMSIG (res.waitstatus));
498 else if (WIFEXITED (res.waitstatus))
499 exit (WEXITSTATUS (res.waitstatus));
500 return 0;
501}