summaryrefslogtreecommitdiff
path: root/miscutils/time.c (plain)
blob: 23d18e160998bd0222deb28f13fddf7058fd675f
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 GPLv2, see file LICENSE in this source tree.
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//config:config TIME
12//config: bool "time"
13//config: default y
14//config: help
15//config: The time command runs the specified program with the given arguments.
16//config: When the command finishes, time writes a message to standard output
17//config: giving timing statistics about this program run.
18
19//applet:IF_TIME(APPLET(time, BB_DIR_USR_BIN, BB_SUID_DROP))
20
21//kbuild:lib-$(CONFIG_TIME) += time.o
22
23//usage:#define time_trivial_usage
24//usage: "[-v] PROG ARGS"
25//usage:#define time_full_usage "\n\n"
26//usage: "Run PROG, display resource usage when it exits\n"
27//usage: "\n -v Verbose"
28
29#include "libbb.h"
30#include <sys/resource.h> /* getrusage */
31#include <android.h>
32
33/* Information on the resources used by a child process. */
34typedef struct {
35 int waitstatus;
36 struct rusage ru;
37 unsigned elapsed_ms; /* Wallclock time of process. */
38} resource_t;
39
40/* msec = milliseconds = 1/1,000 (1*10e-3) second.
41 usec = microseconds = 1/1,000,000 (1*10e-6) second. */
42
43#define UL unsigned long
44
45static const char default_format[] ALIGN1 = "real\t%E\nuser\t%u\nsys\t%T";
46
47/* The output format for the -p option .*/
48static const char posix_format[] ALIGN1 = "real %e\nuser %U\nsys %S";
49
50/* Format string for printing all statistics verbosely.
51 Keep this output to 24 lines so users on terminals can see it all.*/
52static const char long_format[] ALIGN1 =
53 "\tCommand being timed: \"%C\"\n"
54 "\tUser time (seconds): %U\n"
55 "\tSystem time (seconds): %S\n"
56 "\tPercent of CPU this job got: %P\n"
57 "\tElapsed (wall clock) time (h:mm:ss or m:ss): %E\n"
58 "\tAverage shared text size (kbytes): %X\n"
59 "\tAverage unshared data size (kbytes): %D\n"
60 "\tAverage stack size (kbytes): %p\n"
61 "\tAverage total size (kbytes): %K\n"
62 "\tMaximum resident set size (kbytes): %M\n"
63 "\tAverage resident set size (kbytes): %t\n"
64 "\tMajor (requiring I/O) page faults: %F\n"
65 "\tMinor (reclaiming a frame) page faults: %R\n"
66 "\tVoluntary context switches: %w\n"
67 "\tInvoluntary context switches: %c\n"
68 "\tSwaps: %W\n"
69 "\tFile system inputs: %I\n"
70 "\tFile system outputs: %O\n"
71 "\tSocket messages sent: %s\n"
72 "\tSocket messages received: %r\n"
73 "\tSignals delivered: %k\n"
74 "\tPage size (bytes): %Z\n"
75 "\tExit status: %x";
76
77/* Wait for and fill in data on child process PID.
78 Return 0 on error, 1 if ok. */
79/* pid_t is short on BSDI, so don't try to promote it. */
80static void resuse_end(pid_t pid, resource_t *resp)
81{
82 pid_t caught;
83
84 /* Ignore signals, but don't ignore the children. When wait3
85 * returns the child process, set the time the command finished. */
86 while ((caught = wait3(&resp->waitstatus, 0, &resp->ru)) != pid) {
87 if (caught == -1 && errno != EINTR) {
88 bb_perror_msg("wait");
89 return;
90 }
91 }
92 resp->elapsed_ms = monotonic_ms() - resp->elapsed_ms;
93}
94
95static void printargv(char *const *argv)
96{
97 const char *fmt = " %s" + 1;
98 do {
99 printf(fmt, *argv);
100 fmt = " %s";
101 } while (*++argv);
102}
103
104/* Return the number of kilobytes corresponding to a number of pages PAGES.
105 (Actually, we use it to convert pages*ticks into kilobytes*ticks.)
106
107 Try to do arithmetic so that the risk of overflow errors is minimized.
108 This is funky since the pagesize could be less than 1K.
109 Note: Some machines express getrusage statistics in terms of K,
110 others in terms of pages. */
111static unsigned long ptok(const unsigned pagesize, const unsigned long pages)
112{
113 unsigned long tmp;
114
115 /* Conversion. */
116 if (pages > (LONG_MAX / pagesize)) { /* Could overflow. */
117 tmp = pages / 1024; /* Smaller first, */
118 return tmp * pagesize; /* then larger. */
119 }
120 /* Could underflow. */
121 tmp = pages * pagesize; /* Larger first, */
122 return tmp / 1024; /* then smaller. */
123}
124
125/* summarize: Report on the system use of a command.
126
127 Print the FMT argument except that `%' sequences
128 have special meaning, and `\n' and `\t' are translated into
129 newline and tab, respectively, and `\\' is translated into `\'.
130
131 The character following a `%' can be:
132 (* means the tcsh time builtin also recognizes it)
133 % == a literal `%'
134 C == command name and arguments
135* D == average unshared data size in K (ru_idrss+ru_isrss)
136* E == elapsed real (wall clock) time in [hour:]min:sec
137* F == major page faults (required physical I/O) (ru_majflt)
138* I == file system inputs (ru_inblock)
139* K == average total mem usage (ru_idrss+ru_isrss+ru_ixrss)
140* M == maximum resident set size in K (ru_maxrss)
141* O == file system outputs (ru_oublock)
142* P == percent of CPU this job got (total cpu time / elapsed time)
143* R == minor page faults (reclaims; no physical I/O involved) (ru_minflt)
144* S == system (kernel) time (seconds) (ru_stime)
145* T == system time in [hour:]min:sec
146* U == user time (seconds) (ru_utime)
147* u == user time in [hour:]min:sec
148* W == times swapped out (ru_nswap)
149* X == average amount of shared text in K (ru_ixrss)
150 Z == page size
151* c == involuntary context switches (ru_nivcsw)
152 e == elapsed real time in seconds
153* k == signals delivered (ru_nsignals)
154 p == average unshared stack size in K (ru_isrss)
155* r == socket messages received (ru_msgrcv)
156* s == socket messages sent (ru_msgsnd)
157 t == average resident set size in K (ru_idrss)
158* w == voluntary context switches (ru_nvcsw)
159 x == exit status of command
160
161 Various memory usages are found by converting from page-seconds
162 to kbytes by multiplying by the page size, dividing by 1024,
163 and dividing by elapsed real time.
164
165 FMT is the format string, interpreted as described above.
166 COMMAND is the command and args that are being summarized.
167 RESP is resource information on the command. */
168
169#ifndef TICKS_PER_SEC
170#define TICKS_PER_SEC 100
171#endif
172
173static void summarize(const char *fmt, char **command, resource_t *resp)
174{
175 unsigned vv_ms; /* Elapsed virtual (CPU) milliseconds */
176 unsigned cpu_ticks; /* Same, in "CPU ticks" */
177 unsigned pagesize = getpagesize();
178
179 /* Impossible: we do not use WUNTRACED flag in wait()...
180 if (WIFSTOPPED(resp->waitstatus))
181 printf("Command stopped by signal %u\n",
182 WSTOPSIG(resp->waitstatus));
183 else */
184 if (WIFSIGNALED(resp->waitstatus))
185 printf("Command terminated by signal %u\n",
186 WTERMSIG(resp->waitstatus));
187 else if (WIFEXITED(resp->waitstatus) && WEXITSTATUS(resp->waitstatus))
188 printf("Command exited with non-zero status %u\n",
189 WEXITSTATUS(resp->waitstatus));
190
191 vv_ms = (resp->ru.ru_utime.tv_sec + resp->ru.ru_stime.tv_sec) * 1000
192 + (resp->ru.ru_utime.tv_usec + resp->ru.ru_stime.tv_usec) / 1000;
193
194#if (1000 / TICKS_PER_SEC) * TICKS_PER_SEC == 1000
195 /* 1000 is exactly divisible by TICKS_PER_SEC (typical) */
196 cpu_ticks = vv_ms / (1000 / TICKS_PER_SEC);
197#else
198 cpu_ticks = vv_ms * (unsigned long long)TICKS_PER_SEC / 1000;
199#endif
200 if (!cpu_ticks) cpu_ticks = 1; /* we divide by it, must be nonzero */
201
202 while (*fmt) {
203 /* Handle leading literal part */
204 int n = strcspn(fmt, "%\\");
205 if (n) {
206 printf("%.*s", n, fmt);
207 fmt += n;
208 continue;
209 }
210
211 switch (*fmt) {
212#ifdef NOT_NEEDED
213 /* Handle literal char */
214 /* Usually we optimize for size, but there is a limit
215 * for everything. With this we do a lot of 1-byte writes */
216 default:
217 bb_putchar(*fmt);
218 break;
219#endif
220
221 case '%':
222 switch (*++fmt) {
223#ifdef NOT_NEEDED_YET
224 /* Our format strings do not have these */
225 /* and we do not take format str from user */
226 default:
227 bb_putchar('%');
228 /*FALLTHROUGH*/
229 case '%':
230 if (!*fmt) goto ret;
231 bb_putchar(*fmt);
232 break;
233#endif
234 case 'C': /* The command that got timed. */
235 printargv(command);
236 break;
237 case 'D': /* Average unshared data size. */
238 printf("%lu",
239 (ptok(pagesize, (UL) resp->ru.ru_idrss) +
240 ptok(pagesize, (UL) resp->ru.ru_isrss)) / cpu_ticks);
241 break;
242 case 'E': { /* Elapsed real (wall clock) time. */
243 unsigned seconds = resp->elapsed_ms / 1000;
244 if (seconds >= 3600) /* One hour -> h:m:s. */
245 printf("%uh %um %02us",
246 seconds / 3600,
247 (seconds % 3600) / 60,
248 seconds % 60);
249 else
250 printf("%um %u.%02us", /* -> m:s. */
251 seconds / 60,
252 seconds % 60,
253 (unsigned)(resp->elapsed_ms / 10) % 100);
254 break;
255 }
256 case 'F': /* Major page faults. */
257 printf("%lu", resp->ru.ru_majflt);
258 break;
259 case 'I': /* Inputs. */
260 printf("%lu", resp->ru.ru_inblock);
261 break;
262 case 'K': /* Average mem usage == data+stack+text. */
263 printf("%lu",
264 (ptok(pagesize, (UL) resp->ru.ru_idrss) +
265 ptok(pagesize, (UL) resp->ru.ru_isrss) +
266 ptok(pagesize, (UL) resp->ru.ru_ixrss)) / cpu_ticks);
267 break;
268 case 'M': /* Maximum resident set size. */
269 printf("%lu", ptok(pagesize, (UL) resp->ru.ru_maxrss));
270 break;
271 case 'O': /* Outputs. */
272 printf("%lu", resp->ru.ru_oublock);
273 break;
274 case 'P': /* Percent of CPU this job got. */
275 /* % cpu is (total cpu time)/(elapsed time). */
276 if (resp->elapsed_ms > 0)
277 printf("%u%%", (unsigned)(vv_ms * 100 / resp->elapsed_ms));
278 else
279 printf("?%%");
280 break;
281 case 'R': /* Minor page faults (reclaims). */
282 printf("%lu", resp->ru.ru_minflt);
283 break;
284 case 'S': /* System time. */
285 printf("%u.%02u",
286 (unsigned)resp->ru.ru_stime.tv_sec,
287 (unsigned)(resp->ru.ru_stime.tv_usec / 10000));
288 break;
289 case 'T': /* System time. */
290 if (resp->ru.ru_stime.tv_sec >= 3600) /* One hour -> h:m:s. */
291 printf("%uh %um %02us",
292 (unsigned)(resp->ru.ru_stime.tv_sec / 3600),
293 (unsigned)(resp->ru.ru_stime.tv_sec % 3600) / 60,
294 (unsigned)(resp->ru.ru_stime.tv_sec % 60));
295 else
296 printf("%um %u.%02us", /* -> m:s. */
297 (unsigned)(resp->ru.ru_stime.tv_sec / 60),
298 (unsigned)(resp->ru.ru_stime.tv_sec % 60),
299 (unsigned)(resp->ru.ru_stime.tv_usec / 10000));
300 break;
301 case 'U': /* User time. */
302 printf("%u.%02u",
303 (unsigned)resp->ru.ru_utime.tv_sec,
304 (unsigned)(resp->ru.ru_utime.tv_usec / 10000));
305 break;
306 case 'u': /* User time. */
307 if (resp->ru.ru_utime.tv_sec >= 3600) /* One hour -> h:m:s. */
308 printf("%uh %um %02us",
309 (unsigned)(resp->ru.ru_utime.tv_sec / 3600),
310 (unsigned)(resp->ru.ru_utime.tv_sec % 3600) / 60,
311 (unsigned)(resp->ru.ru_utime.tv_sec % 60));
312 else
313 printf("%um %u.%02us", /* -> m:s. */
314 (unsigned)(resp->ru.ru_utime.tv_sec / 60),
315 (unsigned)(resp->ru.ru_utime.tv_sec % 60),
316 (unsigned)(resp->ru.ru_utime.tv_usec / 10000));
317 break;
318 case 'W': /* Times swapped out. */
319 printf("%lu", resp->ru.ru_nswap);
320 break;
321 case 'X': /* Average shared text size. */
322 printf("%lu", ptok(pagesize, (UL) resp->ru.ru_ixrss) / cpu_ticks);
323 break;
324 case 'Z': /* Page size. */
325 printf("%u", pagesize);
326 break;
327 case 'c': /* Involuntary context switches. */
328 printf("%lu", resp->ru.ru_nivcsw);
329 break;
330 case 'e': /* Elapsed real time in seconds. */
331 printf("%u.%02u",
332 (unsigned)resp->elapsed_ms / 1000,
333 (unsigned)(resp->elapsed_ms / 10) % 100);
334 break;
335 case 'k': /* Signals delivered. */
336 printf("%lu", resp->ru.ru_nsignals);
337 break;
338 case 'p': /* Average stack segment. */
339 printf("%lu", ptok(pagesize, (UL) resp->ru.ru_isrss) / cpu_ticks);
340 break;
341 case 'r': /* Incoming socket messages received. */
342 printf("%lu", resp->ru.ru_msgrcv);
343 break;
344 case 's': /* Outgoing socket messages sent. */
345 printf("%lu", resp->ru.ru_msgsnd);
346 break;
347 case 't': /* Average resident set size. */
348 printf("%lu", ptok(pagesize, (UL) resp->ru.ru_idrss) / cpu_ticks);
349 break;
350 case 'w': /* Voluntary context switches. */
351 printf("%lu", resp->ru.ru_nvcsw);
352 break;
353 case 'x': /* Exit status. */
354 printf("%u", WEXITSTATUS(resp->waitstatus));
355 break;
356 }
357 break;
358
359#ifdef NOT_NEEDED_YET
360 case '\\': /* Format escape. */
361 switch (*++fmt) {
362 default:
363 bb_putchar('\\');
364 /*FALLTHROUGH*/
365 case '\\':
366 if (!*fmt) goto ret;
367 bb_putchar(*fmt);
368 break;
369 case 't':
370 bb_putchar('\t');
371 break;
372 case 'n':
373 bb_putchar('\n');
374 break;
375 }
376 break;
377#endif
378 }
379 ++fmt;
380 }
381 /* ret: */
382 bb_putchar('\n');
383}
384
385/* Run command CMD and return statistics on it.
386 Put the statistics in *RESP. */
387static void run_command(char *const *cmd, resource_t *resp)
388{
389 pid_t pid;
390 void (*interrupt_signal)(int);
391 void (*quit_signal)(int);
392
393 resp->elapsed_ms = monotonic_ms();
394 pid = xvfork();
395 if (pid == 0) {
396 /* Child */
397 BB_EXECVP_or_die((char**)cmd);
398 }
399
400 /* Have signals kill the child but not self (if possible). */
401//TODO: just block all sigs? and reenable them in the very end in main?
402 interrupt_signal = signal(SIGINT, SIG_IGN);
403 quit_signal = signal(SIGQUIT, SIG_IGN);
404
405 resuse_end(pid, resp);
406
407 /* Re-enable signals. */
408 signal(SIGINT, interrupt_signal);
409 signal(SIGQUIT, quit_signal);
410}
411
412int time_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
413int time_main(int argc UNUSED_PARAM, char **argv)
414{
415 resource_t res;
416 const char *output_format = default_format;
417 int opt;
418
419 opt_complementary = "-1"; /* at least one arg */
420 /* "+": stop on first non-option */
421 opt = getopt32(argv, "+vp");
422 argv += optind;
423 if (opt & 1)
424 output_format = long_format;
425 if (opt & 2)
426 output_format = posix_format;
427
428 run_command(argv, &res);
429
430 /* Cheat. printf's are shorter :) */
431 xdup2(STDERR_FILENO, STDOUT_FILENO);
432 summarize(output_format, argv, &res);
433
434 if (WIFSTOPPED(res.waitstatus))
435 return WSTOPSIG(res.waitstatus);
436 if (WIFSIGNALED(res.waitstatus))
437 return WTERMSIG(res.waitstatus);
438 if (WIFEXITED(res.waitstatus))
439 return WEXITSTATUS(res.waitstatus);
440 fflush_stdout_and_exit(EXIT_SUCCESS);
441}
442