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