7bab5121c54eb67899d1adf6d3f77550c2db72a2
[oweals/gnunet.git] / src / nse / gnunet-nse-profiler.c
1 /*
2      This file is part of GNUnet.
3      (C) 2011, 2012 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  * TODO:
29  * - need to enable user to specify topology options
30  * - need minor fix with "***"-host argument change in TESTBED (TBD there)
31  * - need to check for leaks (especially FD leaks)
32  * - need to TEST
33  */
34 #include "platform.h"
35 #include "gnunet_testbed_service.h"
36 #include "gnunet_nse_service.h"
37
38
39 /**
40  * Information we track for a peer in the testbed.
41  */
42 struct NSEPeer
43 {
44   /**
45    * Prev reference in DLL.
46    */
47   struct NSEPeer *prev;
48
49   /**
50    * Next reference in DLL.
51    */
52   struct NSEPeer *next;
53
54   /**
55    * Handle with testbed.
56    */
57   struct GNUNET_TESTBED_Peer *daemon;
58
59   /**
60    * Testbed operation to connect to NSE service.
61    */
62   struct GNUNET_TESTBED_Operation *nse_op;
63
64   /**
65    * Handle to statistics service of the peer.
66    */
67   struct GNUNET_STATISTICS_Handle *stats;
68
69   /**
70    * Testbed operation to connect to statistics service.
71    */
72   struct GNUNET_TESTBED_Operation *stats_op;
73   
74   /**
75    * Task scheduled to get statistics from this peer.
76    */
77   GNUNET_SCHEDULER_TaskIdentifier stats_task;
78 };
79
80
81 /**
82  * Context for the stats task?
83  */
84 struct StatsContext
85 {
86
87   /**
88    * How many messages have peers received during the test.
89    */
90   unsigned long long total_nse_received_messages;
91
92   /**
93    * How many messages have peers send during the test (should be == received).
94    */
95   unsigned long long total_nse_transmitted_messages;
96
97   /**
98    * How many messages have travelled an edge in both directions.
99    */
100   unsigned long long total_nse_cross;
101
102   /**
103    * How many extra messages per edge (corrections) have been received.
104    */
105   unsigned long long total_nse_extra;
106
107   /**
108    * How many messages have been discarded.
109    */
110   unsigned long long total_discarded;
111 };
112
113
114 /**
115  * Head of DLL of peers we monitor closely.
116  */
117 static struct NSEPeer *peer_head;
118
119 /**
120  * Tail of DLL of peers we monitor closely.
121  */
122 static struct NSEPeer *peer_tail;
123
124 /**
125  * Return value from 'main' (0 == success)
126  */
127 static int ok;
128
129 /**
130  * Be verbose (configuration option)
131  */
132 static int verbose;
133
134 /**
135  * Name of the file with the hosts to run the test over (configuration option)
136  */ 
137 static char *hosts_file;
138
139 /**
140  * IP address of this system, as seen by the rest of the system (configuration option)
141  */
142 static char *controller_ip;
143
144 /**
145  * Maximum number of peers in the test.
146  */
147 static unsigned int num_peers;
148
149 /**
150  * Total number of rounds to execute.
151  */
152 static unsigned int num_rounds;
153
154 /**
155  * Current round we are in.
156  */
157 static unsigned int current_round;
158
159 /**
160  * Array of size 'num_rounds' with the requested number of peers in the given round.
161  */
162 static unsigned int *num_peers_in_round;
163
164 /**
165  * How many peers are running right now?
166  */
167 static unsigned int peers_running;
168
169 /**
170  * Specification for the numbers of peers to have in each round.
171  */
172 static char *num_peer_spec;
173
174 /**
175  * Handles to all of the running peers.
176  */
177 static struct GNUNET_TESTBED_Peer **daemons;
178
179 /**
180  * Global configuration file
181  */
182 static struct GNUNET_CONFIGURATION_Handle *testing_cfg;
183
184 /**
185  * Maximum number of connections to NSE services.
186  */
187 static unsigned int connection_limit;
188
189 /**
190  * Total number of connections in the whole network.
191  */
192 static unsigned int total_connections;
193
194 /**
195  * File to report results to.
196  */
197 static struct GNUNET_DISK_FileHandle *output_file;
198
199 /**
200  * Filename to log results to.
201  */
202 static char *output_filename;
203
204 /**
205  * File to log connection info, statistics to.
206  */
207 static struct GNUNET_DISK_FileHandle *data_file;
208
209 /**
210  * Filename to log connection info, statistics to.
211  */
212 static char *data_filename;
213
214 /**
215  * How long to wait before triggering next round?
216  * Default: 60 s.
217  */
218 static struct GNUNET_TIME_Relative wait_time = { 60 * 1000 };
219
220 /**
221  * How often do we query for statistics during a round?
222  * Default: 1 s.
223  */
224 static struct GNUNET_TIME_Relative interval = { 1000 };
225
226 /**
227  * Name of the file where we write the topology for each round; NULL for
228  * none.
229  */
230 static char *topology_file;
231
232 /**
233  * List of hosts we use for the testbed.
234  */
235 static struct GNUNET_TESTBED_Host *hosts;
236
237 /**
238  * Size of the 'hosts' array.
239  */
240 static unsigned int num_hosts;
241
242 /**
243  * Handle to the master controller.
244  */
245 static struct GNUNET_TESTBED_Controller *controller;
246
247 /**
248  * Controller start handle.
249  */
250 static struct GNUNET_TESTBED_ControllerProc *copro;
251
252 /**
253  * Testbed handle.
254  */
255 static struct GNUNET_TESTBED_Testbed *testbed;
256
257
258 /**
259  * Clean up all of the monitoring connections to NSE and
260  * STATISTICS that we keep to selected peers.
261  */
262 static void
263 close_monitor_connections ()
264 {
265   struct NSEPeer *pos;
266
267   while (NULL != (pos = peer_head))
268   {
269     if (NULL != pos->nse_op)
270       GNUNET_TESTBED_operation_done (pos->nse_op);
271     if (NULL != pos->stats_op)
272       GNUNET_TESTBED_operation_done (pos->stats_op);
273     GNUNET_CONTAINER_DLL_remove (peer_head, peer_tail, pos);
274     if (GNUNET_SCHEDULER_NO_TASK != pos->stats_task)
275       GNUNET_SCHEDULER_cancel (pos->stats_task);
276     GNUNET_free (pos);
277   }
278 }
279
280
281 /**
282  * Task run on shutdown; cleans up everything.
283  *
284  * @param cls unused
285  * @param tc unused
286  */
287 static void
288 shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
289 {
290   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Ending test.\n");    
291   close_monitor_connections ();
292   if (NULL != testbed)
293     GNUNET_TESTBED_destroy (testbed);
294   if (NULL != controller)
295     GNUNET_TESTBED_controller_disconnect (controller);
296   if (NULL != copro)
297       GNUNET_TESTBED_controller_stop (copro);
298   while (0 > num_hosts)
299     GNUNET_TESTBED_host_destroy (hosts[--num_hosts]);
300   // FIXME: what about closing other files!?
301   if (NULL != data_file)
302     GNUNET_DISK_file_close (data_file);
303 }
304
305
306 /**
307  * Callback to call when network size estimate is updated.
308  *
309  * @param cls closure with the 'struct NSEPeer' providing the update
310  * @param timestamp server timestamp
311  * @param estimate the value of the current network size estimate
312  * @param std_dev standard deviation (rounded down to nearest integer)
313  *                of the size estimation values seen
314  *
315  */
316 static void
317 handle_estimate (void *cls, 
318                  struct GNUNET_TIME_Absolute timestamp,
319                  double estimate, double std_dev)
320 {
321   struct NSEPeer *peer = cls;
322   char output_buffer[512];
323   size_t size;
324
325   if (NULL == output_file)
326     {
327       FPRINTF (stderr,
328                "Received network size estimate from peer %p. Size: %f std.dev. %f\n",
329                peer, estimate, std_dev);
330       return;
331     }
332   size = GNUNET_snprintf (output_buffer, 
333                           sizeof (output_buffer),
334                           "%p %llu %llu %f %f %f\n",
335                           peer, peers_running,
336                           timestamp.abs_value,
337                           GNUNET_NSE_log_estimate_to_n (estimate), estimate,
338                           std_dev);
339   if (size != GNUNET_DISK_file_write (output_file, output_buffer, size))
340     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
341                 "Unable to write to file!\n");
342 }
343
344
345 /**
346  * Process core statistic values.
347  *
348  * @param cls closure
349  * @param subsystem name of subsystem that created the statistic
350  * @param name the name of the datum
351  * @param value the current value
352  * @param is_persistent GNUNET_YES if the value is persistent, GNUNET_NO if not
353  * @return GNUNET_OK to continue, GNUNET_SYSERR to abort iteration
354  */
355 static int
356 core_stats_iterator (void *cls, const char *subsystem, const char *name,
357                      uint64_t value, int is_persistent)
358 {
359   struct NSEPeer *peer = cls;
360   char output_buffer[512];
361   size_t size;
362
363   if (NULL == output_file)
364     {
365       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
366                   "%p -> %s [%s]: %llu\n",
367                   peer, subsystem, name, value);
368       return GNUNET_OK;
369     }
370   size =
371     GNUNET_snprintf (output_buffer,
372                      sizeof (output_buffer),
373                      "%p [%s] %s %llu\n",
374                      peer,
375                      subsystem, name, value);
376   if (size != GNUNET_DISK_file_write (output_file, output_buffer, size))
377     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unable to write to file!\n");
378   return GNUNET_OK;
379 }
380
381
382 /**
383  * Continuation called by "get_stats" function once we are done.
384  *
385  * @param cls closure
386  * @param success GNUNET_OK if statistics were
387  *        successfully obtained, GNUNET_SYSERR if not.
388  */
389 static void
390 core_stats_cont (void *cls, int success);
391
392
393 /**
394  * Function invoked periodically to get the statistics.
395  *
396  * @param cls 'struct NSEPeer' to get stats from
397  * @param tc scheduler context
398  */
399 static void
400 core_get_stats (void *cls,
401                 const struct GNUNET_SCHEDULER_TaskContext *tc)
402 {
403   struct NSEPeer *peer = cls;
404
405   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
406   {
407     GNUNET_TESTBED_operation_done (peer->stats_op);
408     peer->stats = NULL;
409     peer->stats_op = NULL;
410     return;
411   }
412   /* FIXME: code duplication! */
413   GNUNET_STATISTICS_get (peer->stats, "core", NULL,
414                          GNUNET_TIME_UNIT_FOREVER_REL,
415                          &core_stats_cont, 
416                          &core_stats_iterator, peer);
417   GNUNET_STATISTICS_get (peer->stats, "transport", NULL,
418                          GNUNET_TIME_UNIT_FOREVER_REL,
419                          NULL,
420                          &core_stats_iterator, peer);
421   GNUNET_STATISTICS_get (peer->stats, "nse", NULL,
422                          GNUNET_TIME_UNIT_FOREVER_REL,
423                          NULL, 
424                          &core_stats_iterator, peer);
425   peer->stats_task = GNUNET_SCHEDULER_NO_TASK;
426 }
427
428
429 /**
430  * Continuation called by "get_stats" function.
431  *
432  * @param cls closure
433  * @param success GNUNET_OK if statistics were
434  *        successfully obtained, GNUNET_SYSERR if not.
435  */
436 static void
437 core_stats_cont (void *cls, 
438                  int success)
439 {
440   struct NSEPeer *peer = cls;
441
442   if (GNUNET_OK != success)
443     return;
444   peer->stats_task = GNUNET_SCHEDULER_add_delayed (interval,
445                                                    &core_get_stats, peer);
446 }
447
448
449 /**
450  * Adapter function called to establish a connection to
451  * statistics service.
452  * 
453  * @param cls closure
454  * @param cfg configuration of the peer to connect to; will be available until
455  *          GNUNET_TESTBED_operation_done() is called on the operation returned
456  *          from GNUNET_TESTBED_service_connect()
457  * @return service handle to return in 'op_result', NULL on error
458  */
459 static void *
460 statistics_connect_adapter (void *cls,
461                             const struct GNUNET_CONFIGURATION_Handle *cfg)
462 {
463   return GNUNET_STATISTICS_create ("<driver>",
464                                    cfg);
465 }
466
467
468 /**
469  * Adapter function called to destroy a connection to
470  * statistics service.
471  * 
472  * @param cls closure
473  * @param op_result service handle returned from the connect adapter
474  */
475 static void 
476 statistics_disconnect_adapter (void *cls,
477                                void *op_result)
478 {
479   GNUNET_STATISTICS_destroy (op_result, GNUNET_NO);
480 }
481
482
483 /**
484  * Function called by testbed once we are connected to stats service.
485  *
486  * @param cls the 'struct NSEPeer' for which we connected to stats
487  * @param op connect operation handle
488  * @param ca_result handle to stats service
489  * @param emsg error message on failure
490  */
491 static void
492 stat_run (void *cls, 
493           struct GNUNET_TESTBED_Operation *op,
494           void *ca_result,
495           const char *emsg)
496 {
497   struct NSEPeer *current_peer = cls;
498
499   if (NULL == ca_result)
500     {
501       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
502                   "Failed to connect to statistics service: %s\n",
503                   emsg);
504       GNUNET_SCHEDULER_shutdown ();
505       return;
506     }
507   current_peer->stats = ca_result;
508   GNUNET_STATISTICS_get (current_peer->stats, "core", NULL,
509                          GNUNET_TIME_UNIT_FOREVER_REL,
510                          &core_stats_cont, 
511                          &core_stats_iterator, current_peer);
512   GNUNET_STATISTICS_get (current_peer->stats, "transport", NULL,
513                          GNUNET_TIME_UNIT_FOREVER_REL,
514                          NULL, 
515                          &core_stats_iterator, current_peer);
516   GNUNET_STATISTICS_get (current_peer->stats, "nse", NULL,
517                          GNUNET_TIME_UNIT_FOREVER_REL,
518                          NULL, 
519                          &core_stats_iterator, current_peer);
520 }
521
522
523 /**
524  * Adapter function called to establish a connection to
525  * NSE service.
526  * 
527  * @param cls closure (the 'struct NSEPeer')
528  * @param cfg configuration of the peer to connect to; will be available until
529  *          GNUNET_TESTBED_operation_done() is called on the operation returned
530  *          from GNUNET_TESTBED_service_connect()
531  * @return service handle to return in 'op_result', NULL on error
532  */
533 static void *
534 nse_connect_adapter (void *cls,
535                      const struct GNUNET_CONFIGURATION_Handle *cfg)
536 {
537   struct NSEPeer *current_peer = cls;
538
539   return GNUNET_NSE_connect (cfg, &handle_estimate, current_peer);
540 }
541
542
543 /**
544  * Adapter function called to destroy a connection to
545  * NSE service.
546  * 
547  * @param cls closure
548  * @param op_result service handle returned from the connect adapter
549  */
550 static void 
551 nse_disconnect_adapter (void *cls,
552                         void *op_result)
553 {
554   GNUNET_NSE_disconnect (op_result);
555 }
556
557
558 /**
559  * Task run to connect to the NSE and statistics services to a subset of
560  * all of the running peers.
561  *
562  * @param cls handle the peer
563  * @param tc ignored
564  */
565 static void
566 connect_nse_service ()
567 {
568   struct NSEPeer *current_peer;
569   unsigned int i;
570
571   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting to nse service of peers\n");
572   for (i = 0; i < num_peers; i++)
573   {
574     if ((connection_limit > 0) &&
575         (num_peers > connection_limit) && 
576         (0 != (i % (num_peers / connection_limit))))
577       continue;
578     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
579                 "nse-profiler: connecting to nse service of peer %d\n", i);
580     current_peer = GNUNET_malloc (sizeof (struct NSEPeer));
581     current_peer->daemon = daemons[i];
582     current_peer->nse_op 
583       = GNUNET_TESTBED_service_connect (NULL,
584                                         current_peer->daemon,
585                                         "nse",
586                                         NULL, NULL,
587                                         &nse_connect_adapter,
588                                         &nse_disconnect_adapter,
589                                         current_peer);  
590     current_peer->stats_op 
591       = GNUNET_TESTBED_service_connect (NULL,
592                                         current_peer->daemon,
593                                         "statistics",
594                                         &stat_run, current_peer,
595                                         &statistics_connect_adapter,
596                                         &statistics_disconnect_adapter,
597                                         NULL);  
598     GNUNET_CONTAINER_DLL_insert (peer_head, peer_tail, current_peer);
599   }
600 }
601
602
603 /**
604  * Task that starts/stops peers to move to the next round.
605  *
606  * @param cls NULL, unused
607  * @param tc scheduler context (unused)
608  */
609 static void
610 next_round (void *cls, 
611             const struct GNUNET_SCHEDULER_TaskContext *tc);
612
613
614 /**
615  * Continuation called by the "get_all" and "get" functions at the
616  * end of a round.  Obtains the final statistics and writes them to
617  * the file, then either starts the next round, or, if this was the
618  * last round, terminates the run.
619  *
620  * @param cls struct StatsContext
621  * @param success GNUNET_OK if statistics were
622  *        successfully obtained, GNUNET_SYSERR if not.
623  */
624 static void
625 stats_finished_callback (void *cls,
626                          struct GNUNET_TESTBED_Operation *op,
627                          const char *emsg)
628 {
629   struct StatsContext *stats_context = cls;
630   char buf[512];
631   size_t buf_len;
632
633   if (NULL != emsg)
634     {
635       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
636                   "Failed to get statistics: %s\n",
637                   emsg);
638       GNUNET_SCHEDULER_shutdown ();
639       GNUNET_free (stats_context);
640       return;
641     }
642   if (NULL != data_file)
643     {
644       /* Stats lookup successful, write out data */
645       buf_len =
646         GNUNET_snprintf (buf, sizeof (buf),
647                          "TOTAL_NSE_RECEIVED_MESSAGES_%u: %u \n",
648                          current_round,
649                          stats_context->total_nse_received_messages);
650       GNUNET_DISK_file_write (data_file, buf, buf_len);
651       buf_len =
652         GNUNET_snprintf (buf, sizeof (buf),
653                          "TOTAL_NSE_TRANSMITTED_MESSAGES_%u: %u\n",
654                          current_round,
655                          stats_context->total_nse_transmitted_messages);
656       GNUNET_DISK_file_write (data_file, buf, buf_len);    
657       buf_len =
658         GNUNET_snprintf (buf, sizeof (buf),
659                          "TOTAL_NSE_CROSS_%u: %u \n",
660                          current_round,
661                          stats_context->total_nse_cross);
662       GNUNET_DISK_file_write (data_file, buf, buf_len);
663       buf_len =
664         GNUNET_snprintf (buf, sizeof (buf),
665                          "TOTAL_NSE_EXTRA_%u: %u \n",
666                          current_round,
667                          stats_context->total_nse_extra);
668       GNUNET_DISK_file_write (data_file, buf, buf_len);
669       buf_len =
670         GNUNET_snprintf (buf, sizeof (buf),
671                          "TOTAL_NSE_DISCARDED_%u: %u \n",
672                          current_round,
673                          stats_context->total_discarded);
674       GNUNET_DISK_file_write (data_file, buf, buf_len);    
675     }  
676   GNUNET_SCHEDULER_add_now (&next_round, NULL);
677   GNUNET_free (stats_context);
678 }
679
680
681 /**
682  * Callback function to process statistic values.
683  *
684  * @param cls struct StatsContext
685  * @param peer the peer the statistics belong to
686  * @param subsystem name of subsystem that created the statistic
687  * @param name the name of the datum
688  * @param value the current value
689  * @param is_persistent GNUNET_YES if the value is persistent, GNUNET_NO if not
690  * @return GNUNET_OK to continue, GNUNET_SYSERR to abort iteration
691  */
692 static int
693 statistics_iterator (void *cls, 
694                      const struct GNUNET_TESTBED_Peer *peer,
695                      const char *subsystem, const char *name, uint64_t value,
696                      int is_persistent)
697 {
698   struct StatsContext *stats_context = cls;
699   char buf[512];
700   size_t buf_len;
701
702   if (0 != strcmp (subsystem, "nse"))
703     return GNUNET_OK;
704   if (0 == strcmp (name, "# flood messages received"))
705     {
706       stats_context->total_nse_received_messages += value;
707       if ( (verbose > 1) && 
708            (NULL != data_file) )
709         {
710           buf_len =
711             GNUNET_snprintf (buf, sizeof (buf),
712                              "%p %u RECEIVED\n", 
713                              peer, value);
714           GNUNET_DISK_file_write (data_file, buf, buf_len);
715         }
716     }
717   if (0 == strcmp (name, "# flood messages transmitted"))
718     {
719       stats_context->total_nse_transmitted_messages += value;
720       if ( (verbose > 1) &&
721            (NULL != data_file) )
722         {
723           buf_len =
724             GNUNET_snprintf (buf, sizeof (buf),
725                              "%p %u TRANSMITTED\n", 
726                              peer, value);
727           GNUNET_DISK_file_write (data_file, buf, buf_len);
728         }
729     }
730   if (0 == strcmp (name, "# cross messages"))
731     stats_context->total_nse_cross += value;    
732   if (0 == strcmp (name, "# extra messages"))    
733     stats_context->total_nse_extra += value;
734   if (0 == strcmp (name, "# flood messages discarded (clock skew too large)"))
735     stats_context->total_discarded += value;    
736   return GNUNET_OK;
737 }
738
739
740 /**
741  * Function called upon completion of the node start/stop operations
742  * for the current round.  Writes the new topology to disk.
743  */
744 static void
745 write_topology ()
746 {
747   char temp_output_file[1024];
748
749   if (NULL != topology_file)
750     {
751       GNUNET_snprintf (temp_output_file, sizeof (temp_output_file),
752                        "%s_%llu.dot", 
753                        topology_file, current_round);
754       GNUNET_TESTBED_overlay_write_topology_to_file (controller,
755                                                      temp_output_file);
756     }
757 }
758
759
760 /**
761  * We're at the end of a round.  Stop monitoring, write total
762  * number of connections to log and get full stats.  Then trigger
763  * the next round.
764  *
765  * @param cls unused, NULL
766  * @param tc unused
767  */
768 static void
769 finish_round (void *cls, 
770               const struct GNUNET_SCHEDULER_TaskContext *tc)
771 {
772   struct StatsContext *stats_context;
773   char buf[1024];
774   size_t buf_len;
775
776   GNUNET_log (GNUNET_ERROR_TYPE_INFO, 
777               "Have %u connections\n",
778               total_connections);
779   if (NULL != data_file)
780     {
781       buf_len = GNUNET_snprintf (buf, sizeof (buf),
782                                  "CONNECTIONS_0: %u\n", 
783                                  total_connections);
784       GNUNET_DISK_file_write (data_file, buf, buf_len);
785     }
786   close_monitor_connections ();    
787   stats_context = GNUNET_malloc (sizeof (struct StatsContext));
788   GNUNET_TESTBED_get_statistics (num_peers_in_round[current_round], 
789                                  daemons,                                
790                                  &statistics_iterator,
791                                  &stats_finished_callback,
792                                  stats_context);
793 }
794
795
796 /**
797  * We have reached the desired number of peers for the current round.
798  * Run it (by connecting and monitoring a few peers and waiting the
799  * specified delay before finishing the round).
800  */
801 static void
802 run_round ()
803 {
804   write_topology ();
805   connect_nse_service ();
806   GNUNET_SCHEDULER_add_delayed (wait_time,
807                                 &finish_round,
808                                 NULL);
809 }
810
811
812 /**
813  * Task run at the end of a round.  Disconnect from all monitored
814  * peers; then get statistics from *all* peers.
815  *
816  * @param cls NULL, unused
817  * @param tc unused
818  */
819 static void
820 next_round (void *cls, 
821             const struct GNUNET_SCHEDULER_TaskContext *tc)
822 {
823   unsigned int i;
824
825   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "disconnecting nse service of peers\n");
826   current_round++;
827   
828   if (current_round == num_rounds)
829     {
830       /* this was the last round, terminate */
831       GNUNET_SCHEDULER_shutdown ();
832       return;
833     }
834   if (num_peers_in_round[current_round] == peers_running)
835     {
836       /* no need to churn, just run next round */
837       run_round ();
838       return;
839     }
840
841   /* start peers if we have too few */
842   for (i=peers_running;i<num_peers_in_round[current_round];i++)
843     GNUNET_TESTBED_peer_start (daemons[i], NULL, NULL);
844
845   /* stop peers if we have too many */
846   for (i=num_peers_in_round[current_round];i<peers_running;i++)
847     GNUNET_TESTBED_peer_stop (daemons[i], NULL, NULL);
848 }
849
850
851 /**
852  * Function that will be called whenever something in the
853  * testbed changes.
854  *
855  * @param cls closure, NULL
856  * @param event information on what is happening
857  */
858 static void
859 master_controller_cb (void *cls, 
860                       const struct GNUNET_TESTBED_EventInformation *event)
861 {
862   switch (event->type)
863     {
864     case GNUNET_TESTBED_ET_PEER_START:
865       peers_running++;
866       if (num_peers_in_round[current_round] == peers_running)
867         run_round ();
868       break;
869     case GNUNET_TESTBED_ET_PEER_STOP:
870       peers_running--;
871       if (num_peers_in_round[current_round] == peers_running)
872         run_round ();
873       break;
874     case GNUNET_TESTBED_ET_CONNECT:
875       total_connections++;
876     case GNUNET_TESTBED_ET_DISCONNECT:
877       total_connections--;
878     default:
879       break;
880     }
881 }
882
883
884 static void
885 controller_start_cb (void *cls,
886                      const struct GNUNET_CONFIGURATION_Handle *cfg,
887                      int status)
888 {
889   if (GNUNET_OK != status)
890     {
891       copro = NULL;
892       GNUNET_SCHEDULER_shutdown ();
893       return;
894     }
895   num_hosts = GNUNET_TESTBED_hosts_load_from_file (hosts_file,
896                                                    &hosts);
897   if (0 == num_hosts)
898     {
899       fprintf (stderr,
900                "Failed to read host information from `%s'\n", 
901                hosts_file);
902       return;
903     }
904   controller = GNUNET_TESTBED_controller_connect (cfg,
905                                                NULL, 
906                                                0 /* mask */,
907                                                &master_controller_cb, NULL);
908   testbed = GNUNET_TESTBED_create (controller,
909                                    num_hosts, hosts, 
910                                    num_peers,
911                                    cfg,
912                                    0 /* FIXME: topology */,
913                                    NULL /* FIXME: topology options */);
914 }
915
916
917 /**
918  * Actual main function that runs the emulation.
919  *
920  * @param cls unused
921  * @param args remaining args, unused
922  * @param cfgfile name of the configuration
923  * @param cfg configuration handle
924  */
925 static void
926 run (void *cls, char *const *args, const char *cfgfile,
927      const struct GNUNET_CONFIGURATION_Handle *cfg)
928 {
929   char *tok;
930   unsigned int num;
931
932   ok = 1;
933   testing_cfg = GNUNET_CONFIGURATION_dup (cfg);
934   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Starting daemons.\n");
935   if (verbose)
936     GNUNET_CONFIGURATION_set_value_string (testing_cfg, "testing",
937                                            "use_progressbars", "YES");
938   if (NULL == num_peer_spec)
939   {
940     fprintf (stderr, "You need to specify the number of peers to run\n");
941     return;
942   }
943   for (tok = strtok (num_peer_spec, ","); NULL != tok; tok = strtok (NULL, ","))
944     {
945       if (1 != sscanf (tok, "%u", &num))
946         {
947           fprintf (stderr, "You need to specify numbers, not `%s'\n", tok);
948           return;
949         }
950       if (0 == num)
951         {
952           fprintf (stderr, "Refusing to run a round with 0 peers\n");
953           return;
954         }
955       GNUNET_array_grow (num_peers_in_round, num_rounds, num);
956       num_peers = GNUNET_MAX (num_peers, num);
957     }
958   if (0 == num_peers)
959     {
960       fprintf (stderr, "Refusing to run a testbed with no rounds\n");
961       return;
962     }
963   daemons = GNUNET_malloc (sizeof (struct GNUNET_TESTBED_Peer*) * num_peers); 
964   if ( (NULL != data_filename) &&
965        (NULL == (data_file = 
966                  GNUNET_DISK_file_open (data_filename,
967                                         GNUNET_DISK_OPEN_READWRITE |
968                                         GNUNET_DISK_OPEN_TRUNCATE |
969                                         GNUNET_DISK_OPEN_CREATE,
970                                         GNUNET_DISK_PERM_USER_READ |
971                                         GNUNET_DISK_PERM_USER_WRITE))) )
972     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
973                               "open",
974                               data_filename);
975
976   if ( (NULL != output_filename) &&
977        (NULL == (output_file =
978                  GNUNET_DISK_file_open (output_filename,
979                                         GNUNET_DISK_OPEN_READWRITE |
980                                         GNUNET_DISK_OPEN_CREATE,
981                                         GNUNET_DISK_PERM_USER_READ |
982                                         GNUNET_DISK_PERM_USER_WRITE))) )
983     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open",
984                               output_filename);
985
986   if (NULL ==
987       (copro = GNUNET_TESTBED_controller_start (controller_ip, NULL,
988                                                 cfg,
989                                                 &controller_start_cb, NULL)))
990     {
991       fprintf (stderr,
992                "Failed to start controller\n");
993       return;
994     }
995   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
996                                 &shutdown_task, NULL);
997 }
998
999
1000 /**
1001  * Main function.
1002  *
1003  * @return 0 on success
1004  */
1005 int
1006 main (int argc, char *const *argv)
1007 {
1008   static struct GNUNET_GETOPT_CommandLineOption options[] = {
1009     {'C', "connections", "COUNT",
1010      gettext_noop ("limit to the number of connections to NSE services, 0 for none"),
1011      1, &GNUNET_GETOPT_set_string, &num_peer_spec},
1012     {'d', "details", "FILENAME",
1013      gettext_noop ("name of the file for writing connection information and statistics"),
1014      1, &GNUNET_GETOPT_set_string, &data_filename},
1015     {'H', "hosts", "FILENAME",
1016      gettext_noop ("name of the file with the login information for the testbed"),
1017      1, &GNUNET_GETOPT_set_string, &hosts_file},
1018     {'i', "ip", "CONTROLLER_IP",
1019      gettext_noop ("IP address of this system as seen by the rest of the testbed"),
1020      1, &GNUNET_GETOPT_set_string, &controller_ip},
1021     {'I', "interval", "DELAY",
1022      gettext_noop ("delay between queries to statistics during a round"),
1023      1, &GNUNET_GETOPT_set_relative_time, &interval},
1024     {'t', "topology", "FILENAME",
1025      gettext_noop ("prefix of the filenames we use for writing the topology for each round"),
1026      1, &GNUNET_GETOPT_set_string, &topology_file},
1027     {'o', "output", "FILENAME",
1028      gettext_noop ("name of the file for writing the main results"),
1029      1, &GNUNET_GETOPT_set_string, &output_filename},
1030     {'p', "peers", "NETWORKSIZESPEC",
1031      gettext_noop ("Number of peers to run in each round, separated by commas"),
1032      1, &GNUNET_GETOPT_set_string, &num_peer_spec},
1033     {'V', "verbose", NULL,
1034      gettext_noop ("be verbose (print progress information)"),
1035      0, &GNUNET_GETOPT_increment_value, &verbose},
1036     {'w', "wait", "DELAY",
1037      gettext_noop ("delay between rounds"),
1038      1, &GNUNET_GETOPT_set_relative_time, &wait_time},
1039     GNUNET_GETOPT_OPTION_END
1040   };
1041   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
1042     return 2;
1043   GNUNET_log_setup ("nse-profiler", "WARNING", NULL);
1044   if (GNUNET_OK !=
1045       GNUNET_PROGRAM_run (argc, argv, "nse-profiler",
1046                           gettext_noop
1047                           ("Measure quality and performance of the NSE service."),
1048                           options, &run, NULL))
1049     ok = 1;
1050   return ok;
1051 }
1052
1053 /* end of nse-profiler.c */