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