update ct; test throughput in MB/s
[beanstalkd.git] / ct / ct.c
blob2e726072f30f479612fb480bc161f240c1bb8cee
1 // CT - simple-minded unit testing for C
3 #include <signal.h>
4 #include <string.h>
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <stdarg.h>
8 #include <sys/types.h>
9 #include <sys/stat.h>
10 #include <unistd.h>
11 #include <sys/wait.h>
12 #include <fcntl.h>
13 #include <dirent.h>
14 #include <errno.h>
15 #include <sys/time.h>
16 #include <stdint.h>
17 #include "internal.h"
18 #include "ct.h"
21 static char *curdir;
22 static int rjobfd = -1, wjobfd = -1;
23 static int64 bstart, bdur;
24 static int btiming; // bool
25 static int64 bbytes;
26 static const int64 Second = 1000 * 1000 * 1000;
27 static const int64 BenchTime = Second;
28 static const int MaxN = 1000 * 1000 * 1000;
32 #ifdef __MACH__
33 # include <mach/mach_time.h>
35 static int64
36 nstime()
38 return (int64)mach_absolute_time();
41 #else
43 static int64
44 nstime()
46 struct timespec t;
47 clock_gettime(CLOCK_MONOTONIC, &t);
48 return (int64)(t.tv_sec)*Second + t.tv_nsec;
51 #endif
53 void
54 ctlogpn(char *p, int n, char *fmt, ...)
56 va_list arg;
58 printf("%s:%d: ", p, n);
59 va_start(arg, fmt);
60 vprintf(fmt, arg);
61 va_end(arg);
62 putchar('\n');
66 void
67 ctfail(void)
69 fflush(stdout);
70 fflush(stderr);
71 abort();
75 char *
76 ctdir(void)
78 mkdir(curdir, 0700);
79 return curdir;
83 void
84 ctresettimer(void)
86 bdur = 0;
87 bstart = nstime();
91 void
92 ctstarttimer(void)
94 if (!btiming) {
95 bstart = nstime();
96 btiming = 1;
101 void
102 ctstoptimer(void)
104 if (btiming) {
105 bdur += nstime() - bstart;
106 btiming = 0;
111 void
112 ctsetbytes(int n)
114 bbytes = (int64)n;
118 static void
119 die(int code, int err, char *msg)
121 putc('\n', stderr);
123 if (msg && *msg) {
124 fputs(msg, stderr);
125 fputs(": ", stderr);
128 fputs(strerror(err), stderr);
129 putc('\n', stderr);
130 exit(code);
134 static int
135 tmpfd(void)
137 FILE *f = tmpfile();
138 if (!f) {
139 die(1, errno, "tmpfile");
141 return fileno(f);
145 static int
146 failed(int s)
148 return WIFSIGNALED(s) && (WTERMSIG(s) == SIGABRT);
152 static void
153 waittest(Test *ts)
155 Test *t;
156 int pid, stat;
158 pid = wait3(&stat, 0, 0);
159 if (pid == -1) {
160 die(3, errno, "wait");
162 killpg(pid, 9);
164 for (t=ts; t->f; t++) {
165 if (t->pid == pid) {
166 t->status = stat;
167 if (!t->status) {
168 putchar('.');
169 } else if (failed(t->status)) {
170 putchar('F');
171 } else {
172 putchar('E');
174 fflush(stdout);
180 static void
181 start(Test *t)
183 t->fd = tmpfd();
184 strcpy(t->dir, TmpDirPat);
185 mktemp(t->dir);
186 t->pid = fork();
187 if (t->pid < 0) {
188 die(1, errno, "fork");
189 } else if (!t->pid) {
190 setpgid(0, 0);
191 if (dup2(t->fd, 1) == -1) {
192 die(3, errno, "dup2");
194 if (close(t->fd) == -1) {
195 die(3, errno, "fclose");
197 if (dup2(1, 2) == -1) {
198 die(3, errno, "dup2");
200 curdir = t->dir;
201 t->f();
202 _exit(0);
204 setpgid(t->pid, t->pid);
208 static void
209 runalltest(Test *ts, int limit)
211 int nrun = 0;
212 Test *t;
213 for (t=ts; t->f; t++) {
214 if (nrun >= limit) {
215 waittest(ts);
216 nrun--;
218 start(t);
219 nrun++;
221 for (; nrun; nrun--) {
222 waittest(ts);
227 static void
228 copyfd(FILE *out, int in)
230 ssize_t n;
231 char buf[1024]; // arbitrary size
233 while ((n = read(in, buf, sizeof(buf))) != 0) {
234 if (fwrite(buf, 1, n, out) != (size_t)n) {
235 die(3, errno, "fwrite");
241 // Removes path and all of its children.
242 // Writes errors to stderr and keeps going.
243 // If path doesn't exist, rmtree returns silently.
244 static void
245 rmtree(char *path)
247 int r = unlink(path);
248 if (r == 0 || errno == ENOENT) {
249 return; // success
251 int unlinkerr = errno;
253 DIR *d = opendir(path);
254 if (!d) {
255 if (errno == ENOTDIR) {
256 fprintf(stderr, "ct: unlink: %s\n", strerror(unlinkerr));
257 } else {
258 perror("ct: opendir");
260 fprintf(stderr, "ct: path %s\n", path);
261 return;
263 struct dirent *ent;
264 while ((ent = readdir(d))) {
265 if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
266 continue;
268 int n = strlen(path) + 1 + strlen(ent->d_name);
269 char s[n+1];
270 sprintf(s, "%s/%s", path, ent->d_name);
271 rmtree(s);
273 closedir(d);
274 r = rmdir(path);
275 if (r == -1) {
276 perror("ct: rmdir");
277 fprintf(stderr, "ct: path %s\n", path);
282 static void
283 runbenchn(Benchmark *b, int n)
285 int outfd = tmpfd();
286 int durfd = tmpfd();
287 strcpy(b->dir, TmpDirPat);
288 mktemp(b->dir);
289 int pid = fork();
290 if (pid < 0) {
291 die(1, errno, "fork");
292 } else if (!pid) {
293 setpgid(0, 0);
294 if (dup2(outfd, 1) == -1) {
295 die(3, errno, "dup2");
297 if (close(outfd) == -1) {
298 die(3, errno, "fclose");
300 if (dup2(1, 2) == -1) {
301 die(3, errno, "dup2");
303 curdir = b->dir;
304 ctstarttimer();
305 b->f(n);
306 ctstoptimer();
307 write(durfd, &bdur, sizeof bdur);
308 write(durfd, &bbytes, sizeof bbytes);
309 _exit(0);
311 setpgid(pid, pid);
313 pid = waitpid(pid, &b->status, 0);
314 if (pid == -1) {
315 die(3, errno, "wait");
317 killpg(pid, 9);
318 rmtree(b->dir);
319 if (b->status != 0) {
320 putchar('\n');
321 lseek(outfd, 0, SEEK_SET);
322 copyfd(stdout, outfd);
323 return;
326 lseek(durfd, 0, SEEK_SET);
327 int r = read(durfd, &b->dur, sizeof b->dur);
328 if (r != sizeof b->dur) {
329 perror("read");
330 b->status = 1;
332 r = read(durfd, &b->bytes, sizeof b->bytes);
333 if (r != sizeof b->bytes) {
334 perror("read");
335 b->status = 1;
340 // rounddown10 rounds a number down to the nearest power of 10.
341 static int
342 rounddown10(int n)
344 int tens = 0;
345 // tens = floor(log_10(n))
346 while (n >= 10) {
347 n = n / 10;
348 tens++;
350 // result = 10**tens
351 int i, result = 1;
352 for (i = 0; i < tens; i++) {
353 result *= 10;
355 return result;
359 // roundup rounds n up to a number of the form [1eX, 2eX, 5eX].
360 static int
361 roundup(int n)
363 int base = rounddown10(n);
364 if (n == base)
365 return n;
366 if (n <= 2*base)
367 return 2 * base;
368 if (n <= 5*base)
369 return 5 * base;
370 return 10 * base;
374 static int
375 min(int a, int b)
377 if (a < b) {
378 return a;
380 return b;
384 static int
385 max(int a, int b)
387 if (a > b) {
388 return a;
390 return b;
394 static void
395 runbench(Benchmark *b)
397 printf("%s\t", b->name);
398 fflush(stdout);
399 int n = 1;
400 runbenchn(b, n);
401 while (b->status == 0 && b->dur < BenchTime && n < MaxN) {
402 int last = n;
403 // Predict iterations/sec.
404 int nsop = b->dur / n;
405 if (nsop == 0) {
406 n = MaxN;
407 } else {
408 n = BenchTime / nsop;
410 // Run more iterations than we think we'll need for a second (1.5x).
411 // Don't grow too fast in case we had timing errors previously.
412 // Be sure to run at least one more than last time.
413 n = max(min(n+n/2, 100*last), last+1);
414 // Round up to something easy to read.
415 n = roundup(n);
416 runbenchn(b, n);
418 if (b->status == 0) {
419 printf("%8d\t%10lld ns/op", n, b->dur/n);
420 if (b->bytes > 0) {
421 double mbs = 0;
422 if (b->dur > 0) {
423 int64 sec = b->dur / 1000L / 1000L / 1000L;
424 int64 nsec = b->dur % 1000000000L;
425 double dur = (double)sec + (double)nsec*.0000000001;
426 mbs = ((double)b->bytes * (double)n / 1000000) / dur;
428 printf("\t%7.2f MB/s", mbs);
430 putchar('\n');
431 } else {
432 if (failed(b->status)) {
433 printf("failure");
434 } else {
435 printf("error");
436 if (WIFEXITED(b->status)) {
437 printf(" (exit status %d)", WEXITSTATUS(b->status));
439 if (WIFSIGNALED(b->status)) {
440 printf(" (signal %d)", WTERMSIG(b->status));
443 putchar('\n');
448 static void
449 runallbench(Benchmark *b)
451 for (; b->f; b++) {
452 runbench(b);
457 static int
458 report(Test *t)
460 int nfail = 0, nerr = 0;
462 putchar('\n');
463 for (; t->f; t++) {
464 rmtree(t->dir);
465 if (!t->status) {
466 continue;
469 printf("\n%s: ", t->name);
470 if (failed(t->status)) {
471 nfail++;
472 printf("failure");
473 } else {
474 nerr++;
475 printf("error");
476 if (WIFEXITED(t->status)) {
477 printf(" (exit status %d)", WEXITSTATUS(t->status));
479 if (WIFSIGNALED(t->status)) {
480 printf(" (signal %d)", WTERMSIG(t->status));
484 putchar('\n');
485 lseek(t->fd, 0, SEEK_SET);
486 copyfd(stdout, t->fd);
489 if (nfail || nerr) {
490 printf("\n%d failures; %d errors.\n", nfail, nerr);
491 } else {
492 printf("\nPASS\n");
494 return nfail || nerr;
499 readtokens()
501 int n = 1;
502 char c, *s;
503 if ((s = strstr(getenv("MAKEFLAGS"), " --jobserver-fds="))) {
504 rjobfd = (int)strtol(s+17, &s, 10); // skip " --jobserver-fds="
505 wjobfd = (int)strtol(s+1, NULL, 10); // skip comma
507 if (rjobfd >= 0) {
508 fcntl(rjobfd, F_SETFL, fcntl(rjobfd, F_GETFL)|O_NONBLOCK);
509 while (read(rjobfd, &c, 1) > 0) {
510 n++;
513 return n;
517 void
518 writetokens(int n)
520 char c = '+';
521 if (wjobfd >= 0) {
522 fcntl(wjobfd, F_SETFL, fcntl(wjobfd, F_GETFL)|O_NONBLOCK);
523 for (; n>1; n--) {
524 write(wjobfd, &c, 1); // ignore error; nothing we can do anyway
531 main(int argc, char **argv)
533 int n = readtokens();
534 runalltest(ctmaintest, n);
535 writetokens(n);
536 int code = report(ctmaintest);
537 if (code != 0) {
538 return code;
540 if (argc == 2 && strcmp(argv[1], "-b") == 0) {
541 runallbench(ctmainbench);
543 return 0;