Add in a 'time' applet. Adds 4.5k.
authorEric Andersen <andersen@codepoet.org>
Fri, 30 Nov 2001 07:54:32 +0000 (07:54 -0000)
committerEric Andersen <andersen@codepoet.org>
Fri, 30 Nov 2001 07:54:32 +0000 (07:54 -0000)
 -Erik

include/applets.h
include/usage.h
miscutils/config.in
miscutils/time.c [new file with mode: 0644]

index aa112c30b52099185d47da090adea337187a6535..0b9069497132b5ff9ea19b624a24c7fe311c2db5 100644 (file)
 #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
index df1c18ec73129ac2052af22ebb60319f9f31b93d..a3447e83a15fc461e32984a451cb9804b522a63f 100644 (file)
        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 ...]"
index 61b2113bc9fccbe04751ad7a531fac1d07a4f576..20310eb6cd41e511889755a92f9b70811fd21684 100644 (file)
@@ -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 (file)
index 0000000..24a5b9a
--- /dev/null
@@ -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;
+}