2 This file is part of GNUnet.
3 Copyright (C) 2014, 2018 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
22 * @file dht/gnunet_dht_profiler.c
23 * @brief Profiler for GNUnet DHT
24 * @author Sree Harsha Totakura <sreeharsha@totakura.in>
28 #include "gnunet_util_lib.h"
29 #include "gnunet_testbed_service.h"
30 #include "gnunet_dht_service.h"
31 #include "gnunet_constants.h"
34 #define MESSAGE(...) \
35 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, __VA_ARGS__)
38 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
41 * Number of peers which should perform a PUT out of 100 peers
43 static unsigned int put_probability = 100;
48 static const struct GNUNET_CONFIGURATION_Handle *cfg;
51 * Name of the file with the hosts to run the test over
53 static char *hosts_file;
56 * Context for a peer which actively does DHT PUT/GET
61 * Context to hold data of peer
66 * The testbed peer this context belongs to
68 struct GNUNET_TESTBED_Peer *peer;
71 * Testbed operation acting on this peer
73 struct GNUNET_TESTBED_Operation *op;
76 * Active context; NULL if this peer is not an active peer
78 struct ActiveContext *ac;
83 * Context for a peer which actively does DHT PUT/GET
88 * The linked peer context
93 * Handler to the DHT service
95 struct GNUNET_DHT_Handle *dht;
98 * The active context used for our DHT GET
100 struct ActiveContext *get_ac;
105 struct GNUNET_DHT_PutHandle *dht_put;
110 struct GNUNET_DHT_GetHandle *dht_get;
113 * The hashes of the values stored via this activity context.
114 * Array of length #num_puts_per_peer.
116 struct GNUNET_HashCode *hash;
121 struct GNUNET_SCHEDULER_Task *delay_task;
124 * How many puts should we still issue?
126 unsigned int put_count;
129 * The number of peers currently doing GET on our data
136 * An array of contexts. The size of this array should be equal to @a num_peers
138 static struct Context *a_ctx;
141 * Array of active peers
143 static struct ActiveContext *a_ac;
146 * The delay between rounds for collecting statistics
148 static struct GNUNET_TIME_Relative delay_stats;
151 * The delay to start puts.
153 static struct GNUNET_TIME_Relative delay_put;
156 * The delay to start puts.
158 static struct GNUNET_TIME_Relative delay_get;
161 * The timeout for GET and PUT
163 static struct GNUNET_TIME_Relative timeout;
168 static unsigned int num_peers;
171 * Number of active peers
173 static unsigned int n_active;
176 * Number of DHT service connections we currently have
178 static unsigned int n_dht;
181 * Number of DHT PUTs made
183 static unsigned long long n_puts;
186 * Number of DHT PUTs to be made per peer.
188 static unsigned int num_puts_per_peer = 1;
191 * Number of DHT PUTs succeeded
193 static unsigned long long n_puts_ok;
196 * Number of DHT GETs made
198 static unsigned int n_gets;
201 * Number of DHT GETs succeeded
203 static unsigned int n_gets_ok;
206 * Number of DHT GETs succeeded
208 static unsigned int n_gets_fail;
213 static unsigned int replication;
216 * Testbed Operation (to get stats).
218 static struct GNUNET_TESTBED_Operation *bandwidth_stats_op;
221 * Testbed peer handles.
223 static struct GNUNET_TESTBED_Peer **testbed_handles;
226 * Total number of messages sent by peer.
228 static uint64_t outgoing_bandwidth;
231 * Total number of messages received by peer.
233 static uint64_t incoming_bandwidth;
236 * Average number of hops taken to do put.
238 static double average_put_path_length;
241 * Average number of hops taken to do get.
243 static double average_get_path_length;
246 * Total put path length across all peers.
248 static unsigned int total_put_path_length;
251 * Total get path length across all peers.
253 static unsigned int total_get_path_length;
256 * Counter to keep track of peers added to peer_context lists.
258 static int peers_started = 0;
261 * Should we do a PUT (mode = 0) or GET (mode = 1);
272 * Are we shutting down
274 static int in_shutdown = 0;
278 * Connect to DHT services of active peers
281 start_profiling (void);
285 * Shutdown task. Cleanup all resources and operations.
290 do_shutdown (void *cls)
292 struct ActiveContext *ac;
294 in_shutdown = GNUNET_YES;
297 for (unsigned int cnt = 0; cnt < num_peers; cnt++)
299 /* Cleanup active context if this peer is an active peer */
303 if (NULL != ac->delay_task)
304 GNUNET_SCHEDULER_cancel (ac->delay_task);
305 if (NULL != ac->hash)
307 if (NULL != ac->dht_put)
308 GNUNET_DHT_put_cancel (ac->dht_put);
309 if (NULL != ac->dht_get)
310 GNUNET_DHT_get_stop (ac->dht_get);
312 /* Cleanup testbed operation handle at the last as this operation may
313 contain service connection to DHT */
314 if (NULL != a_ctx[cnt].op)
315 GNUNET_TESTBED_operation_done (a_ctx[cnt].op);
320 // FIXME: Should we collect stats only for put/get not for other messages.
321 if (NULL != bandwidth_stats_op)
323 GNUNET_TESTBED_operation_done (bandwidth_stats_op);
324 bandwidth_stats_op = NULL;
326 GNUNET_free_non_null (a_ac);
331 * Stats callback. Finish the stats testbed operation and when all stats have
332 * been iterated, shutdown the test.
335 * @param op the operation that has been finished
336 * @param emsg error message in case the operation has failed; will be NULL if
337 * operation has executed successfully.
340 bandwidth_stats_cont (void *cls,
341 struct GNUNET_TESTBED_Operation *op,
344 MESSAGE ("# Outgoing (core) bandwidth: %llu bytes\n",
345 (unsigned long long) outgoing_bandwidth);
346 MESSAGE ("# Incoming (core) bandwidth: %llu bytes\n",
347 (unsigned long long) incoming_bandwidth);
349 "Benchmark done. Collect data via gnunet-statistics, then press ENTER to exit.\n");
351 GNUNET_SCHEDULER_shutdown ();
356 * Process statistic values.
359 * @param peer the peer the statistic belong to
360 * @param subsystem name of subsystem that created the statistic
361 * @param name the name of the datum
362 * @param value the current value
363 * @param is_persistent #GNUNET_YES if the value is persistent, #GNUNET_NO if not
364 * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
367 bandwidth_stats_iterator (void *cls,
368 const struct GNUNET_TESTBED_Peer *peer,
369 const char *subsystem,
374 static const char *s_sent = "# bytes encrypted";
375 static const char *s_recv = "# bytes decrypted";
377 if (0 == strncmp (s_sent, name, strlen (s_sent)))
378 outgoing_bandwidth = outgoing_bandwidth + value;
379 else if (0 == strncmp (s_recv, name, strlen (s_recv)))
380 incoming_bandwidth = incoming_bandwidth + value;
388 MESSAGE ("# PUTS started: %llu\n",
390 MESSAGE ("# PUTS succeeded: %llu\n",
392 MESSAGE ("# GETS made: %u\n",
394 MESSAGE ("# GETS succeeded: %u\n",
396 MESSAGE ("# GETS failed: %u\n",
398 MESSAGE ("# average_put_path_length: %f\n",
399 average_put_path_length);
400 MESSAGE ("# average_get_path_length: %f\n",
401 average_get_path_length);
403 if (NULL == testbed_handles)
405 MESSAGE ("No peers found\n");
409 bandwidth_stats_op = GNUNET_TESTBED_get_statistics (n_active,
413 &bandwidth_stats_iterator,
414 &bandwidth_stats_cont,
420 * Task to cancel DHT GET.
425 cancel_get (void *cls)
427 struct ActiveContext *ac = cls;
428 struct Context *ctx = ac->ctx;
430 ac->delay_task = NULL;
431 GNUNET_assert (NULL != ac->dht_get);
432 GNUNET_DHT_get_stop (ac->dht_get);
435 GNUNET_assert (NULL != ctx->op);
436 GNUNET_TESTBED_operation_done (ctx->op);
439 /* If profiling is complete, summarize */
440 if (n_active == n_gets_fail + n_gets_ok)
442 average_put_path_length = (double) total_put_path_length
444 average_get_path_length = (double) total_get_path_length
445 / (double ) n_gets_ok;
452 * Iterator called on each result obtained for a DHT
453 * operation that expects a reply
456 * @param exp when will this value expire
457 * @param key key of the result
458 * @param get_path peers on reply path (or NULL if not recorded)
459 * [0] = datastore's first neighbor, [length - 1] = local peer
460 * @param get_path_length number of entries in @a get_path
461 * @param put_path peers on the PUT path (or NULL if not recorded)
462 * [0] = origin, [length - 1] = datastore
463 * @param put_path_length number of entries in @a put_path
464 * @param type type of the result
465 * @param size number of bytes in @a data
466 * @param data pointer to the result data
470 struct GNUNET_TIME_Absolute exp,
471 const struct GNUNET_HashCode *key,
472 const struct GNUNET_PeerIdentity *get_path,
473 unsigned int get_path_length,
474 const struct GNUNET_PeerIdentity *put_path,
475 unsigned int put_path_length,
476 enum GNUNET_BLOCK_Type type,
477 size_t size, const void *data)
479 struct ActiveContext *ac = cls;
480 struct ActiveContext *get_ac = ac->get_ac;
481 struct Context *ctx = ac->ctx;
483 /* we found the data we are looking for */
484 DEBUG ("We found a GET request; %u remaining\n",
485 n_gets - (n_gets_fail + n_gets_ok)); // FIXME: It always prints 1.
488 GNUNET_DHT_get_stop (ac->dht_get);
490 if (ac->delay_task != NULL)
491 GNUNET_SCHEDULER_cancel (ac->delay_task);
492 ac->delay_task = NULL;
493 GNUNET_assert (NULL != ctx->op);
494 GNUNET_TESTBED_operation_done (ctx->op);
497 total_put_path_length = total_put_path_length + (double) put_path_length;
498 total_get_path_length = total_get_path_length + (double) get_path_length;
499 DEBUG ("total_put_path_length = %u,put_path \n",
500 total_put_path_length);
501 /* Summarize if profiling is complete */
502 if (n_active == n_gets_fail + n_gets_ok)
504 average_put_path_length = (double) total_put_path_length
506 average_get_path_length = (double) total_get_path_length
507 / (double ) n_gets_ok;
514 * Task to do DHT GETs
516 * @param cls the active context
519 delayed_get (void *cls)
521 struct ActiveContext *ac = cls;
522 struct ActiveContext *get_ac;
525 ac->delay_task = NULL;
529 r = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
532 if (NULL != get_ac->hash)
537 r = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
539 DEBUG ("GET_REQUEST_START key %s \n",
540 GNUNET_h2s (&get_ac->hash[r]));
541 ac->dht_get = GNUNET_DHT_get_start (ac->dht,
542 GNUNET_BLOCK_TYPE_TEST,
544 1, /* replication level */
547 0, /* extended query and size */
549 ac); /* GET iterator and closure */
552 /* schedule the timeout task for GET */
553 ac->delay_task = GNUNET_SCHEDULER_add_delayed (timeout,
560 * Task to do DHT PUTs. If the "put_count" hits zero,
561 * we stop the TESTBED operation (connection to DHT)
562 * so that others PUTs have a chance.
564 * @param cls the active context
567 delayed_put (void *cls);
571 * Conclude individual PUT operation, schedule the
574 * @param cls the active context
579 struct ActiveContext *ac = cls;
583 ac->delay_task = GNUNET_SCHEDULER_add_now (&delayed_put,
589 * Task to do DHT PUTs. If the "put_count" hits zero,
590 * we stop the TESTBED operation (connection to DHT)
591 * so that others PUTs have a chance.
593 * @param cls the active context
596 delayed_put (void *cls)
598 struct ActiveContext *ac = cls;
602 ac->delay_task = NULL;
603 if (0 == ac->put_count)
605 struct Context *ctx = ac->ctx;
606 struct GNUNET_TESTBED_Operation *op;
608 GNUNET_assert (NULL != ctx);
611 GNUNET_TESTBED_operation_done (op);
616 /* Generate and DHT PUT some random data */
617 block_size = 16; /* minimum */
618 /* make random payload, reserve 512 - 16 bytes for DHT headers */
619 block_size += GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
620 GNUNET_CONSTANTS_MAX_ENCRYPTED_MESSAGE_SIZE
622 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
626 GNUNET_CRYPTO_hash (block,
628 &ac->hash[ac->put_count]);
629 DEBUG ("PUT_REQUEST_START key %s\n",
630 GNUNET_h2s (&ac->hash[ac->put_count]));
631 ac->dht_put = GNUNET_DHT_put (ac->dht,
632 &ac->hash[ac->put_count],
634 GNUNET_DHT_RO_RECORD_ROUTE,
635 GNUNET_BLOCK_TYPE_TEST,
638 GNUNET_TIME_UNIT_FOREVER_ABS, /* expiration time */
640 ac); /* continuation and its closure */
646 * Connection to DHT has been established. Call the delay task.
648 * @param cls the active context
649 * @param op the operation that has been finished
650 * @param ca_result the service handle returned from GNUNET_TESTBED_ConnectAdapter()
651 * @param emsg error message in case the operation has failed; will be NULL if
652 * operation has executed successfully.
655 dht_connected (void *cls,
656 struct GNUNET_TESTBED_Operation *op,
660 struct ActiveContext *ac = cls;
661 struct Context *ctx = ac->ctx;
663 GNUNET_assert (NULL != ctx); // FIXME: Fails
664 GNUNET_assert (NULL != ctx->op);
665 GNUNET_assert (ctx->op == op);
666 ac->dht = (struct GNUNET_DHT_Handle *) ca_result;
669 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
670 "Connection to DHT service failed: %s\n",
672 GNUNET_TESTBED_operation_done (ctx->op); /* Calls dht_disconnect() */
680 struct GNUNET_TIME_Relative peer_delay_put;
682 peer_delay_put.rel_value_us =
683 GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
684 delay_put.rel_value_us);
685 ac->put_count = num_puts_per_peer;
686 ac->hash = calloc (ac->put_count,
687 sizeof(struct GNUNET_HashCode));
688 if (NULL == ac->hash)
690 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
692 GNUNET_SCHEDULER_shutdown ();
695 ac->delay_task = GNUNET_SCHEDULER_add_delayed (peer_delay_put,
703 struct GNUNET_TIME_Relative peer_delay_get;
705 peer_delay_get.rel_value_us =
706 delay_get.rel_value_us
707 + GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
708 delay_get.rel_value_us);
709 ac->delay_task = GNUNET_SCHEDULER_add_delayed (peer_delay_get,
719 * Connect to DHT service and return the DHT client handler
721 * @param cls the active context
722 * @param cfg configuration of the peer to connect to; will be available until
723 * GNUNET_TESTBED_operation_done() is called on the operation returned
724 * from GNUNET_TESTBED_service_connect()
725 * @return service handle to return in 'op_result', NULL on error
728 dht_connect (void *cls,
729 const struct GNUNET_CONFIGURATION_Handle *cfg)
732 return GNUNET_DHT_connect (cfg,
738 * Adapter function called to destroy a connection to
741 * @param cls the active context
742 * @param op_result service handle returned from the connect adapter
745 dht_disconnect (void *cls,
748 struct ActiveContext *ac = cls;
750 GNUNET_assert (NULL != ac->dht);
751 GNUNET_assert (ac->dht == op_result);
752 GNUNET_DHT_disconnect (ac->dht);
757 if (GNUNET_YES == in_shutdown)
762 if (n_puts_ok != ((unsigned long long) n_active) * num_puts_per_peer)
764 /* Start GETs if all PUTs have been made */
770 if ((n_gets_ok + n_gets_fail) != n_active)
778 * Connect to DHT services of active peers
785 DEBUG ("GNUNET_TESTBED_service_connect\n");
786 GNUNET_break (GNUNET_YES != in_shutdown);
787 for (unsigned int i = 0; i < n_active; i++)
789 struct ActiveContext *ac = &a_ac[i];
790 GNUNET_assert (NULL != (ctx = ac->ctx));
791 GNUNET_assert (NULL == ctx->op);
792 ctx->op = GNUNET_TESTBED_service_connect (ctx,
804 * Callback called when DHT service on the peer is started
806 * @param cls the context
807 * @param op the operation that has been finished
808 * @param emsg error message in case the operation has failed; will be NULL if
809 * operation has executed successfully.
812 service_started (void *cls,
813 struct GNUNET_TESTBED_Operation *op,
816 struct Context *ctx = cls;
818 GNUNET_assert (NULL != ctx);
819 GNUNET_assert (NULL != ctx->op);
820 GNUNET_TESTBED_operation_done (ctx->op);
823 DEBUG ("Peers Started = %d; num_peers = %d \n",
826 if (peers_started == num_peers)
832 * Signature of a main function for a testcase.
835 * @param h the run handle
836 * @param num_peers number of peers in 'peers'
837 * @param peers handle to peers run in the testbed
838 * @param links_succeeded the number of overlay link connection attempts that
840 * @param links_failed the number of overlay link
844 struct GNUNET_TESTBED_RunHandle *h,
845 unsigned int num_peers,
846 struct GNUNET_TESTBED_Peer **peers,
847 unsigned int links_succeeded,
848 unsigned int links_failed)
852 testbed_handles = peers;
858 MESSAGE ("%u peers started, %u/%u links up\n",
861 links_succeeded + links_failed);
862 a_ctx = GNUNET_new_array (num_peers,
864 /* select the peers which actively participate in profiling */
865 n_active = num_peers * put_probability / 100;
868 GNUNET_SCHEDULER_shutdown ();
874 a_ac = GNUNET_new_array (n_active,
875 struct ActiveContext);
877 for (unsigned int cnt = 0; cnt < num_peers && ac_cnt < n_active; cnt++)
879 if (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
880 100) >= put_probability)
883 a_ctx[cnt].ac = &a_ac[ac_cnt];
884 a_ac[ac_cnt].ctx = &a_ctx[cnt];
889 /* start DHT service on all peers */
890 for (unsigned int cnt = 0; cnt < num_peers; cnt++)
892 a_ctx[cnt].peer = peers[cnt];
893 a_ctx[cnt].op = GNUNET_TESTBED_peer_manage_service (&a_ctx[cnt],
904 * Main function that will be run by the scheduler.
907 * @param args remaining command-line arguments
908 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
909 * @param config configuration
915 const struct GNUNET_CONFIGURATION_Handle *config)
921 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
922 _ ("Exiting as the number of peers is %u\n"),
928 GNUNET_TESTBED_run (hosts_file,
936 GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
944 * @return 0 on success
951 struct GNUNET_GETOPT_CommandLineOption options[] = {
952 GNUNET_GETOPT_option_uint ('n',
955 gettext_noop ("number of peers to start"),
957 GNUNET_GETOPT_option_uint ('p',
961 "number of PUTs to perform per peer"),
963 GNUNET_GETOPT_option_string ('H',
967 "name of the file with the login information for the testbed"),
969 GNUNET_GETOPT_option_relative_time ('D',
973 "delay between rounds for collecting statistics (default: 30 sec)"),
975 GNUNET_GETOPT_option_relative_time ('P',
979 "delay to start doing PUTs (default: 1 sec)"),
981 GNUNET_GETOPT_option_relative_time ('G',
985 "delay to start doing GETs (default: 5 min)"),
987 GNUNET_GETOPT_option_uint ('r',
990 gettext_noop ("replication degree for DHT PUTs"),
992 GNUNET_GETOPT_option_uint ('R',
996 "chance that a peer is selected at random for PUTs"),
998 GNUNET_GETOPT_option_relative_time ('t',
1002 "timeout for DHT PUT and GET requests (default: 1 min)"),
1004 GNUNET_GETOPT_OPTION_END
1008 GNUNET_STRINGS_get_utf8_args (argc, argv,
1011 /* set default delays */
1012 delay_stats = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10);
1013 delay_put = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10);
1014 delay_get = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10);
1015 timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10);
1016 replication = 1; /* default replication */
1019 GNUNET_PROGRAM_run (argc,
1021 "gnunet-dht-profiler",
1023 "Measure quality and performance of the DHT service."),