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