glitch in the license text detected by hyazinthe, thank you!
[oweals/gnunet.git] / src / datastore / perf_datastore_api.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2004, 2005, 2006, 2007, 2009, 2011, 2015 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your 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      Affero General Public License for more details.
14 */
15 /*
16  * @file datastore/perf_datastore_api.c
17  * @brief performance measurement for the datastore implementation
18  * @author Christian Grothoff
19  *
20  * This testcase inserts a bunch of (variable size) data and then
21  * deletes data until the (reported) database size drops below a given
22  * threshold.  This is iterated 10 times, with the actual size of the
23  * content stored and the number of operations performed being printed
24  * for each iteration.  The code also prints a "I" for every 40 blocks
25  * inserted and a "D" for every 40 blocks deleted.  The deletion
26  * strategy uses the "random" iterator.  Priorities and expiration
27  * dates are set using a pseudo-random value within a realistic range.
28  */
29 #include "platform.h"
30 #include "gnunet_util_lib.h"
31 #include "gnunet_protocols.h"
32 #include "gnunet_datastore_service.h"
33 #include "gnunet_testing_lib.h"
34 #include <gauger.h>
35
36 /**
37  * How long until we give up on transmitting the message?
38  */
39 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15)
40
41 /**
42  * Target datastore size (in bytes).
43  */
44 #define MAX_SIZE (1024LL * 1024 * 4)
45
46 /**
47  * Report progress outside of major reports? Should probably be #GNUNET_YES if
48  * size is > 16 MB.
49  */
50 #define REPORT_ID GNUNET_YES
51
52 /**
53  * Number of put operations equivalent to 1/3rd of #MAX_SIZE
54  */
55 #define PUT_10 MAX_SIZE / 32 / 1024 / 3
56
57 /**
58  * Total number of iterations (each iteration doing
59  * PUT_10 put operations); we report full status every
60  * 10 iterations.  Abort with CTRL-C.
61  */
62 #define ITERATIONS 8
63
64 /**
65  * Total number of iterations to do to go beyond the quota.
66  * The quota is set to 10 MB or 2.5 times #MAX_SIZE,
67  * so we got 16 times #MAX_SIZE to be sure to hit it a LOT.
68  */
69 #define QUOTA_PUTS (MAX_SIZE / 32 / 1024 * 16LL)
70
71
72 /**
73  * Number of bytes stored in the datastore in total.
74  */
75 static unsigned long long stored_bytes;
76
77 /**
78  * Number of entries stored in the datastore in total.
79  */
80 static unsigned long long stored_entries;
81
82 /**
83  * Number of database operations performed.  Inserting
84  * counts as one operation, deleting as two (as deletion
85  * requires selecting a value for deletion first).
86  */
87 static unsigned long long stored_ops;
88
89 /**
90  * Start time of the benchmark.
91  */
92 static struct GNUNET_TIME_Absolute start_time;
93
94 /**
95  * Database backend we use.
96  */
97 static const char *plugin_name;
98
99 /**
100  * Handle to the datastore.
101  */
102 static struct GNUNET_DATASTORE_Handle *datastore;
103
104 /**
105  * Value we return from #main().
106  */
107 static int ok;
108
109 /**
110  * Which phase of the process are we in?
111  */
112 enum RunPhase
113 {
114   /**
115    * We are done (shutting down normally).
116    */
117   RP_DONE = 0,
118
119   /**
120    * We are adding new entries to the datastore.
121    */
122   RP_PUT,
123
124   /**
125    * We are deleting entries from the datastore.
126    */
127   RP_CUT,
128
129   /**
130    * We are putting as much as we can to see how the database performs
131    * when it reaches the quota and has to auto-delete (see #3903).
132    */
133   RP_PUT_QUOTA,
134
135   /**
136    * We are generating a report.
137    */
138   RP_REPORT,
139
140   /**
141    * Execution failed with some kind of error.
142    */
143   RP_ERROR
144 };
145
146
147 /**
148  * Closure we give to all of the functions executing the
149  * benchmark.  Could right now be global, but this allows
150  * us to theoretically run multiple clients "in parallel".
151  */
152 struct CpsRunContext
153 {
154   /**
155    * Execution phase we are in.
156    */
157   enum RunPhase phase;
158
159   /**
160    * Size of the value we are currently storing (during #RP_PUT).
161    */
162   size_t size;
163
164   /**
165    * Current iteration counter, we are done with the benchmark
166    * once it hits #ITERATIONS.
167    */
168   unsigned int i;
169
170   /**
171    * Counts the number of items put in the current phase.
172    * Once it hits #PUT_10, we progress tot he #RP_CUT phase
173    * or are done if @e i reaches #ITERATIONS.
174    */
175   unsigned int j;
176 };
177
178
179 /**
180  * Main state machine.  Executes the next step of the benchmark
181  * depending on the current state.
182  *
183  * @param cls the `struct CpsRunContext`
184  */
185 static void
186 run_continuation (void *cls);
187
188
189 /**
190  * Continuation called to notify client about result of the insertion
191  * operation.  Checks for errors, updates our iteration counters and
192  * continues execution with #run_continuation().
193  *
194  * @param cls the `struct CpsRunContext`
195  * @param success #GNUNET_SYSERR on failure
196  * @param min_expiration minimum expiration time required for content to be stored
197  *                by the datacache at this time, zero for unknown
198  * @param msg NULL on success, otherwise an error message
199  */
200 static void
201 check_success (void *cls,
202                int success,
203                struct GNUNET_TIME_Absolute min_expiration,
204                const char *msg)
205 {
206   struct CpsRunContext *crc = cls;
207
208 #if REPORT_ID
209   FPRINTF (stderr, "%s",  (GNUNET_OK == success) ? "I" : "i");
210 #endif
211   if (GNUNET_OK != success)
212   {
213     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
214                 "Check success failed: `%s'\n",
215                 msg);
216     crc->phase = RP_ERROR;
217     GNUNET_SCHEDULER_add_now (&run_continuation,
218                               crc);
219     return;
220   }
221   stored_bytes += crc->size;
222   stored_ops++;
223   stored_entries++;
224   crc->j++;
225   switch (crc->phase)
226   {
227   case RP_PUT:
228     if (crc->j >= PUT_10)
229     {
230       crc->j = 0;
231       crc->i++;
232       if (crc->i == ITERATIONS)
233         crc->phase = RP_PUT_QUOTA;
234       else
235         crc->phase = RP_CUT;
236     }
237     break;
238   case RP_PUT_QUOTA:
239     if (crc->j >= QUOTA_PUTS)
240     {
241       crc->j = 0;
242       crc->phase = RP_DONE;
243     }
244     break;
245   default:
246     GNUNET_assert (0);
247   }
248   GNUNET_SCHEDULER_add_now (&run_continuation,
249                             crc);
250 }
251
252
253 /**
254  * Continuation called to notify client about result of the
255  * deletion operation.  Checks for errors and continues
256  * execution with #run_continuation().
257  *
258  * @param cls the `struct CpsRunContext`
259  * @param success #GNUNET_SYSERR on failure
260  * @param min_expiration minimum expiration time required for content to be stored
261  *                by the datacache at this time, zero for unknown
262  * @param msg NULL on success, otherwise an error message
263  */
264 static void
265 remove_next (void *cls,
266              int success,
267              struct GNUNET_TIME_Absolute min_expiration,
268              const char *msg)
269 {
270   struct CpsRunContext *crc = cls;
271
272   if (GNUNET_OK != success)
273   {
274     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
275                 "remove_next failed: `%s'\n",
276                 msg);
277     crc->phase = RP_ERROR;
278     GNUNET_SCHEDULER_add_now (&run_continuation,
279                               crc);
280     return;
281   }
282 #if REPORT_ID
283   FPRINTF (stderr, "%s",  "D");
284 #endif
285   GNUNET_assert (GNUNET_OK == success);
286   GNUNET_SCHEDULER_add_now (&run_continuation,
287                             crc);
288 }
289
290
291 /**
292  * We have selected a value for deletion, trigger removal.
293  *
294  * @param cls the `struct CpsRunContext`
295  * @param key key for the content
296  * @param size number of bytes in data
297  * @param data content stored
298  * @param type type of the content
299  * @param priority priority of the content
300  * @param anonymity anonymity-level for the content
301  * @param replication replication-level for the content
302  * @param expiration expiration time for the content
303  * @param uid unique identifier for the datum;
304  *        maybe 0 if no unique identifier is available
305  */
306 static void
307 delete_value (void *cls,
308               const struct GNUNET_HashCode *key,
309               size_t size,
310               const void *data,
311               enum GNUNET_BLOCK_Type type,
312               uint32_t priority,
313               uint32_t anonymity,
314               uint32_t replication,
315               struct GNUNET_TIME_Absolute expiration,
316               uint64_t uid)
317 {
318   struct CpsRunContext *crc = cls;
319
320   GNUNET_assert (NULL != key);
321   stored_ops++;
322   stored_bytes -= size;
323   stored_entries--;
324   stored_ops++;
325   if (stored_bytes < MAX_SIZE)
326     crc->phase = RP_PUT;
327   GNUNET_assert (NULL !=
328                  GNUNET_DATASTORE_remove (datastore,
329                                           key,
330                                           size,
331                                           data, 1, 1,
332                                           &remove_next, crc));
333 }
334
335
336 /**
337  * Main state machine.  Executes the next step of the benchmark
338  * depending on the current state.
339  *
340  * @param cls the `struct CpsRunContext`
341  */
342 static void
343 run_continuation (void *cls)
344 {
345   struct CpsRunContext *crc = cls;
346   size_t size;
347   static struct GNUNET_HashCode key;
348   static char data[65536];
349   char gstr[128];
350
351   ok = (int) crc->phase;
352   switch (crc->phase)
353   {
354   case RP_PUT:
355     memset (&key,
356             256 - crc->i,
357             sizeof (struct GNUNET_HashCode));
358     /* most content is 32k */
359     size = 32 * 1024;
360     if (0 ==
361         GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
362                                   16)) /* but some of it is less! */
363       size = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
364                                        32 * 1024);
365     crc->size = size = size - (size & 7);       /* always multiple of 8 */
366     GNUNET_CRYPTO_hash (&key,
367                         sizeof (struct GNUNET_HashCode),
368                         &key);
369     memset (data,
370             (int) crc->j,
371             size);
372     if (crc->j > 255)
373       memset (data,
374               (int) (crc->j - 255),
375               size / 2);
376     data[0] = crc->i;
377     GNUNET_assert (NULL !=
378                    GNUNET_DATASTORE_put (datastore,
379                                          0,
380                                          &key,
381                                          size,
382                                          data,
383                                          crc->j + 1,
384                                          GNUNET_CRYPTO_random_u32
385                                          (GNUNET_CRYPTO_QUALITY_WEAK, 100),
386                                          crc->j,
387                                          0,
388                                          GNUNET_TIME_relative_to_absolute
389                                          (GNUNET_TIME_relative_multiply
390                                           (GNUNET_TIME_UNIT_SECONDS,
391                                            GNUNET_CRYPTO_random_u32
392                                            (GNUNET_CRYPTO_QUALITY_WEAK, 1000))),
393                                          1,
394                                          1,
395                                          &check_success, crc));
396     break;
397   case RP_CUT:
398     /* trim down below MAX_SIZE again */
399     GNUNET_assert (NULL !=
400                    GNUNET_DATASTORE_get_for_replication (datastore,
401                                                          1, 1,
402                                                          &delete_value,
403                                                          crc));
404     break;
405   case RP_REPORT:
406     printf (
407 #if REPORT_ID
408              "\n"
409 #endif
410              "Stored %llu kB / %lluk ops / %llu ops/s\n",
411              stored_bytes / 1024,  /* used size in k */
412              stored_ops / 1024, /* total operations (in k) */
413              1000LL * 1000LL * stored_ops / (1 +
414                                              GNUNET_TIME_absolute_get_duration
415                                              (start_time).rel_value_us));
416     crc->phase = RP_PUT;
417     crc->j = 0;
418     GNUNET_SCHEDULER_add_now (&run_continuation,
419                               crc);
420     break;
421   case RP_PUT_QUOTA:
422     memset (&key,
423             256 - crc->i,
424             sizeof (struct GNUNET_HashCode));
425     /* most content is 32k */
426     size = 32 * 1024;
427     if (0 ==
428         GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
429                                   16)) /* but some of it is less! */
430       size = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
431                                        32 * 1024);
432     crc->size = size = size - (size & 7);       /* always multiple of 8 */
433     GNUNET_CRYPTO_hash (&key,
434                         sizeof (struct GNUNET_HashCode),
435                         &key);
436     memset (data,
437             (int) crc->j,
438             size);
439     if (crc->j > 255)
440       memset (data,
441               (int) (crc->j - 255),
442               size / 2);
443     data[0] = crc->i;
444     GNUNET_assert (NULL !=
445                    GNUNET_DATASTORE_put (datastore,
446                                          0, /* reservation ID */
447                                          &key,
448                                          size,
449                                          data,
450                                          crc->j + 1, /* type */
451                                          GNUNET_CRYPTO_random_u32
452                                          (GNUNET_CRYPTO_QUALITY_WEAK,
453                                           100), /* priority */
454                                          crc->j, /* anonymity */
455                                          0, /* replication */
456                                          GNUNET_TIME_relative_to_absolute
457                                          (GNUNET_TIME_relative_multiply
458                                           (GNUNET_TIME_UNIT_SECONDS,
459                                            GNUNET_CRYPTO_random_u32
460                                            (GNUNET_CRYPTO_QUALITY_WEAK, 1000))),
461                                          1,
462                                          1,
463                                          &check_success, crc));
464     break;
465
466   case RP_DONE:
467     GNUNET_snprintf (gstr,
468                      sizeof (gstr),
469                      "DATASTORE-%s",
470                      plugin_name);
471     if ((crc->i == ITERATIONS) && (stored_ops > 0))
472     {
473       GAUGER (gstr,
474               "PUT operation duration",
475               GNUNET_TIME_absolute_get_duration (start_time).rel_value_us / 1000LL /
476               stored_ops,
477               "ms/operation");
478       fprintf (stdout,
479                "\nPUT performance: %s for %llu operations\n",
480                GNUNET_STRINGS_relative_time_to_string (GNUNET_TIME_absolute_get_duration (start_time),
481                                                        GNUNET_YES),
482                stored_ops);
483       fprintf (stdout,
484                "PUT performance: %llu ms/operation\n",
485                GNUNET_TIME_absolute_get_duration (start_time).rel_value_us / 1000LL /
486                stored_ops);
487     }
488     GNUNET_DATASTORE_disconnect (datastore,
489                                  GNUNET_YES);
490     GNUNET_free (crc);
491     ok = 0;
492     break;
493   case RP_ERROR:
494     GNUNET_DATASTORE_disconnect (datastore, GNUNET_YES);
495     GNUNET_free (crc);
496     ok = 1;
497     break;
498   default:
499     GNUNET_assert (0);
500   }
501 }
502
503
504 /**
505  * Function called with the result of the initial PUT operation.  If
506  * the PUT succeeded, we start the actual benchmark loop, otherwise we
507  * bail out with an error.
508  *
509  *
510  * @param cls closure
511  * @param success #GNUNET_SYSERR on failure
512  * @param min_expiration minimum expiration time required for content to be stored
513  *                by the datacache at this time, zero for unknown
514  * @param msg NULL on success, otherwise an error message
515  */
516 static void
517 run_tests (void *cls,
518            int success,
519            struct GNUNET_TIME_Absolute min_expiration,
520            const char *msg)
521 {
522   struct CpsRunContext *crc = cls;
523
524   if (success != GNUNET_YES)
525   {
526     FPRINTF (stderr,
527              "Test 'put' operation failed with error `%s' database likely not setup, skipping test.\n",
528              msg);
529     GNUNET_DATASTORE_disconnect (datastore,
530                                  GNUNET_YES);
531     GNUNET_free (crc);
532     return;
533   }
534   GNUNET_SCHEDULER_add_now (&run_continuation,
535                             crc);
536 }
537
538
539 /**
540  * Beginning of the actual execution of the benchmark.
541  * Performs a first test operation (PUT) to verify that
542  * the plugin works at all.
543  *
544  * @param cls NULL
545  * @param cfg configuration to use
546  * @param peer peer handle (unused)
547  */
548 static void
549 run (void *cls,
550      const struct GNUNET_CONFIGURATION_Handle *cfg,
551      struct GNUNET_TESTING_Peer *peer)
552 {
553   struct CpsRunContext *crc;
554   static struct GNUNET_HashCode zkey;
555
556   datastore = GNUNET_DATASTORE_connect (cfg);
557   start_time = GNUNET_TIME_absolute_get ();
558   crc = GNUNET_new (struct CpsRunContext);
559   crc->phase = RP_PUT;
560   if (NULL ==
561       GNUNET_DATASTORE_put (datastore,
562                             0,
563                             &zkey,
564                             4, "TEST",
565                             GNUNET_BLOCK_TYPE_TEST,
566                             0, 0, 0,
567                             GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_SECONDS),
568                             0, 1,
569                             &run_tests, crc))
570   {
571     FPRINTF (stderr,
572              "%s",
573              "Test 'put' operation failed.\n");
574     ok = 1;
575     GNUNET_free (crc);
576   }
577 }
578
579
580 /**
581  * Entry point into the test. Determines which configuration / plugin
582  * we are running with based on the name of the binary and starts
583  * the peer.
584  *
585  * @param argc should be 1
586  * @param argv used to determine plugin / configuration name.
587  * @return 0 on success
588  */
589 int
590 main (int argc,
591       char *argv[])
592 {
593   char cfg_name[128];
594
595   plugin_name = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
596   GNUNET_snprintf (cfg_name,
597                    sizeof (cfg_name),
598                    "test_datastore_api_data_%s.conf",
599                    plugin_name);
600   if (0 !=
601       GNUNET_TESTING_peer_run ("perf-gnunet-datastore",
602                                cfg_name,
603                                &run,
604                                NULL))
605     return 1;
606   FPRINTF (stderr, "%s", "\n");
607   return ok;
608 }
609
610 /* end of perf_datastore_api.c */