changes
[oweals/gnunet.git] / src / nse / nse-profiler.c
1 /*
2      This file is part of GNUnet.
3      (C) 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 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 nse/nse-profiler.c
22  *
23  * @brief Profiling driver for the network size estimation service.
24  *        Generally, the profiler starts a given number of peers,
25  *        then churns some off, waits a certain amount of time, then
26  *        churns again, and repeats.
27  */
28 #include "platform.h"
29 #include "gnunet_testing_lib.h"
30 #include "gnunet_nse_service.h"
31
32 #define VERBOSE GNUNET_NO
33
34 struct NSEPeer
35 {
36   struct NSEPeer *prev;
37
38   struct NSEPeer *next;
39
40   struct GNUNET_TESTING_Daemon *daemon;
41
42   struct GNUNET_NSE_Handle *nse_handle;
43 };
44
45 struct NSEPeer *peer_head;
46
47 struct NSEPeer *peer_tail;
48
49 /**
50  * How long until we give up on connecting the peers?
51  */
52 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1500)
53
54 static int ok;
55
56 /**
57  * Be verbose
58  */
59 static int verbose;
60
61 /**
62  * Total number of peers in the test.
63  */
64 static unsigned long long num_peers;
65
66 /**
67  * Global configuration file
68  */
69 struct GNUNET_CONFIGURATION_Handle *testing_cfg;
70
71 /**
72  * Total number of currently running peers.
73  */
74 static unsigned long long peers_running;
75
76 /**
77  * Current round we are in.
78  */
79 static unsigned long long current_round;
80
81 /**
82  * Peers desired in the next round.
83  */
84 static unsigned long long peers_next_round;
85
86 /**
87  * Total number of connections in the whole network.
88  */
89 static unsigned int total_connections;
90
91 /**
92  * The currently running peer group.
93  */
94 static struct GNUNET_TESTING_PeerGroup *pg;
95
96 /**
97  * File to report results to.
98  */
99 static struct GNUNET_DISK_FileHandle *output_file;
100
101 /**
102  * How many data points to capture before triggering next round?
103  */
104 static struct GNUNET_TIME_Relative wait_time;
105
106 /**
107  * Task called to disconnect peers.
108  */
109 static GNUNET_SCHEDULER_TaskIdentifier disconnect_task;
110
111 /**
112  * Task called to shutdown test.
113  */
114 static GNUNET_SCHEDULER_TaskIdentifier shutdown_handle;
115
116 /**
117  * Task used to churn the network.
118  */
119 static GNUNET_SCHEDULER_TaskIdentifier churn_task;
120
121 char *topology_file;
122
123 /**
124  * Check whether peers successfully shut down.
125  */
126 void
127 shutdown_callback (void *cls, const char *emsg)
128 {
129   if (emsg != NULL)
130     {
131 #if VERBOSE
132       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Shutdown of peers failed!\n");
133 #endif
134       if (ok == 0)
135         ok = 666;
136     }
137   else
138     {
139 #if VERBOSE
140       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
141                   "All peers successfully shut down!\n");
142 #endif
143       ok = 0;
144     }
145 }
146
147 static void
148 shutdown_task (void *cls,
149                const struct GNUNET_SCHEDULER_TaskContext *tc)
150 {
151   struct NSEPeer *pos;
152 #if VERBOSE
153   fprintf(stderr, "Ending test.\n");
154 #endif
155
156   if (disconnect_task != GNUNET_SCHEDULER_NO_TASK)
157     {
158       GNUNET_SCHEDULER_cancel(disconnect_task);
159       disconnect_task = GNUNET_SCHEDULER_NO_TASK;
160     }
161   while (NULL != (pos = peer_head))
162     {
163       if (pos->nse_handle != NULL)
164         GNUNET_NSE_disconnect(pos->nse_handle);
165       GNUNET_CONTAINER_DLL_remove(peer_head, peer_tail, pos);
166       GNUNET_free(pos);
167     }
168
169   GNUNET_TESTING_daemons_stop (pg, TIMEOUT, &shutdown_callback, NULL);
170 }
171
172 /**
173  * Callback to call when network size estimate is updated.
174  *
175  * @param cls closure
176  * @param estimate the value of the current network size estimate
177  * @param std_dev standard deviation (rounded down to nearest integer)
178  *                of the size estimation values seen
179  *
180  */
181 static void
182 handle_estimate (void *cls, double estimate, double std_dev)
183 {
184   struct NSEPeer *peer = cls;
185   char *output_buffer;
186   int size;
187   //fprintf(stderr, "Received network size estimate from peer %s. Size: %f std.dev. %f\n", GNUNET_i2s(&peer->daemon->id), estimate, std_dev);
188   if (output_file != NULL)
189     {
190       size = GNUNET_asprintf(&output_buffer, "%s %u %f %f\n", GNUNET_i2s(&peer->daemon->id), peers_running, estimate, std_dev);
191       if (size != GNUNET_DISK_file_write(output_file, output_buffer, size))
192         GNUNET_log(GNUNET_ERROR_TYPE_WARNING, "%s: Unable to write to file!\n", "nse-profiler");
193     }
194   else
195     fprintf(stderr, "Received network size estimate from peer %s. Size: %f std.dev. %f\n", GNUNET_i2s(&peer->daemon->id), estimate, std_dev);
196
197 }
198
199
200 static void
201 connect_nse_service (void *cls,
202                      const struct GNUNET_SCHEDULER_TaskContext *tc)
203 {
204   struct NSEPeer *current_peer;
205   unsigned int i;
206 #if VERBOSE
207   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "TEST_NSE_MULTIPEER: connecting to nse service of peers\n");
208 #endif
209   GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "TEST_NSE_MULTIPEER: connecting to nse service of peers\n");
210   for (i = 0; i < num_peers; i++)
211     {
212       current_peer = GNUNET_malloc(sizeof(struct NSEPeer));
213       current_peer->daemon = GNUNET_TESTING_daemon_get(pg, i);
214       if (GNUNET_YES == GNUNET_TESTING_daemon_running(GNUNET_TESTING_daemon_get(pg, i)))
215         {
216           current_peer->nse_handle = GNUNET_NSE_connect (current_peer->daemon->cfg, &handle_estimate, current_peer);
217           GNUNET_assert(current_peer->nse_handle != NULL);
218         }
219       GNUNET_CONTAINER_DLL_insert (peer_head, peer_tail, current_peer);
220     }
221 }
222
223 static void
224 churn_peers (void *cls,
225              const struct GNUNET_SCHEDULER_TaskContext *tc);
226
227 static void
228 disconnect_nse_peers (void *cls,
229                       const struct GNUNET_SCHEDULER_TaskContext *tc)
230 {
231   struct NSEPeer *pos;
232   char *buf;
233   disconnect_task = GNUNET_SCHEDULER_NO_TASK;
234   pos = peer_head;
235
236   GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "TEST_NSE_MULTIPEER: disconnecting nse service of peers\n");
237   while (pos != NULL)
238     {
239       if (pos->nse_handle != NULL)
240         {
241           GNUNET_NSE_disconnect(pos->nse_handle);
242           pos->nse_handle = NULL;
243         }
244       pos = pos->next;
245     }
246
247   GNUNET_asprintf(&buf, "round%llu", current_round);
248   if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_number (testing_cfg, "nse-profiler", buf, &peers_next_round))
249     {
250       current_round++;
251       GNUNET_assert(churn_task == GNUNET_SCHEDULER_NO_TASK);
252       churn_task = GNUNET_SCHEDULER_add_now(&churn_peers, NULL);
253     }
254   else /* No more rounds, let's shut it down! */
255     {
256       GNUNET_SCHEDULER_cancel(shutdown_handle);
257       shutdown_handle = GNUNET_SCHEDULER_add_now(&shutdown_task, NULL);
258     }
259   GNUNET_free(buf);
260 }
261
262 /**
263  * Prototype of a function that will be called when a
264  * particular operation was completed the testing library.
265  *
266  * @param cls unused
267  * @param emsg NULL on success
268  */
269 void topology_output_callback (void *cls, const char *emsg)
270 {
271   disconnect_task = GNUNET_SCHEDULER_add_delayed(wait_time, &disconnect_nse_peers, NULL);
272   GNUNET_SCHEDULER_add_now(&connect_nse_service, NULL);
273 }
274
275
276 /**
277  * Prototype of a function that will be called when a
278  * particular operation was completed the testing library.
279  *
280  * @param cls closure
281  * @param emsg NULL on success
282  */
283 static void
284 churn_callback (void *cls, const char *emsg)
285 {
286   char *temp_output_file;
287
288   if (emsg == NULL) /* Everything is okay! */
289     {
290       peers_running = GNUNET_TESTING_daemons_running(pg);
291       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
292                   "Round %lu, churn finished successfully.\n", current_round);
293       GNUNET_assert(disconnect_task == GNUNET_SCHEDULER_NO_TASK);
294       GNUNET_asprintf(&temp_output_file, "%s%lu.dot", topology_file, current_round);
295       GNUNET_TESTING_peergroup_topology_to_file(pg,
296                                                 temp_output_file,
297                                                 &topology_output_callback,
298                                                 NULL);
299       GNUNET_log(GNUNET_ERROR_TYPE_WARNING, "Writing topology to file %s\n", temp_output_file);
300       GNUNET_free(temp_output_file);
301     }
302   else
303     {
304       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
305                   "Round %lu, churn FAILED!!\n", current_round);
306       GNUNET_SCHEDULER_cancel(shutdown_handle);
307       GNUNET_SCHEDULER_add_now(&shutdown_task, NULL);
308     }
309 }
310
311 static void
312 churn_peers (void *cls,
313                       const struct GNUNET_SCHEDULER_TaskContext *tc)
314 {
315   peers_running = GNUNET_TESTING_daemons_running(pg);
316   churn_task = GNUNET_SCHEDULER_NO_TASK;
317   if (peers_next_round == peers_running)
318     {
319       /* Nothing to do... */
320       GNUNET_SCHEDULER_add_now(&connect_nse_service, NULL);
321       GNUNET_assert(disconnect_task == GNUNET_SCHEDULER_NO_TASK);
322       disconnect_task = GNUNET_SCHEDULER_add_delayed(wait_time, &disconnect_nse_peers, NULL);
323       GNUNET_log(GNUNET_ERROR_TYPE_WARNING, "Round %lu, doing nothing!\n", current_round);
324     }
325   else
326     {
327       if (peers_next_round > num_peers)
328         {
329           GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Asked to turn on more peers than have!!\n");
330           GNUNET_SCHEDULER_cancel(shutdown_handle);
331           GNUNET_SCHEDULER_add_now(&shutdown_task, NULL);
332         }
333       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
334                   "Round %lu, turning off %lu peers, turning on %lu peers!\n",
335                   current_round,
336                   (peers_running > peers_next_round) ? peers_running
337                       - peers_next_round : 0,
338                   (peers_next_round > peers_running) ? peers_next_round
339                       - peers_running : 0);
340       GNUNET_TESTING_daemons_churn (pg,
341                                     (peers_running > peers_next_round) ? peers_running
342                                         - peers_next_round
343                                         : 0,
344                                     (peers_next_round > peers_running) ? peers_next_round
345                                         - peers_running
346                                         : 0, wait_time, &churn_callback,
347                                     NULL);
348     }
349 }
350
351
352 static void
353 my_cb (void *cls,
354        const char *emsg)
355 {
356   if (emsg != NULL)
357     {
358       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
359                   "Peergroup callback called with error, aborting test!\n");
360       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Error from testing: `%s'\n");
361       ok = 1;
362       GNUNET_TESTING_daemons_stop (pg, TIMEOUT, &shutdown_callback, NULL);
363       return;
364     }
365 #if VERBOSE
366   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
367               "Peer Group started successfully, connecting to NSE service for each peer!\n");
368 #endif
369   GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Have %u connections\n", total_connections);
370   peers_running = GNUNET_TESTING_daemons_running(pg);
371   GNUNET_SCHEDULER_add_now(&connect_nse_service, NULL);
372   disconnect_task = GNUNET_SCHEDULER_add_delayed(wait_time, &disconnect_nse_peers, NULL);
373 }
374
375 /**
376  * Prototype of a function that will be called whenever
377  * two daemons are connected by the testing library.
378  *
379  * @param cls closure
380  * @param first peer id for first daemon
381  * @param second peer id for the second daemon
382  * @param distance distance between the connected peers
383  * @param first_cfg config for the first daemon
384  * @param second_cfg config for the second daemon
385  * @param first_daemon handle for the first daemon
386  * @param second_daemon handle for the second daemon
387  * @param emsg error message (NULL on success)
388  */
389 void connect_cb (void *cls,
390                  const struct GNUNET_PeerIdentity *first,
391                  const struct GNUNET_PeerIdentity *second,
392                  uint32_t distance,
393                  const struct GNUNET_CONFIGURATION_Handle *first_cfg,
394                  const struct GNUNET_CONFIGURATION_Handle *second_cfg,
395                  struct GNUNET_TESTING_Daemon *first_daemon,
396                  struct GNUNET_TESTING_Daemon *second_daemon,
397                  const char *emsg)
398 {
399   char *second_id;
400
401   second_id = GNUNET_strdup(GNUNET_i2s(second));
402   if (emsg == NULL)
403     total_connections++;
404 }
405
406
407 static void
408 run (void *cls,
409      char *const *args,
410      const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *cfg)
411 {
412   char *temp_str;
413   unsigned long long temp_wait;
414   ok = 1;
415   testing_cfg = GNUNET_CONFIGURATION_create();
416   GNUNET_assert(GNUNET_OK == GNUNET_CONFIGURATION_load(testing_cfg, cfgfile));
417 #if VERBOSE
418   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Starting daemons.\n");
419   GNUNET_CONFIGURATION_set_value_string (testing_cfg, "testing",
420                                            "use_progressbars",
421                                            "YES");
422 #endif
423   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (testing_cfg, "testing", "num_peers", &num_peers))
424     {
425       GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Option TESTING:NUM_PEERS is required!\n");
426       return;
427     }
428
429   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (testing_cfg, "nse-profiler", "wait_time", &temp_wait))
430     {
431       GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Option nse-profiler:wait_time is required!\n");
432       return;
433     }
434
435   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (testing_cfg, "nse-profiler", "topology_output_file", &topology_file))
436     {
437       GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Option nse-profiler:topology_output_file is required!\n");
438       return;
439     }
440
441   wait_time = GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, temp_wait);
442
443   if (GNUNET_YES == GNUNET_CONFIGURATION_get_value_string(cfg, "nse-profiler", "output_file", &temp_str))
444     {
445       output_file = GNUNET_DISK_file_open (temp_str, GNUNET_DISK_OPEN_READWRITE
446                                                       | GNUNET_DISK_OPEN_CREATE,
447                                                       GNUNET_DISK_PERM_USER_READ |
448                                                       GNUNET_DISK_PERM_USER_WRITE);
449       if (output_file == NULL)
450         GNUNET_log(GNUNET_ERROR_TYPE_WARNING, "Failed to open %s for output!\n", temp_str);
451     }
452   GNUNET_free_non_null(temp_str);
453
454   pg = GNUNET_TESTING_peergroup_start(testing_cfg,
455                                       num_peers,
456                                       TIMEOUT,
457                                       &connect_cb,
458                                       &my_cb, NULL,
459                                       NULL);
460   GNUNET_assert (pg != NULL);
461   shutdown_handle = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_get_forever(), &shutdown_task, NULL);
462 }
463
464
465
466 /**
467  * nse-profiler command line options
468  */
469 static struct GNUNET_GETOPT_CommandLineOption options[] = {
470   {'V', "verbose", NULL,
471    gettext_noop ("be verbose (print progress information)"),
472    0, &GNUNET_GETOPT_set_one, &verbose},
473   GNUNET_GETOPT_OPTION_END
474 };
475
476 int
477 main (int argc, char *argv[])
478 {
479   int ret;
480
481   GNUNET_log_setup ("nse-profiler",
482 #if VERBOSE
483                     "DEBUG",
484 #else
485                     "WARNING",
486 #endif
487                     NULL);
488   ret = 1;
489   GNUNET_PROGRAM_run (argc,
490                       argv, "nse-profiler", gettext_noop
491                       ("Run a test of the NSE service."),
492                       options, &run, &ok);
493
494   GNUNET_DISK_directory_remove ("/tmp/nse-profiler");
495   return ret;
496 }
497
498 /* end of nse-profiler.c */