X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=miscutils%2Ftime.c;h=65dbcdcf31ae0edc835a614441b061649430ed22;hb=73af705628ddaedc4c6f7f78b9658d6c01310309;hp=0a2e0b7e2ed86d2fc43bda84130ce5f4438089f0;hpb=b6adbf1be29841501cc49917249e85f273e1df7c;p=oweals%2Fbusybox.git diff --git a/miscutils/time.c b/miscutils/time.c index 0a2e0b7e2..65dbcdcf3 100644 --- a/miscutils/time.c +++ b/miscutils/time.c @@ -1,49 +1,58 @@ /* vi: set sw=4 ts=4: */ -/* `time' utility to display resource usage of processes. +/* 'time' utility to display resource usage of processes. Copyright (C) 1990, 91, 92, 93, 96 Free Software Foundation, Inc. - Licensed under GPL version 2, see file LICENSE in this tarball for details. + Licensed under GPLv2, see file LICENSE in this source tree. */ /* Originally written by David Keppel . Heavily modified by David MacKenzie . Heavily modified for busybox by Erik Andersen */ +//config:config TIME +//config: bool "time (7 kb)" +//config: default y +//config: help +//config: The time command runs the specified program with the given arguments. +//config: When the command finishes, time writes a message to standard output +//config: giving timing statistics about this program run. + +//applet:IF_TIME(APPLET(time, BB_DIR_USR_BIN, BB_SUID_DROP)) + +//kbuild:lib-$(CONFIG_TIME) += time.o + +//usage:#define time_trivial_usage +//usage: "[-vpa] [-o FILE] PROG ARGS" +//usage:#define time_full_usage "\n\n" +//usage: "Run PROG, display resource usage when it exits\n" +//usage: "\n -v Verbose" +//usage: "\n -p POSIX output format" +//usage: "\n -f FMT Custom format" +//usage: "\n -o FILE Write result to FILE" +//usage: "\n -a Append (else overwrite)" #include "libbb.h" - -#define TV_MSEC tv_usec / 1000 +#include /* getrusage */ /* Information on the resources used by a child process. */ typedef struct { int waitstatus; struct rusage ru; - struct timeval start, elapsed; /* Wallclock time of process. */ + unsigned elapsed_ms; /* Wallclock time of process. */ } resource_t; /* msec = milliseconds = 1/1,000 (1*10e-3) second. usec = microseconds = 1/1,000,000 (1*10e-6) second. */ -#ifndef TICKS_PER_SEC -#define TICKS_PER_SEC 100 -#endif - -/* The number of milliseconds in one `tick' used by the `rusage' structure. */ -#define MSEC_PER_TICK (1000 / TICKS_PER_SEC) - -/* Return the number of clock ticks that occur in M milliseconds. */ -#define MSEC_TO_TICKS(m) ((m) / MSEC_PER_TICK) - #define UL unsigned long -static const char *const default_format = "real\t%E\nuser\t%u\nsys\t%T"; +static const char default_format[] ALIGN1 = "real\t%E\nuser\t%u\nsys\t%T"; /* The output format for the -p option .*/ -static const char *const posix_format = "real %e\nuser %U\nsys %S"; - +static const char posix_format[] ALIGN1 = "real %e\nuser %U\nsys %S"; /* Format string for printing all statistics verbosely. Keep this output to 24 lines so users on terminals can see it all.*/ -static const char *const long_format = +static const char long_format[] ALIGN1 = "\tCommand being timed: \"%C\"\n" "\tUser time (seconds): %U\n" "\tSystem time (seconds): %S\n" @@ -68,46 +77,31 @@ static const char *const long_format = "\tPage size (bytes): %Z\n" "\tExit status: %x"; - /* Wait for and fill in data on child process PID. Return 0 on error, 1 if ok. */ - /* pid_t is short on BSDI, so don't try to promote it. */ -static int resuse_end(pid_t pid, resource_t * resp) +static void resuse_end(pid_t pid, resource_t *resp) { - int status; - pid_t caught; /* Ignore signals, but don't ignore the children. When wait3 - returns the child process, set the time the command finished. */ - while ((caught = wait3(&status, 0, &resp->ru)) != pid) { - if (caught == -1) - return 0; - } - - gettimeofday(&resp->elapsed, (struct timezone *) 0); - resp->elapsed.tv_sec -= resp->start.tv_sec; - if (resp->elapsed.tv_usec < resp->start.tv_usec) { - /* Manually carry a one from the seconds field. */ - resp->elapsed.tv_usec += 1000000; - --resp->elapsed.tv_sec; + * returns the child process, set the time the command finished. */ + while ((caught = wait3(&resp->waitstatus, 0, &resp->ru)) != pid) { + if (caught == -1 && errno != EINTR) { + bb_perror_msg("wait"); + return; + } } - resp->elapsed.tv_usec -= resp->start.tv_usec; - - resp->waitstatus = status; - - return 1; + resp->elapsed_ms = monotonic_ms() - resp->elapsed_ms; } -/* Print ARGV, with each entry in ARGV separated by FILLER. */ -static void printargv(char *const *argv, const char *filler) +static void printargv(char *const *argv) { - fputs(*argv, stdout); - while (*++argv) { - fputs(filler, stdout); - fputs(*argv, stdout); - } + const char *fmt = " %s" + 1; + do { + printf(fmt, *argv); + fmt = " %s"; + } while (*++argv); } /* Return the number of kilobytes corresponding to a number of pages PAGES. @@ -117,35 +111,29 @@ static void printargv(char *const *argv, const char *filler) This is funky since the pagesize could be less than 1K. Note: Some machines express getrusage statistics in terms of K, others in terms of pages. */ - -static unsigned long ptok(unsigned long pages) +static unsigned long ptok(const unsigned pagesize, const unsigned long pages) { - static unsigned long ps; unsigned long tmp; - /* Initialization. */ - if (ps == 0) - ps = getpagesize(); - /* Conversion. */ - if (pages > (LONG_MAX / ps)) { /* Could overflow. */ - tmp = pages / 1024; /* Smaller first, */ - return tmp * ps; /* then larger. */ + if (pages > (LONG_MAX / pagesize)) { /* Could overflow. */ + tmp = pages / 1024; /* Smaller first, */ + return tmp * pagesize; /* then larger. */ } /* Could underflow. */ - tmp = pages * ps; /* Larger first, */ - return tmp / 1024; /* then smaller. */ + tmp = pages * pagesize; /* Larger first, */ + return tmp / 1024; /* then smaller. */ } /* summarize: Report on the system use of a command. - Print the FMT argument except that `%' sequences - have special meaning, and `\n' and `\t' are translated into - newline and tab, respectively, and `\\' is translated into `\'. + Print the FMT argument except that '%' sequences + have special meaning, and '\n' and '\t' are translated into + newline and tab, respectively, and '\\' is translated into '\'. - The character following a `%' can be: + The character following a '%' can be: (* means the tcsh time builtin also recognizes it) - % == a literal `%' + % == a literal '%' C == command name and arguments * D == average unshared data size in K (ru_idrss+ru_isrss) * E == elapsed real (wall clock) time in [hour:]min:sec @@ -181,32 +169,38 @@ static unsigned long ptok(unsigned long pages) COMMAND is the command and args that are being summarized. RESP is resource information on the command. */ -static void summarize(const char *fmt, char **command, resource_t * resp) +#ifndef TICKS_PER_SEC +#define TICKS_PER_SEC 100 +#endif + +static void summarize(const char *fmt, char **command, resource_t *resp) { - unsigned long r; /* Elapsed real milliseconds. */ - unsigned long v; /* Elapsed virtual (CPU) milliseconds. */ + unsigned vv_ms; /* Elapsed virtual (CPU) milliseconds */ + unsigned cpu_ticks; /* Same, in "CPU ticks" */ + unsigned pagesize = getpagesize(); + /* Impossible: we do not use WUNTRACED flag in wait()... if (WIFSTOPPED(resp->waitstatus)) - printf("Command stopped by signal %d\n", + printf("Command stopped by signal %u\n", WSTOPSIG(resp->waitstatus)); - else if (WIFSIGNALED(resp->waitstatus)) - printf("Command terminated by signal %d\n", + else */ + if (WIFSIGNALED(resp->waitstatus)) + printf("Command terminated by signal %u\n", WTERMSIG(resp->waitstatus)); else if (WIFEXITED(resp->waitstatus) && WEXITSTATUS(resp->waitstatus)) - printf("Command exited with non-zero status %d\n", + printf("Command exited with non-zero status %u\n", WEXITSTATUS(resp->waitstatus)); - /* Convert all times to milliseconds. Occasionally, one of these values - comes out as zero. Dividing by zero causes problems, so we first - check the time value. If it is zero, then we take `evasive action' - instead of calculating a value. */ - - r = resp->elapsed.tv_sec * 1000 + resp->elapsed.tv_usec / 1000; + vv_ms = (resp->ru.ru_utime.tv_sec + resp->ru.ru_stime.tv_sec) * 1000 + + (resp->ru.ru_utime.tv_usec + resp->ru.ru_stime.tv_usec) / 1000; - v = resp->ru.ru_utime.tv_sec * 1000 + resp->ru.ru_utime.TV_MSEC + - resp->ru.ru_stime.tv_sec * 1000 + resp->ru.ru_stime.TV_MSEC; - - /* putchar() != putc(stdout) in glibc! */ +#if (1000 / TICKS_PER_SEC) * TICKS_PER_SEC == 1000 + /* 1000 is exactly divisible by TICKS_PER_SEC (typical) */ + cpu_ticks = vv_ms / (1000 / TICKS_PER_SEC); +#else + cpu_ticks = vv_ms * (unsigned long long)TICKS_PER_SEC / 1000; +#endif + if (!cpu_ticks) cpu_ticks = 1; /* we divide by it, must be nonzero */ while (*fmt) { /* Handle leading literal part */ @@ -223,7 +217,7 @@ static void summarize(const char *fmt, char **command, resource_t * resp) /* Usually we optimize for size, but there is a limit * for everything. With this we do a lot of 1-byte writes */ default: - putc(*fmt, stdout); + bb_putchar(*fmt); break; #endif @@ -233,139 +227,134 @@ static void summarize(const char *fmt, char **command, resource_t * resp) /* Our format strings do not have these */ /* and we do not take format str from user */ default: - putc('%', stdout); + bb_putchar('%'); /*FALLTHROUGH*/ case '%': if (!*fmt) goto ret; - putc(*fmt, stdout); + bb_putchar(*fmt); break; #endif case 'C': /* The command that got timed. */ - printargv(command, " "); + printargv(command); break; case 'D': /* Average unshared data size. */ printf("%lu", - MSEC_TO_TICKS(v) == 0 ? 0 : - ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v) + - ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v)); - break; - case 'E': /* Elapsed real (wall clock) time. */ - if (resp->elapsed.tv_sec >= 3600) /* One hour -> h:m:s. */ - printf("%ldh %ldm %02lds", - resp->elapsed.tv_sec / 3600, - (resp->elapsed.tv_sec % 3600) / 60, - resp->elapsed.tv_sec % 60); + (ptok(pagesize, (UL) resp->ru.ru_idrss) + + ptok(pagesize, (UL) resp->ru.ru_isrss)) / cpu_ticks); + break; + case 'E': { /* Elapsed real (wall clock) time. */ + unsigned seconds = resp->elapsed_ms / 1000; + if (seconds >= 3600) /* One hour -> h:m:s. */ + printf("%uh %um %02us", + seconds / 3600, + (seconds % 3600) / 60, + seconds % 60); else - printf("%ldm %ld.%02lds", /* -> m:s. */ - resp->elapsed.tv_sec / 60, - resp->elapsed.tv_sec % 60, - resp->elapsed.tv_usec / 10000); + printf("%um %u.%02us", /* -> m:s. */ + seconds / 60, + seconds % 60, + (unsigned)(resp->elapsed_ms / 10) % 100); break; + } case 'F': /* Major page faults. */ - printf("%ld", resp->ru.ru_majflt); + printf("%lu", resp->ru.ru_majflt); break; case 'I': /* Inputs. */ - printf("%ld", resp->ru.ru_inblock); + printf("%lu", resp->ru.ru_inblock); break; case 'K': /* Average mem usage == data+stack+text. */ printf("%lu", - MSEC_TO_TICKS(v) == 0 ? 0 : - ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v) + - ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v) + - ptok((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS(v)); + (ptok(pagesize, (UL) resp->ru.ru_idrss) + + ptok(pagesize, (UL) resp->ru.ru_isrss) + + ptok(pagesize, (UL) resp->ru.ru_ixrss)) / cpu_ticks); break; case 'M': /* Maximum resident set size. */ - printf("%lu", ptok((UL) resp->ru.ru_maxrss)); + printf("%lu", ptok(pagesize, (UL) resp->ru.ru_maxrss)); break; case 'O': /* Outputs. */ - printf("%ld", resp->ru.ru_oublock); + printf("%lu", resp->ru.ru_oublock); break; case 'P': /* Percent of CPU this job got. */ /* % cpu is (total cpu time)/(elapsed time). */ - if (r > 0) - printf("%lu%%", (v * 100 / r)); + if (resp->elapsed_ms > 0) + printf("%u%%", (unsigned)(vv_ms * 100 / resp->elapsed_ms)); else printf("?%%"); break; case 'R': /* Minor page faults (reclaims). */ - printf("%ld", resp->ru.ru_minflt); + printf("%lu", resp->ru.ru_minflt); break; case 'S': /* System time. */ - printf("%ld.%02ld", - resp->ru.ru_stime.tv_sec, - resp->ru.ru_stime.TV_MSEC / 10); + printf("%u.%02u", + (unsigned)resp->ru.ru_stime.tv_sec, + (unsigned)(resp->ru.ru_stime.tv_usec / 10000)); break; case 'T': /* System time. */ if (resp->ru.ru_stime.tv_sec >= 3600) /* One hour -> h:m:s. */ - printf("%ldh %ldm %02lds", - resp->ru.ru_stime.tv_sec / 3600, - (resp->ru.ru_stime.tv_sec % 3600) / 60, - resp->ru.ru_stime.tv_sec % 60); + printf("%uh %um %02us", + (unsigned)(resp->ru.ru_stime.tv_sec / 3600), + (unsigned)(resp->ru.ru_stime.tv_sec % 3600) / 60, + (unsigned)(resp->ru.ru_stime.tv_sec % 60)); else - printf("%ldm %ld.%02lds", /* -> m:s. */ - resp->ru.ru_stime.tv_sec / 60, - resp->ru.ru_stime.tv_sec % 60, - resp->ru.ru_stime.tv_usec / 10000); + printf("%um %u.%02us", /* -> m:s. */ + (unsigned)(resp->ru.ru_stime.tv_sec / 60), + (unsigned)(resp->ru.ru_stime.tv_sec % 60), + (unsigned)(resp->ru.ru_stime.tv_usec / 10000)); break; case 'U': /* User time. */ - printf("%ld.%02ld", - resp->ru.ru_utime.tv_sec, - resp->ru.ru_utime.TV_MSEC / 10); + printf("%u.%02u", + (unsigned)resp->ru.ru_utime.tv_sec, + (unsigned)(resp->ru.ru_utime.tv_usec / 10000)); break; case 'u': /* User time. */ if (resp->ru.ru_utime.tv_sec >= 3600) /* One hour -> h:m:s. */ - printf("%ldh %ldm %02lds", - resp->ru.ru_utime.tv_sec / 3600, - (resp->ru.ru_utime.tv_sec % 3600) / 60, - resp->ru.ru_utime.tv_sec % 60); + printf("%uh %um %02us", + (unsigned)(resp->ru.ru_utime.tv_sec / 3600), + (unsigned)(resp->ru.ru_utime.tv_sec % 3600) / 60, + (unsigned)(resp->ru.ru_utime.tv_sec % 60)); else - printf("%ldm %ld.%02lds", /* -> m:s. */ - resp->ru.ru_utime.tv_sec / 60, - resp->ru.ru_utime.tv_sec % 60, - resp->ru.ru_utime.tv_usec / 10000); + printf("%um %u.%02us", /* -> m:s. */ + (unsigned)(resp->ru.ru_utime.tv_sec / 60), + (unsigned)(resp->ru.ru_utime.tv_sec % 60), + (unsigned)(resp->ru.ru_utime.tv_usec / 10000)); break; case 'W': /* Times swapped out. */ - printf("%ld", resp->ru.ru_nswap); + printf("%lu", resp->ru.ru_nswap); break; case 'X': /* Average shared text size. */ - printf("%lu", - MSEC_TO_TICKS(v) == 0 ? 0 : - ptok((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS(v)); + printf("%lu", ptok(pagesize, (UL) resp->ru.ru_ixrss) / cpu_ticks); break; case 'Z': /* Page size. */ - printf("%d", getpagesize()); + printf("%u", pagesize); break; case 'c': /* Involuntary context switches. */ - printf("%ld", resp->ru.ru_nivcsw); + printf("%lu", resp->ru.ru_nivcsw); break; case 'e': /* Elapsed real time in seconds. */ - printf("%ld.%02ld", - resp->elapsed.tv_sec, resp->elapsed.tv_usec / 10000); + printf("%u.%02u", + (unsigned)resp->elapsed_ms / 1000, + (unsigned)(resp->elapsed_ms / 10) % 100); break; case 'k': /* Signals delivered. */ - printf("%ld", resp->ru.ru_nsignals); + printf("%lu", resp->ru.ru_nsignals); break; case 'p': /* Average stack segment. */ - printf("%lu", - MSEC_TO_TICKS(v) == 0 ? 0 : - ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v)); + printf("%lu", ptok(pagesize, (UL) resp->ru.ru_isrss) / cpu_ticks); break; case 'r': /* Incoming socket messages received. */ - printf("%ld", resp->ru.ru_msgrcv); + printf("%lu", resp->ru.ru_msgrcv); break; case 's': /* Outgoing socket messages sent. */ - printf("%ld", resp->ru.ru_msgsnd); + printf("%lu", resp->ru.ru_msgsnd); break; case 't': /* Average resident set size. */ - printf("%lu", - MSEC_TO_TICKS(v) == 0 ? 0 : - ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v)); + printf("%lu", ptok(pagesize, (UL) resp->ru.ru_idrss) / cpu_ticks); break; case 'w': /* Voluntary context switches. */ - printf("%ld", resp->ru.ru_nvcsw); + printf("%lu", resp->ru.ru_nvcsw); break; case 'x': /* Exit status. */ - printf("%d", WEXITSTATUS(resp->waitstatus)); + printf("%u", WEXITSTATUS(resp->waitstatus)); break; } break; @@ -374,17 +363,17 @@ static void summarize(const char *fmt, char **command, resource_t * resp) case '\\': /* Format escape. */ switch (*++fmt) { default: - putc('\\', stdout); + bb_putchar('\\'); /*FALLTHROUGH*/ case '\\': if (!*fmt) goto ret; - putc(*fmt, stdout); + bb_putchar(*fmt); break; case 't': - putc('\t', stdout); + bb_putchar('\t'); break; case 'n': - putc('\n', stdout); + bb_putchar('\n'); break; } break; @@ -393,82 +382,85 @@ static void summarize(const char *fmt, char **command, resource_t * resp) ++fmt; } /* ret: */ - putc('\n', stdout); + bb_putchar('\n'); } /* Run command CMD and return statistics on it. Put the statistics in *RESP. */ -static void run_command(char *const *cmd, resource_t * resp) +static void run_command(char *const *cmd, resource_t *resp) { - pid_t pid; /* Pid of child. */ - __sighandler_t interrupt_signal, quit_signal; - - gettimeofday(&resp->start, (struct timezone *) 0); - pid = vfork(); /* Run CMD as child process. */ - if (pid < 0) - bb_error_msg_and_die("cannot fork"); - else if (pid == 0) { /* If child. */ - /* Don't cast execvp arguments; that causes errors on some systems, - versus merely warnings if the cast is left off. */ - BB_EXECVP(cmd[0], cmd); - bb_error_msg("cannot run %s", cmd[0]); - _exit(errno == ENOENT ? 127 : 126); + pid_t pid; + void (*interrupt_signal)(int); + void (*quit_signal)(int); + + resp->elapsed_ms = monotonic_ms(); + pid = xvfork(); + if (pid == 0) { + /* Child */ + BB_EXECVP_or_die((char**)cmd); } /* Have signals kill the child but not self (if possible). */ +//TODO: just block all sigs? and re-enable them in the very end in main? interrupt_signal = signal(SIGINT, SIG_IGN); quit_signal = signal(SIGQUIT, SIG_IGN); - if (resuse_end(pid, resp) == 0) - bb_error_msg("error waiting for child process"); + resuse_end(pid, resp); /* Re-enable signals. */ signal(SIGINT, interrupt_signal); signal(SIGQUIT, quit_signal); } -int time_main(int argc, char **argv); -int time_main(int argc, char **argv) +int time_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int time_main(int argc UNUSED_PARAM, char **argv) { resource_t res; - const char *output_format = default_format; - char c; - - goto next; - /* Parse any options -- don't use getopt() here so we don't - * consume the args of our client application... */ - while (argc > 0 && argv[0][0] == '-') { - while ((c = *++*argv)) { - switch (c) { - case 'v': - output_format = long_format; - break; - case 'p': - output_format = posix_format; - break; - default: - bb_show_usage(); - } - } - next: - argv++; - argc--; - if (!argc) - bb_show_usage(); + /* $TIME has lowest prio (-v,-p,-f FMT overrride it) */ + const char *output_format = getenv("TIME") ? : default_format; + char *output_filename; + int output_fd; + int opt; + int ex; + enum { + OPT_v = (1 << 0), + OPT_p = (1 << 1), + OPT_a = (1 << 2), + OPT_o = (1 << 3), + OPT_f = (1 << 4), + }; + + /* "+": stop on first non-option */ + opt = getopt32(argv, "^+" "vpao:f:" "\0" "-1"/*at least one arg*/, + &output_filename, &output_format + ); + argv += optind; + if (opt & OPT_v) + output_format = long_format; + if (opt & OPT_p) + output_format = posix_format; + output_fd = STDERR_FILENO; + if (opt & OPT_o) { + output_fd = xopen(output_filename, + (opt & OPT_a) /* append? */ + ? (O_CREAT | O_WRONLY | O_CLOEXEC | O_APPEND) + : (O_CREAT | O_WRONLY | O_CLOEXEC | O_TRUNC) + ); } run_command(argv, &res); /* Cheat. printf's are shorter :) */ - stdout = stderr; - dup2(2, 1); /* just in case libc does something silly :( */ + xdup2(output_fd, STDOUT_FILENO); summarize(output_format, argv, &res); + ex = WEXITSTATUS(res.waitstatus); + /* Impossible: we do not use WUNTRACED flag in wait()... if (WIFSTOPPED(res.waitstatus)) - return WSTOPSIG(res.waitstatus); + ex = WSTOPSIG(res.waitstatus); + */ if (WIFSIGNALED(res.waitstatus)) - return WTERMSIG(res.waitstatus); - if (WIFEXITED(res.waitstatus)) - return WEXITSTATUS(res.waitstatus); - fflush_stdout_and_exit(0); + ex = WTERMSIG(res.waitstatus); + + fflush_stdout_and_exit(ex); }