time: *const str = "xxx" ==> str[] = "xxx"
[oweals/busybox.git] / miscutils / time.c
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.
4
5    Licensed under GPL version 2, see file LICENSE in this tarball for details.
6 */
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>
10 */
11
12 #include "libbb.h"
13
14 #define TV_MSEC tv_usec / 1000
15
16 /* Information on the resources used by a child process.  */
17 typedef struct {
18         int waitstatus;
19         struct rusage ru;
20         struct timeval start, elapsed;  /* Wallclock time of process.  */
21 } resource_t;
22
23 /* msec = milliseconds = 1/1,000 (1*10e-3) second.
24    usec = microseconds = 1/1,000,000 (1*10e-6) second.  */
25
26 #ifndef TICKS_PER_SEC
27 #define TICKS_PER_SEC 100
28 #endif
29
30 /* The number of milliseconds in one `tick' used by the `rusage' structure.  */
31 #define MSEC_PER_TICK (1000 / TICKS_PER_SEC)
32
33 /* Return the number of clock ticks that occur in M milliseconds.  */
34 #define MSEC_TO_TICKS(m) ((m) / MSEC_PER_TICK)
35
36 #define UL unsigned long
37
38 static const char default_format[] = "real\t%E\nuser\t%u\nsys\t%T";
39
40 /* The output format for the -p option .*/
41 static const char posix_format[] = "real %e\nuser %U\nsys %S";
42
43
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 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"
62         "\tSwaps: %W\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"
69         "\tExit status: %x";
70
71
72 /* Wait for and fill in data on child process PID.
73    Return 0 on error, 1 if ok.  */
74
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)
77 {
78         int status;
79
80         pid_t caught;
81
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) {
85                 if (caught == -1)
86                         return 0;
87         }
88
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;
95         }
96         resp->elapsed.tv_usec -= resp->start.tv_usec;
97
98         resp->waitstatus = status;
99
100         return 1;
101 }
102
103 /* Print ARGV, with each entry in ARGV separated by FILLER.  */
104 static void printargv(char *const *argv, const char *filler)
105 {
106         fputs(*argv, stdout);
107         while (*++argv) {
108                 fputs(filler, stdout);
109                 fputs(*argv, stdout);
110         }
111 }
112
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.)
115
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.  */
120
121 static unsigned long ptok(unsigned long pages)
122 {
123         static unsigned long ps;
124         unsigned long tmp;
125
126         /* Initialization.  */
127         if (ps == 0)
128                 ps = getpagesize();
129
130         /* Conversion.  */
131         if (pages > (LONG_MAX / ps)) {  /* Could overflow.  */
132                 tmp = pages / 1024;     /* Smaller first, */
133                 return tmp * ps;        /* then larger.  */
134         }
135         /* Could underflow.  */
136         tmp = pages * ps;       /* Larger first, */
137         return tmp / 1024;      /* then smaller.  */
138 }
139
140 /* summarize: Report on the system use of a command.
141
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 `\'.
145
146    The character following a `%' can be:
147    (* means the tcsh time builtin also recognizes it)
148    % == a literal `%'
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)
165    Z == page size
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
175
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.
179
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.  */
183
184 static void summarize(const char *fmt, char **command, resource_t * resp)
185 {
186         unsigned long r;        /* Elapsed real milliseconds.  */
187         unsigned long v;        /* Elapsed virtual (CPU) milliseconds.  */
188
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));
198
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.  */
203
204         r = resp->elapsed.tv_sec * 1000 + resp->elapsed.tv_usec / 1000;
205
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;
208
209         /* putchar() != putc(stdout) in glibc! */
210
211         while (*fmt) {
212                 /* Handle leading literal part */
213                 int n = strcspn(fmt, "%\\");
214                 if (n) {
215                         printf("%.*s", n, fmt);
216                         fmt += n;
217                         continue;
218                 }
219
220                 switch (*fmt) {
221 #ifdef NOT_NEEDED
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 */
225                 default:
226                         putc(*fmt, stdout);
227                         break;
228 #endif
229
230                 case '%':
231                         switch (*++fmt) {
232 #ifdef NOT_NEEDED_YET
233                 /* Our format strings do not have these */
234                 /* and we do not take format str from user */
235                         default:
236                                 putc('%', stdout);
237                                 /*FALLTHROUGH*/
238                         case '%':
239                                 if (!*fmt) goto ret;
240                                 putc(*fmt, stdout);
241                                 break;
242 #endif
243                         case 'C':       /* The command that got timed.  */
244                                 printargv(command, " ");
245                                 break;
246                         case 'D':       /* Average unshared data size.  */
247                                 printf("%lu",
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));
251                                 break;
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);
258                                 else
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);
263                                 break;
264                         case 'F':       /* Major page faults.  */
265                                 printf("%ld", resp->ru.ru_majflt);
266                                 break;
267                         case 'I':       /* Inputs.  */
268                                 printf("%ld", resp->ru.ru_inblock);
269                                 break;
270                         case 'K':       /* Average mem usage == data+stack+text.  */
271                                 printf("%lu",
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));
276                                 break;
277                         case 'M':       /* Maximum resident set size.  */
278                                 printf("%lu", ptok((UL) resp->ru.ru_maxrss));
279                                 break;
280                         case 'O':       /* Outputs.  */
281                                 printf("%ld", resp->ru.ru_oublock);
282                                 break;
283                         case 'P':       /* Percent of CPU this job got.  */
284                                 /* % cpu is (total cpu time)/(elapsed time).  */
285                                 if (r > 0)
286                                         printf("%lu%%", (v * 100 / r));
287                                 else
288                                         printf("?%%");
289                                 break;
290                         case 'R':       /* Minor page faults (reclaims).  */
291                                 printf("%ld", resp->ru.ru_minflt);
292                                 break;
293                         case 'S':       /* System time.  */
294                                 printf("%ld.%02ld",
295                                                 resp->ru.ru_stime.tv_sec,
296                                                 resp->ru.ru_stime.TV_MSEC / 10);
297                                 break;
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);
304                                 else
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);
309                                 break;
310                         case 'U':       /* User time.  */
311                                 printf("%ld.%02ld",
312                                                 resp->ru.ru_utime.tv_sec,
313                                                 resp->ru.ru_utime.TV_MSEC / 10);
314                                 break;
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);
321                                 else
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);
326                                 break;
327                         case 'W':       /* Times swapped out.  */
328                                 printf("%ld", resp->ru.ru_nswap);
329                                 break;
330                         case 'X':       /* Average shared text size.  */
331                                 printf("%lu",
332                                                 MSEC_TO_TICKS(v) == 0 ? 0 :
333                                                 ptok((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS(v));
334                                 break;
335                         case 'Z':       /* Page size.  */
336                                 printf("%d", getpagesize());
337                                 break;
338                         case 'c':       /* Involuntary context switches.  */
339                                 printf("%ld", resp->ru.ru_nivcsw);
340                                 break;
341                         case 'e':       /* Elapsed real time in seconds.  */
342                                 printf("%ld.%02ld",
343                                                 resp->elapsed.tv_sec, resp->elapsed.tv_usec / 10000);
344                                 break;
345                         case 'k':       /* Signals delivered.  */
346                                 printf("%ld", resp->ru.ru_nsignals);
347                                 break;
348                         case 'p':       /* Average stack segment.  */
349                                 printf("%lu",
350                                                 MSEC_TO_TICKS(v) == 0 ? 0 :
351                                                 ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v));
352                                 break;
353                         case 'r':       /* Incoming socket messages received.  */
354                                 printf("%ld", resp->ru.ru_msgrcv);
355                                 break;
356                         case 's':       /* Outgoing socket messages sent.  */
357                                 printf("%ld", resp->ru.ru_msgsnd);
358                                 break;
359                         case 't':       /* Average resident set size.  */
360                                 printf("%lu",
361                                                 MSEC_TO_TICKS(v) == 0 ? 0 :
362                                                 ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v));
363                                 break;
364                         case 'w':       /* Voluntary context switches.  */
365                                 printf("%ld", resp->ru.ru_nvcsw);
366                                 break;
367                         case 'x':       /* Exit status.  */
368                                 printf("%d", WEXITSTATUS(resp->waitstatus));
369                                 break;
370                         }
371                         break;
372
373 #ifdef NOT_NEEDED_YET
374                 case '\\':              /* Format escape.  */
375                         switch (*++fmt) {
376                         default:
377                                 putc('\\', stdout);
378                                 /*FALLTHROUGH*/
379                         case '\\':
380                                 if (!*fmt) goto ret;
381                                 putc(*fmt, stdout);
382                                 break;
383                         case 't':
384                                 putc('\t', stdout);
385                                 break;
386                         case 'n':
387                                 putc('\n', stdout);
388                                 break;
389                         }
390                         break;
391 #endif
392                 }
393                 ++fmt;
394         }
395  /* ret: */
396         putc('\n', stdout);
397 }
398
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)
402 {
403         pid_t pid;                      /* Pid of child.  */
404         __sighandler_t interrupt_signal, quit_signal;
405
406         gettimeofday(&resp->start, (struct timezone *) 0);
407         pid = vfork();          /* Run CMD as child process.  */
408         if (pid < 0)
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);
416         }
417
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);
421
422         if (resuse_end(pid, resp) == 0)
423                 bb_error_msg("error waiting for child process");
424
425         /* Re-enable signals.  */
426         signal(SIGINT, interrupt_signal);
427         signal(SIGQUIT, quit_signal);
428 }
429
430 int time_main(int argc, char **argv);
431 int time_main(int argc, char **argv)
432 {
433         resource_t res;
434         const char *output_format = default_format;
435         char c;
436
437         goto next;
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)) {
442                         switch (c) {
443                         case 'v':
444                                 output_format = long_format;
445                                 break;
446                         case 'p':
447                                 output_format = posix_format;
448                                 break;
449                         default:
450                                 bb_show_usage();
451                         }
452                 }
453  next:
454                 argv++;
455                 argc--;
456                 if (!argc)
457                         bb_show_usage();
458         }
459
460         run_command(argv, &res);
461
462         /* Cheat. printf's are shorter :) */
463         stdout = stderr;
464         dup2(2, 1); /* just in case libc does something silly :( */
465         summarize(output_format, argv, &res);
466
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);
474 }