OILS / benchmarks / time-helper.c View on Github | oilshell.org

201 lines, 140 significant
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
13void die_errno(const char *message) {
14 perror(message);
15 exit(1);
16}
17
18void die(const char *message) {
19 fprintf(stderr, "time-helper: %s\n", message);
20 exit(1);
21}
22
23typedef 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
42void 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
50void time_cell(FILE *f, char delimiter, struct timeval *val) {
51 fprintf(f, "%c%ld.%06ld", delimiter, val->tv_sec, val->tv_usec);
52}
53
54int 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
133int 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}