]> git.saurik.com Git - apple/shell_cmds.git/blobdiff - time/time.c
shell_cmds-216.40.4.tar.gz
[apple/shell_cmds.git] / time / time.c
index 0a3cb4738d8203575e86f53e8bf7c405f525a4b2..afaaed0e86b9fda95de530b39a794228090c8cce 100644 (file)
  * SUCH DAMAGE.
  */
 
+#include <errno.h>
+#include <err.h>
+#include <inttypes.h>
+#include <langinfo.h>
+#include <libproc.h>
+#include <locale.h>
 #include <sys/cdefs.h>
-#ifndef lint
-__COPYRIGHT("@(#) Copyright (c) 1987, 1988, 1993\n\
-       The Regents of the University of California.  All rights reserved.\n");
-#endif /* not lint */
-
-#ifndef lint
-#if 0
-static char sccsid[] = "@(#)time.c     8.1 (Berkeley) 6/6/93";
-#endif
-__RCSID("$NetBSD: time.c,v 1.9 1997/10/20 03:28:21 lukem Exp $");
-#endif /* not lint */
-
+#include <sysexits.h>
 #include <sys/types.h>
 #include <sys/time.h>
 #include <sys/resource.h>
 #include <sys/wait.h>
 #include <signal.h>
+#include <stdbool.h>
 #include <stdio.h>
+#include <stdint.h>
 #include <stdlib.h>
+#include <time.h>
 #include <unistd.h>
-#include <errno.h>
-#include <langinfo.h>
-#include <locale.h>
 
 int lflag;
 int portableflag;
+bool child_running = true;
 
-int    main __P((int, char **));
+void
+child_handler(int sig)
+{
+       child_running = false;
+}
 
 int
-main(argc, argv)
-       int argc;
-       char **argv;
+main(int argc, char **argv)
 {
        int pid;
-       int ch, status;
-       struct timeval before, after;
+       int ch, status, rusage_ret = -1;
+       uint64_t before_ns, after_ns, duration_ns, duration_secs, duration_frac_ns;
        struct rusage ru;
-       char * radix = NULL;
+       struct rusage_info_v4 ruinfo;
+       sigset_t sigmask, suspmask, origmask;
 
-#ifdef __GNUC__                /* XXX: borken gcc */
-       (void)&argv;
-#endif
        lflag = 0;
-       while ((ch = getopt(argc, argv, "lp")) != -1)
+       while ((ch = getopt(argc, argv, "lp")) != -1) {
                switch((char)ch) {
                case 'p':
                        portableflag = 1;
@@ -88,37 +84,88 @@ main(argc, argv)
                        break;
                case '?':
                default:
-                       fprintf(stderr, "usage: time [-lp] command.\n");
+                       fprintf(stderr, "usage: time [-lp] <command>\n");
                        exit(1);
                }
+       }
 
-       if (!(argc -= optind))
+       if (!(argc -= optind)) {
                exit(0);
+       }
        argv += optind;
 
-       gettimeofday(&before, (struct timezone *)NULL);
-       switch(pid = vfork()) {
-       case -1:                        /* error */
-               perror("time");
-               exit(1);
-               /* NOTREACHED */
-       case 0:                         /* child */
+       sigemptyset(&sigmask);
+       /*
+        * Block SIGCHLD so that the check for `child_running` doesn't miss the
+        * handler before calling `sigsuspend` and blocking forever.
+        */
+       sigaddset(&sigmask, SIGCHLD);
+       sigprocmask(SIG_BLOCK, &sigmask, &origmask);
+
+       /*
+        * Ensure child signals are handled by the parent prior to fork; otherwise,
+        * they could be missed between the child forking and calling `sigsuspend`.
+        */
+       (void)signal(SIGCHLD, child_handler);
+
+       sigemptyset(&suspmask);
+
+       before_ns = clock_gettime_nsec_np(CLOCK_UPTIME_RAW);
+       /*
+        * NB: Don't add anything between these two lines -- measurement is
+        * happening now.
+        */
+       switch (pid = vfork()) {
+       case -1: /* error */
+               err(EX_OSERR, "time");
+               __builtin_unreachable();
+       case 0: /* child */
+               /*
+                * Allow the child to respond to signals by resetting to the original
+                * signal handling behavior.
+                */
+               (void)sigprocmask(SIG_SETMASK, &origmask, NULL);
                execvp(*argv, argv);
                perror(*argv);
                _exit((errno == ENOENT) ? 127 : 126);
-               /* NOTREACHED */
+               __builtin_unreachable();
+       default: /* parent */
+               break;
        }
 
-       /* parent */
+       /*
+        * Let the child handle signals that normally exit.
+        */
        (void)signal(SIGINT, SIG_IGN);
        (void)signal(SIGQUIT, SIG_IGN);
-       while (wait3(&status, 0, &ru) != pid);
-       gettimeofday(&after, (struct timezone *)NULL);
-       if (!WIFEXITED(status))
+
+       while (child_running) {
+               /*
+                * This would be racy, but SIGCHLD is blocked above (as part of
+                * `sigmask`.
+                */
+               sigsuspend(&suspmask);
+       }
+       /*
+        * NB: Minimize what's added between these statements to preserve the
+        * accuracy of the time measurement.
+        */
+       after_ns = clock_gettime_nsec_np(CLOCK_UPTIME_RAW);
+       if (lflag) {
+               rusage_ret = proc_pid_rusage(pid, RUSAGE_INFO_V4, (void **)&ruinfo);
+       }
+       while (wait3(&status, 0, &ru) != pid) {
+       }
+       if (!WIFEXITED(status)) {
                fprintf(stderr, "Command terminated abnormally.\n");
-       timersub(&after, &before, &after);
+       }
+       duration_ns = after_ns - before_ns;
+       duration_secs = duration_ns / (1000 * 1000 * 1000);
+       duration_frac_ns = duration_ns - (duration_secs * 1000 * 1000 * 1000);
 
        if (portableflag) {
+               char *radix = NULL;
+
                setlocale(LC_ALL, "");
 
                radix = nl_langinfo(RADIXCHAR);
@@ -126,15 +173,15 @@ main(argc, argv)
                        radix = ".";
                }
 
-               fprintf (stderr, "real %9ld%s%02ld\n",
-                       (long)after.tv_sec, radix, (long)after.tv_usec/10000);
-               fprintf (stderr, "user %9ld%s%02ld\n",
+               fprintf(stderr, "real %9" PRIu64 "%s%02" PRIu64 "\n",
+                       duration_secs, radix, duration_frac_ns / (10 * 1000 * 1000));
+               fprintf(stderr, "user %9ld%s%02ld\n",
                        (long)ru.ru_utime.tv_sec, radix, (long)ru.ru_utime.tv_usec/10000);
-               fprintf (stderr, "sys  %9ld%s%02ld\n",
+               fprintf(stderr, "sys  %9ld%s%02ld\n",
                        (long)ru.ru_stime.tv_sec, radix, (long)ru.ru_stime.tv_usec/10000);
        } else {
-               fprintf(stderr, "%9ld.%02ld real ", 
-                       (long)after.tv_sec, (long)after.tv_usec/10000);
+               fprintf(stderr, "%9" PRIu64 ".%02" PRIu64 " real ",
+                       duration_secs, duration_frac_ns / (10 * 1000 * 1000));
                fprintf(stderr, "%9ld.%02ld user ",
                        (long)ru.ru_utime.tv_sec, (long)ru.ru_utime.tv_usec/10000);
                fprintf(stderr, "%9ld.%02ld sys\n",
@@ -142,41 +189,57 @@ main(argc, argv)
        }
 
        if (lflag) {
-               int hz = 100;                   /* XXX */
+               int hz = 100; /* XXX */
                long ticks;
 
                ticks = hz * (ru.ru_utime.tv_sec + ru.ru_stime.tv_sec) +
                     hz * (ru.ru_utime.tv_usec + ru.ru_stime.tv_usec) / 1000000;
 
-               fprintf(stderr, "%10ld  %s\n",
+               fprintf(stderr, "%20ld  %s\n",
                        ru.ru_maxrss, "maximum resident set size");
-               fprintf(stderr, "%10ld  %s\n", ticks ? ru.ru_ixrss / ticks : 0,
+               fprintf(stderr, "%20ld  %s\n", ticks ? ru.ru_ixrss / ticks : 0,
                        "average shared memory size");
-               fprintf(stderr, "%10ld  %s\n", ticks ? ru.ru_idrss / ticks : 0,
+               fprintf(stderr, "%20ld  %s\n", ticks ? ru.ru_idrss / ticks : 0,
                        "average unshared data size");
-               fprintf(stderr, "%10ld  %s\n", ticks ? ru.ru_isrss / ticks : 0,
+               fprintf(stderr, "%20ld  %s\n", ticks ? ru.ru_isrss / ticks : 0,
                        "average unshared stack size");
-               fprintf(stderr, "%10ld  %s\n",
+               fprintf(stderr, "%20ld  %s\n",
                        ru.ru_minflt, "page reclaims");
-               fprintf(stderr, "%10ld  %s\n",
+               fprintf(stderr, "%20ld  %s\n",
                        ru.ru_majflt, "page faults");
-               fprintf(stderr, "%10ld  %s\n",
+               fprintf(stderr, "%20ld  %s\n",
                        ru.ru_nswap, "swaps");
-               fprintf(stderr, "%10ld  %s\n",
+               fprintf(stderr, "%20ld  %s\n",
                        ru.ru_inblock, "block input operations");
-               fprintf(stderr, "%10ld  %s\n",
+               fprintf(stderr, "%20ld  %s\n",
                        ru.ru_oublock, "block output operations");
-               fprintf(stderr, "%10ld  %s\n",
+               fprintf(stderr, "%20ld  %s\n",
                        ru.ru_msgsnd, "messages sent");
-               fprintf(stderr, "%10ld  %s\n",
+               fprintf(stderr, "%20ld  %s\n",
                        ru.ru_msgrcv, "messages received");
-               fprintf(stderr, "%10ld  %s\n",
+               fprintf(stderr, "%20ld  %s\n",
                        ru.ru_nsignals, "signals received");
-               fprintf(stderr, "%10ld  %s\n",
+               fprintf(stderr, "%20ld  %s\n",
                        ru.ru_nvcsw, "voluntary context switches");
-               fprintf(stderr, "%10ld  %s\n",
+               fprintf(stderr, "%20ld  %s\n",
                        ru.ru_nivcsw, "involuntary context switches");
+
+               if (rusage_ret >= 0) {
+                       if (ruinfo.ri_instructions > 0) {
+                               fprintf(stderr, "%20" PRIu64 "  %s\n", ruinfo.ri_instructions,
+                                       "instructions retired");
+                       }
+                       if (ruinfo.ri_cycles > 0) {
+                               fprintf(stderr, "%20" PRIu64 "  %s\n", ruinfo.ri_cycles,
+                                       "cycles elapsed");
+                       }
+                       if (ruinfo.ri_lifetime_max_phys_footprint > 0) {
+                               fprintf(stderr, "%20" PRIu64 "  %s\n",
+                                       ruinfo.ri_lifetime_max_phys_footprint,
+                                       "peak memory footprint");
+                       }
+               }
        }
 
-       exit (WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE);
+       exit(WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE);
 }