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.
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/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
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,
383 struct GNUNET_TIME_Absolute now;
387 GNUNET_assert (NULL != data_file);
388 now = GNUNET_TIME_absolute_get ();
389 flag = strcasecmp (subsystem, "core");
392 size = GNUNET_asprintf (&output_buffer, "%llu %llu %u\n",
393 now.abs_value_us / 1000LL / 1000LL,
397 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Error formatting output buffer.\n");
398 GNUNET_free (output_buffer);
399 return GNUNET_SYSERR;
401 if (size != GNUNET_DISK_file_write (data_file, output_buffer, (size_t) size))
403 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unable to write to file!\n");
404 GNUNET_free (output_buffer);
405 return GNUNET_SYSERR;
407 GNUNET_free (output_buffer);
413 * Called to open a connection to the peer's statistics
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
422 stat_connect_adapter (void *cls,
423 const struct GNUNET_CONFIGURATION_Handle *cfg)
425 struct NSEPeer *peer = cls;
427 peer->sh = GNUNET_STATISTICS_create ("nse-profiler", cfg);
433 * Called to disconnect from peer's statistics service
435 * @param cls peer context
436 * @param op_result service handle returned from the connect adapter
439 stat_disconnect_adapter (void *cls, void *op_result)
441 struct NSEPeer *peer = cls;
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);
455 * Called after successfully opening a connection to a peer's statistics
456 * service; we register statistics monitoring for CORE and NSE here.
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.
465 stat_comp_cb (void *cls, struct GNUNET_TESTBED_Operation *op,
466 void *ca_result, const char *emsg )
468 struct GNUNET_STATISTICS_Handle *sh = ca_result;
469 struct NSEPeer *peer = cls;
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));
486 * Task run to connect to the NSE and statistics services to a subset of
487 * all of the running peers.
490 connect_nse_service ()
492 struct NSEPeer *current_peer;
494 unsigned int connections;
496 if (0 == connection_limit)
498 LOG_DEBUG ("Connecting to nse service of peers\n");
500 for (i = 0; i < num_peers_in_round[current_round]; i++)
502 if ((num_peers_in_round[current_round] > connection_limit) &&
503 (0 != (i % (num_peers_in_round[current_round] / connection_limit))))
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];
509 = GNUNET_TESTBED_service_connect (NULL,
510 current_peer->daemon,
513 &nse_connect_adapter,
514 &nse_disconnect_adapter,
516 if (NULL != data_file)
517 current_peer->stat_op
518 = GNUNET_TESTBED_service_connect (NULL,
519 current_peer->daemon,
523 &stat_connect_adapter,
524 &stat_disconnect_adapter,
526 GNUNET_CONTAINER_DLL_insert (peer_head, peer_tail, current_peer);
527 if (++connections == connection_limit)
534 * Task that starts/stops peers to move to the next round.
536 * @param cls NULL, unused
539 next_round (void *cls);
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
547 * @param cls unused, NULL
550 finish_round (void *cls)
552 LOG (GNUNET_ERROR_TYPE_INFO,
553 "Have %u connections\n",
555 close_monitor_connections ();
556 round_task = GNUNET_SCHEDULER_add_now (&next_round, NULL);
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).
568 LOG_DEBUG ("Running round %u\n",
570 connect_nse_service ();
571 GNUNET_SCHEDULER_add_delayed (wait_time,
578 * Creates an oplist entry and adds it to the oplist DLL
580 static struct OpListEntry *
583 struct OpListEntry *entry;
585 entry = GNUNET_new (struct OpListEntry);
586 GNUNET_CONTAINER_DLL_insert_tail (oplist_head,
594 * Callback to be called when NSE service is started or stopped at peers
597 * @param op the operation handle
598 * @param emsg NULL on success; otherwise an error description
601 manage_service_cb (void *cls,
602 struct GNUNET_TESTBED_Operation *op,
605 struct OpListEntry *entry = cls;
607 GNUNET_TESTBED_operation_done (entry->op);
610 LOG (GNUNET_ERROR_TYPE_ERROR, "Failed to start/stop NSE at a peer\n");
611 GNUNET_SCHEDULER_shutdown ();
614 GNUNET_assert (0 != entry->delta);
615 peers_running += entry->delta;
616 GNUNET_CONTAINER_DLL_remove (oplist_head,
620 if (num_peers_in_round[current_round] == peers_running)
626 * Adjust the number of running peers to match the required number of running
627 * peers for the round
630 adjust_running_peers ()
632 struct OpListEntry *entry;
635 /* start peers if we have too few */
636 for (i=peers_running;i<num_peers_in_round[current_round];i++)
638 entry = make_oplist_entry ();
640 entry->op = GNUNET_TESTBED_peer_manage_service (NULL,
647 /* stop peers if we have too many */
648 for (i=num_peers_in_round[current_round];i<peers_running;i++)
650 entry = make_oplist_entry ();
652 entry->op = GNUNET_TESTBED_peer_manage_service (NULL,
663 * Task run at the end of a round. Disconnect from all monitored
664 * peers; then get statistics from *all* peers.
666 * @param cls NULL, unused
669 next_round (void *cls)
672 LOG_DEBUG ("Disconnecting nse service of peers\n");
674 if (current_round == num_rounds)
676 /* this was the last round, terminate */
678 GNUNET_SCHEDULER_shutdown ();
681 if (num_peers_in_round[current_round] == peers_running)
683 /* no need to churn, just run next round */
687 adjust_running_peers ();
692 * Function that will be called whenever something in the
695 * @param cls closure, NULL
696 * @param event information on what is happening
699 master_controller_cb (void *cls,
700 const struct GNUNET_TESTBED_EventInformation *event)
704 case GNUNET_TESTBED_ET_CONNECT:
707 case GNUNET_TESTBED_ET_DISCONNECT:
717 * Signature of a main function for a testcase.
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
726 * @param links_failed the number of overlay link connection attempts that
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)
739 GNUNET_SCHEDULER_shutdown ();
743 GNUNET_break (num_peers_ == num_peers);
744 peers_running = num_peers;
745 if (num_peers_in_round[current_round] == peers_running)
747 /* no need to churn, just run the starting round */
751 adjust_running_peers ();
756 * Actual main function that runs the emulation.
759 * @param args remaining args, unused
760 * @param cfgfile name of the configuration
761 * @param cfg configuration handle
764 run (void *cls, char *const *args, const char *cfgfile,
765 const struct GNUNET_CONFIGURATION_Handle *cfg)
772 testing_cfg = GNUNET_CONFIGURATION_dup (cfg);
773 LOG_DEBUG ("Starting daemons.\n");
774 if (NULL == num_peer_spec)
776 fprintf (stderr, "You need to specify the number of peers to run\n");
779 for (tok = strtok (num_peer_spec, ","); NULL != tok; tok = strtok (NULL, ","))
781 if (1 != sscanf (tok, "%u", &num))
783 fprintf (stderr, "You need to specify numbers, not `%s'\n", tok);
788 fprintf (stderr, "Refusing to run a round with 0 peers\n");
791 GNUNET_array_append (num_peers_in_round, num_rounds, num);
792 num_peers = GNUNET_MAX (num_peers, num);
796 fprintf (stderr, "Refusing to run a testbed with no rounds\n");
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,
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",
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,
829 master_controller_cb,
830 NULL, /* master_controller_cb cls */
832 NULL); /* test_master cls */
833 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
840 * @return 0 on success
843 main (int argc, char *const *argv)
845 struct GNUNET_GETOPT_CommandLineOption options[] = {
846 GNUNET_GETOPT_option_uint ('C',
849 gettext_noop ("limit to the number of connections to NSE services, 0 for none"),
851 GNUNET_GETOPT_option_string ('d',
854 gettext_noop ("name of the file for writing connection information and statistics"),
857 GNUNET_GETOPT_option_string ('H',
860 gettext_noop ("name of the file with the login information for the testbed"),
863 GNUNET_GETOPT_option_string ('o',
866 gettext_noop ("name of the file for writing the main results"),
870 GNUNET_GETOPT_option_string ('p',
873 gettext_noop ("Number of peers to run in each round, separated by commas"),
876 GNUNET_GETOPT_option_increment_uint ('V',
878 gettext_noop ("be verbose (print progress information)"),
881 GNUNET_GETOPT_option_relative_time ('w',
884 gettext_noop ("delay between rounds"),
886 GNUNET_GETOPT_OPTION_END
888 if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
891 GNUNET_PROGRAM_run (argc, argv, "nse-profiler",
893 ("Measure quality and performance of the NSE service."),
894 options, &run, NULL))
899 /* end of nse-profiler.c */