-using testing in datastore tests
[oweals/gnunet.git] / src / datastore / perf_plugin_datastore.c
1 /*
2      This file is part of GNUnet.
3      (C) 2004, 2005, 2006, 2007, 2009, 2011 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 3, 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 perf_plugin_datastore.c
22  * @brief Profile database plugin directly, focusing on iterators.
23  * @author Christian Grothoff
24  */
25
26 #include "platform.h"
27 #include "gnunet_util_lib.h"
28 #include "gnunet_protocols.h"
29 #include "gnunet_datastore_plugin.h"
30 #include <gauger.h>
31
32 /**
33  * Target datastore size (in bytes).  Realistic sizes are
34  * more like 16 GB (not the default of 16 MB); however,
35  * those take too long to run them in the usual "make check"
36  * sequence.  Hence the value used for shipping is tiny.
37  */
38 #define MAX_SIZE 1024LL * 1024 * 16 * 1
39
40 #define ITERATIONS 2
41
42 /**
43  * Number of put operations equivalent to 1/10th of MAX_SIZE
44  */
45 #define PUT_10 (MAX_SIZE / 32 / 1024 / ITERATIONS)
46
47 static char category[256];
48
49 static unsigned int hits[PUT_10 / 8 + 1];
50
51 static unsigned long long stored_bytes;
52
53 static unsigned long long stored_entries;
54
55 static unsigned long long stored_ops;
56
57 static const char *plugin_name;
58
59 static int ok;
60
61 enum RunPhase
62 {
63   RP_ERROR = 0,
64   RP_PUT,
65   RP_REP_GET,
66   RP_ZA_GET,
67   RP_EXP_GET,
68   RP_DONE
69 };
70
71
72 struct CpsRunContext
73 {
74   unsigned int i;
75   struct GNUNET_TIME_Absolute start;
76   struct GNUNET_TIME_Absolute end;
77   const struct GNUNET_CONFIGURATION_Handle *cfg;
78   struct GNUNET_DATASTORE_PluginFunctions *api;
79   enum RunPhase phase;
80   unsigned int cnt;
81   unsigned int iter;
82   uint64_t offset;
83 };
84
85
86 /**
87  * Function called by plugins to notify us about a
88  * change in their disk utilization.
89  *
90  * @param cls closure (NULL)
91  * @param delta change in disk utilization,
92  *        0 for "reset to empty"
93  */
94 static void
95 disk_utilization_change_cb (void *cls, int delta)
96 {
97 }
98
99
100 static void
101 putValue (struct GNUNET_DATASTORE_PluginFunctions *api, int i, int k)
102 {
103   char value[65536];
104   size_t size;
105   static GNUNET_HashCode key;
106   static int ic;
107   char *msg;
108   unsigned int prio;
109
110   /* most content is 32k */
111   size = 32 * 1024;
112   if (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 16) == 0)   /* but some of it is less! */
113     size = 8 + GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 32 * 1024);
114   size = size - (size & 7);     /* always multiple of 8 */
115
116   /* generate random key */
117   key.bits[0] = (unsigned int) GNUNET_TIME_absolute_get ().abs_value;
118   GNUNET_CRYPTO_hash (&key, sizeof (GNUNET_HashCode), &key);
119   memset (value, i, size);
120   if (i > 255)
121     memset (value, i - 255, size / 2);
122   value[0] = k;
123   memcpy (&value[4], &i, sizeof (i));
124   msg = NULL;
125   prio = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 100);
126   if (GNUNET_OK != api->put (api->cls, &key, size, value, 1 + i % 4 /* type */ ,
127                              prio, i % 4 /* anonymity */ ,
128                              0 /* replication */ ,
129                              GNUNET_TIME_relative_to_absolute
130                              (GNUNET_TIME_relative_multiply
131                               (GNUNET_TIME_UNIT_MILLISECONDS,
132                                60 * 60 * 60 * 1000 +
133                                GNUNET_CRYPTO_random_u32
134                                (GNUNET_CRYPTO_QUALITY_WEAK, 1000))), &msg))
135   {
136     FPRINTF (stderr, "ERROR: `%s'\n", msg);
137     GNUNET_free_non_null (msg);
138     return;
139   }
140   ic++;
141   stored_bytes += size;
142   stored_ops++;
143   stored_entries++;
144 }
145
146 static void
147 test (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
148
149
150 static int
151 iterate_zeros (void *cls, const GNUNET_HashCode * key, uint32_t size,
152                const void *data, enum GNUNET_BLOCK_Type type, uint32_t priority,
153                uint32_t anonymity, struct GNUNET_TIME_Absolute expiration,
154                uint64_t uid)
155 {
156   struct CpsRunContext *crc = cls;
157   int i;
158   const char *cdata = data;
159
160   GNUNET_assert (key != NULL);
161   GNUNET_assert (size >= 8);
162   memcpy (&i, &cdata[4], sizeof (i));
163   hits[i / 8] |= (1 << (i % 8));
164
165   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 
166               "Found result type=%u, priority=%u, size=%u, expire=%llu\n",
167               type, priority, size, (unsigned long long) expiration.abs_value);
168   crc->cnt++;
169   if (crc->cnt == PUT_10 / 4 - 1)
170   {
171     unsigned int bc;
172
173     bc = 0;
174     for (i = 0; i < PUT_10; i++)
175       if (0 != (hits[i / 8] & (1 << (i % 8))))
176         bc++;
177
178     crc->end = GNUNET_TIME_absolute_get ();
179     printf ("%s took %llu ms yielding %u/%u items\n",
180             "Select random zero-anonymity item",
181             (unsigned long long) (crc->end.abs_value - crc->start.abs_value),
182             bc, crc->cnt);
183     if (crc->cnt > 0)
184       GAUGER (category, "Select random zero-anonymity item",
185               (crc->end.abs_value - crc->start.abs_value) / crc->cnt,
186               "ms/item");
187     memset (hits, 0, sizeof (hits));
188     crc->phase++;
189     crc->cnt = 0;
190     crc->start = GNUNET_TIME_absolute_get ();
191   }
192   GNUNET_SCHEDULER_add_now (&test, crc);
193   return GNUNET_OK;
194 }
195
196
197 static int
198 expiration_get (void *cls, const GNUNET_HashCode * key, uint32_t size,
199                 const void *data, enum GNUNET_BLOCK_Type type,
200                 uint32_t priority, uint32_t anonymity,
201                 struct GNUNET_TIME_Absolute expiration, uint64_t uid)
202 {
203   struct CpsRunContext *crc = cls;
204   int i;
205   const char *cdata = data;
206
207   GNUNET_assert (size >= 8);
208   memcpy (&i, &cdata[4], sizeof (i));
209   hits[i / 8] |= (1 << (i % 8));
210   crc->cnt++;
211   if (PUT_10 <= crc->cnt)
212   {
213     unsigned int bc;
214
215     bc = 0;
216     for (i = 0; i < PUT_10; i++)
217       if (0 != (hits[i / 8] & (1 << (i % 8))))
218         bc++;
219
220     crc->end = GNUNET_TIME_absolute_get ();
221     printf ("%s took %llu ms yielding %u/%u items\n",
222             "Selecting and deleting by expiration",
223             (unsigned long long) (crc->end.abs_value - crc->start.abs_value),
224             bc, (unsigned int) PUT_10);
225     if (crc->cnt > 0)
226       GAUGER (category, "Selecting and deleting by expiration",
227               (crc->end.abs_value - crc->start.abs_value) / crc->cnt,
228               "ms/item");
229     memset (hits, 0, sizeof (hits));
230     if (++crc->iter == ITERATIONS)
231       crc->phase++;
232     else
233       crc->phase = RP_PUT;
234     crc->cnt = 0;
235     crc->start = GNUNET_TIME_absolute_get ();
236   }
237   GNUNET_SCHEDULER_add_now (&test, crc);
238   return GNUNET_NO;
239 }
240
241
242 static int
243 replication_get (void *cls, const GNUNET_HashCode * key, uint32_t size,
244                  const void *data, enum GNUNET_BLOCK_Type type,
245                  uint32_t priority, uint32_t anonymity,
246                  struct GNUNET_TIME_Absolute expiration, uint64_t uid)
247 {
248   struct CpsRunContext *crc = cls;
249   int i;
250   const char *cdata = data;
251
252   GNUNET_assert (NULL != key);
253   GNUNET_assert (size >= 8);
254   memcpy (&i, &cdata[4], sizeof (i));
255   hits[i / 8] |= (1 << (i % 8));
256   crc->cnt++;
257   if (PUT_10 <= crc->cnt)
258   {
259     unsigned int bc;
260
261     bc = 0;
262     for (i = 0; i < PUT_10; i++)
263       if (0 != (hits[i / 8] & (1 << (i % 8))))
264         bc++;
265
266     crc->end = GNUNET_TIME_absolute_get ();
267     printf ("%s took %llu ms yielding %u/%u items\n",
268             "Selecting random item for replication",
269             (unsigned long long) (crc->end.abs_value - crc->start.abs_value),
270             bc, (unsigned int) PUT_10);
271     if (crc->cnt > 0)
272       GAUGER (category, "Selecting random item for replication",
273               (crc->end.abs_value - crc->start.abs_value) / crc->cnt,
274               "ms/item");
275     memset (hits, 0, sizeof (hits));
276     crc->phase++;
277     crc->offset = 0;
278     crc->cnt = 0;
279     crc->start = GNUNET_TIME_absolute_get ();
280   }
281
282   GNUNET_SCHEDULER_add_now (&test, crc);
283   return GNUNET_OK;
284 }
285
286
287 /**
288  * Function called when the service shuts
289  * down.  Unloads our datastore plugin.
290  *
291  * @param api api to unload
292  * @param cfg configuration to use
293  */
294 static void
295 unload_plugin (struct GNUNET_DATASTORE_PluginFunctions *api,
296                const struct GNUNET_CONFIGURATION_Handle *cfg)
297 {
298   char *name;
299   char *libname;
300
301   if (GNUNET_OK !=
302       GNUNET_CONFIGURATION_get_value_string (cfg, "DATASTORE", "DATABASE",
303                                              &name))
304   {
305     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
306                 _("No `%s' specified for `%s' in configuration!\n"), "DATABASE",
307                 "DATASTORE");
308     return;
309   }
310   GNUNET_asprintf (&libname, "libgnunet_plugin_datastore_%s", name);
311   GNUNET_break (NULL == GNUNET_PLUGIN_unload (libname, api));
312   GNUNET_free (libname);
313   GNUNET_free (name);
314 }
315
316
317
318 /**
319  * Last task run during shutdown.  Disconnects us from
320  * the transport and core.
321  */
322 static void
323 cleaning_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
324 {
325   struct CpsRunContext *crc = cls;
326
327   unload_plugin (crc->api, crc->cfg);
328   GNUNET_free (crc);
329 }
330
331
332 static void
333 test (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
334 {
335   struct CpsRunContext *crc = cls;
336   int j;
337
338   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
339   {
340     GNUNET_break (0);
341     crc->phase = RP_ERROR;
342   }
343   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 
344               "In phase %d, iteration %u\n", crc->phase, crc->cnt);
345   switch (crc->phase)
346   {
347   case RP_ERROR:
348     GNUNET_break (0);
349     crc->api->drop (crc->api->cls);
350     ok = 1;
351     GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
352                                         &cleaning_task, crc);
353     break;
354   case RP_PUT:
355     crc->start = GNUNET_TIME_absolute_get ();
356     for (j = 0; j < PUT_10; j++)
357       putValue (crc->api, j, crc->i);
358     crc->end = GNUNET_TIME_absolute_get ();
359     {
360       printf ("%s took %llu ms for %llu items\n", "Storing an item",
361               (unsigned long long) (crc->end.abs_value - crc->start.abs_value),
362               PUT_10);
363       if (PUT_10 > 0)
364         GAUGER (category, "Storing an item",
365                 (crc->end.abs_value - crc->start.abs_value) / PUT_10,
366                 "ms/item");
367     }
368     crc->i++;
369     crc->start = GNUNET_TIME_absolute_get ();
370     crc->phase++;
371     GNUNET_SCHEDULER_add_now (&test, crc);
372     break;
373   case RP_REP_GET:
374     crc->api->get_replication (crc->api->cls, &replication_get, crc);
375     break;
376   case RP_ZA_GET:
377     crc->api->get_zero_anonymity (crc->api->cls, crc->offset++, 1,
378                                   &iterate_zeros, crc);
379     break;
380   case RP_EXP_GET:
381     crc->api->get_expiration (crc->api->cls, &expiration_get, crc);
382     break;
383   case RP_DONE:
384     crc->api->drop (crc->api->cls);
385     ok = 0;
386     GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
387                                         &cleaning_task, crc);
388     break;
389   }
390 }
391
392
393 /**
394  * Load the datastore plugin.
395  */
396 static struct GNUNET_DATASTORE_PluginFunctions *
397 load_plugin (const struct GNUNET_CONFIGURATION_Handle *cfg)
398 {
399   static struct GNUNET_DATASTORE_PluginEnvironment env;
400   struct GNUNET_DATASTORE_PluginFunctions *ret;
401   char *name;
402   char *libname;
403
404   if (GNUNET_OK !=
405       GNUNET_CONFIGURATION_get_value_string (cfg, "DATASTORE", "DATABASE",
406                                              &name))
407   {
408     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
409                 _("No `%s' specified for `%s' in configuration!\n"), "DATABASE",
410                 "DATASTORE");
411     return NULL;
412   }
413   env.cfg = cfg;
414   env.duc = &disk_utilization_change_cb;
415   env.cls = NULL;
416   GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Loading `%s' datastore plugin\n"),
417               name);
418   GNUNET_asprintf (&libname, "libgnunet_plugin_datastore_%s", name);
419   if (NULL == (ret = GNUNET_PLUGIN_load (libname, &env)))
420   {
421     FPRINTF (stderr, "Failed to load plugin `%s'!\n", name);
422     return NULL;
423   }
424   GNUNET_free (libname);
425   GNUNET_free (name);
426   return ret;
427 }
428
429
430 static void
431 run (void *cls, char *const *args, const char *cfgfile,
432      const struct GNUNET_CONFIGURATION_Handle *c)
433 {
434   struct GNUNET_DATASTORE_PluginFunctions *api;
435   struct CpsRunContext *crc;
436
437   api = load_plugin (c);
438   if (api == NULL)
439   {
440     FPRINTF (stderr,
441              "%s", "Could not initialize plugin, assuming database not configured. Test not run!\n");
442     return;
443   }
444   crc = GNUNET_malloc (sizeof (struct CpsRunContext));
445   crc->api = api;
446   crc->cfg = c;
447   crc->phase = RP_PUT;
448   ok = 2;
449   GNUNET_SCHEDULER_add_now (&test, crc);
450 }
451
452
453 int
454 main (int argc, char *argv[])
455 {
456   char *pos;
457   char dir_name[128];
458   char cfg_name[128];
459   char *const argv[] = {
460     "perf-plugin-datastore",
461     "-c",
462     cfg_name,
463     NULL
464   };
465   struct GNUNET_GETOPT_CommandLineOption options[] = {
466     GNUNET_GETOPT_OPTION_END
467   };
468
469   /* determine name of plugin to use */
470   plugin_name = argv[0];
471   while (NULL != (pos = strstr (plugin_name, "_")))
472     plugin_name = pos + 1;
473   if (NULL != (pos = strstr (plugin_name, ".")))
474     pos[0] = 0;
475   else
476     pos = (char *) plugin_name;
477   GNUNET_snprintf (dir_name, sizeof (dir_name), "/tmp/perf-gnunet-datastore-%s",
478                    plugin_name);
479   GNUNET_DISK_directory_remove (dir_name);
480   GNUNET_log_setup ("perf-plugin-datastore",
481                     "WARNING",
482                     NULL);
483   GNUNET_snprintf (category, sizeof (category), "DATASTORE-%s", plugin_name);
484   GNUNET_snprintf (cfg_name, sizeof (cfg_name),
485                    "perf_plugin_datastore_data_%s.conf", plugin_name);
486   GNUNET_PROGRAM_run ((sizeof (argv) / sizeof (char *)) - 1, argv,
487                       "perf-plugin-datastore", "nohelp", options, &run, NULL);
488   if (ok != 0)
489     FPRINTF (stderr, "Missed some testcases: %u\n", ok);
490   if (pos != plugin_name)
491     pos[0] = '.';
492   GNUNET_DISK_directory_remove (dir_name);
493
494   return ok;
495 }
496
497 /* end of perf_plugin_datastore.c */