1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34#include "libbb.h"
35
36#ifndef HAVE_WAIT3
37static pid_t wait3(int *status, int options, struct rusage *rusage)
38{
39 return wait4(-1, status, options, rusage);
40}
41#endif
42
43
44typedef struct {
45 int waitstatus;
46 struct rusage ru;
47 unsigned elapsed_ms;
48} resource_t;
49
50
51
52
53#define UL unsigned long
54
55static const char default_format[] ALIGN1 = "real\t%E\nuser\t%u\nsys\t%T";
56
57
58static const char posix_format[] ALIGN1 = "real %e\nuser %U\nsys %S";
59
60
61
62static const char long_format[] ALIGN1 =
63 "\tCommand being timed: \"%C\"\n"
64 "\tUser time (seconds): %U\n"
65 "\tSystem time (seconds): %S\n"
66 "\tPercent of CPU this job got: %P\n"
67 "\tElapsed (wall clock) time (h:mm:ss or m:ss): %E\n"
68 "\tAverage shared text size (kbytes): %X\n"
69 "\tAverage unshared data size (kbytes): %D\n"
70 "\tAverage stack size (kbytes): %p\n"
71 "\tAverage total size (kbytes): %K\n"
72 "\tMaximum resident set size (kbytes): %M\n"
73 "\tAverage resident set size (kbytes): %t\n"
74 "\tMajor (requiring I/O) page faults: %F\n"
75 "\tMinor (reclaiming a frame) page faults: %R\n"
76 "\tVoluntary context switches: %w\n"
77 "\tInvoluntary context switches: %c\n"
78 "\tSwaps: %W\n"
79 "\tFile system inputs: %I\n"
80 "\tFile system outputs: %O\n"
81 "\tSocket messages sent: %s\n"
82 "\tSocket messages received: %r\n"
83 "\tSignals delivered: %k\n"
84 "\tPage size (bytes): %Z\n"
85 "\tExit status: %x";
86
87
88
89
90static void resuse_end(pid_t pid, resource_t *resp)
91{
92 pid_t caught;
93
94
95
96 while ((caught = wait3(&resp->waitstatus, 0, &resp->ru)) != pid) {
97 if (caught == -1 && errno != EINTR) {
98 bb_simple_perror_msg("wait");
99 return;
100 }
101 }
102 resp->elapsed_ms = monotonic_ms() - resp->elapsed_ms;
103}
104
105static void printargv(char *const *argv)
106{
107 const char *fmt = " %s" + 1;
108 do {
109 printf(fmt, *argv);
110 fmt = " %s";
111 } while (*++argv);
112}
113
114
115
116
117
118
119
120
121#ifdef BB_ARCH_FIXED_PAGESIZE
122# define pagesize BB_ARCH_FIXED_PAGESIZE
123# define ptok(pagesize, pages) ptok(pages)
124#endif
125static unsigned long ptok(const unsigned pagesize, const unsigned long pages)
126{
127 unsigned long tmp;
128
129
130 if (pages > (LONG_MAX / pagesize)) {
131 tmp = pages / 1024;
132 return tmp * pagesize;
133 }
134
135 tmp = pages * pagesize;
136 return tmp / 1024;
137}
138#undef pagesize
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184#ifndef TICKS_PER_SEC
185#define TICKS_PER_SEC 100
186#endif
187
188static void summarize(const char *fmt, char **command, resource_t *resp)
189{
190 unsigned vv_ms;
191 unsigned cpu_ticks;
192 unsigned pagesize = bb_getpagesize();
193
194
195
196
197
198
199 if (WIFSIGNALED(resp->waitstatus))
200 printf("Command terminated by signal %u\n",
201 WTERMSIG(resp->waitstatus));
202 else if (WIFEXITED(resp->waitstatus) && WEXITSTATUS(resp->waitstatus))
203 printf("Command exited with non-zero status %u\n",
204 WEXITSTATUS(resp->waitstatus));
205
206 vv_ms = (resp->ru.ru_utime.tv_sec + resp->ru.ru_stime.tv_sec) * 1000
207 + (resp->ru.ru_utime.tv_usec + resp->ru.ru_stime.tv_usec) / 1000;
208
209#if (1000 / TICKS_PER_SEC) * TICKS_PER_SEC == 1000
210
211 cpu_ticks = vv_ms / (1000 / TICKS_PER_SEC);
212#else
213 cpu_ticks = vv_ms * (unsigned long long)TICKS_PER_SEC / 1000;
214#endif
215 if (!cpu_ticks) cpu_ticks = 1;
216
217 while (*fmt) {
218
219 int n = strcspn(fmt, "%\\");
220 if (n) {
221 printf("%.*s", n, fmt);
222 fmt += n;
223 continue;
224 }
225
226 switch (*fmt) {
227#ifdef NOT_NEEDED
228
229
230
231 default:
232 bb_putchar(*fmt);
233 break;
234#endif
235
236 case '%':
237 switch (*++fmt) {
238#ifdef NOT_NEEDED_YET
239
240
241 default:
242 bb_putchar('%');
243
244 case '%':
245 if (!*fmt) goto ret;
246 bb_putchar(*fmt);
247 break;
248#endif
249 case 'C':
250 printargv(command);
251 break;
252 case 'D':
253 printf("%lu",
254 (ptok(pagesize, (UL) resp->ru.ru_idrss) +
255 ptok(pagesize, (UL) resp->ru.ru_isrss)) / cpu_ticks);
256 break;
257 case 'E': {
258 unsigned seconds = resp->elapsed_ms / 1000;
259 if (seconds >= 3600)
260 printf("%uh %um %02us",
261 seconds / 3600,
262 (seconds % 3600) / 60,
263 seconds % 60);
264 else
265 printf("%um %u.%02us",
266 seconds / 60,
267 seconds % 60,
268 (unsigned)(resp->elapsed_ms / 10) % 100);
269 break;
270 }
271 case 'F':
272 printf("%lu", resp->ru.ru_majflt);
273 break;
274 case 'I':
275 printf("%lu", resp->ru.ru_inblock);
276 break;
277 case 'K':
278 printf("%lu",
279 (ptok(pagesize, (UL) resp->ru.ru_idrss) +
280 ptok(pagesize, (UL) resp->ru.ru_isrss) +
281 ptok(pagesize, (UL) resp->ru.ru_ixrss)) / cpu_ticks);
282 break;
283 case 'M':
284 printf("%lu", ptok(pagesize, (UL) resp->ru.ru_maxrss));
285 break;
286 case 'O':
287 printf("%lu", resp->ru.ru_oublock);
288 break;
289 case 'P':
290
291 if (resp->elapsed_ms > 0)
292 printf("%u%%", (unsigned)(vv_ms * 100 / resp->elapsed_ms));
293 else
294 printf("?%%");
295 break;
296 case 'R':
297 printf("%lu", resp->ru.ru_minflt);
298 break;
299 case 'S':
300 printf("%u.%02u",
301 (unsigned)resp->ru.ru_stime.tv_sec,
302 (unsigned)(resp->ru.ru_stime.tv_usec / 10000));
303 break;
304 case 'T':
305 if (resp->ru.ru_stime.tv_sec >= 3600)
306 printf("%uh %um %02us",
307 (unsigned)(resp->ru.ru_stime.tv_sec / 3600),
308 (unsigned)(resp->ru.ru_stime.tv_sec % 3600) / 60,
309 (unsigned)(resp->ru.ru_stime.tv_sec % 60));
310 else
311 printf("%um %u.%02us",
312 (unsigned)(resp->ru.ru_stime.tv_sec / 60),
313 (unsigned)(resp->ru.ru_stime.tv_sec % 60),
314 (unsigned)(resp->ru.ru_stime.tv_usec / 10000));
315 break;
316 case 'U':
317 printf("%u.%02u",
318 (unsigned)resp->ru.ru_utime.tv_sec,
319 (unsigned)(resp->ru.ru_utime.tv_usec / 10000));
320 break;
321 case 'u':
322 if (resp->ru.ru_utime.tv_sec >= 3600)
323 printf("%uh %um %02us",
324 (unsigned)(resp->ru.ru_utime.tv_sec / 3600),
325 (unsigned)(resp->ru.ru_utime.tv_sec % 3600) / 60,
326 (unsigned)(resp->ru.ru_utime.tv_sec % 60));
327 else
328 printf("%um %u.%02us",
329 (unsigned)(resp->ru.ru_utime.tv_sec / 60),
330 (unsigned)(resp->ru.ru_utime.tv_sec % 60),
331 (unsigned)(resp->ru.ru_utime.tv_usec / 10000));
332 break;
333 case 'W':
334 printf("%lu", resp->ru.ru_nswap);
335 break;
336 case 'X':
337 printf("%lu", ptok(pagesize, (UL) resp->ru.ru_ixrss) / cpu_ticks);
338 break;
339 case 'Z':
340 printf("%u", pagesize);
341 break;
342 case 'c':
343 printf("%lu", resp->ru.ru_nivcsw);
344 break;
345 case 'e':
346 printf("%u.%02u",
347 (unsigned)resp->elapsed_ms / 1000,
348 (unsigned)(resp->elapsed_ms / 10) % 100);
349 break;
350 case 'k':
351 printf("%lu", resp->ru.ru_nsignals);
352 break;
353 case 'p':
354 printf("%lu", ptok(pagesize, (UL) resp->ru.ru_isrss) / cpu_ticks);
355 break;
356 case 'r':
357 printf("%lu", resp->ru.ru_msgrcv);
358 break;
359 case 's':
360 printf("%lu", resp->ru.ru_msgsnd);
361 break;
362 case 't':
363 printf("%lu", ptok(pagesize, (UL) resp->ru.ru_idrss) / cpu_ticks);
364 break;
365 case 'w':
366 printf("%lu", resp->ru.ru_nvcsw);
367 break;
368 case 'x':
369 printf("%u", WEXITSTATUS(resp->waitstatus));
370 break;
371 }
372 break;
373
374#ifdef NOT_NEEDED_YET
375 case '\\':
376 switch (*++fmt) {
377 default:
378 bb_putchar('\\');
379
380 case '\\':
381 if (!*fmt) goto ret;
382 bb_putchar(*fmt);
383 break;
384 case 't':
385 bb_putchar('\t');
386 break;
387 case 'n':
388 bb_putchar('\n');
389 break;
390 }
391 break;
392#endif
393 }
394 ++fmt;
395 }
396
397 bb_putchar('\n');
398}
399
400
401
402static void run_command(char *const *cmd, resource_t *resp)
403{
404 pid_t pid;
405 void (*interrupt_signal)(int);
406 void (*quit_signal)(int);
407
408 resp->elapsed_ms = monotonic_ms();
409 pid = xvfork();
410 if (pid == 0) {
411
412 BB_EXECVP_or_die((char**)cmd);
413 }
414
415
416
417 interrupt_signal = signal(SIGINT, SIG_IGN);
418 quit_signal = signal(SIGQUIT, SIG_IGN);
419
420 resuse_end(pid, resp);
421
422
423 signal(SIGINT, interrupt_signal);
424 signal(SIGQUIT, quit_signal);
425}
426
427int time_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
428int time_main(int argc UNUSED_PARAM, char **argv)
429{
430 resource_t res;
431
432 const char *output_format = getenv("TIME") ? : default_format;
433 char *output_filename;
434 int output_fd;
435 int opt;
436 int ex;
437 enum {
438 OPT_v = (1 << 0),
439 OPT_p = (1 << 1),
440 OPT_a = (1 << 2),
441 OPT_o = (1 << 3),
442 OPT_f = (1 << 4),
443 };
444
445
446 opt = getopt32(argv, "^+" "vpao:f:" "\0" "-1",
447 &output_filename, &output_format
448 );
449 argv += optind;
450 if (opt & OPT_v)
451 output_format = long_format;
452 if (opt & OPT_p)
453 output_format = posix_format;
454 output_fd = STDERR_FILENO;
455 if (opt & OPT_o) {
456#ifndef O_CLOEXEC
457# define O_CLOEXEC 0
458#endif
459 output_fd = xopen(output_filename,
460 (opt & OPT_a)
461 ? (O_CREAT | O_WRONLY | O_CLOEXEC | O_APPEND)
462 : (O_CREAT | O_WRONLY | O_CLOEXEC | O_TRUNC)
463 );
464 if (!O_CLOEXEC)
465 close_on_exec_on(output_fd);
466 }
467
468 run_command(argv, &res);
469
470
471 xdup2(output_fd, STDOUT_FILENO);
472 summarize(output_format, argv, &res);
473
474 ex = WEXITSTATUS(res.waitstatus);
475
476
477
478
479 if (WIFSIGNALED(res.waitstatus))
480 ex = WTERMSIG(res.waitstatus);
481
482 fflush_stdout_and_exit(ex);
483}
484