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