* 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;
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);
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",
}
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);
}