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