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 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.
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.
16 * @file nse/gnunet-nse-profiler.c
18 * @brief Profiling driver for the network size estimation service.
19 * Generally, the profiler starts a given number of peers,
20 * then churns some off, waits a certain amount of time, then
21 * churns again, and repeats.
22 * @author Christian Grothoff
23 * @author Nathan Evans
24 * @author Sree Harsha Totakura
28 #include "gnunet_testbed_service.h"
29 #include "gnunet_nse_service.h"
32 * Generic loggins shorthand
34 #define LOG(kind,...) \
35 GNUNET_log (kind, __VA_ARGS__)
38 * Debug logging shorthand
40 #define LOG_DEBUG(...) \
41 LOG (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
45 * Information we track for a peer in the testbed.
50 * Prev reference in DLL.
55 * Next reference in DLL.
60 * Handle with testbed.
62 struct GNUNET_TESTBED_Peer *daemon;
65 * Testbed operation to connect to NSE service.
67 struct GNUNET_TESTBED_Operation *nse_op;
70 * Testbed operation to connect to statistics service
72 struct GNUNET_TESTBED_Operation *stat_op;
75 * Handle to the statistics service
77 struct GNUNET_STATISTICS_Handle *sh;
89 struct OpListEntry *next;
94 struct OpListEntry *prev;
97 * The testbed operation
99 struct GNUNET_TESTBED_Operation *op;
102 * Depending on whether we start or stop NSE service at the peer set this to 1
110 * Head of DLL of peers we monitor closely.
112 static struct NSEPeer *peer_head;
115 * Tail of DLL of peers we monitor closely.
117 static struct NSEPeer *peer_tail;
120 * Return value from 'main' (0 == success)
125 * Be verbose (configuration option)
127 static unsigned int verbose;
130 * Name of the file with the hosts to run the test over (configuration option)
132 static char *hosts_file;
135 * Maximum number of peers in the test.
137 static unsigned int num_peers;
140 * Total number of rounds to execute.
142 static unsigned int num_rounds;
145 * Current round we are in.
147 static unsigned int current_round;
150 * Array of size 'num_rounds' with the requested number of peers in the given round.
152 static unsigned int *num_peers_in_round;
155 * How many peers are running right now?
157 static unsigned int peers_running;
160 * Specification for the numbers of peers to have in each round.
162 static char *num_peer_spec;
165 * Handles to all of the running peers.
167 static struct GNUNET_TESTBED_Peer **daemons;
170 * Global configuration file
172 static struct GNUNET_CONFIGURATION_Handle *testing_cfg;
175 * Maximum number of connections to NSE services.
177 static unsigned int connection_limit;
180 * Total number of connections in the whole network.
182 static unsigned int total_connections;
185 * File to report results to.
187 static struct GNUNET_DISK_FileHandle *output_file;
190 * Filename to log results to.
192 static char *output_filename;
195 * File to log connection info, statistics to.
197 static struct GNUNET_DISK_FileHandle *data_file;
200 * Filename to log connection info, statistics to.
202 static char *data_filename;
205 * How long to wait before triggering next round?
208 static struct GNUNET_TIME_Relative wait_time = { 60 * 1000 };
211 * DLL head for operation list
213 static struct OpListEntry *oplist_head;
216 * DLL tail for operation list
218 static struct OpListEntry *oplist_tail;
221 * Task running each round of the experiment.
223 static struct GNUNET_SCHEDULER_Task *round_task;
227 * Clean up all of the monitoring connections to NSE and
228 * STATISTICS that we keep to selected peers.
231 close_monitor_connections ()
234 struct OpListEntry *oplist_entry;
236 while (NULL != (pos = peer_head))
238 if (NULL != pos->nse_op)
239 GNUNET_TESTBED_operation_done (pos->nse_op);
240 if (NULL != pos->stat_op)
241 GNUNET_TESTBED_operation_done (pos->stat_op);
242 GNUNET_CONTAINER_DLL_remove (peer_head, peer_tail, pos);
245 while (NULL != (oplist_entry = oplist_head))
247 GNUNET_CONTAINER_DLL_remove (oplist_head, oplist_tail, oplist_entry);
248 GNUNET_TESTBED_operation_done (oplist_entry->op);
249 GNUNET_free (oplist_entry);
255 * Task run on shutdown; cleans up everything.
260 shutdown_task (void *cls)
262 LOG_DEBUG ("Ending test.\n");
263 close_monitor_connections ();
264 if (NULL != round_task)
266 GNUNET_SCHEDULER_cancel (round_task);
269 if (NULL != data_file)
271 GNUNET_DISK_file_close (data_file);
274 if (NULL != output_file)
276 GNUNET_DISK_file_close (output_file);
279 if (NULL != testing_cfg)
281 GNUNET_CONFIGURATION_destroy (testing_cfg);
288 * Callback to call when network size estimate is updated.
290 * @param cls closure with the 'struct NSEPeer' providing the update
291 * @param timestamp server timestamp
292 * @param estimate the value of the current network size estimate
293 * @param std_dev standard deviation (rounded down to nearest integer)
294 * of the size estimation values seen
297 handle_estimate (void *cls,
298 struct GNUNET_TIME_Absolute timestamp,
299 double estimate, double std_dev)
301 struct NSEPeer *peer = cls;
302 char output_buffer[512];
305 if (NULL == output_file)
308 "Received network size estimate from peer %p. Size: %f std.dev. %f\n",
309 peer, estimate, std_dev);
312 size = GNUNET_snprintf (output_buffer,
313 sizeof (output_buffer),
314 "%p %llu %llu %f %f %f\n",
316 (unsigned long long) timestamp.abs_value_us,
317 GNUNET_NSE_log_estimate_to_n (estimate), estimate,
319 if (size != GNUNET_DISK_file_write (output_file, output_buffer, size))
320 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
321 "Unable to write to file!\n");
326 * Adapter function called to establish a connection to
329 * @param cls closure (the 'struct NSEPeer')
330 * @param cfg configuration of the peer to connect to; will be available until
331 * GNUNET_TESTBED_operation_done() is called on the operation returned
332 * from GNUNET_TESTBED_service_connect()
333 * @return service handle to return in 'op_result', NULL on error
336 nse_connect_adapter (void *cls,
337 const struct GNUNET_CONFIGURATION_Handle *cfg)
339 struct NSEPeer *current_peer = cls;
341 return GNUNET_NSE_connect (cfg, &handle_estimate, current_peer);
346 * Adapter function called to destroy a connection to
350 * @param op_result service handle returned from the connect adapter
353 nse_disconnect_adapter (void *cls,
356 GNUNET_NSE_disconnect (op_result);
361 * Callback function to process statistic values.
363 * @param cls `struct NSEPeer`
364 * @param subsystem name of subsystem that created the statistic
365 * @param name the name of the datum
366 * @param value the current value
367 * @param is_persistent #GNUNET_YES if the value is persistent, #GNUNET_NO if not
368 * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
371 stat_iterator (void *cls,
372 const char *subsystem,
378 struct GNUNET_TIME_Absolute now;
382 GNUNET_assert (NULL != data_file);
383 now = GNUNET_TIME_absolute_get ();
384 flag = strcasecmp (subsystem, "core");
387 size = GNUNET_asprintf (&output_buffer, "%llu %llu %u\n",
388 now.abs_value_us / 1000LL / 1000LL,
392 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Error formatting output buffer.\n");
393 GNUNET_free (output_buffer);
394 return GNUNET_SYSERR;
396 if (size != GNUNET_DISK_file_write (data_file, output_buffer, (size_t) size))
398 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unable to write to file!\n");
399 GNUNET_free (output_buffer);
400 return GNUNET_SYSERR;
402 GNUNET_free (output_buffer);
408 * Called to open a connection to the peer's statistics
410 * @param cls peer context
411 * @param cfg configuration of the peer to connect to; will be available until
412 * GNUNET_TESTBED_operation_done() is called on the operation returned
413 * from GNUNET_TESTBED_service_connect()
414 * @return service handle to return in 'op_result', NULL on error
417 stat_connect_adapter (void *cls,
418 const struct GNUNET_CONFIGURATION_Handle *cfg)
420 struct NSEPeer *peer = cls;
422 peer->sh = GNUNET_STATISTICS_create ("nse-profiler", cfg);
428 * Called to disconnect from peer's statistics service
430 * @param cls peer context
431 * @param op_result service handle returned from the connect adapter
434 stat_disconnect_adapter (void *cls, void *op_result)
436 struct NSEPeer *peer = cls;
438 GNUNET_break (GNUNET_OK == GNUNET_STATISTICS_watch_cancel
439 (peer->sh, "core", "# peers connected",
440 stat_iterator, peer));
441 GNUNET_break (GNUNET_OK == GNUNET_STATISTICS_watch_cancel
442 (peer->sh, "nse", "# peers connected",
443 stat_iterator, peer));
444 GNUNET_STATISTICS_destroy (op_result, GNUNET_NO);
450 * Called after successfully opening a connection to a peer's statistics
451 * service; we register statistics monitoring for CORE and NSE here.
453 * @param cls the callback closure from functions generating an operation
454 * @param op the operation that has been finished
455 * @param ca_result the service handle returned from GNUNET_TESTBED_ConnectAdapter()
456 * @param emsg error message in case the operation has failed; will be NULL if
457 * operation has executed successfully.
460 stat_comp_cb (void *cls, struct GNUNET_TESTBED_Operation *op,
461 void *ca_result, const char *emsg )
463 struct GNUNET_STATISTICS_Handle *sh = ca_result;
464 struct NSEPeer *peer = cls;
471 GNUNET_break (GNUNET_OK == GNUNET_STATISTICS_watch
472 (sh, "core", "# peers connected",
473 stat_iterator, peer));
474 GNUNET_break (GNUNET_OK == GNUNET_STATISTICS_watch
475 (sh, "nse", "# peers connected",
476 stat_iterator, peer));
481 * Task run to connect to the NSE and statistics services to a subset of
482 * all of the running peers.
485 connect_nse_service ()
487 struct NSEPeer *current_peer;
489 unsigned int connections;
491 if (0 == connection_limit)
493 LOG_DEBUG ("Connecting to nse service of peers\n");
495 for (i = 0; i < num_peers_in_round[current_round]; i++)
497 if ((num_peers_in_round[current_round] > connection_limit) &&
498 (0 != (i % (num_peers_in_round[current_round] / connection_limit))))
500 LOG_DEBUG ("Connecting to nse service of peer %d\n", i);
501 current_peer = GNUNET_new (struct NSEPeer);
502 current_peer->daemon = daemons[i];
504 = GNUNET_TESTBED_service_connect (NULL,
505 current_peer->daemon,
508 &nse_connect_adapter,
509 &nse_disconnect_adapter,
511 if (NULL != data_file)
512 current_peer->stat_op
513 = GNUNET_TESTBED_service_connect (NULL,
514 current_peer->daemon,
518 &stat_connect_adapter,
519 &stat_disconnect_adapter,
521 GNUNET_CONTAINER_DLL_insert (peer_head, peer_tail, current_peer);
522 if (++connections == connection_limit)
529 * Task that starts/stops peers to move to the next round.
531 * @param cls NULL, unused
534 next_round (void *cls);
538 * We're at the end of a round. Stop monitoring, write total
539 * number of connections to log and get full stats. Then trigger
542 * @param cls unused, NULL
545 finish_round (void *cls)
547 LOG (GNUNET_ERROR_TYPE_INFO,
548 "Have %u connections\n",
550 close_monitor_connections ();
551 round_task = GNUNET_SCHEDULER_add_now (&next_round, NULL);
556 * We have reached the desired number of peers for the current round.
557 * Run it (by connecting and monitoring a few peers and waiting the
558 * specified delay before finishing the round).
563 LOG_DEBUG ("Running round %u\n",
565 connect_nse_service ();
566 GNUNET_SCHEDULER_add_delayed (wait_time,
573 * Creates an oplist entry and adds it to the oplist DLL
575 static struct OpListEntry *
578 struct OpListEntry *entry;
580 entry = GNUNET_new (struct OpListEntry);
581 GNUNET_CONTAINER_DLL_insert_tail (oplist_head,
589 * Callback to be called when NSE service is started or stopped at peers
592 * @param op the operation handle
593 * @param emsg NULL on success; otherwise an error description
596 manage_service_cb (void *cls,
597 struct GNUNET_TESTBED_Operation *op,
600 struct OpListEntry *entry = cls;
602 GNUNET_TESTBED_operation_done (entry->op);
605 LOG (GNUNET_ERROR_TYPE_ERROR, "Failed to start/stop NSE at a peer\n");
606 GNUNET_SCHEDULER_shutdown ();
609 GNUNET_assert (0 != entry->delta);
610 peers_running += entry->delta;
611 GNUNET_CONTAINER_DLL_remove (oplist_head,
615 if (num_peers_in_round[current_round] == peers_running)
621 * Adjust the number of running peers to match the required number of running
622 * peers for the round
625 adjust_running_peers ()
627 struct OpListEntry *entry;
630 /* start peers if we have too few */
631 for (i=peers_running;i<num_peers_in_round[current_round];i++)
633 entry = make_oplist_entry ();
635 entry->op = GNUNET_TESTBED_peer_manage_service (NULL,
642 /* stop peers if we have too many */
643 for (i=num_peers_in_round[current_round];i<peers_running;i++)
645 entry = make_oplist_entry ();
647 entry->op = GNUNET_TESTBED_peer_manage_service (NULL,
658 * Task run at the end of a round. Disconnect from all monitored
659 * peers; then get statistics from *all* peers.
661 * @param cls NULL, unused
664 next_round (void *cls)
667 LOG_DEBUG ("Disconnecting nse service of peers\n");
669 if (current_round == num_rounds)
671 /* this was the last round, terminate */
673 GNUNET_SCHEDULER_shutdown ();
676 if (num_peers_in_round[current_round] == peers_running)
678 /* no need to churn, just run next round */
682 adjust_running_peers ();
687 * Function that will be called whenever something in the
690 * @param cls closure, NULL
691 * @param event information on what is happening
694 master_controller_cb (void *cls,
695 const struct GNUNET_TESTBED_EventInformation *event)
699 case GNUNET_TESTBED_ET_CONNECT:
702 case GNUNET_TESTBED_ET_DISCONNECT:
712 * Signature of a main function for a testcase.
715 * @param h the run handle
716 * @param num_peers_ number of peers in 'peers'
717 * @param peers handle to peers run in the testbed. NULL upon timeout (see
718 * GNUNET_TESTBED_test_run()).
719 * @param links_succeeded the number of overlay link connection attempts that
721 * @param links_failed the number of overlay link connection attempts that
725 test_master (void *cls,
726 struct GNUNET_TESTBED_RunHandle *h,
727 unsigned int num_peers_,
728 struct GNUNET_TESTBED_Peer **peers,
729 unsigned int links_succeeded,
730 unsigned int links_failed)
734 GNUNET_SCHEDULER_shutdown ();
738 GNUNET_break (num_peers_ == num_peers);
739 peers_running = num_peers;
740 if (num_peers_in_round[current_round] == peers_running)
742 /* no need to churn, just run the starting round */
746 adjust_running_peers ();
751 * Actual main function that runs the emulation.
754 * @param args remaining args, unused
755 * @param cfgfile name of the configuration
756 * @param cfg configuration handle
759 run (void *cls, char *const *args, const char *cfgfile,
760 const struct GNUNET_CONFIGURATION_Handle *cfg)
767 testing_cfg = GNUNET_CONFIGURATION_dup (cfg);
768 LOG_DEBUG ("Starting daemons.\n");
769 if (NULL == num_peer_spec)
771 fprintf (stderr, "You need to specify the number of peers to run\n");
774 for (tok = strtok (num_peer_spec, ","); NULL != tok; tok = strtok (NULL, ","))
776 if (1 != sscanf (tok, "%u", &num))
778 fprintf (stderr, "You need to specify numbers, not `%s'\n", tok);
783 fprintf (stderr, "Refusing to run a round with 0 peers\n");
786 GNUNET_array_append (num_peers_in_round, num_rounds, num);
787 num_peers = GNUNET_MAX (num_peers, num);
791 fprintf (stderr, "Refusing to run a testbed with no rounds\n");
794 if ( (NULL != data_filename) &&
795 (NULL == (data_file =
796 GNUNET_DISK_file_open (data_filename,
797 GNUNET_DISK_OPEN_READWRITE |
798 GNUNET_DISK_OPEN_TRUNCATE |
799 GNUNET_DISK_OPEN_CREATE,
800 GNUNET_DISK_PERM_USER_READ |
801 GNUNET_DISK_PERM_USER_WRITE))) )
802 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
806 if ( (NULL != output_filename) &&
807 (NULL == (output_file =
808 GNUNET_DISK_file_open (output_filename,
809 GNUNET_DISK_OPEN_READWRITE |
810 GNUNET_DISK_OPEN_CREATE,
811 GNUNET_DISK_PERM_USER_READ |
812 GNUNET_DISK_PERM_USER_WRITE))) )
813 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open",
816 event_mask |= (1LL << GNUNET_TESTBED_ET_PEER_START);
817 event_mask |= (1LL << GNUNET_TESTBED_ET_PEER_STOP);
818 event_mask |= (1LL << GNUNET_TESTBED_ET_CONNECT);
819 event_mask |= (1LL << GNUNET_TESTBED_ET_DISCONNECT);
820 GNUNET_TESTBED_run (hosts_file,
824 master_controller_cb,
825 NULL, /* master_controller_cb cls */
827 NULL); /* test_master cls */
828 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
835 * @return 0 on success
838 main (int argc, char *const *argv)
840 struct GNUNET_GETOPT_CommandLineOption options[] = {
841 GNUNET_GETOPT_option_uint ('C',
844 gettext_noop ("limit to the number of connections to NSE services, 0 for none"),
846 GNUNET_GETOPT_option_string ('d',
849 gettext_noop ("name of the file for writing connection information and statistics"),
852 GNUNET_GETOPT_option_string ('H',
855 gettext_noop ("name of the file with the login information for the testbed"),
858 GNUNET_GETOPT_option_string ('o',
861 gettext_noop ("name of the file for writing the main results"),
865 GNUNET_GETOPT_option_string ('p',
868 gettext_noop ("Number of peers to run in each round, separated by commas"),
871 GNUNET_GETOPT_option_increment_uint ('V',
873 gettext_noop ("be verbose (print progress information)"),
876 GNUNET_GETOPT_option_relative_time ('w',
879 gettext_noop ("delay between rounds"),
881 GNUNET_GETOPT_OPTION_END
883 if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
886 GNUNET_PROGRAM_run (argc, argv, "nse-profiler",
888 ("Measure quality and performance of the NSE service."),
889 options, &run, NULL))
894 /* end of nse-profiler.c */