Implement early stopping.
[oweals/gnunet.git] / src / consensus / gnunet-consensus-profiler.c
1 /*
2       This file is part of GNUnet
3       Copyright (C) 2012 GNUnet e.V.
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., 51 Franklin Street, Fifth Floor,
18       Boston, MA 02110-1301, USA.
19  */
20
21 /**
22  * @file consensus/gnunet-consensus-profiler.c
23  * @brief profiling tool for gnunet-consensus
24  * @author Florian Dold
25  */
26 #include "platform.h"
27 #include "gnunet_util_lib.h"
28 #include "gnunet_time_lib.h"
29 #include "gnunet_consensus_service.h"
30 #include "gnunet_testbed_service.h"
31
32 static unsigned int num_peers = 2;
33
34 static unsigned int replication = 1;
35
36 static unsigned int num_values = 5;
37
38 static struct GNUNET_TIME_Relative conclude_timeout;
39
40 static struct GNUNET_TIME_Relative consensus_delay;
41
42 static struct GNUNET_CONSENSUS_Handle **consensus_handles;
43
44 static struct GNUNET_TESTBED_Operation **testbed_operations;
45
46 static unsigned int num_connected_handles;
47
48 static struct GNUNET_TESTBED_Peer **peers;
49
50 static struct GNUNET_PeerIdentity *peer_ids;
51
52 static unsigned int num_retrieved_peer_ids;
53
54 static struct GNUNET_HashCode session_id;
55
56 static unsigned int peers_done = 0;
57
58 static unsigned *results_for_peer;
59
60 /**
61  * The profiler will write statistics
62  * for all peers to the file with this name.
63  */
64 static char *statistics_filename;
65
66 /**
67  * The profiler will write statistics
68  * for all peers to this file.
69  */
70 static FILE *statistics_file;
71
72 static int verbose;
73
74 /**
75  * Start time for all consensuses.
76  */
77 static struct GNUNET_TIME_Absolute start;
78
79 /**
80  * Deadline for all consensuses.
81  */
82 static struct GNUNET_TIME_Absolute deadline;
83
84
85 /**
86  * Signature of the event handler function called by the
87  * respective event controller.
88  *
89  * @param cls closure
90  * @param event information about the event
91  */
92 static void
93 controller_cb (void *cls,
94                const struct GNUNET_TESTBED_EventInformation *event)
95 {
96   GNUNET_assert (0);
97 }
98
99
100 static void
101 statistics_done_db (void *cls,
102                     struct
103                     GNUNET_TESTBED_Operation
104                     *op,
105                     const char *emsg)
106 {
107   GNUNET_assert (NULL == emsg);
108   GNUNET_TESTBED_operation_done (op);
109   if (NULL != statistics_file)
110     fclose (statistics_file);
111   GNUNET_SCHEDULER_shutdown ();
112 }
113
114
115 /**
116  * Callback function to process statistic values from all peers.
117  *
118  * @param cls closure
119  * @param peer the peer the statistic belong to
120  * @param subsystem name of subsystem that created the statistic
121  * @param name the name of the datum
122  * @param value the current value
123  * @param is_persistent GNUNET_YES if the value is persistent, GNUNET_NO if not
124  * @return GNUNET_OK to continue, GNUNET_SYSERR to abort iteration
125  */
126 static int
127 statistics_cb (void *cls,
128                const struct GNUNET_TESTBED_Peer *peer,
129                const char *subsystem,
130                const char *name,
131                uint64_t value,
132                int is_persistent)
133 {
134   if (NULL != statistics_file)
135   {
136     fprintf (statistics_file, "P%u\t%s\t%s\t%lu\n", GNUNET_TESTBED_get_index (peer), subsystem, name, (unsigned long) value);
137   }
138   return GNUNET_OK;
139 }
140
141
142 static void
143 destroy (void *cls, const struct GNUNET_SCHEDULER_TaskContext *ctx)
144 {
145   struct GNUNET_CONSENSUS_Handle *consensus = cls;
146
147   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
148               "destroying consensus\n");
149   GNUNET_CONSENSUS_destroy (consensus);
150   peers_done++;
151   if (peers_done == num_peers)
152   {
153     unsigned int i;
154     for (i = 0; i < num_peers; i++)
155       GNUNET_TESTBED_operation_done (testbed_operations[i]);
156     for (i = 0; i < num_peers; i++)
157       printf ("P%u got %u of %u elements\n",
158               i,
159               results_for_peer[i],
160               num_values);
161     if (NULL != statistics_filename)
162       statistics_file = fopen (statistics_filename, "w");
163     GNUNET_TESTBED_get_statistics (num_peers, peers, NULL, NULL,
164                                    statistics_cb,
165                                    statistics_done_db,
166                                    NULL);
167   }
168 }
169
170
171 /**
172  * Called when a conclusion was successful.
173  *
174  * @param cls closure, the consensus handle
175  * @return #GNUNET_YES if more consensus groups should be offered,
176  *         #GNUNET_NO if not
177  */
178 static void
179 conclude_cb (void *cls)
180 {
181   struct GNUNET_CONSENSUS_Handle **chp = cls;
182
183   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
184               "consensus %d done\n",
185               chp - consensus_handles);
186   GNUNET_SCHEDULER_add_now (destroy, *chp);
187 }
188
189
190 static void
191 generate_indices (int *indices)
192 {
193   int j;
194   j = 0;
195   while (j < replication)
196   {
197     int n;
198     int k;
199     int repeat;
200     n = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, num_peers);
201     repeat = GNUNET_NO;
202     for (k = 0; k < j; k++)
203       if (indices[k] == n)
204       {
205         repeat = GNUNET_YES;
206         break;
207       }
208     if (GNUNET_NO == repeat)
209       indices[j++] = n;
210   }
211 }
212
213
214 static void
215 do_consensus ()
216 {
217   int unique_indices[replication];
218   unsigned int i;
219
220   for (i = 0; i < num_values; i++)
221   {
222     unsigned int j;
223     struct GNUNET_HashCode val;
224     struct GNUNET_SET_Element element;
225
226     generate_indices (unique_indices);
227     GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_WEAK, &val);
228
229     element.data = &val;
230     element.size = sizeof (val);
231     for (j = 0; j < replication; j++)
232     {
233       int cid;
234
235       cid = unique_indices[j];
236       GNUNET_CONSENSUS_insert (consensus_handles[cid],
237                                &element,
238                                NULL, NULL);
239     }
240   }
241
242   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
243               "all elements inserted, calling conclude\n");
244
245   for (i = 0; i < num_peers; i++)
246     GNUNET_CONSENSUS_conclude (consensus_handles[i],
247                                conclude_cb, &consensus_handles[i]);
248 }
249
250
251 /**
252  * Callback to be called when a service connect operation is completed
253  *
254  * @param cls the callback closure from functions generating an operation
255  * @param op the operation that has been finished
256  * @param ca_result the service handle returned from GNUNET_TESTBED_ConnectAdapter()
257  * @param emsg error message in case the operation has failed; will be NULL if
258  *          operation has executed successfully.
259  */
260 static void
261 connect_complete (void *cls,
262                   struct GNUNET_TESTBED_Operation *op,
263                   void *ca_result,
264                   const char *emsg)
265 {
266
267   if (NULL != emsg)
268   {
269     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
270                 "testbed connect emsg: %s\n",
271                 emsg);
272     GNUNET_assert (0);
273   }
274
275   num_connected_handles++;
276
277   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
278               "connect complete\n");
279
280   if (num_connected_handles == num_peers)
281   {
282     do_consensus ();
283   }
284 }
285
286
287 static void
288 new_element_cb (void *cls,
289                 const struct GNUNET_SET_Element *element)
290 {
291   struct GNUNET_CONSENSUS_Handle **chp = cls;
292   int idx = chp - consensus_handles;
293
294   GNUNET_assert (NULL != cls);
295
296   results_for_peer[idx]++;
297
298   GNUNET_assert (sizeof (struct GNUNET_HashCode) == element->size);
299
300   if (GNUNET_YES == verbose)
301   {
302     printf ("P%d received %s\n",
303             idx,
304             GNUNET_h2s ((struct GNUNET_HashCode *) element->data));
305   }
306 }
307
308
309 /**
310  * Adapter function called to establish a connection to
311  * a service.
312  *
313  * @param cls closure
314  * @param cfg configuration of the peer to connect to; will be available until
315  *          GNUNET_TESTBED_operation_done() is called on the operation returned
316  *          from GNUNET_TESTBED_service_connect()
317  * @return service handle to return in 'op_result', NULL on error
318  */
319 static void *
320 connect_adapter (void *cls,
321                  const struct GNUNET_CONFIGURATION_Handle *cfg)
322 {
323   struct GNUNET_CONSENSUS_Handle **chp = cls;
324   struct GNUNET_CONSENSUS_Handle *consensus;
325   chp = (struct GNUNET_CONSENSUS_Handle **) cls;
326
327   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
328               "connect adapter, %d peers\n",
329               num_peers);
330   consensus = GNUNET_CONSENSUS_create (cfg,
331                                        num_peers, peer_ids,
332                                        &session_id,
333                                        start,
334                                        deadline,
335                                        &new_element_cb, chp);
336   *chp = (struct GNUNET_CONSENSUS_Handle *) consensus;
337   return consensus;
338 }
339
340
341 /**
342  * Adapter function called to destroy a connection to
343  * a service.
344  *
345  * @param cls closure
346  * @param op_result service handle returned from the connect adapter
347  */
348 static void
349 disconnect_adapter(void *cls, void *op_result)
350 {
351   /* FIXME: what to do here? */
352 }
353
354
355 /**
356  * Callback to be called when the requested peer information is available
357  *
358  * @param cb_cls the closure from GNUNET_TETSBED_peer_get_information()
359  * @param op the operation this callback corresponds to
360  * @param pinfo the result; will be NULL if the operation has failed
361  * @param emsg error message if the operation has failed; will be NULL if the
362  *          operation is successfull
363  */
364 static void
365 peer_info_cb (void *cb_cls,
366               struct GNUNET_TESTBED_Operation *op,
367               const struct GNUNET_TESTBED_PeerInformation *pinfo,
368               const char *emsg)
369 {
370   struct GNUNET_PeerIdentity *p;
371   int i;
372
373   GNUNET_assert (NULL == emsg);
374
375   p = (struct GNUNET_PeerIdentity *) cb_cls;
376
377   if (pinfo->pit == GNUNET_TESTBED_PIT_IDENTITY)
378   {
379     *p = *pinfo->result.id;
380     num_retrieved_peer_ids++;
381     if (num_retrieved_peer_ids == num_peers)
382       for (i = 0; i < num_peers; i++)
383         testbed_operations[i] =
384             GNUNET_TESTBED_service_connect (NULL, peers[i], "consensus", connect_complete, NULL,
385                                             connect_adapter, disconnect_adapter, &consensus_handles[i]);
386   }
387   else
388   {
389     GNUNET_assert (0);
390   }
391
392   GNUNET_TESTBED_operation_done (op);
393 }
394
395
396 /**
397  * Signature of a main function for a testcase.
398  *
399  * @param cls closure
400  * @param h the run handle
401  * @param num_peers number of peers in 'peers'
402  * @param started_peers handle to peers run in the testbed.  NULL upon timeout (see
403  *          GNUNET_TESTBED_test_run()).
404  * @param links_succeeded the number of overlay link connection attempts that
405  *          succeeded
406  * @param links_failed the number of overlay link connection attempts that
407  *          failed
408  */
409 static void
410 test_master (void *cls,
411              struct GNUNET_TESTBED_RunHandle *h,
412              unsigned int num_peers,
413              struct GNUNET_TESTBED_Peer **started_peers,
414              unsigned int links_succeeded,
415              unsigned int links_failed)
416 {
417   int i;
418
419   GNUNET_log_setup ("gnunet-consensus", "INFO", NULL);
420
421   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "test master\n");
422
423   peers = started_peers;
424
425   peer_ids = GNUNET_malloc (num_peers * sizeof (struct GNUNET_PeerIdentity));
426
427   results_for_peer = GNUNET_malloc (num_peers * sizeof (unsigned int));
428   consensus_handles = GNUNET_malloc (num_peers * sizeof (struct ConsensusHandle *));
429   testbed_operations = GNUNET_malloc (num_peers * sizeof (struct ConsensusHandle *));
430
431   for (i = 0; i < num_peers; i++)
432     GNUNET_TESTBED_peer_get_information (peers[i],
433                                          GNUNET_TESTBED_PIT_IDENTITY,
434                                          peer_info_cb,
435                                          &peer_ids[i]);
436 }
437
438
439 static void
440 run (void *cls, char *const *args, const char *cfgfile,
441      const struct GNUNET_CONFIGURATION_Handle *cfg)
442 {
443   static char *session_str = "gnunet-consensus/test";
444   char *topology;
445   int topology_cmp_result;
446
447   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "testbed", "OVERLAY_TOPOLOGY", &topology))
448   {
449     fprintf (stderr,
450              "'OVERLAY_TOPOLOGY' not found in 'testbed' config section, "
451              "seems like you passed the wrong configuration file\n");
452     return;
453   }
454
455   topology_cmp_result = strcasecmp (topology, "NONE");
456   GNUNET_free (topology);
457
458   if (0 == topology_cmp_result)
459   {
460     fprintf (stderr,
461              "'OVERLAY_TOPOLOGY' set to 'NONE', "
462              "seems like you passed the wrong configuration file\n");
463     return;
464   }
465
466   if (num_peers < replication)
467   {
468     fprintf (stderr, "k must be <=n\n");
469     return;
470   }
471
472   start = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), consensus_delay);
473   deadline = GNUNET_TIME_absolute_add (start, conclude_timeout);
474
475   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
476               "running gnunet-consensus\n");
477
478   GNUNET_CRYPTO_hash (session_str, strlen(session_str), &session_id);
479
480   (void) GNUNET_TESTBED_test_run ("gnunet-consensus",
481                                   cfgfile,
482                                   num_peers,
483                                   0,
484                                   controller_cb,
485                                   NULL,
486                                   test_master,
487                                   NULL);
488 }
489
490
491 int
492 main (int argc, char **argv)
493 {
494    static const struct GNUNET_GETOPT_CommandLineOption options[] = {
495       { 'n', "num-peers", NULL,
496         gettext_noop ("number of peers in consensus"),
497         GNUNET_YES, &GNUNET_GETOPT_set_uint, &num_peers },
498       { 'k', "value-replication", NULL,
499         gettext_noop ("how many peers (random selection without replacement) receive one value?"),
500         GNUNET_YES, &GNUNET_GETOPT_set_uint, &replication },
501       { 'x', "num-values", NULL,
502         gettext_noop ("number of values"),
503         GNUNET_YES, &GNUNET_GETOPT_set_uint, &num_values },
504       { 't', "timeout", NULL,
505         gettext_noop ("consensus timeout"),
506         GNUNET_YES, &GNUNET_GETOPT_set_relative_time, &conclude_timeout },
507       { 'd', "delay", NULL,
508         gettext_noop ("delay until consensus starts"),
509         GNUNET_YES, &GNUNET_GETOPT_set_relative_time, &consensus_delay },
510       { 's', "statistics", NULL,
511         gettext_noop ("write statistics to file"),
512         GNUNET_YES, &GNUNET_GETOPT_set_filename, &statistics_filename },
513       { 'V', "verbose", NULL,
514         gettext_noop ("be more verbose (print received values)"),
515         GNUNET_NO, &GNUNET_GETOPT_set_one, &verbose },
516       GNUNET_GETOPT_OPTION_END
517   };
518   conclude_timeout = GNUNET_TIME_UNIT_SECONDS;
519   GNUNET_PROGRAM_run2 (argc, argv, "gnunet-consensus-profiler",
520                       "help",
521                       options, &run, NULL, GNUNET_YES);
522   return 0;
523 }
524