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, ...) GNUNET_log (kind, __VA_ARGS__)
42 * Debug logging shorthand
44 #define LOG_DEBUG(...) LOG (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
48 * Information we track for a peer in the testbed.
53 * Prev reference in DLL.
58 * Next reference in DLL.
63 * Handle with testbed.
65 struct GNUNET_TESTBED_Peer *daemon;
68 * Testbed operation to connect to NSE service.
70 struct GNUNET_TESTBED_Operation *nse_op;
73 * Testbed operation to connect to statistics service
75 struct GNUNET_TESTBED_Operation *stat_op;
78 * Handle to the statistics service
80 struct GNUNET_STATISTICS_Handle *sh;
92 struct OpListEntry *next;
97 struct OpListEntry *prev;
100 * The testbed operation
102 struct GNUNET_TESTBED_Operation *op;
105 * Depending on whether we start or stop NSE service at the peer set this to 1
113 * Head of DLL of peers we monitor closely.
115 static struct NSEPeer *peer_head;
118 * Tail of DLL of peers we monitor closely.
120 static struct NSEPeer *peer_tail;
123 * Return value from 'main' (0 == success)
128 * Be verbose (configuration option)
130 static unsigned int verbose;
133 * Name of the file with the hosts to run the test over (configuration option)
135 static char *hosts_file;
138 * Maximum number of peers in the test.
140 static unsigned int num_peers;
143 * Total number of rounds to execute.
145 static unsigned int num_rounds;
148 * Current round we are in.
150 static unsigned int current_round;
153 * Array of size 'num_rounds' with the requested number of peers in the given round.
155 static unsigned int *num_peers_in_round;
158 * How many peers are running right now?
160 static unsigned int peers_running;
163 * Specification for the numbers of peers to have in each round.
165 static char *num_peer_spec;
168 * Handles to all of the running peers.
170 static struct GNUNET_TESTBED_Peer **daemons;
173 * Global configuration file
175 static struct GNUNET_CONFIGURATION_Handle *testing_cfg;
178 * Maximum number of connections to NSE services.
180 static unsigned int connection_limit;
183 * Total number of connections in the whole network.
185 static unsigned int total_connections;
188 * File to report results to.
190 static struct GNUNET_DISK_FileHandle *output_file;
193 * Filename to log results to.
195 static char *output_filename;
198 * File to log connection info, statistics to.
200 static struct GNUNET_DISK_FileHandle *data_file;
203 * Filename to log connection info, statistics to.
205 static char *data_filename;
208 * How long to wait before triggering next round?
211 static struct GNUNET_TIME_Relative wait_time = { 60 * 1000 };
214 * DLL head for operation list
216 static struct OpListEntry *oplist_head;
219 * DLL tail for operation list
221 static struct OpListEntry *oplist_tail;
224 * Task running each round of the experiment.
226 static struct GNUNET_SCHEDULER_Task *round_task;
230 * Clean up all of the monitoring connections to NSE and
231 * STATISTICS that we keep to selected peers.
234 close_monitor_connections ()
237 struct OpListEntry *oplist_entry;
239 while (NULL != (pos = peer_head))
241 if (NULL != pos->nse_op)
242 GNUNET_TESTBED_operation_done (pos->nse_op);
243 if (NULL != pos->stat_op)
244 GNUNET_TESTBED_operation_done (pos->stat_op);
245 GNUNET_CONTAINER_DLL_remove (peer_head, peer_tail, pos);
248 while (NULL != (oplist_entry = oplist_head))
250 GNUNET_CONTAINER_DLL_remove (oplist_head, oplist_tail, oplist_entry);
251 GNUNET_TESTBED_operation_done (oplist_entry->op);
252 GNUNET_free (oplist_entry);
258 * Task run on shutdown; cleans up everything.
263 shutdown_task (void *cls)
265 LOG_DEBUG ("Ending test.\n");
266 close_monitor_connections ();
267 if (NULL != round_task)
269 GNUNET_SCHEDULER_cancel (round_task);
272 if (NULL != data_file)
274 GNUNET_DISK_file_close (data_file);
277 if (NULL != output_file)
279 GNUNET_DISK_file_close (output_file);
282 if (NULL != testing_cfg)
284 GNUNET_CONFIGURATION_destroy (testing_cfg);
291 * Callback to call when network size estimate is updated.
293 * @param cls closure with the 'struct NSEPeer' providing the update
294 * @param timestamp server timestamp
295 * @param estimate the value of the current network size estimate
296 * @param std_dev standard deviation (rounded down to nearest integer)
297 * of the size estimation values seen
300 handle_estimate (void *cls,
301 struct GNUNET_TIME_Absolute timestamp,
305 struct NSEPeer *peer = cls;
306 char output_buffer[512];
309 if (NULL == output_file)
312 "Received network size estimate from peer %p. Size: %f std.dev. %f\n",
318 size = GNUNET_snprintf (output_buffer,
319 sizeof(output_buffer),
320 "%p %llu %llu %f %f %f\n",
323 (unsigned long long) timestamp.abs_value_us,
324 GNUNET_NSE_log_estimate_to_n (estimate),
327 if (size != GNUNET_DISK_file_write (output_file, output_buffer, size))
328 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unable to write to file!\n");
333 * Adapter function called to establish a connection to
336 * @param cls closure (the 'struct NSEPeer')
337 * @param cfg configuration of the peer to connect to; will be available until
338 * GNUNET_TESTBED_operation_done() is called on the operation returned
339 * from GNUNET_TESTBED_service_connect()
340 * @return service handle to return in 'op_result', NULL on error
343 nse_connect_adapter (void *cls, const struct GNUNET_CONFIGURATION_Handle *cfg)
345 struct NSEPeer *current_peer = cls;
347 return GNUNET_NSE_connect (cfg, &handle_estimate, current_peer);
352 * Adapter function called to destroy a connection to
356 * @param op_result service handle returned from the connect adapter
359 nse_disconnect_adapter (void *cls, void *op_result)
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,
394 now.abs_value_us / 1000LL / 1000LL,
399 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Error formatting output buffer.\n");
400 GNUNET_free (output_buffer);
401 return GNUNET_SYSERR;
403 if (size != GNUNET_DISK_file_write (data_file, output_buffer, (size_t) size))
405 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unable to write to file!\n");
406 GNUNET_free (output_buffer);
407 return GNUNET_SYSERR;
409 GNUNET_free (output_buffer);
415 * Called to open a connection to the peer's statistics
417 * @param cls peer context
418 * @param cfg configuration of the peer to connect to; will be available until
419 * GNUNET_TESTBED_operation_done() is called on the operation returned
420 * from GNUNET_TESTBED_service_connect()
421 * @return service handle to return in 'op_result', NULL on error
424 stat_connect_adapter (void *cls, const struct GNUNET_CONFIGURATION_Handle *cfg)
426 struct NSEPeer *peer = cls;
428 peer->sh = GNUNET_STATISTICS_create ("nse-profiler", cfg);
434 * Called to disconnect from peer's statistics service
436 * @param cls peer context
437 * @param op_result service handle returned from the connect adapter
440 stat_disconnect_adapter (void *cls, void *op_result)
442 struct NSEPeer *peer = cls;
444 GNUNET_break (GNUNET_OK ==
445 GNUNET_STATISTICS_watch_cancel (peer->sh,
450 GNUNET_break (GNUNET_OK ==
451 GNUNET_STATISTICS_watch_cancel (peer->sh,
456 GNUNET_STATISTICS_destroy (op_result, GNUNET_NO);
462 * Called after successfully opening a connection to a peer's statistics
463 * service; we register statistics monitoring for CORE and NSE here.
465 * @param cls the callback closure from functions generating an operation
466 * @param op the operation that has been finished
467 * @param ca_result the service handle returned from GNUNET_TESTBED_ConnectAdapter()
468 * @param emsg error message in case the operation has failed; will be NULL if
469 * operation has executed successfully.
472 stat_comp_cb (void *cls,
473 struct GNUNET_TESTBED_Operation *op,
477 struct GNUNET_STATISTICS_Handle *sh = ca_result;
478 struct NSEPeer *peer = cls;
485 GNUNET_break (GNUNET_OK == GNUNET_STATISTICS_watch (sh,
490 GNUNET_break (GNUNET_OK == GNUNET_STATISTICS_watch (sh,
499 * Task run to connect to the NSE and statistics services to a subset of
500 * all of the running peers.
503 connect_nse_service ()
505 struct NSEPeer *current_peer;
507 unsigned int connections;
509 if (0 == connection_limit)
511 LOG_DEBUG ("Connecting to nse service of peers\n");
513 for (i = 0; i < num_peers_in_round[current_round]; i++)
515 if ((num_peers_in_round[current_round] > connection_limit) &&
516 (0 != (i % (num_peers_in_round[current_round] / connection_limit))))
518 LOG_DEBUG ("Connecting to nse service of peer %d\n", i);
519 current_peer = GNUNET_new (struct NSEPeer);
520 current_peer->daemon = daemons[i];
521 current_peer->nse_op =
522 GNUNET_TESTBED_service_connect (NULL,
523 current_peer->daemon,
527 &nse_connect_adapter,
528 &nse_disconnect_adapter,
530 if (NULL != data_file)
531 current_peer->stat_op =
532 GNUNET_TESTBED_service_connect (NULL,
533 current_peer->daemon,
537 &stat_connect_adapter,
538 &stat_disconnect_adapter,
540 GNUNET_CONTAINER_DLL_insert (peer_head, peer_tail, current_peer);
541 if (++connections == connection_limit)
548 * Task that starts/stops peers to move to the next round.
550 * @param cls NULL, unused
553 next_round (void *cls);
557 * We're at the end of a round. Stop monitoring, write total
558 * number of connections to log and get full stats. Then trigger
561 * @param cls unused, NULL
564 finish_round (void *cls)
566 LOG (GNUNET_ERROR_TYPE_INFO, "Have %u connections\n", total_connections);
567 close_monitor_connections ();
568 round_task = GNUNET_SCHEDULER_add_now (&next_round, NULL);
573 * We have reached the desired number of peers for the current round.
574 * Run it (by connecting and monitoring a few peers and waiting the
575 * specified delay before finishing the round).
580 LOG_DEBUG ("Running round %u\n", current_round);
581 connect_nse_service ();
582 GNUNET_SCHEDULER_add_delayed (wait_time, &finish_round, NULL);
587 * Creates an oplist entry and adds it to the oplist DLL
589 static struct OpListEntry *
592 struct OpListEntry *entry;
594 entry = GNUNET_new (struct OpListEntry);
595 GNUNET_CONTAINER_DLL_insert_tail (oplist_head, oplist_tail, entry);
601 * Callback to be called when NSE service is started or stopped at peers
604 * @param op the operation handle
605 * @param emsg NULL on success; otherwise an error description
608 manage_service_cb (void *cls,
609 struct GNUNET_TESTBED_Operation *op,
612 struct OpListEntry *entry = cls;
614 GNUNET_TESTBED_operation_done (entry->op);
617 LOG (GNUNET_ERROR_TYPE_ERROR, "Failed to start/stop NSE at a peer\n");
618 GNUNET_SCHEDULER_shutdown ();
621 GNUNET_assert (0 != entry->delta);
622 peers_running += entry->delta;
623 GNUNET_CONTAINER_DLL_remove (oplist_head, oplist_tail, entry);
625 if (num_peers_in_round[current_round] == peers_running)
631 * Adjust the number of running peers to match the required number of running
632 * peers for the round
635 adjust_running_peers ()
637 struct OpListEntry *entry;
640 /* start peers if we have too few */
641 for (i = peers_running; i < num_peers_in_round[current_round]; i++)
643 entry = make_oplist_entry ();
645 entry->op = GNUNET_TESTBED_peer_manage_service (NULL,
652 /* stop peers if we have too many */
653 for (i = num_peers_in_round[current_round]; i < peers_running; i++)
655 entry = make_oplist_entry ();
657 entry->op = GNUNET_TESTBED_peer_manage_service (NULL,
668 * Task run at the end of a round. Disconnect from all monitored
669 * peers; then get statistics from *all* peers.
671 * @param cls NULL, unused
674 next_round (void *cls)
677 LOG_DEBUG ("Disconnecting nse service of peers\n");
679 if (current_round == num_rounds)
681 /* this was the last round, terminate */
683 GNUNET_SCHEDULER_shutdown ();
686 if (num_peers_in_round[current_round] == peers_running)
688 /* no need to churn, just run next round */
692 adjust_running_peers ();
697 * Function that will be called whenever something in the
700 * @param cls closure, NULL
701 * @param event information on what is happening
704 master_controller_cb (void *cls,
705 const struct GNUNET_TESTBED_EventInformation *event)
709 case GNUNET_TESTBED_ET_CONNECT:
713 case GNUNET_TESTBED_ET_DISCONNECT:
724 * Signature of a main function for a testcase.
727 * @param h the run handle
728 * @param num_peers_ number of peers in 'peers'
729 * @param peers handle to peers run in the testbed. NULL upon timeout (see
730 * GNUNET_TESTBED_test_run()).
731 * @param links_succeeded the number of overlay link connection attempts that
733 * @param links_failed the number of overlay link connection attempts that
737 test_master (void *cls,
738 struct GNUNET_TESTBED_RunHandle *h,
739 unsigned int num_peers_,
740 struct GNUNET_TESTBED_Peer **peers,
741 unsigned int links_succeeded,
742 unsigned int links_failed)
746 GNUNET_SCHEDULER_shutdown ();
750 GNUNET_break (num_peers_ == num_peers);
751 peers_running = num_peers;
752 if (num_peers_in_round[current_round] == peers_running)
754 /* no need to churn, just run the starting round */
758 adjust_running_peers ();
763 * Actual main function that runs the emulation.
766 * @param args remaining args, unused
767 * @param cfgfile name of the configuration
768 * @param cfg configuration handle
774 const struct GNUNET_CONFIGURATION_Handle *cfg)
781 testing_cfg = GNUNET_CONFIGURATION_dup (cfg);
782 LOG_DEBUG ("Starting daemons.\n");
783 if (NULL == num_peer_spec)
785 fprintf (stderr, "You need to specify the number of peers to run\n");
788 for (tok = strtok (num_peer_spec, ","); NULL != tok; tok = strtok (NULL, ","))
790 if (1 != sscanf (tok, "%u", &num))
792 fprintf (stderr, "You need to specify numbers, not `%s'\n", tok);
797 fprintf (stderr, "Refusing to run a round with 0 peers\n");
800 GNUNET_array_append (num_peers_in_round, num_rounds, num);
801 num_peers = GNUNET_MAX (num_peers, num);
805 fprintf (stderr, "Refusing to run a testbed with no rounds\n");
808 if ((NULL != data_filename) &&
810 (data_file = GNUNET_DISK_file_open (data_filename,
811 GNUNET_DISK_OPEN_READWRITE
812 | GNUNET_DISK_OPEN_TRUNCATE
813 | GNUNET_DISK_OPEN_CREATE,
814 GNUNET_DISK_PERM_USER_READ
815 | GNUNET_DISK_PERM_USER_WRITE))))
816 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", data_filename);
818 if ((NULL != output_filename) &&
820 (output_file = GNUNET_DISK_file_open (output_filename,
821 GNUNET_DISK_OPEN_READWRITE
822 | GNUNET_DISK_OPEN_CREATE,
823 GNUNET_DISK_PERM_USER_READ
824 | GNUNET_DISK_PERM_USER_WRITE))))
825 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", output_filename);
827 event_mask |= (1LL << GNUNET_TESTBED_ET_PEER_START);
828 event_mask |= (1LL << GNUNET_TESTBED_ET_PEER_STOP);
829 event_mask |= (1LL << GNUNET_TESTBED_ET_CONNECT);
830 event_mask |= (1LL << GNUNET_TESTBED_ET_DISCONNECT);
831 GNUNET_TESTBED_run (hosts_file,
835 master_controller_cb,
836 NULL, /* master_controller_cb cls */
838 NULL); /* test_master cls */
839 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
846 * @return 0 on success
849 main (int argc, char *const *argv)
851 struct GNUNET_GETOPT_CommandLineOption options[] =
852 { GNUNET_GETOPT_option_uint (
857 "limit to the number of connections to NSE services, 0 for none"),
859 GNUNET_GETOPT_option_string (
864 "name of the file for writing connection information and statistics"),
867 GNUNET_GETOPT_option_string (
872 "name of the file with the login information for the testbed"),
875 GNUNET_GETOPT_option_string (
879 gettext_noop ("name of the file for writing the main results"),
883 GNUNET_GETOPT_option_string (
888 "Number of peers to run in each round, separated by commas"),
891 GNUNET_GETOPT_option_increment_uint (
894 gettext_noop ("be verbose (print progress information)"),
897 GNUNET_GETOPT_option_relative_time ('w',
900 gettext_noop ("delay between rounds"),
902 GNUNET_GETOPT_OPTION_END };
904 if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
908 GNUNET_PROGRAM_run (argc,
912 "Measure quality and performance of the NSE service."),
920 /* end of nse-profiler.c */