diff options
Diffstat (limited to 'miscutils/time.c')
-rw-r--r-- | miscutils/time.c | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/miscutils/time.c b/miscutils/time.c new file mode 100644 index 000000000..3f1451c83 --- /dev/null +++ b/miscutils/time.c | |||
@@ -0,0 +1,467 @@ | |||
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 "busybox.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 *const default_format = "real\t%E\nuser\t%u\nsys\t%T"; | ||
39 | |||
40 | /* The output format for the -p option .*/ | ||
41 | static const char *const 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 *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" | ||
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" "\tExit status: %x"; | ||
69 | |||
70 | |||
71 | /* Wait for and fill in data on child process PID. | ||
72 | Return 0 on error, 1 if ok. */ | ||
73 | |||
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) | ||
76 | { | ||
77 | int status; | ||
78 | |||
79 | pid_t caught; | ||
80 | |||
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) { | ||
84 | if (caught == -1) | ||
85 | return 0; | ||
86 | } | ||
87 | |||
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; | ||
94 | } | ||
95 | resp->elapsed.tv_usec -= resp->start.tv_usec; | ||
96 | |||
97 | resp->waitstatus = status; | ||
98 | |||
99 | return 1; | ||
100 | } | ||
101 | |||
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) | ||
104 | { | ||
105 | char *const *av; | ||
106 | |||
107 | av = argv; | ||
108 | fputs(*av, fp); | ||
109 | while (*++av) { | ||
110 | fputs(filler, fp); | ||
111 | fputs(*av, fp); | ||
112 | } | ||
113 | if (ferror(fp)) | ||
114 | bb_error_msg_and_die(bb_msg_write_error); | ||
115 | } | ||
116 | |||
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.) | ||
119 | |||
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. */ | ||
124 | |||
125 | static unsigned long ptok(unsigned long pages) | ||
126 | { | ||
127 | static unsigned long ps = 0; | ||
128 | unsigned long tmp; | ||
129 | static long size = LONG_MAX; | ||
130 | |||
131 | /* Initialization. */ | ||
132 | if (ps == 0) | ||
133 | ps = (long) getpagesize(); | ||
134 | |||
135 | /* Conversion. */ | ||
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. */ | ||
142 | } | ||
143 | return size; | ||
144 | } | ||
145 | |||
146 | /* summarize: Report on the system use of a command. | ||
147 | |||
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 `\'. | ||
151 | |||
152 | The character following a `%' can be: | ||
153 | (* means the tcsh time builtin also recognizes it) | ||
154 | % == a literal `%' | ||
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) | ||
171 | Z == page size | ||
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 | ||
181 | |||
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. | ||
185 | |||
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. */ | ||
190 | |||
191 | static void summarize(FILE * fp, const char *fmt, char **command, | ||
192 | resource_t * resp) | ||
193 | { | ||
194 | unsigned long r; /* Elapsed real milliseconds. */ | ||
195 | unsigned long v; /* Elapsed virtual (CPU) milliseconds. */ | ||
196 | |||
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)); | ||
206 | |||
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. */ | ||
211 | |||
212 | r = resp->elapsed.tv_sec * 1000 + resp->elapsed.tv_usec / 1000; | ||
213 | |||
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; | ||
216 | |||
217 | while (*fmt) { | ||
218 | switch (*fmt) { | ||
219 | case '%': | ||
220 | switch (*++fmt) { | ||
221 | case '%': /* Literal '%'. */ | ||
222 | putc('%', fp); | ||
223 | break; | ||
224 | case 'C': /* The command that got timed. */ | ||
225 | fprintargv(fp, command, " "); | ||
226 | break; | ||
227 | case 'D': /* Average unshared data size. */ | ||
228 | fprintf(fp, "%lu", | ||
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)); | ||
232 | break; | ||
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); | ||
239 | else | ||
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); | ||
244 | break; | ||
245 | case 'F': /* Major page faults. */ | ||
246 | fprintf(fp, "%ld", resp->ru.ru_majflt); | ||
247 | break; | ||
248 | case 'I': /* Inputs. */ | ||
249 | fprintf(fp, "%ld", resp->ru.ru_inblock); | ||
250 | break; | ||
251 | case 'K': /* Average mem usage == data+stack+text. */ | ||
252 | fprintf(fp, "%lu", | ||
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)); | ||
257 | break; | ||
258 | case 'M': /* Maximum resident set size. */ | ||
259 | fprintf(fp, "%lu", ptok((UL) resp->ru.ru_maxrss)); | ||
260 | break; | ||
261 | case 'O': /* Outputs. */ | ||
262 | fprintf(fp, "%ld", resp->ru.ru_oublock); | ||
263 | break; | ||
264 | case 'P': /* Percent of CPU this job got. */ | ||
265 | /* % cpu is (total cpu time)/(elapsed time). */ | ||
266 | if (r > 0) | ||
267 | fprintf(fp, "%lu%%", (v * 100 / r)); | ||
268 | else | ||
269 | fprintf(fp, "?%%"); | ||
270 | break; | ||
271 | case 'R': /* Minor page faults (reclaims). */ | ||
272 | fprintf(fp, "%ld", resp->ru.ru_minflt); | ||
273 | break; | ||
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); | ||
278 | break; | ||
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); | ||
285 | else | ||
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); | ||
290 | break; | ||
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); | ||
295 | break; | ||
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); | ||
302 | else | ||
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); | ||
307 | break; | ||
308 | case 'W': /* Times swapped out. */ | ||
309 | fprintf(fp, "%ld", resp->ru.ru_nswap); | ||
310 | break; | ||
311 | case 'X': /* Average shared text size. */ | ||
312 | fprintf(fp, "%lu", | ||
313 | MSEC_TO_TICKS(v) == 0 ? 0 : | ||
314 | ptok((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS(v)); | ||
315 | break; | ||
316 | case 'Z': /* Page size. */ | ||
317 | fprintf(fp, "%d", getpagesize()); | ||
318 | break; | ||
319 | case 'c': /* Involuntary context switches. */ | ||
320 | fprintf(fp, "%ld", resp->ru.ru_nivcsw); | ||
321 | break; | ||
322 | case 'e': /* Elapsed real time in seconds. */ | ||
323 | fprintf(fp, "%ld.%02ld", | ||
324 | resp->elapsed.tv_sec, resp->elapsed.tv_usec / 10000); | ||
325 | break; | ||
326 | case 'k': /* Signals delivered. */ | ||
327 | fprintf(fp, "%ld", resp->ru.ru_nsignals); | ||
328 | break; | ||
329 | case 'p': /* Average stack segment. */ | ||
330 | fprintf(fp, "%lu", | ||
331 | MSEC_TO_TICKS(v) == 0 ? 0 : | ||
332 | ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v)); | ||
333 | break; | ||
334 | case 'r': /* Incoming socket messages received. */ | ||
335 | fprintf(fp, "%ld", resp->ru.ru_msgrcv); | ||
336 | break; | ||
337 | case 's': /* Outgoing socket messages sent. */ | ||
338 | fprintf(fp, "%ld", resp->ru.ru_msgsnd); | ||
339 | break; | ||
340 | case 't': /* Average resident set size. */ | ||
341 | fprintf(fp, "%lu", | ||
342 | MSEC_TO_TICKS(v) == 0 ? 0 : | ||
343 | ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v)); | ||
344 | break; | ||
345 | case 'w': /* Voluntary context switches. */ | ||
346 | fprintf(fp, "%ld", resp->ru.ru_nvcsw); | ||
347 | break; | ||
348 | case 'x': /* Exit status. */ | ||
349 | fprintf(fp, "%d", WEXITSTATUS(resp->waitstatus)); | ||
350 | break; | ||
351 | case '\0': | ||
352 | putc('?', fp); | ||
353 | return; | ||
354 | default: | ||
355 | putc('?', fp); | ||
356 | putc(*fmt, fp); | ||
357 | } | ||
358 | ++fmt; | ||
359 | break; | ||
360 | |||
361 | case '\\': /* Format escape. */ | ||
362 | switch (*++fmt) { | ||
363 | case 't': | ||
364 | putc('\t', fp); | ||
365 | break; | ||
366 | case 'n': | ||
367 | putc('\n', fp); | ||
368 | break; | ||
369 | case '\\': | ||
370 | putc('\\', fp); | ||
371 | break; | ||
372 | default: | ||
373 | putc('?', fp); | ||
374 | putc('\\', fp); | ||
375 | putc(*fmt, fp); | ||
376 | } | ||
377 | ++fmt; | ||
378 | break; | ||
379 | |||
380 | default: | ||
381 | putc(*fmt++, fp); | ||
382 | } | ||
383 | |||
384 | if (ferror(fp)) | ||
385 | bb_error_msg_and_die(bb_msg_write_error); | ||
386 | } | ||
387 | putc('\n', fp); | ||
388 | |||
389 | if (ferror(fp)) | ||
390 | bb_error_msg_and_die(bb_msg_write_error); | ||
391 | } | ||
392 | |||
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) | ||
396 | { | ||
397 | pid_t pid; /* Pid of child. */ | ||
398 | __sighandler_t interrupt_signal, quit_signal; | ||
399 | |||
400 | gettimeofday(&resp->start, (struct timezone *) 0); | ||
401 | pid = vfork(); /* Run CMD as child process. */ | ||
402 | if (pid < 0) | ||
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. */ | ||
407 | execvp(cmd[0], cmd); | ||
408 | bb_error_msg("cannot run %s", cmd[0]); | ||
409 | _exit(errno == ENOENT ? 127 : 126); | ||
410 | } | ||
411 | |||
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); | ||
415 | |||
416 | if (resuse_end(pid, resp) == 0) | ||
417 | bb_error_msg("error waiting for child process"); | ||
418 | |||
419 | /* Re-enable signals. */ | ||
420 | signal(SIGINT, interrupt_signal); | ||
421 | signal(SIGQUIT, quit_signal); | ||
422 | } | ||
423 | |||
424 | int time_main(int argc, char **argv) | ||
425 | { | ||
426 | int gotone; | ||
427 | resource_t res; | ||
428 | const char *output_format = default_format; | ||
429 | |||
430 | argc--; | ||
431 | argv++; | ||
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 == '-') { | ||
435 | gotone = 0; | ||
436 | while (gotone == 0 && *++(*argv)) { | ||
437 | switch (**argv) { | ||
438 | case 'v': | ||
439 | output_format = long_format; | ||
440 | break; | ||
441 | case 'p': | ||
442 | output_format = posix_format; | ||
443 | break; | ||
444 | default: | ||
445 | bb_show_usage(); | ||
446 | } | ||
447 | argc--; | ||
448 | argv++; | ||
449 | gotone = 1; | ||
450 | } | ||
451 | } | ||
452 | |||
453 | if (argv == NULL || *argv == NULL) | ||
454 | bb_show_usage(); | ||
455 | |||
456 | run_command(argv, &res); | ||
457 | summarize(stderr, output_format, argv, &res); | ||
458 | fflush(stderr); | ||
459 | |||
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)); | ||
466 | return 0; | ||
467 | } | ||