nse 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 /**
122  * Check whether peers successfully shut down.
123  */
124 void
125 shutdown_callback (void *cls, const char *emsg)
126 {
127   if (emsg != NULL)
128     {
129 #if VERBOSE
130       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Shutdown of peers failed!\n");
131 #endif
132       if (ok == 0)
133         ok = 666;
134     }
135   else
136     {
137 #if VERBOSE
138       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
139                   "All peers successfully shut down!\n");
140 #endif
141       ok = 0;
142     }
143 }
144
145 static void
146 shutdown_task (void *cls,
147                const struct GNUNET_SCHEDULER_TaskContext *tc)
148 {
149   struct NSEPeer *pos;
150 #if VERBOSE
151   fprintf(stderr, "Ending test.\n");
152 #endif
153
154   if (disconnect_task != GNUNET_SCHEDULER_NO_TASK)
155     {
156       GNUNET_SCHEDULER_cancel(disconnect_task);
157       disconnect_task = GNUNET_SCHEDULER_NO_TASK;
158     }
159   while (NULL != (pos = peer_head))
160     {
161       if (pos->nse_handle != NULL)
162         GNUNET_NSE_disconnect(pos->nse_handle);
163       GNUNET_CONTAINER_DLL_remove(peer_head, peer_tail, pos);
164       GNUNET_free(pos);
165     }
166
167   GNUNET_TESTING_daemons_stop (pg, TIMEOUT, &shutdown_callback, NULL);
168 }
169
170 /**
171  * Callback to call when network size estimate is updated.
172  *
173  * @param cls closure
174  * @param estimate the value of the current network size estimate
175  * @param std_dev standard deviation (rounded down to nearest integer)
176  *                of the size estimation values seen
177  *
178  */
179 static void
180 handle_estimate (void *cls, double estimate, double std_dev)
181 {
182   struct NSEPeer *peer = cls;
183   char *output_buffer;
184   int size;
185   //fprintf(stderr, "Received network size estimate from peer %s. Size: %f std.dev. %f\n", GNUNET_i2s(&peer->daemon->id), estimate, std_dev);
186   if (output_file != NULL)
187     {
188       size = GNUNET_asprintf(&output_buffer, "%s %u %f %f\n", GNUNET_i2s(&peer->daemon->id), peers_running, estimate, std_dev);
189       if (size != GNUNET_DISK_file_write(output_file, output_buffer, size))
190         GNUNET_log(GNUNET_ERROR_TYPE_WARNING, "%s: Unable to write to file!\n", "nse-profiler");
191     }
192   else
193     fprintf(stderr, "Received network size estimate from peer %s. Size: %f std.dev. %f\n", GNUNET_i2s(&peer->daemon->id), estimate, std_dev);
194
195 }
196
197
198 static void
199 connect_nse_service (void *cls,
200                      const struct GNUNET_SCHEDULER_TaskContext *tc)
201 {
202   struct NSEPeer *current_peer;
203   unsigned int i;
204 #if VERBOSE
205   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "TEST_NSE_MULTIPEER: connecting to nse service of peers\n");
206 #endif
207   GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "TEST_NSE_MULTIPEER: connecting to nse service of peers\n");
208   for (i = 0; i < num_peers; i++)
209     {
210       current_peer = GNUNET_malloc(sizeof(struct NSEPeer));
211       current_peer->daemon = GNUNET_TESTING_daemon_get(pg, i);
212       if (GNUNET_YES == GNUNET_TESTING_daemon_running(GNUNET_TESTING_daemon_get(pg, i)))
213         {
214           current_peer->nse_handle = GNUNET_NSE_connect (current_peer->daemon->cfg, &handle_estimate, current_peer);
215           GNUNET_assert(current_peer->nse_handle != NULL);
216         }
217       GNUNET_CONTAINER_DLL_insert (peer_head, peer_tail, current_peer);
218     }
219 }
220
221 static void
222 churn_peers (void *cls,
223              const struct GNUNET_SCHEDULER_TaskContext *tc);
224
225 static void
226 disconnect_nse_peers (void *cls,
227                       const struct GNUNET_SCHEDULER_TaskContext *tc)
228 {
229   struct NSEPeer *pos;
230   char *buf;
231   disconnect_task = GNUNET_SCHEDULER_NO_TASK;
232   pos = peer_head;
233
234   GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "TEST_NSE_MULTIPEER: disconnecting nse service of peers\n");
235   while (pos != NULL)
236     {
237       if (pos->nse_handle != NULL)
238         {
239           GNUNET_NSE_disconnect(pos->nse_handle);
240           pos->nse_handle = NULL;
241         }
242       pos = pos->next;
243     }
244
245   GNUNET_asprintf(&buf, "round%llu", current_round);
246   if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_number (testing_cfg, "nse-profiler", buf, &peers_next_round))
247     {
248       current_round++;
249       GNUNET_assert(churn_task == GNUNET_SCHEDULER_NO_TASK);
250       churn_task = GNUNET_SCHEDULER_add_now(&churn_peers, NULL);
251     }
252   else /* No more rounds, let's shut it down! */
253     {
254       GNUNET_SCHEDULER_cancel(shutdown_handle);
255       shutdown_handle = GNUNET_SCHEDULER_add_now(&shutdown_task, NULL);
256     }
257   GNUNET_free(buf);
258 }
259
260 /**
261  * Prototype of a function that will be called when a
262  * particular operation was completed the testing library.
263  *
264  * @param cls closure
265  * @param emsg NULL on success
266  */
267 static void
268 churn_callback (void *cls, const char *emsg)
269 {
270   if (emsg == NULL) /* Everything is okay! */
271     {
272       peers_running = GNUNET_TESTING_daemons_running(pg);
273       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
274                   "Round %lu, churn finished successfully.\n", current_round);
275       GNUNET_assert(disconnect_task == GNUNET_SCHEDULER_NO_TASK);
276       disconnect_task = GNUNET_SCHEDULER_add_delayed(wait_time, &disconnect_nse_peers, NULL);
277       GNUNET_SCHEDULER_add_now(&connect_nse_service, NULL);
278     }
279   else
280     {
281       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
282                   "Round %lu, churn FAILED!!\n", current_round);
283       GNUNET_SCHEDULER_cancel(shutdown_handle);
284       GNUNET_SCHEDULER_add_now(&shutdown_task, NULL);
285     }
286 }
287
288 static void
289 churn_peers (void *cls,
290                       const struct GNUNET_SCHEDULER_TaskContext *tc)
291 {
292   peers_running = GNUNET_TESTING_daemons_running(pg);
293   churn_task = GNUNET_SCHEDULER_NO_TASK;
294   if (peers_next_round == peers_running)
295     {
296       /* Nothing to do... */
297       GNUNET_SCHEDULER_add_now(&connect_nse_service, NULL);
298       GNUNET_assert(disconnect_task == GNUNET_SCHEDULER_NO_TASK);
299       disconnect_task = GNUNET_SCHEDULER_add_delayed(wait_time, &disconnect_nse_peers, NULL);
300       GNUNET_log(GNUNET_ERROR_TYPE_WARNING, "Round %lu, doing nothing!\n", current_round);
301     }
302   else
303     {
304       if (peers_next_round > num_peers)
305         {
306           GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Asked to turn on more peers than have!!\n");
307           GNUNET_SCHEDULER_cancel(shutdown_handle);
308           GNUNET_SCHEDULER_add_now(&shutdown_task, NULL);
309         }
310       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
311                   "Round %lu, turning off %lu peers, turning on %lu peers!\n",
312                   current_round,
313                   (peers_running > peers_next_round) ? peers_running
314                       - peers_next_round : 0,
315                   (peers_next_round > peers_running) ? peers_next_round
316                       - peers_running : 0);
317       GNUNET_TESTING_daemons_churn (pg,
318                                     (peers_running > peers_next_round) ? peers_running
319                                         - peers_next_round
320                                         : 0,
321                                     (peers_next_round > peers_running) ? peers_next_round
322                                         - peers_running
323                                         : 0, wait_time, &churn_callback,
324                                     NULL);
325     }
326 }
327
328
329
330 static void
331 my_cb (void *cls,
332        const char *emsg)
333 {
334   if (emsg != NULL)
335     {
336       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
337                   "Peergroup callback called with error, aborting test!\n");
338       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Error from testing: `%s'\n");
339       ok = 1;
340       GNUNET_TESTING_daemons_stop (pg, TIMEOUT, &shutdown_callback, NULL);
341       return;
342     }
343 #if VERBOSE
344   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
345               "Peer Group started successfully, connecting to NSE service for each peer!\n");
346 #endif
347   GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Have %u connections\n", total_connections);
348   peers_running = GNUNET_TESTING_daemons_running(pg);
349   GNUNET_SCHEDULER_add_now(&connect_nse_service, NULL);
350   disconnect_task = GNUNET_SCHEDULER_add_delayed(wait_time, &disconnect_nse_peers, NULL);
351 }
352
353 /**
354  * Prototype of a function that will be called whenever
355  * two daemons are connected by the testing library.
356  *
357  * @param cls closure
358  * @param first peer id for first daemon
359  * @param second peer id for the second daemon
360  * @param distance distance between the connected peers
361  * @param first_cfg config for the first daemon
362  * @param second_cfg config for the second daemon
363  * @param first_daemon handle for the first daemon
364  * @param second_daemon handle for the second daemon
365  * @param emsg error message (NULL on success)
366  */
367 void connect_cb (void *cls,
368                  const struct GNUNET_PeerIdentity *first,
369                  const struct GNUNET_PeerIdentity *second,
370                  uint32_t distance,
371                  const struct GNUNET_CONFIGURATION_Handle *first_cfg,
372                  const struct GNUNET_CONFIGURATION_Handle *second_cfg,
373                  struct GNUNET_TESTING_Daemon *first_daemon,
374                  struct GNUNET_TESTING_Daemon *second_daemon,
375                  const char *emsg)
376 {
377   char *second_id;
378
379   second_id = GNUNET_strdup(GNUNET_i2s(second));
380   if (emsg == NULL)
381     total_connections++;
382 }
383
384
385 static void
386 run (void *cls,
387      char *const *args,
388      const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *cfg)
389 {
390   char *temp_str;
391   unsigned long long temp_wait;
392   ok = 1;
393   testing_cfg = GNUNET_CONFIGURATION_create();
394   GNUNET_assert(GNUNET_OK == GNUNET_CONFIGURATION_load(testing_cfg, cfgfile));
395 #if VERBOSE
396   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Starting daemons.\n");
397   GNUNET_CONFIGURATION_set_value_string (testing_cfg, "testing",
398                                            "use_progressbars",
399                                            "YES");
400 #endif
401   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (testing_cfg, "testing", "num_peers", &num_peers))
402     {
403       GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Option TESTING:NUM_PEERS is required!\n");
404       return;
405     }
406
407   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (testing_cfg, "nse-profiler", "wait_time", &temp_wait))
408     {
409       GNUNET_log(GNUNET_ERROR_TYPE_ERROR, "Option nse-profiler:wait_time is required!\n");
410       return;
411     }
412   wait_time = GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, temp_wait);
413
414   if (GNUNET_YES == GNUNET_CONFIGURATION_get_value_string(cfg, "nse-profiler", "output_file", &temp_str))
415     {
416       output_file = GNUNET_DISK_file_open (temp_str, GNUNET_DISK_OPEN_READWRITE
417                                                                   | GNUNET_DISK_OPEN_CREATE,
418                                                                   GNUNET_DISK_PERM_USER_READ |
419                                                                   GNUNET_DISK_PERM_USER_WRITE);
420       if (output_file == NULL)
421         GNUNET_log(GNUNET_ERROR_TYPE_WARNING, "Failed to open %s for output!\n", temp_str);
422     }
423   GNUNET_free_non_null(temp_str);
424
425   pg = GNUNET_TESTING_peergroup_start(testing_cfg,
426                                       num_peers,
427                                       TIMEOUT,
428                                       &connect_cb,
429                                       &my_cb, NULL,
430                                       NULL);
431   GNUNET_assert (pg != NULL);
432   shutdown_handle = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_get_forever(), &shutdown_task, NULL);
433 }
434
435
436
437 /**
438  * nse-profiler command line options
439  */
440 static struct GNUNET_GETOPT_CommandLineOption options[] = {
441   {'V', "verbose", NULL,
442    gettext_noop ("be verbose (print progress information)"),
443    0, &GNUNET_GETOPT_set_one, &verbose},
444   GNUNET_GETOPT_OPTION_END
445 };
446
447 int
448 main (int argc, char *argv[])
449 {
450   int ret;
451
452   GNUNET_log_setup ("nse-profiler",
453 #if VERBOSE
454                     "DEBUG",
455 #else
456                     "WARNING",
457 #endif
458                     NULL);
459   ret = 1;
460   GNUNET_PROGRAM_run (argc,
461                       argv, "nse-profiler", gettext_noop
462                       ("Run a test of the NSE service."),
463                       options, &run, &ok);
464
465   GNUNET_DISK_directory_remove ("/tmp/nse-profiler");
466   return ret;
467 }
468
469 /* end of nse-profiler.c */