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 | int argc;
|
38 | char **argv;
|
39 | } Spec;
|
40 |
|
41 | // Write CSV/TSV cells of different types
|
42 | void int_cell(FILE *f, char delimiter, int val) {
|
43 | if (delimiter != 0) { // NUL is invalid delimiter
|
44 | fprintf(f, "%c%d", delimiter, val);
|
45 | } else {
|
46 | fprintf(f, "%d", val);
|
47 | }
|
48 | }
|
49 |
|
50 | void time_cell(FILE *f, char delimiter, struct timeval *val) {
|
51 | fprintf(f, "%c%ld.%06ld", delimiter, val->tv_sec, val->tv_usec);
|
52 | }
|
53 |
|
54 | int time_helper(Spec *spec, FILE *f) {
|
55 | char *prog = spec->argv[0];
|
56 |
|
57 | struct timeval start;
|
58 | struct timeval end;
|
59 |
|
60 | int status = 0;
|
61 | switch (fork()) {
|
62 | case -1:
|
63 | die_errno("fork");
|
64 | break;
|
65 |
|
66 | case 0: // child exec
|
67 | if (execvp(prog, spec->argv) < 0) {
|
68 | fprintf(stderr, "time-helper: error executing '%s'\n", prog);
|
69 | die_errno("execvp");
|
70 | }
|
71 | assert(0); // execvp() never returns
|
72 |
|
73 | default: // parent measures elapsed time of child
|
74 | if (gettimeofday(&start, NULL) < 0) {
|
75 | die_errno("gettimeofday");
|
76 | }
|
77 | wait(&status);
|
78 | if (gettimeofday(&end, NULL) < 0) {
|
79 | die_errno("gettimeofday");
|
80 | }
|
81 | break;
|
82 | }
|
83 | // fprintf(stderr, "done waiting\n");
|
84 |
|
85 | struct timeval elapsed;
|
86 | timersub(&end, &start, &elapsed);
|
87 |
|
88 | struct rusage usage;
|
89 | getrusage(RUSAGE_CHILDREN, &usage);
|
90 |
|
91 | // struct timeval *user = &usage.ru_utime;
|
92 | // struct timeval *sys = &usage.ru_stime;
|
93 |
|
94 | // this is like the definition of $? that shell use
|
95 | int exit_status = -1;
|
96 | if (WIFEXITED(status)) {
|
97 | exit_status = WEXITSTATUS(status);
|
98 | } else if (WIFSIGNALED(status)) {
|
99 | exit_status = 128 + WTERMSIG(status);
|
100 | } else {
|
101 | // We didn't pass WUNTRACED, so normally we won't get this. But ptrace()
|
102 | // will get here.
|
103 | ;
|
104 | }
|
105 |
|
106 | char d = spec->delimiter;
|
107 | // NO delimiter at first!
|
108 | if (spec->x) {
|
109 | int_cell(f, 0, exit_status);
|
110 | }
|
111 | if (spec->e) {
|
112 | time_cell(f, d, &elapsed);
|
113 | }
|
114 | if (spec->y) {
|
115 | time_cell(f, d, &start);
|
116 | }
|
117 | if (spec->z) {
|
118 | time_cell(f, d, &end);
|
119 | }
|
120 | if (spec->U) {
|
121 | time_cell(f, d, &usage.ru_utime);
|
122 | }
|
123 | if (spec->S) {
|
124 | time_cell(f, d, &usage.ru_stime);
|
125 | }
|
126 | if (spec->M) {
|
127 | int_cell(f, d, usage.ru_maxrss);
|
128 | }
|
129 |
|
130 | return exit_status;
|
131 | }
|
132 |
|
133 | int main(int argc, char **argv) {
|
134 | Spec spec = {0};
|
135 |
|
136 | spec.out_path = "/dev/null"; // default value
|
137 |
|
138 | // http://www.gnu.org/software/libc/manual/html_node/Example-of-Getopt.html
|
139 | // + means to be strict about flag parsing.
|
140 | int c;
|
141 | while ((c = getopt(argc, argv, "+o:ad:vxeyzUSM")) != -1) {
|
142 | switch (c) {
|
143 | case 'o':
|
144 | spec.out_path = optarg;
|
145 | break;
|
146 | case 'a':
|
147 | spec.append = true;
|
148 | break;
|
149 |
|
150 | case 'd':
|
151 | spec.delimiter = optarg[0];
|
152 | break;
|
153 | case 'v':
|
154 | spec.verbose = true;
|
155 | break;
|
156 |
|
157 | case 'x':
|
158 | spec.x = true;
|
159 | break;
|
160 | case 'e':
|
161 | spec.e = true;
|
162 | break;
|
163 | case 'y':
|
164 | spec.y = true;
|
165 | break;
|
166 | case 'z':
|
167 | spec.z = true;
|
168 | break;
|
169 | case 'U':
|
170 | spec.U = true;
|
171 | break;
|
172 | case 'S':
|
173 | spec.S = true;
|
174 | break;
|
175 | case 'M':
|
176 | spec.M = true;
|
177 | break;
|
178 |
|
179 | case '?': // getopt library will print error
|
180 | return 2;
|
181 |
|
182 | default:
|
183 | abort(); // should never happen
|
184 | }
|
185 | }
|
186 |
|
187 | int a = optind; // index into argv
|
188 | if (a == argc) {
|
189 | die("expected a command to run");
|
190 | }
|
191 |
|
192 | spec.argv = argv + a;
|
193 | spec.argc = argc - a;
|
194 |
|
195 | char *mode = spec.append ? "a" : "w";
|
196 | FILE *f = fopen(spec.out_path, mode);
|
197 | int exit_status = time_helper(&spec, f);
|
198 | fclose(f);
|
199 |
|
200 | return exit_status;
|
201 | }
|