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