busybox/miscutils/time.c
<<
>>
Prefs
   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 GPLv2, see file LICENSE in this source tree.
   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