2 This file is part of GNUnet.
3 Copyright (C) 2011, 2012 GNUnet e.V.
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.
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.
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., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
21 * @file nse/gnunet-nse-profiler.c
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
33 #include "gnunet_testbed_service.h"
34 #include "gnunet_nse_service.h"
37 * Generic loggins shorthand
39 #define LOG(kind,...) \
40 GNUNET_log (kind, __VA_ARGS__)
43 * Debug logging shorthand
45 #define LOG_DEBUG(...) \
46 LOG (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
50 * Information we track for a peer in the testbed.
55 * Prev reference in DLL.
60 * Next reference in DLL.
65 * Handle with testbed.
67 struct GNUNET_TESTBED_Peer *daemon;
70 * Testbed operation to connect to NSE service.
72 struct GNUNET_TESTBED_Operation *nse_op;
75 * Testbed operation to connect to statistics service
77 struct GNUNET_TESTBED_Operation *stat_op;
80 * Handle to the statistics service
82 struct GNUNET_STATISTICS_Handle *sh;
94 struct OpListEntry *next;
99 struct OpListEntry *prev;
102 * The testbed operation
104 struct GNUNET_TESTBED_Operation *op;
107 * Depending on whether we start or stop NSE service at the peer set this to 1
115 * Head of DLL of peers we monitor closely.
117 static struct NSEPeer *peer_head;
120 * Tail of DLL of peers we monitor closely.
122 static struct NSEPeer *peer_tail;
125 * Return value from 'main' (0 == success)
130 * Be verbose (configuration option)
132 static unsigned int verbose;
135 * Name of the file with the hosts to run the test over (configuration option)
137 static char *hosts_file;
140 * Maximum number of peers in the test.
142 static unsigned int num_peers;
145 * Total number of rounds to execute.
147 static unsigned int num_rounds;
150 * Current round we are in.
152 static unsigned int current_round;
155 * Array of size 'num_rounds' with the requested number of peers in the given round.
157 static unsigned int *num_peers_in_round;
160 * How many peers are running right now?
162 static unsigned int peers_running;
165 * Specification for the numbers of peers to have in each round.
167 static char *num_peer_spec;
170 * Handles to all of the running peers.
172 static struct GNUNET_TESTBED_Peer **daemons;
175 * Global configuration file
177 static struct GNUNET_CONFIGURATION_Handle *testing_cfg;
180 * Maximum number of connections to NSE services.
182 static unsigned int connection_limit;
185 * Total number of connections in the whole network.
187 static unsigned int total_connections;
190 * File to report results to.
192 static struct GNUNET_DISK_FileHandle *output_file;
195 * Filename to log results to.
197 static char *output_filename;
200 * File to log connection info, statistics to.
202 static struct GNUNET_DISK_FileHandle *data_file;
205 * Filename to log connection info, statistics to.
207 static char *data_filename;
210 * How long to wait before triggering next round?
213 static struct GNUNET_TIME_Relative wait_time = { 60 * 1000 };
216 * DLL head for operation list
218 static struct OpListEntry *oplist_head;
221 * DLL tail for operation list
223 static struct OpListEntry *oplist_tail;
226 * Task running each round of the experiment.
228 static struct GNUNET_SCHEDULER_Task *round_task;
232 * Clean up all of the monitoring connections to NSE and
233 * STATISTICS that we keep to selected peers.
236 close_monitor_connections ()
239 struct OpListEntry *oplist_entry;
241 while (NULL != (pos = peer_head))
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);
250 while (NULL != (oplist_entry = oplist_head))
252 GNUNET_CONTAINER_DLL_remove (oplist_head, oplist_tail, oplist_entry);
253 GNUNET_TESTBED_operation_done (oplist_entry->op);
254 GNUNET_free (oplist_entry);
260 * Task run on shutdown; cleans up everything.
265 shutdown_task (void *cls)
267 LOG_DEBUG ("Ending test.\n");
268 close_monitor_connections ();
269 if (NULL != round_task)
271 GNUNET_SCHEDULER_cancel (round_task);
274 if (NULL != data_file)
276 GNUNET_DISK_file_close (data_file);
279 if (NULL != output_file)
281 GNUNET_DISK_file_close (output_file);
284 if (NULL != testing_cfg)
286 GNUNET_CONFIGURATION_destroy (testing_cfg);
293 * Callback to call when network size estimate is updated.
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
302 handle_estimate (void *cls,
303 struct GNUNET_TIME_Absolute timestamp,
304 double estimate, double std_dev)
306 struct NSEPeer *peer = cls;
307 char output_buffer[512];
310 if (NULL == output_file)
313 "Received network size estimate from peer %p. Size: %f std.dev. %f\n",
314 peer, estimate, std_dev);
317 size = GNUNET_snprintf (output_buffer,
318 sizeof (output_buffer),
319 "%p %llu %llu %f %f %f\n",
321 (unsigned long long) timestamp.abs_value_us,
322 GNUNET_NSE_log_estimate_to_n (estimate), estimate,
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");
331 * Adapter function called to establish a connection to
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
341 nse_connect_adapter (void *cls,
342 const struct GNUNET_CONFIGURATION_Handle *cfg)
344 struct NSEPeer *current_peer = cls;
346 return GNUNET_NSE_connect (cfg, &handle_estimate, current_peer);
351 * Adapter function called to destroy a connection to
355 * @param op_result service handle returned from the connect adapter
358 nse_disconnect_adapter (void *cls,
361 GNUNET_NSE_disconnect (op_result);
366 * Callback function to process statistic values.
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
376 stat_iterator (void *cls,
377 const char *subsystem,
379 uint64_t value, int is_persistent)
382 struct GNUNET_TIME_Absolute now;
386 GNUNET_assert (NULL != data_file);
387 now = GNUNET_TIME_absolute_get ();
388 flag = strcasecmp (subsystem, "core");
391 size = GNUNET_asprintf (&output_buffer, "%llu %llu %u\n",
392 now.abs_value_us / 1000LL / 1000LL,
394 if (size != GNUNET_DISK_file_write (data_file, output_buffer, size))
396 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unable to write to file!\n");
397 GNUNET_free (output_buffer);
398 return GNUNET_SYSERR;
400 GNUNET_free (output_buffer);
406 * Called to open a connection to the peer's statistics
408 * @param cls peer context
409 * @param cfg configuration of the peer to connect to; will be available until
410 * GNUNET_TESTBED_operation_done() is called on the operation returned
411 * from GNUNET_TESTBED_service_connect()
412 * @return service handle to return in 'op_result', NULL on error
415 stat_connect_adapter (void *cls,
416 const struct GNUNET_CONFIGURATION_Handle *cfg)
418 struct NSEPeer *peer = cls;
420 peer->sh = GNUNET_STATISTICS_create ("nse-profiler", cfg);
426 * Called to disconnect from peer's statistics service
428 * @param cls peer context
429 * @param op_result service handle returned from the connect adapter
432 stat_disconnect_adapter (void *cls, void *op_result)
434 struct NSEPeer *peer = cls;
436 GNUNET_break (GNUNET_OK == GNUNET_STATISTICS_watch_cancel
437 (peer->sh, "core", "# peers connected",
438 stat_iterator, peer));
439 GNUNET_break (GNUNET_OK == GNUNET_STATISTICS_watch_cancel
440 (peer->sh, "nse", "# peers connected",
441 stat_iterator, peer));
442 GNUNET_STATISTICS_destroy (op_result, GNUNET_NO);
448 * Called after successfully opening a connection to a peer's statistics
449 * service; we register statistics monitoring for CORE and NSE here.
451 * @param cls the callback closure from functions generating an operation
452 * @param op the operation that has been finished
453 * @param ca_result the service handle returned from GNUNET_TESTBED_ConnectAdapter()
454 * @param emsg error message in case the operation has failed; will be NULL if
455 * operation has executed successfully.
458 stat_comp_cb (void *cls, struct GNUNET_TESTBED_Operation *op,
459 void *ca_result, const char *emsg )
461 struct GNUNET_STATISTICS_Handle *sh = ca_result;
462 struct NSEPeer *peer = cls;
469 GNUNET_break (GNUNET_OK == GNUNET_STATISTICS_watch
470 (sh, "core", "# peers connected",
471 stat_iterator, peer));
472 GNUNET_break (GNUNET_OK == GNUNET_STATISTICS_watch
473 (sh, "nse", "# peers connected",
474 stat_iterator, peer));
479 * Task run to connect to the NSE and statistics services to a subset of
480 * all of the running peers.
483 connect_nse_service ()
485 struct NSEPeer *current_peer;
487 unsigned int connections;
489 if (0 == connection_limit)
491 LOG_DEBUG ("Connecting to nse service of peers\n");
493 for (i = 0; i < num_peers_in_round[current_round]; i++)
495 if ((num_peers_in_round[current_round] > connection_limit) &&
496 (0 != (i % (num_peers_in_round[current_round] / connection_limit))))
498 LOG_DEBUG ("Connecting to nse service of peer %d\n", i);
499 current_peer = GNUNET_new (struct NSEPeer);
500 current_peer->daemon = daemons[i];
502 = GNUNET_TESTBED_service_connect (NULL,
503 current_peer->daemon,
506 &nse_connect_adapter,
507 &nse_disconnect_adapter,
509 if (NULL != data_file)
510 current_peer->stat_op
511 = GNUNET_TESTBED_service_connect (NULL,
512 current_peer->daemon,
516 &stat_connect_adapter,
517 &stat_disconnect_adapter,
519 GNUNET_CONTAINER_DLL_insert (peer_head, peer_tail, current_peer);
520 if (++connections == connection_limit)
527 * Task that starts/stops peers to move to the next round.
529 * @param cls NULL, unused
532 next_round (void *cls);
536 * We're at the end of a round. Stop monitoring, write total
537 * number of connections to log and get full stats. Then trigger
540 * @param cls unused, NULL
543 finish_round (void *cls)
545 LOG (GNUNET_ERROR_TYPE_INFO,
546 "Have %u connections\n",
548 close_monitor_connections ();
549 round_task = GNUNET_SCHEDULER_add_now (&next_round, NULL);
554 * We have reached the desired number of peers for the current round.
555 * Run it (by connecting and monitoring a few peers and waiting the
556 * specified delay before finishing the round).
561 LOG_DEBUG ("Running round %u\n",
563 connect_nse_service ();
564 GNUNET_SCHEDULER_add_delayed (wait_time,
571 * Creates an oplist entry and adds it to the oplist DLL
573 static struct OpListEntry *
576 struct OpListEntry *entry;
578 entry = GNUNET_new (struct OpListEntry);
579 GNUNET_CONTAINER_DLL_insert_tail (oplist_head,
587 * Callback to be called when NSE service is started or stopped at peers
590 * @param op the operation handle
591 * @param emsg NULL on success; otherwise an error description
594 manage_service_cb (void *cls,
595 struct GNUNET_TESTBED_Operation *op,
598 struct OpListEntry *entry = cls;
600 GNUNET_TESTBED_operation_done (entry->op);
603 LOG (GNUNET_ERROR_TYPE_ERROR, "Failed to start/stop NSE at a peer\n");
604 GNUNET_SCHEDULER_shutdown ();
607 GNUNET_assert (0 != entry->delta);
608 peers_running += entry->delta;
609 GNUNET_CONTAINER_DLL_remove (oplist_head,
613 if (num_peers_in_round[current_round] == peers_running)
619 * Adjust the number of running peers to match the required number of running
620 * peers for the round
623 adjust_running_peers ()
625 struct OpListEntry *entry;
628 /* start peers if we have too few */
629 for (i=peers_running;i<num_peers_in_round[current_round];i++)
631 entry = make_oplist_entry ();
633 entry->op = GNUNET_TESTBED_peer_manage_service (NULL,
640 /* stop peers if we have too many */
641 for (i=num_peers_in_round[current_round];i<peers_running;i++)
643 entry = make_oplist_entry ();
645 entry->op = GNUNET_TESTBED_peer_manage_service (NULL,
656 * Task run at the end of a round. Disconnect from all monitored
657 * peers; then get statistics from *all* peers.
659 * @param cls NULL, unused
662 next_round (void *cls)
665 LOG_DEBUG ("Disconnecting nse service of peers\n");
667 if (current_round == num_rounds)
669 /* this was the last round, terminate */
671 GNUNET_SCHEDULER_shutdown ();
674 if (num_peers_in_round[current_round] == peers_running)
676 /* no need to churn, just run next round */
680 adjust_running_peers ();
685 * Function that will be called whenever something in the
688 * @param cls closure, NULL
689 * @param event information on what is happening
692 master_controller_cb (void *cls,
693 const struct GNUNET_TESTBED_EventInformation *event)
697 case GNUNET_TESTBED_ET_CONNECT:
700 case GNUNET_TESTBED_ET_DISCONNECT:
710 * Signature of a main function for a testcase.
713 * @param h the run handle
714 * @param num_peers_ number of peers in 'peers'
715 * @param peers handle to peers run in the testbed. NULL upon timeout (see
716 * GNUNET_TESTBED_test_run()).
717 * @param links_succeeded the number of overlay link connection attempts that
719 * @param links_failed the number of overlay link connection attempts that
723 test_master (void *cls,
724 struct GNUNET_TESTBED_RunHandle *h,
725 unsigned int num_peers_,
726 struct GNUNET_TESTBED_Peer **peers,
727 unsigned int links_succeeded,
728 unsigned int links_failed)
732 GNUNET_SCHEDULER_shutdown ();
736 GNUNET_break (num_peers_ == num_peers);
737 peers_running = num_peers;
738 if (num_peers_in_round[current_round] == peers_running)
740 /* no need to churn, just run the starting round */
744 adjust_running_peers ();
749 * Actual main function that runs the emulation.
752 * @param args remaining args, unused
753 * @param cfgfile name of the configuration
754 * @param cfg configuration handle
757 run (void *cls, char *const *args, const char *cfgfile,
758 const struct GNUNET_CONFIGURATION_Handle *cfg)
765 testing_cfg = GNUNET_CONFIGURATION_dup (cfg);
766 LOG_DEBUG ("Starting daemons.\n");
767 if (NULL == num_peer_spec)
769 fprintf (stderr, "You need to specify the number of peers to run\n");
772 for (tok = strtok (num_peer_spec, ","); NULL != tok; tok = strtok (NULL, ","))
774 if (1 != sscanf (tok, "%u", &num))
776 fprintf (stderr, "You need to specify numbers, not `%s'\n", tok);
781 fprintf (stderr, "Refusing to run a round with 0 peers\n");
784 GNUNET_array_append (num_peers_in_round, num_rounds, num);
785 num_peers = GNUNET_MAX (num_peers, num);
789 fprintf (stderr, "Refusing to run a testbed with no rounds\n");
792 if ( (NULL != data_filename) &&
793 (NULL == (data_file =
794 GNUNET_DISK_file_open (data_filename,
795 GNUNET_DISK_OPEN_READWRITE |
796 GNUNET_DISK_OPEN_TRUNCATE |
797 GNUNET_DISK_OPEN_CREATE,
798 GNUNET_DISK_PERM_USER_READ |
799 GNUNET_DISK_PERM_USER_WRITE))) )
800 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
804 if ( (NULL != output_filename) &&
805 (NULL == (output_file =
806 GNUNET_DISK_file_open (output_filename,
807 GNUNET_DISK_OPEN_READWRITE |
808 GNUNET_DISK_OPEN_CREATE,
809 GNUNET_DISK_PERM_USER_READ |
810 GNUNET_DISK_PERM_USER_WRITE))) )
811 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open",
814 event_mask |= (1LL << GNUNET_TESTBED_ET_PEER_START);
815 event_mask |= (1LL << GNUNET_TESTBED_ET_PEER_STOP);
816 event_mask |= (1LL << GNUNET_TESTBED_ET_CONNECT);
817 event_mask |= (1LL << GNUNET_TESTBED_ET_DISCONNECT);
818 GNUNET_TESTBED_run (hosts_file,
822 master_controller_cb,
823 NULL, /* master_controller_cb cls */
825 NULL); /* test_master cls */
826 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
833 * @return 0 on success
836 main (int argc, char *const *argv)
838 struct GNUNET_GETOPT_CommandLineOption options[] = {
839 GNUNET_GETOPT_option_uint ('C',
842 gettext_noop ("limit to the number of connections to NSE services, 0 for none"),
844 GNUNET_GETOPT_option_string ('d',
847 gettext_noop ("name of the file for writing connection information and statistics"),
850 GNUNET_GETOPT_option_string ('H',
853 gettext_noop ("name of the file with the login information for the testbed"),
856 GNUNET_GETOPT_option_string ('o',
859 gettext_noop ("name of the file for writing the main results"),
863 GNUNET_GETOPT_option_string ('p',
866 gettext_noop ("Number of peers to run in each round, separated by commas"),
869 GNUNET_GETOPT_option_increment_uint ('V',
871 gettext_noop ("be verbose (print progress information)"),
874 GNUNET_GETOPT_option_relative_time ('w',
877 gettext_noop ("delay between rounds"),
879 GNUNET_GETOPT_OPTION_END
881 if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
884 GNUNET_PROGRAM_run (argc, argv, "nse-profiler",
886 ("Measure quality and performance of the NSE service."),
887 options, &run, NULL))
892 /* end of nse-profiler.c */