| 1 | #define _GNU_SOURCE  // for timersub()
 | 
| 2 | #include <assert.h>
 | 
| 3 | #include <errno.h>
 | 
| 4 | #include <getopt.h>
 | 
| 5 | #include <stdbool.h>
 | 
| 6 | #include <stdio.h>
 | 
| 7 | #include <stdlib.h>        // exit()
 | 
| 8 | #include <sys/resource.h>  // getrusage()
 | 
| 9 | #include <sys/time.h>
 | 
| 10 | #include <sys/wait.h>
 | 
| 11 | #include <unistd.h>
 | 
| 12 | 
 | 
| 13 | void die_errno(const char *message) {
 | 
| 14 |   perror(message);
 | 
| 15 |   exit(1);
 | 
| 16 | }
 | 
| 17 | 
 | 
| 18 | void die(const char *message) {
 | 
| 19 |   fprintf(stderr, "time-helper: %s\n", message);
 | 
| 20 |   exit(1);
 | 
| 21 | }
 | 
| 22 | 
 | 
| 23 | typedef struct Spec_t {
 | 
| 24 |   char *out_path;
 | 
| 25 |   bool append;
 | 
| 26 | 
 | 
| 27 |   char delimiter;  // delimiter should be tab or ,
 | 
| 28 |   bool verbose;    // whether to show verbose logging
 | 
| 29 | 
 | 
| 30 |   bool x;  // %x status
 | 
| 31 |   bool e;  // %e elapsed
 | 
| 32 |   bool y;  // start time
 | 
| 33 |   bool z;  // end time
 | 
| 34 |   bool U;  // %U user time
 | 
| 35 |   bool S;  // %S system time
 | 
| 36 |   bool M;  // %M maxrss
 | 
| 37 |   bool m;  // page faults, context switches, etc.
 | 
| 38 |   int argc;
 | 
| 39 |   char **argv;
 | 
| 40 | } Spec;
 | 
| 41 | 
 | 
| 42 | // Write CSV/TSV cells of different types
 | 
| 43 | void int_cell(FILE *f, char delimiter, int val) {
 | 
| 44 |   if (delimiter != 0) {  // NUL is invalid delimiter
 | 
| 45 |     fprintf(f, "%c%d", delimiter, val);
 | 
| 46 |   } else {
 | 
| 47 |     fprintf(f, "%d", val);
 | 
| 48 |   }
 | 
| 49 | }
 | 
| 50 | 
 | 
| 51 | void time_cell(FILE *f, char delimiter, struct timeval *val) {
 | 
| 52 |   fprintf(f, "%c%ld.%06ld", delimiter, val->tv_sec, val->tv_usec);
 | 
| 53 | }
 | 
| 54 | 
 | 
| 55 | int time_helper(Spec *spec, FILE *f) {
 | 
| 56 |   char *prog = spec->argv[0];
 | 
| 57 | 
 | 
| 58 |   struct timeval start;
 | 
| 59 |   struct timeval end;
 | 
| 60 | 
 | 
| 61 |   int status = 0;
 | 
| 62 |   switch (fork()) {
 | 
| 63 |   case -1:
 | 
| 64 |     die_errno("fork");
 | 
| 65 |     break;
 | 
| 66 | 
 | 
| 67 |   case 0:  // child exec
 | 
| 68 |     if (execvp(prog, spec->argv) < 0) {
 | 
| 69 |       fprintf(stderr, "time-helper: error executing '%s'\n", prog);
 | 
| 70 |       die_errno("execvp");
 | 
| 71 |     }
 | 
| 72 |     assert(0);  // execvp() never returns
 | 
| 73 | 
 | 
| 74 |   default:  // parent measures elapsed time of child
 | 
| 75 |     if (gettimeofday(&start, NULL) < 0) {
 | 
| 76 |       die_errno("gettimeofday");
 | 
| 77 |     }
 | 
| 78 |     wait(&status);
 | 
| 79 |     if (gettimeofday(&end, NULL) < 0) {
 | 
| 80 |       die_errno("gettimeofday");
 | 
| 81 |     }
 | 
| 82 |     break;
 | 
| 83 |   }
 | 
| 84 |   // fprintf(stderr, "done waiting\n");
 | 
| 85 | 
 | 
| 86 |   struct timeval elapsed;
 | 
| 87 |   timersub(&end, &start, &elapsed);
 | 
| 88 | 
 | 
| 89 |   struct rusage usage;
 | 
| 90 |   getrusage(RUSAGE_CHILDREN, &usage);
 | 
| 91 | 
 | 
| 92 |   // struct timeval *user = &usage.ru_utime;
 | 
| 93 |   // struct timeval *sys = &usage.ru_stime;
 | 
| 94 | 
 | 
| 95 |   // this is like the definition of $? that shell use
 | 
| 96 |   int exit_status = -1;
 | 
| 97 |   if (WIFEXITED(status)) {
 | 
| 98 |     exit_status = WEXITSTATUS(status);
 | 
| 99 |   } else if (WIFSIGNALED(status)) {
 | 
| 100 |     exit_status = 128 + WTERMSIG(status);
 | 
| 101 |   } else {
 | 
| 102 |     // We didn't pass WUNTRACED, so normally we won't get this.  But ptrace()
 | 
| 103 |     // will get here.
 | 
| 104 |     ;
 | 
| 105 |   }
 | 
| 106 | 
 | 
| 107 |   char d = spec->delimiter;
 | 
| 108 |   // NO delimiter at first!
 | 
| 109 |   if (spec->x) {
 | 
| 110 |     int_cell(f, 0, exit_status);
 | 
| 111 |   }
 | 
| 112 |   if (spec->e) {
 | 
| 113 |     time_cell(f, d, &elapsed);
 | 
| 114 |   }
 | 
| 115 |   if (spec->y) {
 | 
| 116 |     time_cell(f, d, &start);
 | 
| 117 |   }
 | 
| 118 |   if (spec->z) {
 | 
| 119 |     time_cell(f, d, &end);
 | 
| 120 |   }
 | 
| 121 |   if (spec->U) {
 | 
| 122 |     time_cell(f, d, &usage.ru_utime);
 | 
| 123 |   }
 | 
| 124 |   if (spec->S) {
 | 
| 125 |     time_cell(f, d, &usage.ru_stime);
 | 
| 126 |   }
 | 
| 127 |   if (spec->M) {
 | 
| 128 |     int_cell(f, d, usage.ru_maxrss);
 | 
| 129 |   }
 | 
| 130 |   if (spec->m) {
 | 
| 131 |     int_cell(f, d, usage.ru_minflt);
 | 
| 132 |     int_cell(f, d, usage.ru_majflt);
 | 
| 133 |     int_cell(f, d, usage.ru_nswap);
 | 
| 134 |     int_cell(f, d, usage.ru_inblock);
 | 
| 135 |     int_cell(f, d, usage.ru_oublock);
 | 
| 136 |     int_cell(f, d, usage.ru_nsignals);
 | 
| 137 |     int_cell(f, d, usage.ru_nvcsw);
 | 
| 138 |     int_cell(f, d, usage.ru_nivcsw);
 | 
| 139 |   }
 | 
| 140 | 
 | 
| 141 |   return exit_status;
 | 
| 142 | }
 | 
| 143 | 
 | 
| 144 | int main(int argc, char **argv) {
 | 
| 145 |   Spec spec = {0};
 | 
| 146 | 
 | 
| 147 |   spec.out_path = "/dev/null";  // default value
 | 
| 148 | 
 | 
| 149 |   // http://www.gnu.org/software/libc/manual/html_node/Example-of-Getopt.html
 | 
| 150 |   // + means to be strict about flag parsing.
 | 
| 151 |   int c;
 | 
| 152 |   while ((c = getopt(argc, argv, "+o:ad:vxeyzUSMm")) != -1) {
 | 
| 153 |     switch (c) {
 | 
| 154 |     case 'o':
 | 
| 155 |       spec.out_path = optarg;
 | 
| 156 |       break;
 | 
| 157 |     case 'a':
 | 
| 158 |       spec.append = true;
 | 
| 159 |       break;
 | 
| 160 | 
 | 
| 161 |     case 'd':
 | 
| 162 |       spec.delimiter = optarg[0];
 | 
| 163 |       break;
 | 
| 164 |     case 'v':
 | 
| 165 |       spec.verbose = true;
 | 
| 166 |       break;
 | 
| 167 | 
 | 
| 168 |     case 'x':
 | 
| 169 |       spec.x = true;
 | 
| 170 |       break;
 | 
| 171 |     case 'e':
 | 
| 172 |       spec.e = true;
 | 
| 173 |       break;
 | 
| 174 |     case 'y':
 | 
| 175 |       spec.y = true;
 | 
| 176 |       break;
 | 
| 177 |     case 'z':
 | 
| 178 |       spec.z = true;
 | 
| 179 |       break;
 | 
| 180 | 
 | 
| 181 |       // --rusage
 | 
| 182 |     case 'U':
 | 
| 183 |       spec.U = true;
 | 
| 184 |       break;
 | 
| 185 |     case 'S':
 | 
| 186 |       spec.S = true;
 | 
| 187 |       break;
 | 
| 188 |     case 'M':
 | 
| 189 |       spec.M = true;
 | 
| 190 |       break;
 | 
| 191 | 
 | 
| 192 |     case 'm':  // --rusage-2
 | 
| 193 |       spec.m = true;
 | 
| 194 |       break;
 | 
| 195 | 
 | 
| 196 |     case '?':  // getopt library will print error
 | 
| 197 |       return 2;
 | 
| 198 | 
 | 
| 199 |     default:
 | 
| 200 |       abort();  // should never happen
 | 
| 201 |     }
 | 
| 202 |   }
 | 
| 203 | 
 | 
| 204 |   int a = optind;  // index into argv
 | 
| 205 |   if (a == argc) {
 | 
| 206 |     die("expected a command to run");
 | 
| 207 |   }
 | 
| 208 | 
 | 
| 209 |   spec.argv = argv + a;
 | 
| 210 |   spec.argc = argc - a;
 | 
| 211 | 
 | 
| 212 |   char *mode = spec.append ? "a" : "w";
 | 
| 213 |   FILE *f = fopen(spec.out_path, mode);
 | 
| 214 |   int exit_status = time_helper(&spec, f);
 | 
| 215 |   fclose(f);
 | 
| 216 | 
 | 
| 217 |   return exit_status;
 | 
| 218 | }
 |