b88dbc3fcc2852041e5ea6dc056ed1480decb904
[oweals/gnunet.git] / src / nse / gnunet-nse-profiler.c
1 /*
2      This file is part of GNUnet.
3      (C) 2011 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
46 struct StatsContext
47 {
48   unsigned long long total_nse_bytes;
49 };
50
51
52 static struct NSEPeer *peer_head;
53
54 static struct NSEPeer *peer_tail;
55
56 /**
57  * How long until we give up on connecting the peers?
58  */
59 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1500)
60
61 static int ok;
62
63 /**
64  * Be verbose
65  */
66 static int verbose;
67
68 /**
69  * Total number of peers in the test.
70  */
71 static unsigned long long num_peers;
72
73 /**
74  * Global configuration file
75  */
76 static struct GNUNET_CONFIGURATION_Handle *testing_cfg;
77
78 /**
79  * Total number of currently running peers.
80  */
81 static unsigned long long peers_running;
82
83 /**
84  * Current round we are in.
85  */
86 static unsigned long long current_round;
87
88 /**
89  * Peers desired in the next round.
90  */
91 static unsigned long long peers_next_round;
92
93 /**
94  * Maximum number of connections to NSE services.
95  */
96 static unsigned long long connection_limit;
97
98 /**
99  * Total number of connections in the whole network.
100  */
101 static unsigned int total_connections;
102
103 /**
104  * The currently running peer group.
105  */
106 static struct GNUNET_TESTING_PeerGroup *pg;
107
108 /**
109  * File to report results to.
110  */
111 static struct GNUNET_DISK_FileHandle *output_file;
112
113 /**
114  * File to log connection info, statistics to.
115  */
116 static struct GNUNET_DISK_FileHandle *data_file;
117
118 /**
119  * How many data points to capture before triggering next round?
120  */
121 static struct GNUNET_TIME_Relative wait_time;
122
123 /**
124  * Task called to disconnect peers.
125  */
126 static GNUNET_SCHEDULER_TaskIdentifier disconnect_task;
127
128 /**
129  * Task called to shutdown test.
130  */
131 static GNUNET_SCHEDULER_TaskIdentifier shutdown_handle;
132
133 /**
134  * Task used to churn the network.
135  */
136 static GNUNET_SCHEDULER_TaskIdentifier churn_task;
137
138 static char *topology_file;
139
140 static char *data_filename;
141
142 static uint64_t clock_skew;
143
144 /**
145  * Check whether peers successfully shut down.
146  */
147 static void
148 shutdown_callback (void *cls, const char *emsg)
149 {
150   if (emsg != NULL)
151   {
152 #if VERBOSE
153     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Shutdown of peers failed!\n");
154 #endif
155     if (ok == 0)
156       ok = 666;
157   }
158   else
159   {
160 #if VERBOSE
161     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "All peers successfully shut down!\n");
162 #endif
163     ok = 0;
164   }
165 }
166
167
168 static void
169 shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
170 {
171   struct NSEPeer *pos;
172
173 #if VERBOSE
174   fprintf (stderr, "Ending test.\n");
175 #endif
176
177   if (disconnect_task != GNUNET_SCHEDULER_NO_TASK)
178   {
179     GNUNET_SCHEDULER_cancel (disconnect_task);
180     disconnect_task = GNUNET_SCHEDULER_NO_TASK;
181   }
182   while (NULL != (pos = peer_head))
183   {
184     if (pos->nse_handle != NULL)
185       GNUNET_NSE_disconnect (pos->nse_handle);
186     GNUNET_CONTAINER_DLL_remove (peer_head, peer_tail, pos);
187     GNUNET_free (pos);
188   }
189
190   if (data_file != NULL)
191     GNUNET_DISK_file_close (data_file);
192   GNUNET_TESTING_daemons_stop (pg, TIMEOUT, &shutdown_callback, NULL);
193 }
194
195
196 /**
197  * Callback to call when network size estimate is updated.
198  *
199  * @param cls closure
200  * @param timestamp server timestamp
201  * @param estimate the value of the current network size estimate
202  * @param std_dev standard deviation (rounded down to nearest integer)
203  *                of the size estimation values seen
204  *
205  */
206 static void
207 handle_estimate (void *cls, struct GNUNET_TIME_Absolute timestamp,
208                  double estimate, double std_dev)
209 {
210   struct NSEPeer *peer = cls;
211   char *output_buffer;
212   size_t size;
213
214   if (output_file != NULL)
215   {
216     size = GNUNET_asprintf (&output_buffer,
217                             "%s %llu %llu %f %f %f\n",
218                             GNUNET_i2s (&peer->daemon->id),
219                             peers_running,
220                             timestamp.abs_value,
221                             GNUNET_NSE_log_estimate_to_n (estimate),
222                             estimate, std_dev);
223     if (size != GNUNET_DISK_file_write (output_file, output_buffer, size))
224       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unable to write to file!\n");
225     GNUNET_free (output_buffer);
226   }
227   else
228     fprintf (stderr,
229              "Received network size estimate from peer %s. Size: %f std.dev. %f\n",
230              GNUNET_i2s (&peer->daemon->id), estimate, std_dev);
231
232 }
233
234
235 static void
236 connect_nse_service (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
237 {
238   struct NSEPeer *current_peer;
239   unsigned int i;
240
241 #if VERBOSE
242   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting to nse service of peers\n");
243 #endif
244   for (i = 0; i < num_peers; i++)
245   {
246     if ((connection_limit > 0) && (i % (num_peers / connection_limit) != 0))
247       continue;
248 #if VERBOSE
249     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
250                 "nse-profiler: connecting to nse service of peer %d\n", i);
251 #endif
252     current_peer = GNUNET_malloc (sizeof (struct NSEPeer));
253     current_peer->daemon = GNUNET_TESTING_daemon_get (pg, i);
254     if (GNUNET_YES ==
255         GNUNET_TESTING_daemon_running (GNUNET_TESTING_daemon_get (pg, i)))
256     {
257       current_peer->nse_handle = GNUNET_NSE_connect (current_peer->daemon->cfg,
258                                                      &handle_estimate,
259                                                      current_peer);
260       GNUNET_assert (current_peer->nse_handle != NULL);
261     }
262     GNUNET_CONTAINER_DLL_insert (peer_head, peer_tail, current_peer);
263   }
264 }
265
266
267 static void
268 churn_peers (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
269
270
271 /**
272  * Continuation called by the "get_all" and "get" functions.
273  *
274  * @param cls struct StatsContext
275  * @param success GNUNET_OK if statistics were
276  *        successfully obtained, GNUNET_SYSERR if not.
277  */
278 static void
279 stats_finished_callback (void *cls, int success)
280 {
281   struct StatsContext *stats_context = cls;
282   char *buf;
283   int buf_len;
284
285   if ((GNUNET_OK == success) && (data_file != NULL))
286   {
287     /* Stats lookup successful, write out data */
288     buf = NULL;
289     buf_len = GNUNET_asprintf (&buf,
290                                "TOTAL_NSE_BYTES: %u\n",
291                                stats_context->total_nse_bytes);
292     if (buf_len > 0)
293     {
294       GNUNET_DISK_file_write (data_file, buf, buf_len);
295     }
296     GNUNET_free_non_null (buf);
297   }
298
299   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == shutdown_handle);
300   shutdown_handle = GNUNET_SCHEDULER_add_now (&shutdown_task, NULL);
301   GNUNET_free (stats_context);
302 }
303
304
305 /**
306  * Callback function to process statistic values.
307  *
308  * @param cls struct StatsContext
309  * @param peer the peer the statistics belong to
310  * @param subsystem name of subsystem that created the statistic
311  * @param name the name of the datum
312  * @param value the current value
313  * @param is_persistent GNUNET_YES if the value is persistent, GNUNET_NO if not
314  * @return GNUNET_OK to continue, GNUNET_SYSERR to abort iteration
315  */
316 static int
317 statistics_iterator (void *cls,
318                      const struct GNUNET_PeerIdentity *peer,
319                      const char *subsystem,
320                      const char *name, uint64_t value, int is_persistent)
321 {
322   struct StatsContext *stats_context = cls;
323
324   if ((0 == strstr (subsystem, "nse")) &&
325       (0 == strstr (name, "# flood messages received")))
326     stats_context->total_nse_bytes += value;
327   return GNUNET_OK;
328 }
329
330
331 static void
332 disconnect_nse_peers (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
333 {
334   struct NSEPeer *pos;
335   char *buf;
336   struct StatsContext *stats_context;
337
338   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "disconnecting nse service of peers\n");
339   disconnect_task = GNUNET_SCHEDULER_NO_TASK;
340   pos = peer_head;
341   while (NULL != (pos = peer_head))
342   {
343     if (pos->nse_handle != NULL)
344     {
345       GNUNET_NSE_disconnect (pos->nse_handle);
346       pos->nse_handle = NULL;
347     }
348     GNUNET_CONTAINER_DLL_remove (peer_head, peer_tail, pos);
349     GNUNET_free (pos);
350   }
351
352   GNUNET_asprintf (&buf, "round%llu", current_round);
353   if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_number (testing_cfg,
354                                                           "nse-profiler",
355                                                           buf,
356                                                           &peers_next_round))
357   {
358     current_round++;
359     GNUNET_assert (churn_task == GNUNET_SCHEDULER_NO_TASK);
360     churn_task = GNUNET_SCHEDULER_add_now (&churn_peers, NULL);
361   }
362   else                          /* No more rounds, let's shut it down! */
363   {
364     stats_context = GNUNET_malloc (sizeof (struct StatsContext));
365     GNUNET_SCHEDULER_cancel (shutdown_handle);
366     shutdown_handle = GNUNET_SCHEDULER_NO_TASK;
367     GNUNET_TESTING_get_statistics (pg,
368                                    &stats_finished_callback,
369                                    &statistics_iterator, stats_context);
370   }
371   GNUNET_free (buf);
372 }
373
374
375 /**
376  * FIXME.
377  *
378  * @param cls unused
379  * @param emsg NULL on success
380  */
381 static void
382 topology_output_callback (void *cls, const char *emsg)
383 {
384   disconnect_task = GNUNET_SCHEDULER_add_delayed (wait_time,
385                                                   &disconnect_nse_peers, NULL);
386   GNUNET_SCHEDULER_add_now (&connect_nse_service, NULL);
387 }
388
389
390 /**
391  * FIXME.
392  *
393  * @param cls closure
394  * @param emsg NULL on success
395  */
396 static void
397 churn_callback (void *cls, const char *emsg)
398 {
399   char *temp_output_file;
400
401   if (emsg == NULL)             /* Everything is okay! */
402   {
403     peers_running = peers_next_round;
404     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
405                 "Round %llu, churn finished successfully.\n", current_round);
406     GNUNET_assert (disconnect_task == GNUNET_SCHEDULER_NO_TASK);
407     GNUNET_asprintf (&temp_output_file,
408                      "%s_%llu.dot", topology_file, current_round);
409     GNUNET_TESTING_peergroup_topology_to_file (pg,
410                                                temp_output_file,
411                                                &topology_output_callback, NULL);
412     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
413                 "Writing topology to file %s\n", temp_output_file);
414     GNUNET_free (temp_output_file);
415   }
416   else
417   {
418     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
419                 "Round %llu, churn FAILED!!\n", current_round);
420     GNUNET_SCHEDULER_cancel (shutdown_handle);
421     shutdown_handle = GNUNET_SCHEDULER_add_now (&shutdown_task, NULL);
422   }
423 }
424
425
426 static void
427 churn_peers (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
428 {
429   /* peers_running = GNUNET_TESTING_daemons_running(pg); */
430   churn_task = GNUNET_SCHEDULER_NO_TASK;
431   if (peers_next_round == peers_running)
432   {
433     /* Nothing to do... */
434     GNUNET_SCHEDULER_add_now (&connect_nse_service, NULL);
435     GNUNET_assert (disconnect_task == GNUNET_SCHEDULER_NO_TASK);
436     disconnect_task = GNUNET_SCHEDULER_add_delayed (wait_time,
437                                                     &disconnect_nse_peers,
438                                                     NULL);
439     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Round %lu, doing nothing!\n",
440                 current_round);
441   }
442   else
443   {
444     if (peers_next_round > num_peers)
445     {
446       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
447                   "Asked to turn on more peers than we have!!\n");
448       GNUNET_SCHEDULER_cancel (shutdown_handle);
449       GNUNET_SCHEDULER_add_now (&shutdown_task, NULL);
450     }
451     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
452                 "Round %llu, turning off %llu peers, turning on %llu peers!\n",
453                 current_round,
454                 (peers_running > peers_next_round)
455                 ? peers_running - peers_next_round
456                 : 0,
457                 (peers_next_round > peers_running)
458                 ? peers_next_round - peers_running : 0);
459     GNUNET_TESTING_daemons_churn (pg, "nse",
460                                   (peers_running > peers_next_round)
461                                   ? peers_running - peers_next_round
462                                   : 0,
463                                   (peers_next_round > peers_running)
464                                   ? peers_next_round - peers_running
465                                   : 0, wait_time, &churn_callback, NULL);
466   }
467 }
468
469
470 static void
471 nse_started_cb (void *cls, const char *emsg)
472 {
473   GNUNET_SCHEDULER_add_now (&connect_nse_service, NULL);
474   disconnect_task =
475       GNUNET_SCHEDULER_add_delayed (wait_time, &disconnect_nse_peers, NULL);
476 }
477
478
479 static void
480 my_cb (void *cls, const char *emsg)
481 {
482   char *buf;
483   int buf_len;
484
485   if (emsg != NULL)
486   {
487     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
488                 "Peergroup callback called with error, aborting test!\n");
489     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Error from testing: `%s'\n");
490     ok = 1;
491     GNUNET_TESTING_daemons_stop (pg, TIMEOUT, &shutdown_callback, NULL);
492     return;
493   }
494 #if VERBOSE
495   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
496               "Peer Group started successfully, connecting to NSE service for each peer!\n");
497 #endif
498   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
499               "Have %u connections\n", total_connections);
500   if (data_file != NULL)
501   {
502     buf = NULL;
503     buf_len = GNUNET_asprintf (&buf, "CONNECTIONS_0: %u\n", total_connections);
504     if (buf_len > 0)
505       GNUNET_DISK_file_write (data_file, buf, buf_len);
506     GNUNET_free (buf);
507   }
508   peers_running = GNUNET_TESTING_daemons_running (pg);
509   GNUNET_TESTING_daemons_start_service (pg,
510                                         "nse",
511                                         wait_time, &nse_started_cb, NULL);
512
513 }
514
515
516 /**
517  * Function that will be called whenever two daemons are connected by
518  * the testing library.
519  *
520  * @param cls closure
521  * @param first peer id for first daemon
522  * @param second peer id for the second daemon
523  * @param distance distance between the connected peers
524  * @param first_cfg config for the first daemon
525  * @param second_cfg config for the second daemon
526  * @param first_daemon handle for the first daemon
527  * @param second_daemon handle for the second daemon
528  * @param emsg error message (NULL on success)
529  */
530 static void
531 connect_cb (void *cls,
532             const struct GNUNET_PeerIdentity *first,
533             const struct GNUNET_PeerIdentity *second,
534             uint32_t distance,
535             const struct GNUNET_CONFIGURATION_Handle *first_cfg,
536             const struct GNUNET_CONFIGURATION_Handle *second_cfg,
537             struct GNUNET_TESTING_Daemon *first_daemon,
538             struct GNUNET_TESTING_Daemon *second_daemon, const char *emsg)
539 {
540   if (emsg == NULL)
541     total_connections++;
542 }
543
544
545 static void
546 run (void *cls,
547      char *const *args,
548      const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *cfg)
549 {
550   char *temp_str;
551   unsigned long long temp_wait;
552   struct GNUNET_TESTING_Host *hosts;
553
554   ok = 1;
555   testing_cfg = GNUNET_CONFIGURATION_create ();
556 #if VERBOSE
557   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Starting daemons.\n");
558   GNUNET_CONFIGURATION_set_value_string (testing_cfg,
559                                          "testing", "use_progressbars", "YES");
560 #endif
561   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (testing_cfg,
562                                                           "testing",
563                                                           "num_peers",
564                                                           &num_peers))
565   {
566     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
567                 "Option TESTING:NUM_PEERS is required!\n");
568     return;
569   }
570
571   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (testing_cfg,
572                                                           "nse-profiler",
573                                                           "wait_time",
574                                                           &temp_wait))
575   {
576     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
577                 "Option nse-profiler:wait_time is required!\n");
578     return;
579   }
580
581   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (testing_cfg,
582                                                           "nse-profiler",
583                                                           "connection_limit",
584                                                           &connection_limit))
585   {
586     connection_limit = 0;
587   }
588
589   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (testing_cfg,
590                                                           "nse-profiler",
591                                                           "topology_output_file",
592                                                           &topology_file))
593   {
594     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
595                 "Option nse-profiler:topology_output_file is required!\n");
596     return;
597   }
598
599   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (testing_cfg,
600                                                           "nse-profiler",
601                                                           "data_output_file",
602                                                           &data_filename))
603   {
604     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
605                 "Option nse-profiler:data_output_file is required!\n");
606     return;
607   }
608
609   if (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (testing_cfg,
610                                                           "nse-profiler",
611                                                           "skew_clock"))
612   {
613     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Setting our clock as skewed...\n");
614     clock_skew = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
615                                            GNUNET_TIME_UNIT_MINUTES.rel_value);
616   }
617
618
619   data_file = GNUNET_DISK_file_open (data_filename,
620                                      GNUNET_DISK_OPEN_READWRITE
621                                      | GNUNET_DISK_OPEN_CREATE,
622                                      GNUNET_DISK_PERM_USER_READ |
623                                      GNUNET_DISK_PERM_USER_WRITE);
624   if (data_file == NULL)
625     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
626                 "Failed to open %s for output!\n", data_filename);
627   GNUNET_free (data_filename);
628
629   wait_time =
630       GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, temp_wait);
631
632   if (GNUNET_YES == GNUNET_CONFIGURATION_get_value_string (cfg,
633                                                            "nse-profiler",
634                                                            "output_file",
635                                                            &temp_str))
636   {
637     output_file = GNUNET_DISK_file_open (temp_str, GNUNET_DISK_OPEN_READWRITE
638                                          | GNUNET_DISK_OPEN_CREATE,
639                                          GNUNET_DISK_PERM_USER_READ |
640                                          GNUNET_DISK_PERM_USER_WRITE);
641     if (output_file == NULL)
642       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
643                   "Failed to open %s for output!\n", temp_str);
644   }
645   GNUNET_free_non_null (temp_str);
646
647   hosts = GNUNET_TESTING_hosts_load (testing_cfg);
648
649   pg = GNUNET_TESTING_peergroup_start (testing_cfg,
650                                        num_peers,
651                                        TIMEOUT,
652                                        &connect_cb, &my_cb, NULL, hosts);
653   GNUNET_assert (pg != NULL);
654   shutdown_handle =
655       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_get_forever (),
656                                     &shutdown_task, NULL);
657 }
658
659
660
661 /**
662  * nse-profiler command line options
663  */
664 static struct GNUNET_GETOPT_CommandLineOption options[] = {
665   {'V', "verbose", NULL,
666    gettext_noop ("be verbose (print progress information)"),
667    0, &GNUNET_GETOPT_set_one, &verbose},
668   GNUNET_GETOPT_OPTION_END
669 };
670
671
672 int
673 main (int argc, char *argv[])
674 {
675   GNUNET_log_setup ("nse-profiler",
676 #if VERBOSE
677                     "DEBUG",
678 #else
679                     "WARNING",
680 #endif
681                     NULL);
682   GNUNET_PROGRAM_run (argc,
683                       argv, "nse-profiler",
684                       gettext_noop
685                       ("Measure quality and performance of the NSE service."),
686                       options, &run, NULL);
687 #if REMOVE_DIR
688   GNUNET_DISK_directory_remove ("/tmp/nse-profiler");
689 #endif
690   return ok;
691 }
692
693 /* end of nse-profiler.c */