tdb2: update tools/speed.c, tools/tdbtool.c and tools/tdbtorture.c to new API
[ccan] / ccan / tdb2 / tools / tdbtorture.c
1 /* this tests tdb by doing lots of ops from several simultaneous
2    writers - that stresses the locking code. 
3 */
4
5 #include <ccan/tdb2/tdb2.h>
6 #include <stdlib.h>
7 #include <err.h>
8 #include <getopt.h>
9 #include <stdarg.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <errno.h>
13 #include <unistd.h>
14 #include <sys/types.h>
15 #include <fcntl.h>
16 #include <time.h>
17 #include <sys/wait.h>
18
19 //#define REOPEN_PROB 30
20 #define DELETE_PROB 8
21 #define STORE_PROB 4
22 #define APPEND_PROB 6
23 #define TRANSACTION_PROB 10
24 #define TRANSACTION_PREPARE_PROB 2
25 #define LOCKSTORE_PROB 5
26 #define TRAVERSE_PROB 20
27 #define TRAVERSE_MOD_PROB 100
28 #define TRAVERSE_ABORT_PROB 500
29 #define CULL_PROB 100
30 #define KEYLEN 3
31 #define DATALEN 100
32
33 static struct tdb_context *db;
34 static int in_transaction;
35 static int in_traverse;
36 static int error_count;
37 #if TRANSACTION_PROB
38 static int always_transaction = 0;
39 #endif
40 static int loopnum;
41 static int count_pipe;
42 static union tdb_attribute log_attr;
43 static union tdb_attribute seed_attr;
44
45 static void tdb_log(struct tdb_context *tdb, enum tdb_log_level level,
46                     void *private, const char *message)
47 {
48         fputs(message, stdout);
49         fflush(stdout);
50 #if 0
51         {
52                 char *ptr;
53                 signal(SIGUSR1, SIG_IGN);
54                 asprintf(&ptr,"xterm -e gdb /proc/%d/exe %d", getpid(), getpid());
55                 system(ptr);
56                 free(ptr);
57         }
58 #endif  
59 }
60
61 #include "../private.h"
62
63 static void segv_handler(int signal, siginfo_t *info, void *p)
64 {
65         char string[100];
66
67         sprintf(string, "%u: death at %p (map_ptr %p, map_size %llu)\n",
68                 getpid(), info->si_addr, db->map_ptr, db->map_size);
69         write(2, string, strlen(string));
70         sleep(60);
71         _exit(11);
72 }       
73
74 static void fatal(const char *why)
75 {
76         perror(why);
77         error_count++;
78 }
79
80 static char *randbuf(int len)
81 {
82         char *buf;
83         int i;
84         buf = (char *)malloc(len+1);
85
86         for (i=0;i<len;i++) {
87                 buf[i] = 'a' + (rand() % 26);
88         }
89         buf[i] = 0;
90         return buf;
91 }
92
93 static void addrec_db(void);
94 static int modify_traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf,
95                            void *state)
96 {
97 #if CULL_PROB
98         if (random() % CULL_PROB == 0) {
99                 tdb_delete(tdb, key);
100         }
101 #endif
102
103 #if TRAVERSE_MOD_PROB
104         if (random() % TRAVERSE_MOD_PROB == 0) {
105                 addrec_db();
106         }
107 #endif
108
109 #if TRAVERSE_ABORT_PROB
110         if (random() % TRAVERSE_ABORT_PROB == 0)
111                 return 1;
112 #endif
113
114         return 0;
115 }
116
117 static void addrec_db(void)
118 {
119         int klen, dlen;
120         char *k, *d;
121         TDB_DATA key, data;
122
123         klen = 1 + (rand() % KEYLEN);
124         dlen = 1 + (rand() % DATALEN);
125
126         k = randbuf(klen);
127         d = randbuf(dlen);
128
129         key.dptr = (unsigned char *)k;
130         key.dsize = klen+1;
131
132         data.dptr = (unsigned char *)d;
133         data.dsize = dlen+1;
134
135 #if REOPEN_PROB
136         if (in_traverse == 0 && in_transaction == 0 && random() % REOPEN_PROB == 0) {
137                 tdb_reopen_all(0);
138                 goto next;
139         } 
140 #endif
141
142 #if TRANSACTION_PROB
143         if (in_traverse == 0 && in_transaction == 0 && (always_transaction || random() % TRANSACTION_PROB == 0)) {
144                 if (tdb_transaction_start(db) != 0) {
145                         fatal("tdb_transaction_start failed");
146                 }
147                 in_transaction++;
148                 goto next;
149         }
150         if (in_traverse == 0 && in_transaction && random() % TRANSACTION_PROB == 0) {
151                 if (random() % TRANSACTION_PREPARE_PROB == 0) {
152                         if (tdb_transaction_prepare_commit(db) != 0) {
153                                 fatal("tdb_transaction_prepare_commit failed");
154                         }
155                 }
156                 if (tdb_transaction_commit(db) != 0) {
157                         fatal("tdb_transaction_commit failed");
158                 }
159                 in_transaction--;
160                 goto next;
161         }
162
163         if (in_traverse == 0 && in_transaction && random() % TRANSACTION_PROB == 0) {
164                 tdb_transaction_cancel(db);
165                 in_transaction--;
166                 goto next;
167         }
168 #endif
169
170 #if DELETE_PROB
171         if (random() % DELETE_PROB == 0) {
172                 tdb_delete(db, key);
173                 goto next;
174         }
175 #endif
176
177 #if STORE_PROB
178         if (random() % STORE_PROB == 0) {
179                 if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
180                         fatal("tdb_store failed");
181                 }
182                 goto next;
183         }
184 #endif
185
186 #if APPEND_PROB
187         if (random() % APPEND_PROB == 0) {
188                 if (tdb_append(db, key, data) != 0) {
189                         fatal("tdb_append failed");
190                 }
191                 goto next;
192         }
193 #endif
194
195 #if LOCKSTORE_PROB
196         if (random() % LOCKSTORE_PROB == 0) {
197                 tdb_chainlock(db, key);
198                 tdb_fetch(db, key, &data);
199                 if (tdb_store(db, key, data, TDB_REPLACE) != 0) {
200                         fatal("tdb_store failed");
201                 }
202                 if (data.dptr) free(data.dptr);
203                 tdb_chainunlock(db, key);
204                 goto next;
205         } 
206 #endif
207
208 #if TRAVERSE_PROB
209         /* FIXME: recursive traverses break transactions? */
210         if (in_traverse == 0 && random() % TRAVERSE_PROB == 0) {
211                 in_traverse++;
212                 tdb_traverse(db, modify_traverse, NULL);
213                 in_traverse--;
214                 goto next;
215         }
216 #endif
217
218         if (tdb_fetch(db, key, &data) == TDB_SUCCESS)
219                 free(data.dptr);
220
221 next:
222         free(k);
223         free(d);
224 }
225
226 static int traverse_fn(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf,
227                        void *state)
228 {
229         tdb_delete(tdb, key);
230         return 0;
231 }
232
233 static void usage(void)
234 {
235         printf("Usage: tdbtorture"
236 #if TRANSACTION_PROB
237                " [-t]"
238 #endif
239                " [-k] [-n NUM_PROCS] [-l NUM_LOOPS] [-s SEED]\n");
240         exit(0);
241 }
242
243 static void send_count_and_suicide(int sig)
244 {
245         /* This ensures our successor can continue where we left off. */
246         write(count_pipe, &loopnum, sizeof(loopnum));
247         /* This gives a unique signature. */
248         kill(getpid(), SIGUSR2);
249 }
250
251 static int run_child(int i, int seed, unsigned num_loops, unsigned start)
252 {
253         struct sigaction act = { .sa_sigaction = segv_handler,
254                                  .sa_flags = SA_SIGINFO };
255         sigaction(11, &act, NULL);      
256
257         db = tdb_open("torture.tdb", TDB_DEFAULT, O_RDWR | O_CREAT, 0600,
258                       &log_attr);
259         if (!db) {
260                 fatal("db open failed");
261         }
262
263 #if 0
264         if (i == 0) {
265                 printf("pid %i\n", getpid());
266                 sleep(9);
267         } else
268                 sleep(10);
269 #endif
270
271         srand(seed + i);
272         srandom(seed + i);
273
274         /* Set global, then we're ready to handle being killed. */
275         loopnum = start;
276         signal(SIGUSR1, send_count_and_suicide);
277
278         for (;loopnum<num_loops && error_count == 0;loopnum++) {
279                 addrec_db();
280         }
281
282         if (error_count == 0) {
283                 tdb_traverse(db, NULL, NULL);
284 #if TRANSACTION_PROB
285                 if (always_transaction) {
286                         while (in_transaction) {
287                                 tdb_transaction_cancel(db);
288                                 in_transaction--;
289                         }
290                         if (tdb_transaction_start(db) != 0)
291                                 fatal("tdb_transaction_start failed");
292                 }
293 #endif
294                 tdb_traverse(db, traverse_fn, NULL);
295                 tdb_traverse(db, traverse_fn, NULL);
296
297 #if TRANSACTION_PROB
298                 if (always_transaction) {
299                         if (tdb_transaction_commit(db) != 0)
300                                 fatal("tdb_transaction_commit failed");
301                 }
302 #endif
303         }
304
305         tdb_close(db);
306
307         return (error_count < 100 ? error_count : 100);
308 }
309
310 int main(int argc, char * const *argv)
311 {
312         int i, seed = -1;
313         int num_loops = 5000;
314         int num_procs = 3;
315         int c, pfds[2];
316         extern char *optarg;
317         pid_t *pids;
318         int kill_random = 0;
319         int *done;
320
321         log_attr.base.attr = TDB_ATTRIBUTE_LOG;
322         log_attr.base.next = &seed_attr;
323         log_attr.log.log_fn = tdb_log;
324         seed_attr.base.attr = TDB_ATTRIBUTE_SEED;
325
326         while ((c = getopt(argc, argv, "n:l:s:thk")) != -1) {
327                 switch (c) {
328                 case 'n':
329                         num_procs = strtol(optarg, NULL, 0);
330                         break;
331                 case 'l':
332                         num_loops = strtol(optarg, NULL, 0);
333                         break;
334                 case 's':
335                         seed = strtol(optarg, NULL, 0);
336                         break;
337                 case 't':
338 #if TRANSACTION_PROB
339                         always_transaction = 1;
340 #else
341                         fprintf(stderr, "Transactions not supported\n");
342                         usage();
343 #endif
344                         break;
345                 case 'k':
346                         kill_random = 1;
347                         break;
348                 default:
349                         usage();
350                 }
351         }
352
353         unlink("torture.tdb");
354
355         if (seed == -1) {
356                 seed = (getpid() + time(NULL)) & 0x7FFFFFFF;
357         }
358         seed_attr.seed.seed = (((uint64_t)seed) << 32) | seed; 
359
360         if (num_procs == 1 && !kill_random) {
361                 /* Don't fork for this case, makes debugging easier. */
362                 error_count = run_child(0, seed, num_loops, 0);
363                 goto done;
364         }
365
366         pids = (pid_t *)calloc(sizeof(pid_t), num_procs);
367         done = (int *)calloc(sizeof(int), num_procs);
368
369         if (pipe(pfds) != 0) {
370                 perror("Creating pipe");
371                 exit(1);
372         }
373         count_pipe = pfds[1];
374
375         for (i=0;i<num_procs;i++) {
376                 if ((pids[i]=fork()) == 0) {
377                         close(pfds[0]);
378                         if (i == 0) {
379                                 printf("testing with %d processes, %d loops, seed=%d%s\n", 
380                                        num_procs, num_loops, seed, 
381 #if TRANSACTION_PROB
382                                        always_transaction ? " (all within transactions)" : ""
383 #else
384                                        ""
385 #endif
386                                         );
387                         }
388                         exit(run_child(i, seed, num_loops, 0));
389                 }
390         }
391
392         while (num_procs) {
393                 int status, j;
394                 pid_t pid;
395
396                 if (error_count != 0) {
397                         /* try and stop the test on any failure */
398                         for (j=0;j<num_procs;j++) {
399                                 if (pids[j] != 0) {
400                                         kill(pids[j], SIGTERM);
401                                 }
402                         }
403                 }
404
405                 pid = waitpid(-1, &status, kill_random ? WNOHANG : 0);
406                 if (pid == 0) {
407                         struct timespec ts;
408
409                         /* Sleep for 1/10 second. */
410                         ts.tv_sec = 0;
411                         ts.tv_nsec = 100000000;
412                         nanosleep(&ts, NULL);
413
414                         /* Kill someone. */
415                         kill(pids[random() % num_procs], SIGUSR1);
416                         continue;
417                 }
418
419                 if (pid == -1) {
420                         perror("failed to wait for child\n");
421                         exit(1);
422                 }
423
424                 for (j=0;j<num_procs;j++) {
425                         if (pids[j] == pid) break;
426                 }
427                 if (j == num_procs) {
428                         printf("unknown child %d exited!?\n", (int)pid);
429                         exit(1);
430                 }
431                 if (WIFSIGNALED(status)) {
432                         if (WTERMSIG(status) == SIGUSR2
433                             || WTERMSIG(status) == SIGUSR1) {
434                                 /* SIGUSR2 means they wrote to pipe. */
435                                 if (WTERMSIG(status) == SIGUSR2) {
436                                         read(pfds[0], &done[j],
437                                              sizeof(done[j]));
438                                 }
439                                 pids[j] = fork();
440                                 if (pids[j] == 0)
441                                         exit(run_child(j, seed, num_loops,
442                                                        done[j]));
443                                 printf("Restarting child %i for %u-%u\n",
444                                        j, done[j], num_loops);
445                                 continue;
446                         }
447                         printf("child %d exited with signal %d\n",
448                                (int)pid, WTERMSIG(status));
449                         error_count++;
450                 } else {
451                         if (WEXITSTATUS(status) != 0) {
452                                 printf("child %d exited with status %d\n",
453                                        (int)pid, WEXITSTATUS(status));
454                                 error_count++;
455                         }
456                 }
457                 memmove(&pids[j], &pids[j+1],
458                         (num_procs - j - 1)*sizeof(pids[0]));
459                 num_procs--;
460         }
461
462         free(pids);
463
464 done:
465         if (error_count == 0) {
466                 db = tdb_open("torture.tdb", TDB_DEFAULT, O_RDWR | O_CREAT,
467                               0600, &log_attr);
468                 if (!db) {
469                         fatal("db open failed");
470                 }
471                 if (tdb_check(db, NULL, NULL) == -1) {
472                         printf("db check failed");
473                         exit(1);
474                 }
475                 tdb_close(db);
476                 printf("OK\n");
477         }
478
479         return error_count;
480 }