improving datastore API
[oweals/gnunet.git] / src / datastore / perf_datastore_api.c
1 /*
2      This file is part of GNUnet.
3      (C) 2004, 2005, 2006, 2007 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 applications/sqstore_sqlite/sqlitetest2.c
22  * @brief Test for the sqstore implementations.
23  * @author Christian Grothoff
24  *
25  * This testcase inserts a bunch of (variable size) data and then deletes
26  * data until the (reported) database size drops below a given threshold.
27  * This is iterated 10 times, with the actual size of the content stored,
28  * the database size reported and the file size on disk being printed for
29  * 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 alternates between "lowest priority" and "earliest expiration".
32  * Priorities and expiration dates are set using a pseudo-random value
33  * within a realistic range.
34  * <p>
35  *
36  * Note that the disk overhead calculations are not very sane for
37  * MySQL: we take the entire /var/lib/mysql directory (best we can
38  * do for ISAM), which may contain other data and which never
39  * shrinks.  The scanning of the entire mysql directory during
40  * each report is also likely to be the cause of a minor
41  * slowdown compared to sqlite.<p>
42  */
43
44 #include "platform.h"
45 #include "gnunet_util.h"
46 #include "gnunet_protocols.h"
47 #include "gnunet_sqstore_service.h"
48 #include "core.h"
49
50 #define ASSERT(x) do { if (! (x)) { printf("Error at %s:%d\n", __FILE__, __LINE__); goto FAILURE;} } while (0)
51
52 /**
53  * Target datastore size (in bytes).
54  * <p>
55  * Example impact of total size on the reported number
56  * of operations (insert and delete) per second (once
57  * roughly stabilized -- this is not "sound" experimental
58  * data but just a rough idea) for a particular machine:
59  * <pre>
60  *    4: 60   at   7k ops total
61  *    8: 50   at   3k ops total
62  *   16: 48   at   8k ops total
63  *   32: 46   at   8k ops total
64  *   64: 61   at   9k ops total
65  *  128: 89   at   9k ops total
66  * 4092: 11   at 383k ops total (12 GB stored, 14.8 GB DB size on disk, 2.5 GB reported)
67  * </pre>
68  * Pure insertion performance into an empty DB initially peaks
69  * at about 400 ops.  The performance seems to drop especially
70  * once the existing (fragmented) ISAM space is filled up and
71  * the DB needs to grow on disk.  This could be explained with
72  * ISAM looking more carefully for defragmentation opportunities.
73  * <p>
74  * MySQL disk space overheads (for otherwise unused database when
75  * run with 128 MB target data size; actual size 651 MB, useful
76  * data stored 520 MB) are quite large in the range of 25-30%.
77  * <p>
78  * This kind of processing seems to be IO bound (system is roughly
79  * at 90% wait, 10% CPU).  This is with MySQL 5.0.
80  *
81  */
82 #define MAX_SIZE 1024LL * 1024 * 16
83
84 /**
85  * Report progress outside of major reports? Should probably be GNUNET_YES if
86  * size is > 16 MB.
87  */
88 #define REPORT_ID GNUNET_NO
89
90 /**
91  * Number of put operations equivalent to 1/10th of MAX_SIZE
92  */
93 #define PUT_10 MAX_SIZE / 32 / 1024 / 10
94
95 /**
96  * Progress report frequency.  1/10th of a put operation block.
97  */
98 #define REP_FREQ PUT_10 / 10
99
100 /**
101  * Total number of iterations (each iteration doing
102  * PUT_10 put operations); we report full status every
103  * 10 iterations.  Abort with CTRL-C.
104  */
105 #define ITERATIONS 100
106
107 /**
108  * Name of the database on disk.
109  * You may have to adjust this path and the access
110  * permission to the respective directory in order
111  * to obtain all of the performance information.
112  */
113 #define DB_NAME "/tmp/gnunet-sqlite-sqstore-test/data/fs/"
114
115 static unsigned long long stored_bytes;
116
117 static unsigned long long stored_entries;
118
119 static unsigned long long stored_ops;
120
121 static GNUNET_CronTime start_time;
122
123 static int
124 putValue (GNUNET_SQstore_ServiceAPI * api, int i, int k)
125 {
126   GNUNET_DatastoreValue *value;
127   size_t size;
128   static GNUNET_HashCode key;
129   static int ic;
130
131   /* most content is 32k */
132   size = sizeof (GNUNET_DatastoreValue) + 32 * 1024;
133   if (GNUNET_random_u32 (GNUNET_RANDOM_QUALITY_WEAK, 16) == 0)  /* but some of it is less! */
134     size =
135       sizeof (GNUNET_DatastoreValue) +
136       GNUNET_random_u32 (GNUNET_RANDOM_QUALITY_WEAK, 32 * 1024);
137   size = size - (size & 7);     /* always multiple of 8 */
138
139   /* generate random key */
140   GNUNET_hash (&key, sizeof (GNUNET_HashCode), &key);
141   value = GNUNET_malloc (size);
142   value->size = htonl (size);
143   value->type = htonl (i);
144   value->priority =
145     htonl (GNUNET_random_u32 (GNUNET_RANDOM_QUALITY_WEAK, 100));
146   value->anonymity_level = htonl (i);
147   value->expiration_time =
148     GNUNET_htonll (GNUNET_get_time () +
149                    GNUNET_random_u32 (GNUNET_RANDOM_QUALITY_WEAK, 1000));
150   memset (&value[1], i, size - sizeof (GNUNET_DatastoreValue));
151   if (i > 255)
152     memset (&value[1], i - 255, (size - sizeof (GNUNET_DatastoreValue)) / 2);
153   ((char *) &value[1])[0] = k;
154   if (GNUNET_OK != api->put (&key, value))
155     {
156       GNUNET_free (value);
157       fprintf (stderr, "E");
158       return GNUNET_SYSERR;
159     }
160   ic++;
161 #if REPORT_ID
162   if (ic % REP_FREQ == 0)
163     fprintf (stderr, "I");
164 #endif
165   stored_bytes += ntohl (value->size);
166   stored_ops++;
167   stored_entries++;
168   GNUNET_free (value);
169   return GNUNET_OK;
170 }
171
172 static int
173 iterateDelete (const GNUNET_HashCode * key,
174                const GNUNET_DatastoreValue * val, void *cls,
175                unsigned long long uid)
176 {
177   GNUNET_SQstore_ServiceAPI *api = cls;
178   static int dc;
179
180   if (api->getSize () < MAX_SIZE)
181     return GNUNET_SYSERR;
182   if (GNUNET_shutdown_test () == GNUNET_YES)
183     return GNUNET_SYSERR;
184   dc++;
185 #if REPORT_ID
186   if (dc % REP_FREQ == 0)
187     fprintf (stderr, "D");
188 #endif
189   stored_bytes -= ntohl (val->size);
190   stored_entries--;
191   return GNUNET_NO;
192 }
193
194 /**
195  * Add testcode here!
196  */
197 static int
198 test (GNUNET_SQstore_ServiceAPI * api)
199 {
200   int i;
201   int j;
202   unsigned long long size;
203   int have_file;
204   struct stat sbuf;
205
206   have_file = 0 == stat (DB_NAME, &sbuf);
207
208   for (i = 0; i < ITERATIONS; i++)
209     {
210 #if REPORT_ID
211       fprintf (stderr, ".");
212 #endif
213       /* insert data equivalent to 1/10th of MAX_SIZE */
214       for (j = 0; j < PUT_10; j++)
215         {
216           ASSERT (GNUNET_OK == putValue (api, j, i));
217           if (GNUNET_shutdown_test () == GNUNET_YES)
218             break;
219         }
220
221       /* trim down below MAX_SIZE again */
222       if ((i % 2) == 0)
223         api->iterateLowPriority (0, &iterateDelete, api);
224       else
225         api->iterateExpirationTime (0, &iterateDelete, api);
226
227       size = 0;
228       if (have_file)
229         GNUNET_disk_file_size (NULL, DB_NAME, &size, GNUNET_NO);
230       printf (
231 #if REPORT_ID
232                "\n"
233 #endif
234                "Useful %llu, API %llu, disk %llu (%.2f%%) / %lluk ops / %llu ops/s\n", stored_bytes / 1024,     /* used size in k */
235                api->getSize () / 1024,  /* API-reported size in k */
236                size / 1024,     /* disk size in kb */
237                (100.0 * size / stored_bytes) - 100,     /* overhead */
238                (stored_ops * 2 - stored_entries) / 1024,        /* total operations (in k) */
239                1000 * (stored_ops * 2 - stored_entries) / (1 + GNUNET_get_time () - start_time));       /* operations per second */
240       if (GNUNET_shutdown_test () == GNUNET_YES)
241         break;
242     }
243   api->drop ();
244   return GNUNET_OK;
245
246 FAILURE:
247   api->drop ();
248   return GNUNET_SYSERR;
249 }
250
251 int
252 main (int argc, char *argv[])
253 {
254   GNUNET_SQstore_ServiceAPI *api;
255   int ok;
256   struct GNUNET_GC_Configuration *cfg;
257   struct GNUNET_CronManager *cron;
258
259   cfg = GNUNET_GC_create ();
260   if (-1 == GNUNET_GC_parse_configuration (cfg, "check.conf"))
261     {
262       GNUNET_GC_free (cfg);
263       return -1;
264     }
265   cron = GNUNET_cron_create (NULL);
266   GNUNET_CORE_init (NULL, cfg, cron, NULL);
267   api = GNUNET_CORE_request_service ("sqstore");
268   if (api != NULL)
269     {
270       start_time = GNUNET_get_time ();
271       ok = test (api);
272       GNUNET_CORE_release_service (api);
273     }
274   else
275     ok = GNUNET_SYSERR;
276   GNUNET_CORE_done ();
277   GNUNET_cron_destroy (cron);
278   GNUNET_GC_free (cfg);
279   if (ok == GNUNET_SYSERR)
280     return 1;
281   return 0;
282 }
283
284 /* end of mysqltest2.c */