X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Fnse%2Fgnunet-nse-profiler.c;h=b8fa293d58de6faf86dcf5095a660b044eac89a9;hb=80d2de6cdc4d253c7fbc6a4bc067d856aab9cca9;hp=9342480c2534eb9f4b0e5da8463489190458d250;hpb=563b71afee70ac213a8bb28ce0697fcbae06aac3;p=oweals%2Fgnunet.git diff --git a/src/nse/gnunet-nse-profiler.c b/src/nse/gnunet-nse-profiler.c index 9342480c2..b8fa293d5 100644 --- a/src/nse/gnunet-nse-profiler.c +++ b/src/nse/gnunet-nse-profiler.c @@ -1,6 +1,6 @@ /* This file is part of GNUnet. - (C) 2011 Christian Grothoff (and other contributing authors) + Copyright (C) 2011, 2012 Christian Grothoff (and other contributing authors) GNUnet is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published @@ -14,8 +14,8 @@ You should have received a copy of the GNU General Public License along with GNUnet; see the file COPYING. If not, write to the - Free Software Foundation, Inc., 59 Temple Place - Suite 330, - Boston, MA 02111-1307, USA. + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ /** * @file nse/gnunet-nse-profiler.c @@ -24,76 +24,167 @@ * Generally, the profiler starts a given number of peers, * then churns some off, waits a certain amount of time, then * churns again, and repeats. + * @author Christian Grothoff + * @author Nathan Evans + * @author Sree Harsha Totakura */ + #include "platform.h" -#include "gnunet_testing_lib.h" +#include "gnunet_testbed_service.h" #include "gnunet_nse_service.h" -#define VERBOSE GNUNET_NO +/** + * Generic loggins shorthand + */ +#define LOG(kind,...) \ + GNUNET_log (kind, __VA_ARGS__) + +/** + * Debug logging shorthand + */ +#define LOG_DEBUG(...) \ + LOG (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__) + +/** + * Information we track for a peer in the testbed. + */ struct NSEPeer { + /** + * Prev reference in DLL. + */ struct NSEPeer *prev; + /** + * Next reference in DLL. + */ struct NSEPeer *next; - struct GNUNET_TESTING_Daemon *daemon; - - struct GNUNET_NSE_Handle *nse_handle; + /** + * Handle with testbed. + */ + struct GNUNET_TESTBED_Peer *daemon; + + /** + * Testbed operation to connect to NSE service. + */ + struct GNUNET_TESTBED_Operation *nse_op; + + /** + * Testbed operation to connect to statistics service + */ + struct GNUNET_TESTBED_Operation *stat_op; + + /** + * Handle to the statistics service + */ + struct GNUNET_STATISTICS_Handle *sh; }; -struct StatsContext +/** + * Operation map entry + */ +struct OpListEntry { - unsigned long long total_nse_bytes; + /** + * DLL next ptr + */ + struct OpListEntry *next; + + /** + * DLL prev ptr + */ + struct OpListEntry *prev; + + /** + * The testbed operation + */ + struct GNUNET_TESTBED_Operation *op; + + /** + * Depending on whether we start or stop NSE service at the peer set this to 1 + * or -1 + */ + int delta; }; +/** + * Head of DLL of peers we monitor closely. + */ static struct NSEPeer *peer_head; +/** + * Tail of DLL of peers we monitor closely. + */ static struct NSEPeer *peer_tail; /** - * How long until we give up on connecting the peers? + * Return value from 'main' (0 == success) */ -#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1500) - static int ok; /** - * Be verbose + * Be verbose (configuration option) */ static int verbose; /** - * Total number of peers in the test. + * Name of the file with the hosts to run the test over (configuration option) */ -static unsigned long long num_peers; +static char *hosts_file; /** - * Global configuration file + * Maximum number of peers in the test. */ -static struct GNUNET_CONFIGURATION_Handle *testing_cfg; +static unsigned int num_peers; /** - * Total number of currently running peers. + * Total number of rounds to execute. */ -static unsigned long long peers_running; +static unsigned int num_rounds; /** * Current round we are in. */ -static unsigned long long current_round; +static unsigned int current_round; + +/** + * Array of size 'num_rounds' with the requested number of peers in the given round. + */ +static unsigned int *num_peers_in_round; + +/** + * How many peers are running right now? + */ +static unsigned int peers_running; + +/** + * Specification for the numbers of peers to have in each round. + */ +static char *num_peer_spec; + +/** + * Handles to all of the running peers. + */ +static struct GNUNET_TESTBED_Peer **daemons; /** - * Peers desired in the next round. + * Global configuration file + */ +static struct GNUNET_CONFIGURATION_Handle *testing_cfg; + +/** + * The shutdown task */ -static unsigned long long peers_next_round; +static struct GNUNET_SCHEDULER_Task * shutdown_task_id; /** * Maximum number of connections to NSE services. */ -static unsigned long long connection_limit; +static unsigned int connection_limit; /** * Total number of connections in the whole network. @@ -101,14 +192,14 @@ static unsigned long long connection_limit; static unsigned int total_connections; /** - * The currently running peer group. + * File to report results to. */ -static struct GNUNET_TESTING_PeerGroup *pg; +static struct GNUNET_DISK_FileHandle *output_file; /** - * File to report results to. + * Filename to log results to. */ -static struct GNUNET_DISK_FileHandle *output_file; +static char *output_filename; /** * File to log connection info, statistics to. @@ -116,565 +207,684 @@ static struct GNUNET_DISK_FileHandle *output_file; static struct GNUNET_DISK_FileHandle *data_file; /** - * How many data points to capture before triggering next round? + * Filename to log connection info, statistics to. */ -static struct GNUNET_TIME_Relative wait_time; +static char *data_filename; /** - * Task called to disconnect peers. + * How long to wait before triggering next round? + * Default: 60 s. */ -static GNUNET_SCHEDULER_TaskIdentifier disconnect_task; +static struct GNUNET_TIME_Relative wait_time = { 60 * 1000 }; /** - * Task called to shutdown test. + * DLL head for operation list */ -static GNUNET_SCHEDULER_TaskIdentifier shutdown_handle; +static struct OpListEntry *oplist_head; /** - * Task used to churn the network. + * DLL tail for operation list */ -static GNUNET_SCHEDULER_TaskIdentifier churn_task; +static struct OpListEntry *oplist_tail; -static char *topology_file; - -static char *data_filename; +/** + * Are we shutting down + */ +static int shutting_down; -static uint64_t clock_skew; /** - * Check whether peers successfully shut down. + * Clean up all of the monitoring connections to NSE and + * STATISTICS that we keep to selected peers. */ static void -shutdown_callback (void *cls, const char *emsg) +close_monitor_connections () { - if (emsg != NULL) + struct NSEPeer *pos; + struct OpListEntry *oplist_entry; + + while (NULL != (pos = peer_head)) { -#if VERBOSE - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Shutdown of peers failed!\n"); -#endif - if (ok == 0) - ok = 666; + if (NULL != pos->nse_op) + GNUNET_TESTBED_operation_done (pos->nse_op); + if (NULL != pos->stat_op) + GNUNET_TESTBED_operation_done (pos->stat_op); + GNUNET_CONTAINER_DLL_remove (peer_head, peer_tail, pos); + GNUNET_free (pos); } - else + while (NULL != (oplist_entry = oplist_head)) { -#if VERBOSE - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "All peers successfully shut down!\n"); -#endif - ok = 0; + GNUNET_CONTAINER_DLL_remove (oplist_head, oplist_tail, oplist_entry); + GNUNET_TESTBED_operation_done (oplist_entry->op); + GNUNET_free (oplist_entry); } } +/** + * Task run on shutdown; cleans up everything. + * + * @param cls unused + * @param tc unused + */ static void shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { - struct NSEPeer *pos; - -#if VERBOSE - fprintf (stderr, "Ending test.\n"); -#endif - - if (disconnect_task != GNUNET_SCHEDULER_NO_TASK) + shutdown_task_id = NULL; + if (GNUNET_YES == shutting_down) + return; + shutting_down = GNUNET_YES; + LOG_DEBUG ("Ending test.\n"); + close_monitor_connections (); + if (NULL != data_file) { - GNUNET_SCHEDULER_cancel (disconnect_task); - disconnect_task = GNUNET_SCHEDULER_NO_TASK; + GNUNET_DISK_file_close (data_file); + data_file = NULL; } - while (NULL != (pos = peer_head)) + if (NULL != output_file) { - if (pos->nse_handle != NULL) - GNUNET_NSE_disconnect (pos->nse_handle); - GNUNET_CONTAINER_DLL_remove (peer_head, peer_tail, pos); - GNUNET_free (pos); + GNUNET_DISK_file_close (output_file); + output_file = NULL; } + if (NULL != testing_cfg) + GNUNET_CONFIGURATION_destroy (testing_cfg); + testing_cfg = NULL; +} - if (data_file != NULL) - GNUNET_DISK_file_close (data_file); - GNUNET_TESTING_daemons_stop (pg, TIMEOUT, &shutdown_callback, NULL); + +/** + * Schedules shutdown task to be run now + */ +static void +shutdown_now () +{ + if (NULL != shutdown_task_id) + GNUNET_SCHEDULER_cancel (shutdown_task_id); + shutdown_task_id = GNUNET_SCHEDULER_add_now (&shutdown_task, NULL); } /** * Callback to call when network size estimate is updated. * - * @param cls closure + * @param cls closure with the 'struct NSEPeer' providing the update * @param timestamp server timestamp * @param estimate the value of the current network size estimate * @param std_dev standard deviation (rounded down to nearest integer) * of the size estimation values seen - * */ static void -handle_estimate (void *cls, struct GNUNET_TIME_Absolute timestamp, +handle_estimate (void *cls, + struct GNUNET_TIME_Absolute timestamp, double estimate, double std_dev) { struct NSEPeer *peer = cls; - char *output_buffer; + char output_buffer[512]; size_t size; - if (output_file != NULL) - { - size = - GNUNET_asprintf (&output_buffer, "%s %llu %llu %f %f %f\n", - GNUNET_i2s (&peer->daemon->id), peers_running, - timestamp.abs_value, - GNUNET_NSE_log_estimate_to_n (estimate), estimate, - std_dev); - if (size != GNUNET_DISK_file_write (output_file, output_buffer, size)) - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unable to write to file!\n"); - GNUNET_free (output_buffer); - } - else - fprintf (stderr, - "Received network size estimate from peer %s. Size: %f std.dev. %f\n", - GNUNET_i2s (&peer->daemon->id), estimate, std_dev); - + if (NULL == output_file) + { + FPRINTF (stderr, + "Received network size estimate from peer %p. Size: %f std.dev. %f\n", + peer, estimate, std_dev); + return; + } + size = GNUNET_snprintf (output_buffer, + sizeof (output_buffer), + "%p %llu %llu %f %f %f\n", + peer, peers_running, + (unsigned long long) timestamp.abs_value_us, + GNUNET_NSE_log_estimate_to_n (estimate), estimate, + std_dev); + if (size != GNUNET_DISK_file_write (output_file, output_buffer, size)) + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unable to write to file!\n"); } -static void -connect_nse_service (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +/** + * Adapter function called to establish a connection to + * NSE service. + * + * @param cls closure (the 'struct NSEPeer') + * @param cfg configuration of the peer to connect to; will be available until + * GNUNET_TESTBED_operation_done() is called on the operation returned + * from GNUNET_TESTBED_service_connect() + * @return service handle to return in 'op_result', NULL on error + */ +static void * +nse_connect_adapter (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg) { - struct NSEPeer *current_peer; - unsigned int i; + struct NSEPeer *current_peer = cls; -#if VERBOSE - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting to nse service of peers\n"); -#endif - for (i = 0; i < num_peers; i++) - { - if ((connection_limit > 0) && (i % (num_peers / connection_limit) != 0)) - continue; -#if VERBOSE - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "nse-profiler: connecting to nse service of peer %d\n", i); -#endif - current_peer = GNUNET_malloc (sizeof (struct NSEPeer)); - current_peer->daemon = GNUNET_TESTING_daemon_get (pg, i); - if (GNUNET_YES == - GNUNET_TESTING_test_daemon_running (GNUNET_TESTING_daemon_get (pg, i))) - { - current_peer->nse_handle = - GNUNET_NSE_connect (current_peer->daemon->cfg, &handle_estimate, - current_peer); - GNUNET_assert (current_peer->nse_handle != NULL); - } - GNUNET_CONTAINER_DLL_insert (peer_head, peer_tail, current_peer); - } + return GNUNET_NSE_connect (cfg, &handle_estimate, current_peer); } -static void -churn_peers (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc); - - /** - * Continuation called by the "get_all" and "get" functions. + * Adapter function called to destroy a connection to + * NSE service. * - * @param cls struct StatsContext - * @param success GNUNET_OK if statistics were - * successfully obtained, GNUNET_SYSERR if not. + * @param cls closure + * @param op_result service handle returned from the connect adapter */ static void -stats_finished_callback (void *cls, int success) +nse_disconnect_adapter (void *cls, + void *op_result) { - struct StatsContext *stats_context = cls; - char *buf; - int buf_len; - - if ((GNUNET_OK == success) && (data_file != NULL)) - { - /* Stats lookup successful, write out data */ - buf = NULL; - buf_len = - GNUNET_asprintf (&buf, "TOTAL_NSE_BYTES: %u\n", - stats_context->total_nse_bytes); - if (buf_len > 0) - { - GNUNET_DISK_file_write (data_file, buf, buf_len); - } - GNUNET_free_non_null (buf); - } - - GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == shutdown_handle); - shutdown_handle = GNUNET_SCHEDULER_add_now (&shutdown_task, NULL); - GNUNET_free (stats_context); + GNUNET_NSE_disconnect (op_result); } /** * Callback function to process statistic values. * - * @param cls struct StatsContext - * @param peer the peer the statistics belong to + * @param cls `struct NSEPeer` * @param subsystem name of subsystem that created the statistic * @param name the name of the datum * @param value the current value - * @param is_persistent GNUNET_YES if the value is persistent, GNUNET_NO if not - * @return GNUNET_OK to continue, GNUNET_SYSERR to abort iteration + * @param is_persistent #GNUNET_YES if the value is persistent, #GNUNET_NO if not + * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration */ static int -statistics_iterator (void *cls, const struct GNUNET_PeerIdentity *peer, - const char *subsystem, const char *name, uint64_t value, - int is_persistent) +stat_iterator (void *cls, + const char *subsystem, + const char *name, + uint64_t value, int is_persistent) { - struct StatsContext *stats_context = cls; - - if ((0 == strstr (subsystem, "nse")) && - (0 == strstr (name, "# flood messages received"))) - stats_context->total_nse_bytes += value; + char *output_buffer; + struct GNUNET_TIME_Absolute now; + size_t size; + unsigned int flag; + + GNUNET_assert (NULL != data_file); + now = GNUNET_TIME_absolute_get (); + flag = strcasecmp (subsystem, "core"); + if (0 != flag) + flag = 1; + size = GNUNET_asprintf (&output_buffer, "%llu %llu %u\n", + now.abs_value_us / 1000LL / 1000LL, + value, flag); + if (size != GNUNET_DISK_file_write (data_file, output_buffer, size)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Unable to write to file!\n"); + GNUNET_free (output_buffer); + return GNUNET_SYSERR; + } + GNUNET_free (output_buffer); return GNUNET_OK; } -static void -disconnect_nse_peers (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +/** + * Called to open a connection to the peer's statistics + * + * @param cls peer context + * @param cfg configuration of the peer to connect to; will be available until + * GNUNET_TESTBED_operation_done() is called on the operation returned + * from GNUNET_TESTBED_service_connect() + * @return service handle to return in 'op_result', NULL on error + */ +static void * +stat_connect_adapter (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg) { - struct NSEPeer *pos; - char *buf; - struct StatsContext *stats_context; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "disconnecting nse service of peers\n"); - disconnect_task = GNUNET_SCHEDULER_NO_TASK; - pos = peer_head; - while (NULL != (pos = peer_head)) - { - if (pos->nse_handle != NULL) - { - GNUNET_NSE_disconnect (pos->nse_handle); - pos->nse_handle = NULL; - } - GNUNET_CONTAINER_DLL_remove (peer_head, peer_tail, pos); - GNUNET_free (pos); - } + struct NSEPeer *peer = cls; - GNUNET_asprintf (&buf, "round%llu", current_round); - if (GNUNET_OK == - GNUNET_CONFIGURATION_get_value_number (testing_cfg, "nse-profiler", buf, - &peers_next_round)) - { - current_round++; - GNUNET_assert (churn_task == GNUNET_SCHEDULER_NO_TASK); - churn_task = GNUNET_SCHEDULER_add_now (&churn_peers, NULL); - } - else /* No more rounds, let's shut it down! */ - { - stats_context = GNUNET_malloc (sizeof (struct StatsContext)); - GNUNET_SCHEDULER_cancel (shutdown_handle); - shutdown_handle = GNUNET_SCHEDULER_NO_TASK; - GNUNET_TESTING_get_statistics (pg, &stats_finished_callback, - &statistics_iterator, stats_context); - } - GNUNET_free (buf); + peer->sh = GNUNET_STATISTICS_create ("nse-profiler", cfg); + return peer->sh; } /** - * FIXME. + * Called to disconnect from peer's statistics service * - * @param cls unused - * @param emsg NULL on success + * @param cls peer context + * @param op_result service handle returned from the connect adapter */ static void -topology_output_callback (void *cls, const char *emsg) +stat_disconnect_adapter (void *cls, void *op_result) { - disconnect_task = - GNUNET_SCHEDULER_add_delayed (wait_time, &disconnect_nse_peers, NULL); - GNUNET_SCHEDULER_add_now (&connect_nse_service, NULL); + struct NSEPeer *peer = cls; + + GNUNET_break (GNUNET_OK == GNUNET_STATISTICS_watch_cancel + (peer->sh, "core", "# peers connected", + stat_iterator, peer)); + GNUNET_break (GNUNET_OK == GNUNET_STATISTICS_watch_cancel + (peer->sh, "nse", "# peers connected", + stat_iterator, peer)); + GNUNET_STATISTICS_destroy (op_result, GNUNET_NO); + peer->sh = NULL; } /** - * FIXME. + * Called after successfully opening a connection to a peer's statistics + * service; we register statistics monitoring for CORE and NSE here. * - * @param cls closure - * @param emsg NULL on success + * @param cls the callback closure from functions generating an operation + * @param op the operation that has been finished + * @param ca_result the service handle returned from GNUNET_TESTBED_ConnectAdapter() + * @param emsg error message in case the operation has failed; will be NULL if + * operation has executed successfully. */ static void -churn_callback (void *cls, const char *emsg) +stat_comp_cb (void *cls, struct GNUNET_TESTBED_Operation *op, + void *ca_result, const char *emsg ) { - char *temp_output_file; + struct GNUNET_STATISTICS_Handle *sh = ca_result; + struct NSEPeer *peer = cls; - if (emsg == NULL) /* Everything is okay! */ - { - peers_running = peers_next_round; - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Round %llu, churn finished successfully.\n", current_round); - GNUNET_assert (disconnect_task == GNUNET_SCHEDULER_NO_TASK); - GNUNET_asprintf (&temp_output_file, "%s_%llu.dot", topology_file, - current_round); - GNUNET_TESTING_peergroup_topology_to_file (pg, temp_output_file, - &topology_output_callback, NULL); - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Writing topology to file %s\n", - temp_output_file); - GNUNET_free (temp_output_file); - } - else + if (NULL != emsg) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Round %llu, churn FAILED!!\n", - current_round); - GNUNET_SCHEDULER_cancel (shutdown_handle); - shutdown_handle = GNUNET_SCHEDULER_add_now (&shutdown_task, NULL); + GNUNET_break (0); + return; } + GNUNET_break (GNUNET_OK == GNUNET_STATISTICS_watch + (sh, "core", "# peers connected", + stat_iterator, peer)); + GNUNET_break (GNUNET_OK == GNUNET_STATISTICS_watch + (sh, "nse", "# peers connected", + stat_iterator, peer)); } +/** + * Task run to connect to the NSE and statistics services to a subset of + * all of the running peers. + */ static void -churn_peers (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +connect_nse_service () { - /* peers_running = GNUNET_TESTING_daemons_running(pg); */ - churn_task = GNUNET_SCHEDULER_NO_TASK; - if (peers_next_round == peers_running) - { - /* Nothing to do... */ - GNUNET_SCHEDULER_add_now (&connect_nse_service, NULL); - GNUNET_assert (disconnect_task == GNUNET_SCHEDULER_NO_TASK); - disconnect_task = - GNUNET_SCHEDULER_add_delayed (wait_time, &disconnect_nse_peers, NULL); - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Round %lu, doing nothing!\n", - current_round); - } - else + struct NSEPeer *current_peer; + unsigned int i; + unsigned int connections; + + if (0 == connection_limit) + return; + LOG_DEBUG ("Connecting to nse service of peers\n"); + connections = 0; + for (i = 0; i < num_peers_in_round[current_round]; i++) { - if (peers_next_round > num_peers) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Asked to turn on more peers than we have!!\n"); - GNUNET_SCHEDULER_cancel (shutdown_handle); - GNUNET_SCHEDULER_add_now (&shutdown_task, NULL); - } - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Round %llu, turning off %llu peers, turning on %llu peers!\n", - current_round, - (peers_running > - peers_next_round) ? peers_running - peers_next_round : 0, - (peers_next_round > - peers_running) ? peers_next_round - peers_running : 0); - GNUNET_TESTING_daemons_churn (pg, "nse", - (peers_running > - peers_next_round) ? peers_running - - peers_next_round : 0, - (peers_next_round > - peers_running) ? peers_next_round - - peers_running : 0, wait_time, &churn_callback, - NULL); + if ((num_peers_in_round[current_round] > connection_limit) && + (0 != (i % (num_peers_in_round[current_round] / connection_limit)))) + continue; + LOG_DEBUG ("Connecting to nse service of peer %d\n", i); + current_peer = GNUNET_new (struct NSEPeer); + current_peer->daemon = daemons[i]; + current_peer->nse_op + = GNUNET_TESTBED_service_connect (NULL, + current_peer->daemon, + "nse", + NULL, NULL, + &nse_connect_adapter, + &nse_disconnect_adapter, + current_peer); + if (NULL != data_file) + current_peer->stat_op + = GNUNET_TESTBED_service_connect (NULL, + current_peer->daemon, + "statistics", + stat_comp_cb, + current_peer, + &stat_connect_adapter, + &stat_disconnect_adapter, + current_peer); + GNUNET_CONTAINER_DLL_insert (peer_head, peer_tail, current_peer); + if (++connections == connection_limit) + break; } } +/** + * Task that starts/stops peers to move to the next round. + * + * @param cls NULL, unused + * @param tc scheduler context (unused) + */ +static void +next_round (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc); + + +/** + * We're at the end of a round. Stop monitoring, write total + * number of connections to log and get full stats. Then trigger + * the next round. + * + * @param cls unused, NULL + * @param tc unused + */ static void -nse_started_cb (void *cls, const char *emsg) +finish_round (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) { - GNUNET_SCHEDULER_add_now (&connect_nse_service, NULL); - disconnect_task = - GNUNET_SCHEDULER_add_delayed (wait_time, &disconnect_nse_peers, NULL); + if (0 != (GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason)) + return; + LOG (GNUNET_ERROR_TYPE_INFO, "Have %u connections\n", total_connections); + close_monitor_connections (); + GNUNET_SCHEDULER_add_now (&next_round, NULL); } +/** + * We have reached the desired number of peers for the current round. + * Run it (by connecting and monitoring a few peers and waiting the + * specified delay before finishing the round). + */ static void -my_cb (void *cls, const char *emsg) +run_round () { - char *buf; - int buf_len; + LOG_DEBUG ("Running round %u\n", current_round); + connect_nse_service (); + GNUNET_SCHEDULER_add_delayed (wait_time, + &finish_round, + NULL); +} - if (emsg != NULL) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Peergroup callback called with error, aborting test!\n"); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Error from testing: `%s'\n"); - ok = 1; - GNUNET_TESTING_daemons_stop (pg, TIMEOUT, &shutdown_callback, NULL); - return; - } -#if VERBOSE - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Peer Group started successfully, connecting to NSE service for each peer!\n"); -#endif - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Have %u connections\n", - total_connections); - if (data_file != NULL) - { - buf = NULL; - buf_len = GNUNET_asprintf (&buf, "CONNECTIONS_0: %u\n", total_connections); - if (buf_len > 0) - GNUNET_DISK_file_write (data_file, buf, buf_len); - GNUNET_free (buf); - } - peers_running = GNUNET_TESTING_daemons_running (pg); - GNUNET_TESTING_daemons_start_service (pg, "nse", wait_time, &nse_started_cb, - NULL); +/** + * Creates an oplist entry and adds it to the oplist DLL + */ +static struct OpListEntry * +make_oplist_entry () +{ + struct OpListEntry *entry; + + entry = GNUNET_new (struct OpListEntry); + GNUNET_CONTAINER_DLL_insert_tail (oplist_head, oplist_tail, entry); + return entry; } /** - * Function that will be called whenever two daemons are connected by - * the testing library. + * Callback to be called when NSE service is started or stopped at peers * - * @param cls closure - * @param first peer id for first daemon - * @param second peer id for the second daemon - * @param distance distance between the connected peers - * @param first_cfg config for the first daemon - * @param second_cfg config for the second daemon - * @param first_daemon handle for the first daemon - * @param second_daemon handle for the second daemon - * @param emsg error message (NULL on success) + * @param cls NULL + * @param op the operation handle + * @param emsg NULL on success; otherwise an error description */ static void -connect_cb (void *cls, const struct GNUNET_PeerIdentity *first, - const struct GNUNET_PeerIdentity *second, uint32_t distance, - const struct GNUNET_CONFIGURATION_Handle *first_cfg, - const struct GNUNET_CONFIGURATION_Handle *second_cfg, - struct GNUNET_TESTING_Daemon *first_daemon, - struct GNUNET_TESTING_Daemon *second_daemon, const char *emsg) +manage_service_cb (void *cls, struct GNUNET_TESTBED_Operation *op, + const char *emsg) { - if (emsg == NULL) - total_connections++; + struct OpListEntry *entry = cls; + + GNUNET_TESTBED_operation_done (entry->op); + if (NULL != emsg) + { + LOG (GNUNET_ERROR_TYPE_ERROR, "Failed to start/stop NSE at a peer\n"); + GNUNET_SCHEDULER_shutdown (); + return; + } + GNUNET_assert (0 != entry->delta); + peers_running += entry->delta; + GNUNET_CONTAINER_DLL_remove (oplist_head, oplist_tail, entry); + GNUNET_free (entry); + if (num_peers_in_round[current_round] == peers_running) + run_round (); } +/** + * Adjust the number of running peers to match the required number of running + * peers for the round + */ static void -run (void *cls, char *const *args, const char *cfgfile, - const struct GNUNET_CONFIGURATION_Handle *cfg) +adjust_running_peers () { - char *temp_str; - unsigned long long temp_wait; - struct GNUNET_TESTING_Host *hosts; + struct OpListEntry *entry; + unsigned int i; - ok = 1; - testing_cfg = GNUNET_CONFIGURATION_create (); -#if VERBOSE - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Starting daemons.\n"); - GNUNET_CONFIGURATION_set_value_string (testing_cfg, "testing", - "use_progressbars", "YES"); -#endif - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_number (testing_cfg, "testing", - "num_peers", &num_peers)) + /* start peers if we have too few */ + for (i=peers_running;idelta = 1; + entry->op = GNUNET_TESTBED_peer_manage_service (NULL, + daemons[i], + "nse", + &manage_service_cb, + entry, + 1); } - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_number (testing_cfg, "nse-profiler", - "wait_time", &temp_wait)) + /* stop peers if we have too many */ + for (i=num_peers_in_round[current_round];idelta = -1; + entry->op = GNUNET_TESTBED_peer_manage_service (NULL, + daemons[i], + "nse", + &manage_service_cb, + entry, + 0); } +} - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_number (testing_cfg, "nse-profiler", - "connection_limit", - &connection_limit)) - { - connection_limit = 0; - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (testing_cfg, "nse-profiler", - "topology_output_file", - &topology_file)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Option nse-profiler:topology_output_file is required!\n"); +/** + * Task run at the end of a round. Disconnect from all monitored + * peers; then get statistics from *all* peers. + * + * @param cls NULL, unused + * @param tc unused + */ +static void +next_round (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + if (0 != (GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason)) return; - } + LOG_DEBUG ("Disconnecting nse service of peers\n"); + current_round++; + if (current_round == num_rounds) + { + /* this was the last round, terminate */ + ok = 0; + GNUNET_SCHEDULER_shutdown (); + return; + } + if (num_peers_in_round[current_round] == peers_running) + { + /* no need to churn, just run next round */ + run_round (); + return; + } + adjust_running_peers (); +} - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (testing_cfg, "nse-profiler", - "data_output_file", - &data_filename)) + +/** + * Function that will be called whenever something in the + * testbed changes. + * + * @param cls closure, NULL + * @param event information on what is happening + */ +static void +master_controller_cb (void *cls, + const struct GNUNET_TESTBED_EventInformation *event) +{ + switch (event->type) + { + case GNUNET_TESTBED_ET_CONNECT: + total_connections++; + break; + case GNUNET_TESTBED_ET_DISCONNECT: + total_connections--; + break; + default: + break; + } +} + + +/** + * Signature of a main function for a testcase. + * + * @param cls NULL + * @param h the run handle + * @param num_peers_ number of peers in 'peers' + * @param peers handle to peers run in the testbed. NULL upon timeout (see + * GNUNET_TESTBED_test_run()). + * @param links_succeeded the number of overlay link connection attempts that + * succeeded + * @param links_failed the number of overlay link connection attempts that + * failed + */ +static void +test_master (void *cls, + struct GNUNET_TESTBED_RunHandle *h, + unsigned int num_peers_, + struct GNUNET_TESTBED_Peer **peers, + unsigned int links_succeeded, + unsigned int links_failed) +{ + if (NULL == peers) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Option nse-profiler:data_output_file is required!\n"); + shutdown_now (); return; } - - if (GNUNET_YES == - GNUNET_CONFIGURATION_get_value_yesno (testing_cfg, "nse-profiler", - "skew_clock")) + daemons = peers; + GNUNET_break (num_peers_ == num_peers); + peers_running = num_peers; + if (num_peers_in_round[current_round] == peers_running) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Setting our clock as skewed...\n"); - clock_skew = - GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, - GNUNET_TIME_UNIT_MINUTES.rel_value); + /* no need to churn, just run the starting round */ + run_round (); + return; } + adjust_running_peers (); +} - data_file = - GNUNET_DISK_file_open (data_filename, - GNUNET_DISK_OPEN_READWRITE | - GNUNET_DISK_OPEN_CREATE, - GNUNET_DISK_PERM_USER_READ | - GNUNET_DISK_PERM_USER_WRITE); - if (data_file == NULL) - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to open %s for output!\n", - data_filename); - GNUNET_free (data_filename); - - wait_time = - GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, temp_wait); +/** + * Actual main function that runs the emulation. + * + * @param cls unused + * @param args remaining args, unused + * @param cfgfile name of the configuration + * @param cfg configuration handle + */ +static void +run (void *cls, char *const *args, const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + char *tok; + uint64_t event_mask; + unsigned int num; - if (GNUNET_YES == - GNUNET_CONFIGURATION_get_value_string (cfg, "nse-profiler", "output_file", - &temp_str)) + ok = 1; + testing_cfg = GNUNET_CONFIGURATION_dup (cfg); + LOG_DEBUG ("Starting daemons.\n"); + if (NULL == num_peer_spec) { - output_file = - GNUNET_DISK_file_open (temp_str, - GNUNET_DISK_OPEN_READWRITE | - GNUNET_DISK_OPEN_CREATE, - GNUNET_DISK_PERM_USER_READ | - GNUNET_DISK_PERM_USER_WRITE); - if (output_file == NULL) - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to open %s for output!\n", - temp_str); + fprintf (stderr, "You need to specify the number of peers to run\n"); + return; } - GNUNET_free_non_null (temp_str); - - hosts = GNUNET_TESTING_hosts_load (testing_cfg); - - pg = GNUNET_TESTING_peergroup_start (testing_cfg, num_peers, TIMEOUT, - &connect_cb, &my_cb, NULL, hosts); - GNUNET_assert (pg != NULL); - shutdown_handle = - GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_get_forever (), + for (tok = strtok (num_peer_spec, ","); NULL != tok; tok = strtok (NULL, ",")) + { + if (1 != sscanf (tok, "%u", &num)) + { + fprintf (stderr, "You need to specify numbers, not `%s'\n", tok); + return; + } + if (0 == num) + { + fprintf (stderr, "Refusing to run a round with 0 peers\n"); + return; + } + GNUNET_array_append (num_peers_in_round, num_rounds, num); + num_peers = GNUNET_MAX (num_peers, num); + } + if (0 == num_peers) + { + fprintf (stderr, "Refusing to run a testbed with no rounds\n"); + return; + } + if ( (NULL != data_filename) && + (NULL == (data_file = + GNUNET_DISK_file_open (data_filename, + GNUNET_DISK_OPEN_READWRITE | + GNUNET_DISK_OPEN_TRUNCATE | + GNUNET_DISK_OPEN_CREATE, + GNUNET_DISK_PERM_USER_READ | + GNUNET_DISK_PERM_USER_WRITE))) ) + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "open", + data_filename); + + if ( (NULL != output_filename) && + (NULL == (output_file = + GNUNET_DISK_file_open (output_filename, + GNUNET_DISK_OPEN_READWRITE | + GNUNET_DISK_OPEN_CREATE, + GNUNET_DISK_PERM_USER_READ | + GNUNET_DISK_PERM_USER_WRITE))) ) + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", + output_filename); + event_mask = 0LL; + event_mask |= (1LL << GNUNET_TESTBED_ET_PEER_START); + event_mask |= (1LL << GNUNET_TESTBED_ET_PEER_STOP); + event_mask |= (1LL << GNUNET_TESTBED_ET_CONNECT); + event_mask |= (1LL << GNUNET_TESTBED_ET_DISCONNECT); + GNUNET_TESTBED_run (hosts_file, + cfg, + num_peers, + event_mask, + master_controller_cb, + NULL, /* master_controller_cb cls */ + &test_master, + NULL); /* test_master cls */ + shutdown_task_id = + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &shutdown_task, NULL); } - /** - * nse-profiler command line options + * Main function. + * + * @return 0 on success */ -static struct GNUNET_GETOPT_CommandLineOption options[] = { - {'V', "verbose", NULL, - gettext_noop ("be verbose (print progress information)"), - 0, &GNUNET_GETOPT_set_one, &verbose}, - GNUNET_GETOPT_OPTION_END -}; - - int -main (int argc, char *argv[]) +main (int argc, char *const *argv) { - GNUNET_log_setup ("nse-profiler", -#if VERBOSE - "DEBUG", -#else - "WARNING", -#endif - NULL); - GNUNET_PROGRAM_run (argc, argv, "nse-profiler", - gettext_noop - ("Measure quality and performance of the NSE service."), - options, &run, NULL); -#if REMOVE_DIR - GNUNET_DISK_directory_remove ("/tmp/nse-profiler"); -#endif + static struct GNUNET_GETOPT_CommandLineOption options[] = { + {'C', "connections", "COUNT", + gettext_noop ("limit to the number of connections to NSE services, 0 for none"), + 1, &GNUNET_GETOPT_set_uint, &connection_limit}, + {'d', "details", "FILENAME", + gettext_noop ("name of the file for writing connection information and statistics"), + 1, &GNUNET_GETOPT_set_string, &data_filename}, + {'H', "hosts", "FILENAME", + gettext_noop ("name of the file with the login information for the testbed"), + 1, &GNUNET_GETOPT_set_string, &hosts_file}, + {'o', "output", "FILENAME", + gettext_noop ("name of the file for writing the main results"), + 1, &GNUNET_GETOPT_set_string, &output_filename}, + {'p', "peers", "NETWORKSIZESPEC", + gettext_noop ("Number of peers to run in each round, separated by commas"), + 1, &GNUNET_GETOPT_set_string, &num_peer_spec}, + {'V', "verbose", NULL, + gettext_noop ("be verbose (print progress information)"), + 0, &GNUNET_GETOPT_increment_value, &verbose}, + {'w', "wait", "DELAY", + gettext_noop ("delay between rounds"), + 1, &GNUNET_GETOPT_set_relative_time, &wait_time}, + GNUNET_GETOPT_OPTION_END + }; + if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv)) + return 2; + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, argv, "nse-profiler", + gettext_noop + ("Measure quality and performance of the NSE service."), + options, &run, NULL)) + ok = 1; return ok; }