| 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 | }
|