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