diff options
-rw-r--r-- | include/applets.h | 3 | ||||
-rw-r--r-- | include/usage.h | 7 | ||||
-rw-r--r-- | miscutils/config.in | 1 | ||||
-rw-r--r-- | miscutils/time.c | 502 |
4 files changed, 513 insertions, 0 deletions
diff --git a/include/applets.h b/include/applets.h index aa112c30b..0b9069497 100644 --- a/include/applets.h +++ b/include/applets.h @@ -437,6 +437,9 @@ #ifdef CONFIG_TFTP APPLET(tftp, tftp_main, _BB_DIR_USR_BIN) #endif +#ifdef BB_TIME + APPLET(time, time_main, _BB_DIR_USR_BIN) +#endif #ifdef CONFIG_TOUCH APPLET(touch, touch_main, _BB_DIR_BIN) #endif diff --git a/include/usage.h b/include/usage.h index df1c18ec7..a3447e83a 100644 --- a/include/usage.h +++ b/include/usage.h @@ -1701,6 +1701,13 @@ USAGE_TFTP_BS( \ "\t-b SIZE\tTransfer blocks of SIZE octets.\n" \ ) +#define time_trivial_usage \ + "[OPTION]... COMMAND [ARGS...]" +#define time_full_usage \ + "Runs the program COMMAND with arguments ARGS. When COMMAND finishes,\n" + "COMMAND's resource usage information is displayed\n\n" + "Options:\n" \ + "\t-v\tDisplays verbose resource usage information." #define touch_trivial_usage \ "[-c] FILE [FILE ...]" diff --git a/miscutils/config.in b/miscutils/config.in index 61b2113bc..20310eb6c 100644 --- a/miscutils/config.in +++ b/miscutils/config.in @@ -13,6 +13,7 @@ bool 'makedevs' CONFIG_MAKEDEVS bool 'mktemp' CONFIG_MKTEMP bool 'mt' CONFIG_MT bool 'readlink' CONFIG_READLINK +bool 'time' CONFIG_TIME bool 'update' CONFIG_UPDATE bool 'watchdog' CONFIG_WATCHDOG diff --git a/miscutils/time.c b/miscutils/time.c new file mode 100644 index 000000000..24a5b9aae --- /dev/null +++ b/miscutils/time.c @@ -0,0 +1,502 @@ +/* `time' utility to display resource usage of processes. + Copyright (C) 1990, 91, 92, 93, 96 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ + +/* Originally written by David Keppel <pardo@cs.washington.edu>. + Heavily modified by David MacKenzie <djm@gnu.ai.mit.edu>. + Heavily modified for busybox by Erik Andersen <andersen@codepoet.org> + */ + +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <errno.h> +#include <getopt.h> +#include <string.h> +#include <limits.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/types.h> /* For pid_t. */ +#include <sys/wait.h> +#include <sys/param.h> /* For getpagesize, maybe. */ + +#define TV_MSEC tv_usec / 1000 +#include <sys/resource.h> +#include "busybox.h" + +/* Information on the resources used by a child process. */ +typedef struct +{ + int waitstatus; + struct rusage ru; + struct timeval start, elapsed; /* 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"; + +/* The output format for the -p option .*/ +static const char *const posix_format = "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 = + "\tCommand being timed: \"%C\"\n" + "\tUser time (seconds): %U\n" + "\tSystem time (seconds): %S\n" + "\tPercent of CPU this job got: %P\n" + "\tElapsed (wall clock) time (h:mm:ss or m:ss): %E\n" + "\tAverage shared text size (kbytes): %X\n" + "\tAverage unshared data size (kbytes): %D\n" + "\tAverage stack size (kbytes): %p\n" + "\tAverage total size (kbytes): %K\n" + "\tMaximum resident set size (kbytes): %M\n" + "\tAverage resident set size (kbytes): %t\n" + "\tMajor (requiring I/O) page faults: %F\n" + "\tMinor (reclaiming a frame) page faults: %R\n" + "\tVoluntary context switches: %w\n" + "\tInvoluntary context switches: %c\n" + "\tSwaps: %W\n" + "\tFile system inputs: %I\n" + "\tFile system outputs: %O\n" + "\tSocket messages sent: %s\n" + "\tSocket messages received: %r\n" + "\tSignals delivered: %k\n" + "\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) +{ + 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; + } + resp->elapsed.tv_usec -= resp->start.tv_usec; + + resp->waitstatus = status; + + return 1; +} + +/* Print ARGV to FP, with each entry in ARGV separated by FILLER. */ +static void fprintargv (FILE *fp, char *const *argv, const char *filler) +{ + char *const *av; + + av = argv; + fputs (*av, fp); + while (*++av) + { + fputs (filler, fp); + fputs (*av, fp); + } + if (ferror (fp)) + error_msg_and_die("write error"); +} + +/* Return the number of kilobytes corresponding to a number of pages PAGES. + (Actually, we use it to convert pages*ticks into kilobytes*ticks.) + + Try to do arithmetic so that the risk of overflow errors is minimized. + 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 ps = 0; + unsigned long tmp; + static long size = LONG_MAX; + + /* Initialization. */ + if (ps == 0) + ps = (long) getpagesize (); + + /* Conversion. */ + if (pages > (LONG_MAX / ps)) + { /* Could overflow. */ + tmp = pages / 1024; /* Smaller first, */ + size = tmp * ps; /* then larger. */ + } + else + { /* Could underflow. */ + tmp = pages * ps; /* Larger first, */ + size = tmp / 1024; /* then smaller. */ + } + return size; +} + +/* summarize: Report on the system use of a command. + + Copy the FMT argument to FP 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: + (* means the tcsh time builtin also recognizes it) + % == 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 +* F == major page faults (required physical I/O) (ru_majflt) +* I == file system inputs (ru_inblock) +* K == average total mem usage (ru_idrss+ru_isrss+ru_ixrss) +* M == maximum resident set size in K (ru_maxrss) +* O == file system outputs (ru_oublock) +* P == percent of CPU this job got (total cpu time / elapsed time) +* R == minor page faults (reclaims; no physical I/O involved) (ru_minflt) +* S == system (kernel) time (seconds) (ru_stime) +* T == system time in [hour:]min:sec +* U == user time (seconds) (ru_utime) +* u == user time in [hour:]min:sec +* W == times swapped out (ru_nswap) +* X == average amount of shared text in K (ru_ixrss) + Z == page size +* c == involuntary context switches (ru_nivcsw) + e == elapsed real time in seconds +* k == signals delivered (ru_nsignals) + p == average unshared stack size in K (ru_isrss) +* r == socket messages received (ru_msgrcv) +* s == socket messages sent (ru_msgsnd) + t == average resident set size in K (ru_idrss) +* w == voluntary context switches (ru_nvcsw) + x == exit status of command + + Various memory usages are found by converting from page-seconds + to kbytes by multiplying by the page size, dividing by 1024, + and dividing by elapsed real time. + + FP is the stream to print to. + FMT is the format string, interpreted as described above. + COMMAND is the command and args that are being summarized. + RESP is resource information on the command. */ + +static void summarize (FILE *fp, const char *fmt, char **command, resource_t *resp) +{ + unsigned long r; /* Elapsed real milliseconds. */ + unsigned long v; /* Elapsed virtual (CPU) milliseconds. */ + + if (WIFSTOPPED (resp->waitstatus)) + fprintf (fp, "Command stopped by signal %d\n", WSTOPSIG (resp->waitstatus)); + else if (WIFSIGNALED (resp->waitstatus)) + fprintf (fp, "Command terminated by signal %d\n", WTERMSIG (resp->waitstatus)); + else if (WIFEXITED (resp->waitstatus) && WEXITSTATUS (resp->waitstatus)) + fprintf (fp, "Command exited with non-zero status %d\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; + + 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; + + while (*fmt) + { + switch (*fmt) + { + case '%': + switch (*++fmt) + { + case '%': /* Literal '%'. */ + putc ('%', fp); + break; + case 'C': /* The command that got timed. */ + fprintargv (fp, command, " "); + break; + case 'D': /* Average unshared data size. */ + fprintf (fp, "%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. */ + fprintf (fp, "%ldh %ldm %02lds", + resp->elapsed.tv_sec / 3600, + (resp->elapsed.tv_sec % 3600) / 60, + resp->elapsed.tv_sec % 60); + else + fprintf (fp, "%ldm %ld.%02lds", /* -> m:s. */ + resp->elapsed.tv_sec / 60, + resp->elapsed.tv_sec % 60, + resp->elapsed.tv_usec / 10000); + break; + case 'F': /* Major page faults. */ + fprintf (fp, "%ld", resp->ru.ru_majflt); + break; + case 'I': /* Inputs. */ + fprintf (fp, "%ld", resp->ru.ru_inblock); + break; + case 'K': /* Average mem usage == data+stack+text. */ + fprintf (fp, "%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)); + break; + case 'M': /* Maximum resident set size. */ + fprintf (fp, "%lu", ptok ((UL) resp->ru.ru_maxrss)); + break; + case 'O': /* Outputs. */ + fprintf (fp, "%ld", resp->ru.ru_oublock); + break; + case 'P': /* Percent of CPU this job got. */ + /* % cpu is (total cpu time)/(elapsed time). */ + if (r > 0) + fprintf (fp, "%lu%%", (v * 100 / r)); + else + fprintf (fp, "?%%"); + break; + case 'R': /* Minor page faults (reclaims). */ + fprintf (fp, "%ld", resp->ru.ru_minflt); + break; + case 'S': /* System time. */ + fprintf (fp, "%ld.%02ld", + resp->ru.ru_stime.tv_sec, + resp->ru.ru_stime.TV_MSEC / 10); + break; + case 'T': /* System time. */ + if (resp->ru.ru_stime.tv_sec >= 3600) /* One hour -> h:m:s. */ + fprintf (fp, "%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); + else + fprintf (fp, "%ldm %ld.%02lds", /* -> m:s. */ + resp->ru.ru_stime.tv_sec / 60, + resp->ru.ru_stime.tv_sec % 60, + resp->ru.ru_stime.tv_sec / 10000); + break; + case 'U': /* User time. */ + fprintf (fp, "%ld.%02ld", + resp->ru.ru_utime.tv_sec, + resp->ru.ru_utime.TV_MSEC / 10); + break; + case 'u': /* User time. */ + if (resp->ru.ru_utime.tv_sec >= 3600) /* One hour -> h:m:s. */ + fprintf (fp, "%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); + else + fprintf (fp, "%ldm %ld.%02lds", /* -> m:s. */ + resp->ru.ru_utime.tv_sec / 60, + resp->ru.ru_utime.tv_sec % 60, + resp->ru.ru_utime.tv_sec / 10000); + break; + case 'W': /* Times swapped out. */ + fprintf (fp, "%ld", resp->ru.ru_nswap); + break; + case 'X': /* Average shared text size. */ + fprintf (fp, "%lu", + MSEC_TO_TICKS (v) == 0 ? 0 : + ptok ((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS (v)); + break; + case 'Z': /* Page size. */ + fprintf (fp, "%d", getpagesize ()); + break; + case 'c': /* Involuntary context switches. */ + fprintf (fp, "%ld", resp->ru.ru_nivcsw); + break; + case 'e': /* Elapsed real time in seconds. */ + fprintf (fp, "%ld.%02ld", + resp->elapsed.tv_sec, + resp->elapsed.tv_usec / 10000); + break; + case 'k': /* Signals delivered. */ + fprintf (fp, "%ld", resp->ru.ru_nsignals); + break; + case 'p': /* Average stack segment. */ + fprintf (fp, "%lu", + MSEC_TO_TICKS (v) == 0 ? 0 : + ptok ((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS (v)); + break; + case 'r': /* Incoming socket messages received. */ + fprintf (fp, "%ld", resp->ru.ru_msgrcv); + break; + case 's': /* Outgoing socket messages sent. */ + fprintf (fp, "%ld", resp->ru.ru_msgsnd); + break; + case 't': /* Average resident set size. */ + fprintf (fp, "%lu", + MSEC_TO_TICKS (v) == 0 ? 0 : + ptok ((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS (v)); + break; + case 'w': /* Voluntary context switches. */ + fprintf (fp, "%ld", resp->ru.ru_nvcsw); + break; + case 'x': /* Exit status. */ + fprintf (fp, "%d", WEXITSTATUS (resp->waitstatus)); + break; + case '\0': + putc ('?', fp); + return; + default: + putc ('?', fp); + putc (*fmt, fp); + } + ++fmt; + break; + + case '\\': /* Format escape. */ + switch (*++fmt) + { + case 't': + putc ('\t', fp); + break; + case 'n': + putc ('\n', fp); + break; + case '\\': + putc ('\\', fp); + break; + default: + putc ('?', fp); + putc ('\\', fp); + putc (*fmt, fp); + } + ++fmt; + break; + + default: + putc (*fmt++, fp); + } + + if (ferror (fp)) + error_msg_and_die("write error"); + } + putc ('\n', fp); + + if (ferror (fp)) + error_msg_and_die("write error"); +} + +/* Run command CMD and return statistics on it. + Put the statistics in *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 = fork (); /* Run CMD as child process. */ + if (pid < 0) + 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. */ + execvp (cmd[0], cmd); + error_msg("cannot run %s", cmd[0]); + _exit (errno == ENOENT ? 127 : 126); + } + + /* Have signals kill the child but not self (if possible). */ + interrupt_signal = signal (SIGINT, SIG_IGN); + quit_signal = signal (SIGQUIT, SIG_IGN); + + if (resuse_end (pid, resp) == 0) + error_msg("error waiting for child process"); + + /* Re-enable signals. */ + signal (SIGINT, interrupt_signal); + signal (SIGQUIT, quit_signal); +} + +extern int time_main (int argc, char **argv) +{ + int gotone; + resource_t res; + const char *output_format = default_format; + + argc--; + argv++; + /* Parse any options -- don't use getopt() here so we don't + * consume the args of our client application... */ + while (argc > 0 && **argv == '-') { + gotone = 0; + while (gotone==0 && *++(*argv)) { + switch (**argv) { + case 'v': + output_format = long_format; + break; + case 'p': + output_format = posix_format; + break; + default: + show_usage(); + } + argc--; + argv++; + gotone = 1; + } + } + + if (argv == NULL || *argv == NULL) + show_usage(); + + run_command (argv, &res); + summarize (stdout, output_format, argv, &res); + fflush (stdout); + + if (WIFSTOPPED (res.waitstatus)) + exit (WSTOPSIG (res.waitstatus)); + else if (WIFSIGNALED (res.waitstatus)) + exit (WTERMSIG (res.waitstatus)); + else if (WIFEXITED (res.waitstatus)) + exit (WEXITSTATUS (res.waitstatus)); + return 0; +} |