Add proper per-file copyright notices/licenses and top-level license.
[bluesky.git] / logbench / logbench.c
1 /* Blue Sky: File Systems in the Cloud
2  *
3  * Copyright (C) 2010  The Regents of the University of California
4  * Written by Michael Vrable <mvrable@cs.ucsd.edu>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. Neither the name of the University nor the names of its contributors
15  *    may be used to endorse or promote products derived from this software
16  *    without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30
31 /* A simple tool for benchmarking various logging strategies.
32  *
33  * We want to log a series of key/value pairs.  Approaches that we try include:
34  *  - Data written directly into the filesystem.
35  *  - Data is written to a Berkeley DB.
36  *  - Data is appended to a log file.
37  * In all cases we want to ensure that data is persistent on disk so it could
38  * be used for crash recovery.  We measure how many log records we can write
39  * per second to gauge performance. */
40
41 #define _GNU_SOURCE
42 #define _ATFILE_SOURCE
43
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <errno.h>
48 #include <time.h>
49 #include <sys/types.h>
50 #include <sys/stat.h>
51 #include <fcntl.h>
52 #include <unistd.h>
53
54 #include <db.h>
55 #include <glib.h>
56
57 struct item {
58     char *key;
59     char *data;
60     size_t len;
61 };
62
63 int queue_capacity = 1024;
64 int item_size = 1024;
65 int opt_threads = 1;
66 int opt_batchsize = 1;
67 int opt_writes = (1 << 12);
68 int opt_bdb_async = FALSE;
69
70 GAsyncQueue *queue;
71 int outstanding = 0;
72 GMutex *lock;
73 GCond *cond_empty, *cond_full;
74
75 int64_t get_ns()
76 {
77     struct timespec ts;
78     clock_gettime(CLOCK_MONOTONIC, &ts);
79
80     return ts.tv_sec * 1000000000LL + ts.tv_nsec;
81 }
82
83 struct item *get_item()
84 {
85     return (struct item *)g_async_queue_pop(queue);
86 }
87
88 void finish_item(struct item *item)
89 {
90     g_free(item->key);
91     g_free(item->data);
92     g_free(item);
93
94     g_mutex_lock(lock);
95     outstanding--;
96     if (outstanding == 0)
97         g_cond_signal(cond_empty);
98     if (outstanding < queue_capacity)
99         g_cond_signal(cond_full);
100     g_mutex_unlock(lock);
101 }
102
103 void writebuf(int fd, const char *buf, size_t len)
104 {
105     while (len > 0) {
106         ssize_t written;
107         written = write(fd, buf, len);
108         if (written < 0 && errno == EINTR)
109             continue;
110         g_assert(written >= 0);
111         buf += written;
112         len -= written;
113     }
114 }
115
116 /************************ Direct-to-filesystem logging ***********************/
117 static int dir_fd = -1;
118
119 gpointer fslog_thread(gpointer d)
120 {
121     while (TRUE) {
122         struct item *item = get_item();
123
124         int fd = openat(dir_fd, item->key, O_CREAT|O_WRONLY|O_TRUNC, 0666);
125         g_assert(fd >= 0);
126
127         writebuf(fd, item->data, item->len);
128
129         finish_item(item);
130
131         fsync(fd);
132         fsync(dir_fd);
133         close(fd);
134     }
135
136     return NULL;
137 }
138
139 void launch_fslog()
140 {
141     dir_fd = open(".", O_DIRECTORY);
142     g_assert(dir_fd >= 0);
143
144     for (int i = 0; i < 1; i++)
145         g_thread_create(fslog_thread, NULL, FALSE, NULL);
146 }
147
148 /****************************** Single-File Log ******************************/
149 gpointer flatlog_thread(gpointer d)
150 {
151     int fd = open("logfile", O_CREAT|O_WRONLY|O_TRUNC, 0666);
152     g_assert(fd >= 0);
153
154     int count = 0;
155
156     while (TRUE) {
157         struct item *item = get_item();
158
159         writebuf(fd, item->key, strlen(item->key) + 1);
160         writebuf(fd, (char *)&item->len, sizeof(item->len));
161         writebuf(fd, item->data, item->len);
162
163         count++;
164         if (count % opt_batchsize == 0)
165             fdatasync(fd);
166
167         finish_item(item);
168     }
169
170     return NULL;
171 }
172
173 void launch_flatlog()
174 {
175     g_thread_create(flatlog_thread, NULL, FALSE, NULL);
176 }
177
178 /************************* Transactional Berkeley DB *************************/
179 gpointer bdb_thread(gpointer d)
180 {
181     int res;
182     DB_ENV *env;
183     DB *db;
184     DB_TXN *txn = NULL;
185     int count = 0;
186
187     res = db_env_create(&env, 0);
188     g_assert(res == 0);
189
190     res = env->open(env, ".",
191                     DB_CREATE | DB_RECOVER | DB_INIT_LOCK | DB_INIT_LOG
192                      | DB_INIT_MPOOL | DB_INIT_TXN | DB_THREAD, 0644);
193     g_assert(res == 0);
194
195     if (opt_bdb_async) {
196         res = env->set_flags(env, DB_TXN_WRITE_NOSYNC, 1);
197         g_assert(res == 0);
198     }
199
200     res = db_create(&db, env, 0);
201     g_assert(res == 0);
202
203     res = db->open(db, NULL, "log.db", "log", DB_BTREE,
204                    DB_CREATE | DB_THREAD | DB_AUTO_COMMIT, 0644);
205     g_assert(res == 0);
206
207     while (TRUE) {
208         if (txn == NULL && !opt_bdb_async) {
209             res = env->txn_begin(env, NULL, &txn, 0);
210             g_assert(res == 0);
211         }
212
213         struct item *item = get_item();
214
215         DBT key, value;
216         memset(&key, 0, sizeof(key));
217         memset(&value, 0, sizeof(value));
218
219         key.data = item->key;
220         key.size = strlen(item->key);
221
222         value.data = item->data;
223         value.size = item->len;
224
225         res = db->put(db, opt_bdb_async ? NULL : txn, &key, &value, 0);
226         g_assert(res == 0);
227
228         count++;
229         if (count % opt_batchsize == 0) {
230             if (opt_bdb_async) {
231                 env->txn_checkpoint(env, 0, 0, 0);
232             } else {
233                 txn->commit(txn, 0);
234                 txn = NULL;
235             }
236         }
237
238         finish_item(item);
239     }
240
241     return NULL;
242 }
243
244 void launch_bdb()
245 {
246     g_thread_create(bdb_thread, NULL, FALSE, NULL);
247 }
248
249 int main(int argc, char *argv[])
250 {
251     int64_t time_start, time_end;
252
253     g_thread_init(NULL);
254     queue = g_async_queue_new();
255     lock = g_mutex_new();
256     cond_empty = g_cond_new();
257     cond_full = g_cond_new();
258
259     int opt;
260     int backend = 0;
261     while ((opt = getopt(argc, argv, "at:s:b:n:BFD")) != -1) {
262         switch (opt) {
263         case 'a':
264             // Make BDB log writes more asynchronous
265             opt_bdb_async = TRUE;
266             break;
267         case 't':
268             // Set number of log worker threads
269             opt_threads = atoi(optarg);
270             break;
271         case 's':
272             // Set item size (in bytes)
273             item_size = atoi(optarg);
274             break;
275         case 'b':
276             // Set batch size
277             opt_batchsize = atoi(optarg);
278             break;
279         case 'n':
280             // Set object count
281             opt_writes = atoi(optarg);
282             break;
283         case 'B':
284             // Select BDB backend
285             backend = 'b';
286             break;
287         case 'F':
288             // Select flat file backend
289             backend = 'f';
290             break;
291         case 'D':
292             // Select file system directory backend
293             backend = 'd';
294             break;
295         default: /* '?' */
296             fprintf(stderr, "Usage: %s [-t threads] {-B|-F|-D}\n",
297                     argv[0]);
298             return EXIT_FAILURE;
299         }
300     }
301
302     switch (backend) {
303     case 'b':
304         launch_bdb();
305         break;
306     case 'f':
307         launch_flatlog();
308         break;
309     case 'd':
310         launch_fslog();
311         break;
312     default:
313         fprintf(stderr, "Backend not selected!\n");
314         return EXIT_FAILURE;
315     }
316
317     time_start = get_ns();
318     for (int i = 0; i < opt_writes; i++) {
319         struct item *item = g_new(struct item, 1);
320         item->key = g_strdup_printf("item-%06d", i);
321         item->data = g_malloc(item_size);
322         item->len = item_size;
323
324         g_mutex_lock(lock);
325         g_async_queue_push(queue, item);
326         outstanding++;
327         if (outstanding == opt_batchsize)
328             g_cond_wait(cond_empty, lock);
329         g_mutex_unlock(lock);
330     }
331
332     g_mutex_lock(lock);
333     while (outstanding > 0)
334         g_cond_wait(cond_empty, lock);
335     g_mutex_unlock(lock);
336     time_end = get_ns();
337
338     double elapsed = (time_end - time_start) / 1e9;
339     printf("Elapsed: %f s\nThroughput: %f txn/s, %f MiB/s\n",
340            elapsed, opt_writes / elapsed,
341            opt_writes / elapsed * item_size / (1 << 20));
342
343     if (backend == 'b' && opt_bdb_async)
344         backend = 'B';
345
346     FILE *f = fopen("../logbench.data", "a");
347     g_assert(f != NULL);
348     fprintf(f, "%c\t%d\t%d\t%d\t%f\t%f\t%f\n",
349             backend, item_size, opt_writes, opt_batchsize,
350             elapsed, opt_writes / elapsed,
351             opt_writes / elapsed * item_size / (1 << 20));
352     fclose(f);
353
354     return 0;
355 }