3
0

time.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. /* `time' utility to display resource usage of processes.
  2. Copyright (C) 1990, 91, 92, 93, 96 Free Software Foundation, Inc.
  3. This program is free software; you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation; either version 2, or (at your option)
  6. any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program; if not, write to the Free Software
  13. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
  14. 02111-1307, USA. */
  15. /* Originally written by David Keppel <pardo@cs.washington.edu>.
  16. Heavily modified by David MacKenzie <djm@gnu.ai.mit.edu>.
  17. Heavily modified for busybox by Erik Andersen <andersen@codepoet.org>
  18. */
  19. #include <stdlib.h>
  20. #include <stdio.h>
  21. #include <signal.h>
  22. #include <errno.h>
  23. #include <getopt.h>
  24. #include <string.h>
  25. #include <limits.h>
  26. #include <unistd.h>
  27. #include <sys/time.h>
  28. #include <sys/types.h> /* For pid_t. */
  29. #include <sys/wait.h>
  30. #include <sys/param.h> /* For getpagesize, maybe. */
  31. #define TV_MSEC tv_usec / 1000
  32. #include <sys/resource.h>
  33. #include "busybox.h"
  34. /* Information on the resources used by a child process. */
  35. typedef struct
  36. {
  37. int waitstatus;
  38. struct rusage ru;
  39. struct timeval start, elapsed; /* Wallclock time of process. */
  40. } resource_t;
  41. /* msec = milliseconds = 1/1,000 (1*10e-3) second.
  42. usec = microseconds = 1/1,000,000 (1*10e-6) second. */
  43. #ifndef TICKS_PER_SEC
  44. #define TICKS_PER_SEC 100
  45. #endif
  46. /* The number of milliseconds in one `tick' used by the `rusage' structure. */
  47. #define MSEC_PER_TICK (1000 / TICKS_PER_SEC)
  48. /* Return the number of clock ticks that occur in M milliseconds. */
  49. #define MSEC_TO_TICKS(m) ((m) / MSEC_PER_TICK)
  50. #define UL unsigned long
  51. static const char *const default_format = "real\t%E\nuser\t%u\nsys\t%T";
  52. /* The output format for the -p option .*/
  53. static const char *const posix_format = "real %e\nuser %U\nsys %S";
  54. /* Format string for printing all statistics verbosely.
  55. Keep this output to 24 lines so users on terminals can see it all.*/
  56. static const char *const long_format =
  57. "\tCommand being timed: \"%C\"\n"
  58. "\tUser time (seconds): %U\n"
  59. "\tSystem time (seconds): %S\n"
  60. "\tPercent of CPU this job got: %P\n"
  61. "\tElapsed (wall clock) time (h:mm:ss or m:ss): %E\n"
  62. "\tAverage shared text size (kbytes): %X\n"
  63. "\tAverage unshared data size (kbytes): %D\n"
  64. "\tAverage stack size (kbytes): %p\n"
  65. "\tAverage total size (kbytes): %K\n"
  66. "\tMaximum resident set size (kbytes): %M\n"
  67. "\tAverage resident set size (kbytes): %t\n"
  68. "\tMajor (requiring I/O) page faults: %F\n"
  69. "\tMinor (reclaiming a frame) page faults: %R\n"
  70. "\tVoluntary context switches: %w\n"
  71. "\tInvoluntary context switches: %c\n"
  72. "\tSwaps: %W\n"
  73. "\tFile system inputs: %I\n"
  74. "\tFile system outputs: %O\n"
  75. "\tSocket messages sent: %s\n"
  76. "\tSocket messages received: %r\n"
  77. "\tSignals delivered: %k\n"
  78. "\tPage size (bytes): %Z\n"
  79. "\tExit status: %x";
  80. /* Wait for and fill in data on child process PID.
  81. Return 0 on error, 1 if ok. */
  82. /* pid_t is short on BSDI, so don't try to promote it. */
  83. static int resuse_end (pid_t pid, resource_t *resp)
  84. {
  85. int status;
  86. pid_t caught;
  87. /* Ignore signals, but don't ignore the children. When wait3
  88. returns the child process, set the time the command finished. */
  89. while ((caught = wait3 (&status, 0, &resp->ru)) != pid)
  90. {
  91. if (caught == -1)
  92. return 0;
  93. }
  94. gettimeofday (&resp->elapsed, (struct timezone *) 0);
  95. resp->elapsed.tv_sec -= resp->start.tv_sec;
  96. if (resp->elapsed.tv_usec < resp->start.tv_usec)
  97. {
  98. /* Manually carry a one from the seconds field. */
  99. resp->elapsed.tv_usec += 1000000;
  100. --resp->elapsed.tv_sec;
  101. }
  102. resp->elapsed.tv_usec -= resp->start.tv_usec;
  103. resp->waitstatus = status;
  104. return 1;
  105. }
  106. /* Print ARGV to FP, with each entry in ARGV separated by FILLER. */
  107. static void fprintargv (FILE *fp, char *const *argv, const char *filler)
  108. {
  109. char *const *av;
  110. av = argv;
  111. fputs (*av, fp);
  112. while (*++av)
  113. {
  114. fputs (filler, fp);
  115. fputs (*av, fp);
  116. }
  117. if (ferror (fp))
  118. bb_error_msg_and_die("write error");
  119. }
  120. /* Return the number of kilobytes corresponding to a number of pages PAGES.
  121. (Actually, we use it to convert pages*ticks into kilobytes*ticks.)
  122. Try to do arithmetic so that the risk of overflow errors is minimized.
  123. This is funky since the pagesize could be less than 1K.
  124. Note: Some machines express getrusage statistics in terms of K,
  125. others in terms of pages. */
  126. static unsigned long ptok (unsigned long pages)
  127. {
  128. static unsigned long ps = 0;
  129. unsigned long tmp;
  130. static long size = LONG_MAX;
  131. /* Initialization. */
  132. if (ps == 0)
  133. ps = (long) getpagesize ();
  134. /* Conversion. */
  135. if (pages > (LONG_MAX / ps))
  136. { /* Could overflow. */
  137. tmp = pages / 1024; /* Smaller first, */
  138. size = tmp * ps; /* then larger. */
  139. }
  140. else
  141. { /* Could underflow. */
  142. tmp = pages * ps; /* Larger first, */
  143. size = tmp / 1024; /* then smaller. */
  144. }
  145. return size;
  146. }
  147. /* summarize: Report on the system use of a command.
  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. The character following a `%' can be:
  152. (* means the tcsh time builtin also recognizes it)
  153. % == a literal `%'
  154. C == command name and arguments
  155. * D == average unshared data size in K (ru_idrss+ru_isrss)
  156. * E == elapsed real (wall clock) time in [hour:]min:sec
  157. * F == major page faults (required physical I/O) (ru_majflt)
  158. * I == file system inputs (ru_inblock)
  159. * K == average total mem usage (ru_idrss+ru_isrss+ru_ixrss)
  160. * M == maximum resident set size in K (ru_maxrss)
  161. * O == file system outputs (ru_oublock)
  162. * P == percent of CPU this job got (total cpu time / elapsed time)
  163. * R == minor page faults (reclaims; no physical I/O involved) (ru_minflt)
  164. * S == system (kernel) time (seconds) (ru_stime)
  165. * T == system time in [hour:]min:sec
  166. * U == user time (seconds) (ru_utime)
  167. * u == user time in [hour:]min:sec
  168. * W == times swapped out (ru_nswap)
  169. * X == average amount of shared text in K (ru_ixrss)
  170. Z == page size
  171. * c == involuntary context switches (ru_nivcsw)
  172. e == elapsed real time in seconds
  173. * k == signals delivered (ru_nsignals)
  174. p == average unshared stack size in K (ru_isrss)
  175. * r == socket messages received (ru_msgrcv)
  176. * s == socket messages sent (ru_msgsnd)
  177. t == average resident set size in K (ru_idrss)
  178. * w == voluntary context switches (ru_nvcsw)
  179. x == exit status of command
  180. Various memory usages are found by converting from page-seconds
  181. to kbytes by multiplying by the page size, dividing by 1024,
  182. and dividing by elapsed real time.
  183. FP is the stream to print to.
  184. FMT is the format string, interpreted as described above.
  185. COMMAND is the command and args that are being summarized.
  186. RESP is resource information on the command. */
  187. static void summarize (FILE *fp, const char *fmt, char **command, resource_t *resp)
  188. {
  189. unsigned long r; /* Elapsed real milliseconds. */
  190. unsigned long v; /* Elapsed virtual (CPU) milliseconds. */
  191. if (WIFSTOPPED (resp->waitstatus))
  192. fprintf (fp, "Command stopped by signal %d\n", WSTOPSIG (resp->waitstatus));
  193. else if (WIFSIGNALED (resp->waitstatus))
  194. fprintf (fp, "Command terminated by signal %d\n", WTERMSIG (resp->waitstatus));
  195. else if (WIFEXITED (resp->waitstatus) && WEXITSTATUS (resp->waitstatus))
  196. fprintf (fp, "Command exited with non-zero status %d\n", WEXITSTATUS (resp->waitstatus));
  197. /* Convert all times to milliseconds. Occasionally, one of these values
  198. comes out as zero. Dividing by zero causes problems, so we first
  199. check the time value. If it is zero, then we take `evasive action'
  200. instead of calculating a value. */
  201. r = resp->elapsed.tv_sec * 1000 + resp->elapsed.tv_usec / 1000;
  202. v = resp->ru.ru_utime.tv_sec * 1000 + resp->ru.ru_utime.TV_MSEC +
  203. resp->ru.ru_stime.tv_sec * 1000 + resp->ru.ru_stime.TV_MSEC;
  204. while (*fmt)
  205. {
  206. switch (*fmt)
  207. {
  208. case '%':
  209. switch (*++fmt)
  210. {
  211. case '%': /* Literal '%'. */
  212. putc ('%', fp);
  213. break;
  214. case 'C': /* The command that got timed. */
  215. fprintargv (fp, command, " ");
  216. break;
  217. case 'D': /* Average unshared data size. */
  218. fprintf (fp, "%lu",
  219. MSEC_TO_TICKS (v) == 0 ? 0 :
  220. ptok ((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS (v) +
  221. ptok ((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS (v));
  222. break;
  223. case 'E': /* Elapsed real (wall clock) time. */
  224. if (resp->elapsed.tv_sec >= 3600) /* One hour -> h:m:s. */
  225. fprintf (fp, "%ldh %ldm %02lds",
  226. resp->elapsed.tv_sec / 3600,
  227. (resp->elapsed.tv_sec % 3600) / 60,
  228. resp->elapsed.tv_sec % 60);
  229. else
  230. fprintf (fp, "%ldm %ld.%02lds", /* -> m:s. */
  231. resp->elapsed.tv_sec / 60,
  232. resp->elapsed.tv_sec % 60,
  233. resp->elapsed.tv_usec / 10000);
  234. break;
  235. case 'F': /* Major page faults. */
  236. fprintf (fp, "%ld", resp->ru.ru_majflt);
  237. break;
  238. case 'I': /* Inputs. */
  239. fprintf (fp, "%ld", resp->ru.ru_inblock);
  240. break;
  241. case 'K': /* Average mem usage == data+stack+text. */
  242. fprintf (fp, "%lu",
  243. MSEC_TO_TICKS (v) == 0 ? 0 :
  244. ptok ((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS (v) +
  245. ptok ((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS (v) +
  246. ptok ((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS (v));
  247. break;
  248. case 'M': /* Maximum resident set size. */
  249. fprintf (fp, "%lu", ptok ((UL) resp->ru.ru_maxrss));
  250. break;
  251. case 'O': /* Outputs. */
  252. fprintf (fp, "%ld", resp->ru.ru_oublock);
  253. break;
  254. case 'P': /* Percent of CPU this job got. */
  255. /* % cpu is (total cpu time)/(elapsed time). */
  256. if (r > 0)
  257. fprintf (fp, "%lu%%", (v * 100 / r));
  258. else
  259. fprintf (fp, "?%%");
  260. break;
  261. case 'R': /* Minor page faults (reclaims). */
  262. fprintf (fp, "%ld", resp->ru.ru_minflt);
  263. break;
  264. case 'S': /* System time. */
  265. fprintf (fp, "%ld.%02ld",
  266. resp->ru.ru_stime.tv_sec,
  267. resp->ru.ru_stime.TV_MSEC / 10);
  268. break;
  269. case 'T': /* System time. */
  270. if (resp->ru.ru_stime.tv_sec >= 3600) /* One hour -> h:m:s. */
  271. fprintf (fp, "%ldh %ldm %02lds",
  272. resp->ru.ru_stime.tv_sec / 3600,
  273. (resp->ru.ru_stime.tv_sec % 3600) / 60,
  274. resp->ru.ru_stime.tv_sec % 60);
  275. else
  276. fprintf (fp, "%ldm %ld.%02lds", /* -> m:s. */
  277. resp->ru.ru_stime.tv_sec / 60,
  278. resp->ru.ru_stime.tv_sec % 60,
  279. resp->ru.ru_stime.tv_usec / 10000);
  280. break;
  281. case 'U': /* User time. */
  282. fprintf (fp, "%ld.%02ld",
  283. resp->ru.ru_utime.tv_sec,
  284. resp->ru.ru_utime.TV_MSEC / 10);
  285. break;
  286. case 'u': /* User time. */
  287. if (resp->ru.ru_utime.tv_sec >= 3600) /* One hour -> h:m:s. */
  288. fprintf (fp, "%ldh %ldm %02lds",
  289. resp->ru.ru_utime.tv_sec / 3600,
  290. (resp->ru.ru_utime.tv_sec % 3600) / 60,
  291. resp->ru.ru_utime.tv_sec % 60);
  292. else
  293. fprintf (fp, "%ldm %ld.%02lds", /* -> m:s. */
  294. resp->ru.ru_utime.tv_sec / 60,
  295. resp->ru.ru_utime.tv_sec % 60,
  296. resp->ru.ru_utime.tv_usec / 10000);
  297. break;
  298. case 'W': /* Times swapped out. */
  299. fprintf (fp, "%ld", resp->ru.ru_nswap);
  300. break;
  301. case 'X': /* Average shared text size. */
  302. fprintf (fp, "%lu",
  303. MSEC_TO_TICKS (v) == 0 ? 0 :
  304. ptok ((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS (v));
  305. break;
  306. case 'Z': /* Page size. */
  307. fprintf (fp, "%d", getpagesize ());
  308. break;
  309. case 'c': /* Involuntary context switches. */
  310. fprintf (fp, "%ld", resp->ru.ru_nivcsw);
  311. break;
  312. case 'e': /* Elapsed real time in seconds. */
  313. fprintf (fp, "%ld.%02ld",
  314. resp->elapsed.tv_sec,
  315. resp->elapsed.tv_usec / 10000);
  316. break;
  317. case 'k': /* Signals delivered. */
  318. fprintf (fp, "%ld", resp->ru.ru_nsignals);
  319. break;
  320. case 'p': /* Average stack segment. */
  321. fprintf (fp, "%lu",
  322. MSEC_TO_TICKS (v) == 0 ? 0 :
  323. ptok ((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS (v));
  324. break;
  325. case 'r': /* Incoming socket messages received. */
  326. fprintf (fp, "%ld", resp->ru.ru_msgrcv);
  327. break;
  328. case 's': /* Outgoing socket messages sent. */
  329. fprintf (fp, "%ld", resp->ru.ru_msgsnd);
  330. break;
  331. case 't': /* Average resident set size. */
  332. fprintf (fp, "%lu",
  333. MSEC_TO_TICKS (v) == 0 ? 0 :
  334. ptok ((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS (v));
  335. break;
  336. case 'w': /* Voluntary context switches. */
  337. fprintf (fp, "%ld", resp->ru.ru_nvcsw);
  338. break;
  339. case 'x': /* Exit status. */
  340. fprintf (fp, "%d", WEXITSTATUS (resp->waitstatus));
  341. break;
  342. case '\0':
  343. putc ('?', fp);
  344. return;
  345. default:
  346. putc ('?', fp);
  347. putc (*fmt, fp);
  348. }
  349. ++fmt;
  350. break;
  351. case '\\': /* Format escape. */
  352. switch (*++fmt)
  353. {
  354. case 't':
  355. putc ('\t', fp);
  356. break;
  357. case 'n':
  358. putc ('\n', fp);
  359. break;
  360. case '\\':
  361. putc ('\\', fp);
  362. break;
  363. default:
  364. putc ('?', fp);
  365. putc ('\\', fp);
  366. putc (*fmt, fp);
  367. }
  368. ++fmt;
  369. break;
  370. default:
  371. putc (*fmt++, fp);
  372. }
  373. if (ferror (fp))
  374. bb_error_msg_and_die("write error");
  375. }
  376. putc ('\n', fp);
  377. if (ferror (fp))
  378. bb_error_msg_and_die("write error");
  379. }
  380. /* Run command CMD and return statistics on it.
  381. Put the statistics in *RESP. */
  382. static void run_command (char *const *cmd, resource_t *resp)
  383. {
  384. pid_t pid; /* Pid of child. */
  385. __sighandler_t interrupt_signal, quit_signal;
  386. gettimeofday (&resp->start, (struct timezone *) 0);
  387. pid = fork (); /* Run CMD as child process. */
  388. if (pid < 0)
  389. bb_error_msg_and_die("cannot fork");
  390. else if (pid == 0)
  391. { /* If child. */
  392. /* Don't cast execvp arguments; that causes errors on some systems,
  393. versus merely warnings if the cast is left off. */
  394. execvp (cmd[0], cmd);
  395. bb_error_msg("cannot run %s", cmd[0]);
  396. _exit (errno == ENOENT ? 127 : 126);
  397. }
  398. /* Have signals kill the child but not self (if possible). */
  399. interrupt_signal = signal (SIGINT, SIG_IGN);
  400. quit_signal = signal (SIGQUIT, SIG_IGN);
  401. if (resuse_end (pid, resp) == 0)
  402. bb_error_msg("error waiting for child process");
  403. /* Re-enable signals. */
  404. signal (SIGINT, interrupt_signal);
  405. signal (SIGQUIT, quit_signal);
  406. }
  407. extern int time_main (int argc, char **argv)
  408. {
  409. int gotone;
  410. resource_t res;
  411. const char *output_format = default_format;
  412. argc--;
  413. argv++;
  414. /* Parse any options -- don't use getopt() here so we don't
  415. * consume the args of our client application... */
  416. while (argc > 0 && **argv == '-') {
  417. gotone = 0;
  418. while (gotone==0 && *++(*argv)) {
  419. switch (**argv) {
  420. case 'v':
  421. output_format = long_format;
  422. break;
  423. case 'p':
  424. output_format = posix_format;
  425. break;
  426. default:
  427. bb_show_usage();
  428. }
  429. argc--;
  430. argv++;
  431. gotone = 1;
  432. }
  433. }
  434. if (argv == NULL || *argv == NULL)
  435. bb_show_usage();
  436. run_command (argv, &res);
  437. summarize (stderr, output_format, argv, &res);
  438. fflush (stderr);
  439. if (WIFSTOPPED (res.waitstatus))
  440. exit (WSTOPSIG (res.waitstatus));
  441. else if (WIFSIGNALED (res.waitstatus))
  442. exit (WTERMSIG (res.waitstatus));
  443. else if (WIFEXITED (res.waitstatus))
  444. exit (WEXITSTATUS (res.waitstatus));
  445. return 0;
  446. }