a3cb7cb0f3ba8d11e6636cdb99e9207c7c6cf22c
[oweals/gnunet.git] / src / datastore / perf_datastore_api.c
1 /*
2      This file is part of GNUnet.
3      (C) 2004, 2005, 2006, 2007, 2009 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20 /*
21  * @file datastore/perf_datastore_api.c
22  * @brief performance measurement for the datastore implementation
23  * @author Christian Grothoff
24  *
25  * This testcase inserts a bunch of (variable size) data and then
26  * deletes data until the (reported) database size drops below a given
27  * threshold.  This is iterated 10 times, with the actual size of the
28  * content stored and the number of operations performed being printed
29  * for each iteration.  The code also prints a "I" for every 40 blocks
30  * inserted and a "D" for every 40 blocks deleted.  The deletion
31  * strategy uses the "random" iterator.  Priorities and expiration
32  * dates are set using a pseudo-random value within a realistic range.
33  */
34
35 #include "platform.h"
36 #include "gnunet_util_lib.h"
37 #include "gnunet_protocols.h"
38 #include "gnunet_datastore_service.h"
39
40 #define VERBOSE GNUNET_YES
41
42 /**
43  * How long until we give up on transmitting the message?
44  */
45 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15)
46
47
48 static struct GNUNET_DATASTORE_Handle *datastore;
49
50 /**
51  * Target datastore size (in bytes).
52  * <p>
53  * Example impact of total size on the reported number
54  * of operations (insert and delete) per second (once
55  * roughly stabilized -- this is not "sound" experimental
56  * data but just a rough idea) for a particular machine:
57  * <pre>
58  *    4: 60   at   7k ops total
59  *    8: 50   at   3k ops total
60  *   16: 48   at   8k ops total
61  *   32: 46   at   8k ops total
62  *   64: 61   at   9k ops total
63  *  128: 89   at   9k ops total
64  * 4092: 11   at 383k ops total (12 GB stored, 14.8 GB DB size on disk, 2.5 GB reported)
65  * </pre>
66  * Pure insertion performance into an empty DB initially peaks
67  * at about 400 ops.  The performance seems to drop especially
68  * once the existing (fragmented) ISAM space is filled up and
69  * the DB needs to grow on disk.  This could be explained with
70  * ISAM looking more carefully for defragmentation opportunities.
71  * <p>
72  * MySQL disk space overheads (for otherwise unused database when
73  * run with 128 MB target data size; actual size 651 MB, useful
74  * data stored 520 MB) are quite large in the range of 25-30%.
75  * <p>
76  * This kind of processing seems to be IO bound (system is roughly
77  * at 90% wait, 10% CPU).  This is with MySQL 5.0.
78  *
79  */
80 #define MAX_SIZE 1024LL * 1024 * 16
81
82 /**
83  * Report progress outside of major reports? Should probably be GNUNET_YES if
84  * size is > 16 MB.
85  */
86 #define REPORT_ID GNUNET_NO
87
88 /**
89  * Number of put operations equivalent to 1/10th of MAX_SIZE
90  */
91 #define PUT_10 MAX_SIZE / 32 / 1024 / 10
92
93 /**
94  * Progress report frequency.  1/10th of a put operation block.
95  */
96 #define REP_FREQ PUT_10 / 10
97
98 /**
99  * Total number of iterations (each iteration doing
100  * PUT_10 put operations); we report full status every
101  * 10 iterations.  Abort with CTRL-C.
102  */
103 #define ITERATIONS 100
104
105
106 static unsigned long long stored_bytes;
107
108 static unsigned long long stored_entries;
109
110 static unsigned long long stored_ops;
111
112 static struct GNUNET_TIME_Absolute start_time;
113
114 static int ok;
115
116 enum RunPhase
117   {
118     RP_DONE = 0,
119     RP_PUT,
120     RP_CUT,
121     RP_REPORT
122   };
123
124
125 struct CpsRunContext
126 {
127   struct GNUNET_SCHEDULER_Handle *sched;
128   struct GNUNET_CONFIGURATION_Handle *cfg;
129   enum RunPhase phase;
130   int j;
131   unsigned long long size;
132   int i;
133
134   GNUNET_HashCode key;
135   uint32_t esize;
136   char data[65536];
137 };
138
139
140
141 static void
142 run_continuation (void *cls,
143                   const struct GNUNET_SCHEDULER_TaskContext *tc);
144
145
146
147
148 static void
149 check_success (void *cls,
150                int success,
151                const char *msg)
152 {
153   static int ic;
154
155   struct CpsRunContext *crc = cls;
156   if (GNUNET_OK != success)
157     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
158                 "%s\n", msg);
159   GNUNET_assert (GNUNET_OK == success);
160   ic++;
161 #if REPORT_ID
162   if (ic % REP_FREQ == 0)
163     fprintf (stderr, "I");
164 #endif
165   stored_bytes += crc->size;
166   stored_ops++;
167   stored_entries++;
168   crc->j++;
169   if (crc->j == PUT_10)
170     {
171       crc->j = 0;
172       crc->i++;
173       if (crc->i == ITERATIONS)
174         crc->phase = RP_DONE;
175       else
176         crc->phase = RP_CUT;
177     }
178   GNUNET_SCHEDULER_add_continuation (crc->sched,
179                                      GNUNET_NO,
180                                      &run_continuation,
181                                      crc,
182                                      GNUNET_SCHEDULER_REASON_PREREQ_DONE);
183 }
184
185
186 /**
187  * Continuation called to notify client about result of the
188  * operation.
189  *
190  * @param cls closure
191  * @param success GNUNET_SYSERR on failure
192  * @param msg NULL on success, otherwise an error message
193  */
194 static void 
195 remove_next(void *cls,
196             int success,
197             const char *msg)
198 {
199   struct CpsRunContext *crc = cls;
200
201   static int dc;
202   dc++;
203 #if REPORT_ID
204   if (dc % REP_FREQ == 0)
205     fprintf (stderr, "D");
206 #endif
207   GNUNET_assert (GNUNET_OK == success);
208   GNUNET_SCHEDULER_add_continuation (crc->sched,
209                                      GNUNET_NO,
210                                      &run_continuation,
211                                      crc,
212                                      GNUNET_SCHEDULER_REASON_PREREQ_DONE);
213 }
214
215
216
217 static void
218 do_delete (void *cls,
219            const struct GNUNET_SCHEDULER_TaskContext *tc)
220 {
221   struct CpsRunContext *crc = cls;
222
223   stored_bytes -= crc->esize;
224   stored_entries--;
225   GNUNET_DATASTORE_remove (datastore,
226                            &crc->key,
227                            crc->esize,
228                            crc->data,
229                            &remove_next,
230                            crc,
231                            TIMEOUT);
232 }
233
234
235
236 static void 
237 delete_value (void *cls,
238               const GNUNET_HashCode * key,
239               uint32_t size,
240               const void *data,
241               uint32_t type,
242               uint32_t priority,
243               uint32_t anonymity,
244               struct GNUNET_TIME_Absolute
245               expiration, uint64_t uid)
246 {
247   struct CpsRunContext *crc = cls;
248
249   if (key == NULL)
250     {
251       crc->phase = RP_REPORT;
252       if (stored_bytes < MAX_SIZE)
253         {
254           GNUNET_SCHEDULER_add_continuation (crc->sched,
255                                              GNUNET_NO,
256                                              &run_continuation,
257                                              crc,
258                                              GNUNET_SCHEDULER_REASON_PREREQ_DONE);
259           return;     
260         }
261       GNUNET_SCHEDULER_add_after (crc->sched,
262                                   GNUNET_NO,
263                                   GNUNET_SCHEDULER_PRIORITY_HIGH,
264                                   GNUNET_SCHEDULER_NO_PREREQUISITE_TASK,
265                                   &do_delete,
266                                   crc);
267       return;
268     }
269   if (stored_bytes < MAX_SIZE)
270     return;     
271   crc->key = *key;
272   crc->esize = size;
273   memcpy (crc->data, data, size);
274 }
275
276
277 static void
278 run_continuation (void *cls,
279                   const struct GNUNET_SCHEDULER_TaskContext *tc)
280 {
281   struct CpsRunContext *crc = cls;
282   size_t size;
283   static GNUNET_HashCode key;
284   static char data[65536];
285   int i;
286   int k;
287
288   ok = (int) crc->phase;
289   switch (crc->phase)
290     {
291     case RP_PUT:
292       memset (&key, 256 - crc->i, sizeof (GNUNET_HashCode));
293       i = crc->j;
294       k = crc->i;
295       /* most content is 32k */
296       size = 32 * 1024;
297       if (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 16) == 0)  /* but some of it is less! */
298         size = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 32 * 1024);
299       crc->size = size = size - (size & 7);     /* always multiple of 8 */
300       GNUNET_CRYPTO_hash (&key, sizeof (GNUNET_HashCode), &key);
301       memset (data, i, size);
302       if (i > 255)
303         memset (data, i - 255, size / 2);
304       data[0] = k;
305       GNUNET_DATASTORE_put (datastore,
306                             0,
307                             &key,
308                             size,
309                             data,
310                             i+1,
311                             GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 100),
312                             i,
313                             GNUNET_TIME_relative_to_absolute 
314                             (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
315                                                             GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 1000))),
316                             TIMEOUT,
317                             &check_success, 
318                             crc);
319       break;
320     case RP_CUT:
321       /* trim down below MAX_SIZE again */
322       GNUNET_DATASTORE_get_random (datastore, 
323                                    &delete_value,
324                                    crc,
325                                    TIMEOUT);
326       break;
327     case RP_REPORT:
328       size = 0;
329       printf (
330 #if REPORT_ID
331                "\n"
332 #endif
333                "Stored %llu kB / %lluk ops / %llu ops/s\n", 
334                stored_bytes / 1024,     /* used size in k */
335                (stored_ops * 2 - stored_entries) / 1024,        /* total operations (in k) */
336                1000 * (stored_ops * 2 - stored_entries) / (1 + GNUNET_TIME_absolute_get_duration(start_time).value));       /* operations per second */
337       crc->phase = RP_PUT;
338       GNUNET_SCHEDULER_add_continuation (crc->sched,
339                                          GNUNET_NO,
340                                          &run_continuation,
341                                          crc,
342                                          GNUNET_SCHEDULER_REASON_PREREQ_DONE);
343       break;
344     case RP_DONE:
345       GNUNET_DATASTORE_disconnect (datastore, GNUNET_YES);
346       ok = 0;
347       break;
348     }
349 }
350
351
352 static void
353 run (void *cls,
354      struct GNUNET_SCHEDULER_Handle *sched,
355      char *const *args,
356      const char *cfgfile, struct GNUNET_CONFIGURATION_Handle *cfg)
357 {
358   struct CpsRunContext *crc;
359
360   datastore = GNUNET_DATASTORE_connect (cfg, sched);
361
362   crc = GNUNET_malloc(sizeof(struct CpsRunContext));
363   crc->sched = sched;
364   crc->cfg = cfg;
365   crc->phase = RP_PUT;
366   GNUNET_SCHEDULER_add_continuation (crc->sched,
367                                      GNUNET_NO,
368                                      &run_continuation,
369                                      crc,
370                                      GNUNET_SCHEDULER_REASON_PREREQ_DONE);
371 }
372
373
374 static int
375 check ()
376 {
377   pid_t pid;
378   char *const argv[] = { 
379     "perf-datastore-api",
380     "-c",
381     "test_datastore_api_data.conf",
382 #if VERBOSE
383     "-L", "DEBUG",
384 #endif
385     NULL
386   };
387   struct GNUNET_GETOPT_CommandLineOption options[] = {
388     GNUNET_GETOPT_OPTION_END
389   };
390   pid = GNUNET_OS_start_process ("gnunet-service-datastore",
391                                  "gnunet-service-datastore",
392 #if VERBOSE
393                                  "-L", "DEBUG",
394 #endif
395                                  "-c", "test_datastore_api_data.conf", NULL);
396   sleep (1);
397   GNUNET_PROGRAM_run ((sizeof (argv) / sizeof (char *)) - 1,
398                       argv, "perf-datastore-api", "nohelp",
399                       options, &run, NULL);
400   if (0 != PLIBC_KILL (pid, SIGTERM))
401     {
402       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
403       ok = 1;
404     }
405   GNUNET_OS_process_wait(pid);
406   return ok;
407 }
408
409
410 int
411 main (int argc, char *argv[])
412 {
413   int ret;
414
415   GNUNET_DISK_directory_remove ("/tmp/test-gnunetd-datastore");
416   GNUNET_log_setup ("perf-datastore-api",
417 #if VERBOSE
418                     "DEBUG",
419 #else
420                     "WARNING",
421 #endif
422                     NULL);
423   ret = check ();
424
425   return ret;
426 }
427
428
429 /* end of perf_datastore_api.c */