- name inside file
[oweals/gnunet.git] / src / mesh / gnunet-mesh-profiler.c
1 /*
2      This file is part of GNUnet.
3      (C) 2011 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
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      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20 /**
21  * @file mesh/gnunet-mesh-profiler.c
22  *
23  * @brief Profiler for mesh experiments.
24  */
25 #include <stdio.h>
26 #include "platform.h"
27 #include "mesh_test_lib.h"
28 #include "gnunet_mesh_service.h"
29 #include "gnunet_statistics_service.h"
30
31
32 /**
33  * How namy peers to run
34  */
35 #define TOTAL_PEERS 1000
36
37 /**
38  * How long until we give up on connecting the peers?
39  */
40 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 120)
41
42 /**
43  * Time to wait for stuff that should be rather fast
44  */
45 #define SHORT_TIME GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60)
46
47
48 struct MeshPeer
49 {
50   /**
51    * Testbed Operation (to get peer id, etc).
52    */
53   struct GNUNET_TESTBED_Operation *op;
54
55   /**
56    * Peer ID.
57    */
58   struct GNUNET_PeerIdentity id;
59
60   /**
61    * Mesh handle for the root peer
62    */
63   struct GNUNET_MESH_Handle *mesh;
64
65   /**
66    * Channel handle for the root peer
67    */
68   struct GNUNET_MESH_Channel *ch;
69
70   /**
71    * Channel handle for the dest peer
72    */
73   struct GNUNET_MESH_Channel *incoming_ch;
74
75   /**
76    * Number of payload packes sent
77    */
78   int data_sent;
79
80   /**
81    * Number of payload packets received
82    */
83   int data_received;
84
85   struct MeshPeer *dest;
86   struct MeshPeer *incoming;
87   GNUNET_SCHEDULER_TaskIdentifier ping_task;
88   struct GNUNET_TIME_Absolute timestamp;
89 };
90
91 /**
92  * GNUNET_PeerIdentity -> MeshPeer
93  */
94 static struct GNUNET_CONTAINER_MultiPeerMap *ids;
95
96 /**
97  * Testbed peer handles.
98  */
99 static struct GNUNET_TESTBED_Peer **testbed_handles;
100
101 /**
102  * Testbed Operation (to get stats).
103  */
104 static struct GNUNET_TESTBED_Operation *stats_op;
105
106 /**
107  * How many events have happened
108  */
109 static int ok;
110
111 /**
112  * Number of events expected to conclude the test successfully.
113  */
114 static int ok_goal;
115
116 /**
117  * Size of each test packet
118  */
119 size_t size_payload = sizeof (struct GNUNET_MessageHeader) + sizeof (uint32_t);
120
121 /**
122  * Operation to get peer ids.
123  */
124 struct MeshPeer peers[TOTAL_PEERS];
125
126 /**
127  * Peer ids counter.
128  */
129 static unsigned int p_ids;
130
131 /**
132  * Total number of currently running peers.
133  */
134 static unsigned long long peers_running;
135
136 /**
137  * Test context (to shut down).
138  */
139 static struct GNUNET_MESH_TEST_Context *test_ctx;
140
141 /**
142  * Task called to shutdown test.
143  */
144 static GNUNET_SCHEDULER_TaskIdentifier shutdown_handle;
145
146 /**
147  * Task called to disconnect peers, before shutdown.
148  */
149 static GNUNET_SCHEDULER_TaskIdentifier disconnect_task;
150
151 /**
152  * Task to perform tests
153  */
154 static GNUNET_SCHEDULER_TaskIdentifier test_task;
155
156
157 /**
158  * Flag to notify callbacks not to generate any new traffic anymore.
159  */
160 static int test_finished;
161
162 /**
163  * Calculate a random delay.
164  *
165  * @param max Exclusive maximum, in ms.
166  *
167  * @return A time between 0 a max-1 ms.
168  */
169 static struct GNUNET_TIME_Relative
170 delay_ms_rnd (unsigned int max)
171 {
172   unsigned int rnd;
173
174   rnd = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, max);
175   return GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, rnd);
176 }
177
178
179 /**
180  * Get the index of a peer in the peers array.
181  *
182  * @param peer Peer whose index to get.
183  *
184  * @return Index of peer in peers.
185  */
186 static unsigned int
187 get_index (struct MeshPeer *peer)
188 {
189   return peer - peers;
190 }
191
192
193 /**
194  * Show the results of the test (banwidth acheived) and log them to GAUGER
195  */
196 static void
197 show_end_data (void)
198 {
199 }
200
201
202 /**
203  * Shut down peergroup, clean up.
204  *
205  * @param cls Closure (unused).
206  * @param tc Task Context.
207  */
208 static void
209 shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
210 {
211   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Ending test.\n");
212   shutdown_handle = GNUNET_SCHEDULER_NO_TASK;
213 }
214
215
216 /**
217  * Disconnect from mesh services af all peers, call shutdown.
218  *
219  * @param cls Closure (unused).
220  * @param tc Task Context.
221  */
222 static void
223 disconnect_mesh_peers (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
224 {
225   long line = (long) cls;
226   unsigned int i;
227
228   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
229               "disconnecting mesh service of peers, called from line %ld\n",
230               line);
231   disconnect_task = GNUNET_SCHEDULER_NO_TASK;
232   for (i = 0; i < TOTAL_PEERS; i++)
233   {
234     GNUNET_TESTBED_operation_done (peers[i].op);
235     GNUNET_MESH_channel_destroy (peers[i].ch);
236     GNUNET_MESH_channel_destroy (peers[i].incoming_ch);
237   }
238   GNUNET_MESH_TEST_cleanup (test_ctx);
239   if (GNUNET_SCHEDULER_NO_TASK != shutdown_handle)
240   {
241     GNUNET_SCHEDULER_cancel (shutdown_handle);
242   }
243   shutdown_handle = GNUNET_SCHEDULER_add_now (&shutdown_task, NULL);
244 }
245
246
247 /**
248  * Finish test normally: schedule disconnect and shutdown
249  *
250  * @param line Line in the code the abort is requested from (__LINE__).
251  */
252 static void
253 abort_test (long line)
254 {
255   if (disconnect_task != GNUNET_SCHEDULER_NO_TASK)
256   {
257     GNUNET_SCHEDULER_cancel (disconnect_task);
258     disconnect_task = GNUNET_SCHEDULER_add_now (&disconnect_mesh_peers,
259                                                 (void *) line);
260   }
261 }
262
263 /**
264  * Stats callback. Finish the stats testbed operation and when all stats have
265  * been iterated, shutdown the test.
266  *
267  * @param cls closure
268  * @param op the operation that has been finished
269  * @param emsg error message in case the operation has failed; will be NULL if
270  *          operation has executed successfully.
271  */
272 static void
273 stats_cont (void *cls, struct GNUNET_TESTBED_Operation *op, const char *emsg)
274 {
275   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "... collecting statistics done.\n");
276   GNUNET_TESTBED_operation_done (stats_op);
277
278   if (GNUNET_SCHEDULER_NO_TASK != disconnect_task)
279     GNUNET_SCHEDULER_cancel (disconnect_task);
280   disconnect_task = GNUNET_SCHEDULER_add_now (&disconnect_mesh_peers,
281                                               (void *) __LINE__);
282
283 }
284
285
286 /**
287  * Process statistic values.
288  *
289  * @param cls closure
290  * @param peer the peer the statistic belong to
291  * @param subsystem name of subsystem that created the statistic
292  * @param name the name of the datum
293  * @param value the current value
294  * @param is_persistent GNUNET_YES if the value is persistent, GNUNET_NO if not
295  * @return GNUNET_OK to continue, GNUNET_SYSERR to abort iteration
296  */
297 static int
298 stats_iterator (void *cls, const struct GNUNET_TESTBED_Peer *peer,
299                 const char *subsystem, const char *name,
300                 uint64_t value, int is_persistent)
301 {
302   uint32_t i;
303
304   i = GNUNET_TESTBED_get_index (peer);
305   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "  %u - %s [%s]: %llu\n",
306               i, subsystem, name, value);
307
308   return GNUNET_OK;
309 }
310
311
312 /**
313  * Task check that keepalives were sent and received.
314  *
315  * @param cls Closure (NULL).
316  * @param tc Task Context.
317  */
318 static void
319 collect_stats (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
320 {
321   if ((GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason) != 0)
322     return;
323
324   disconnect_task = GNUNET_SCHEDULER_NO_TASK;
325   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Start collecting statistics...\n");
326   stats_op = GNUNET_TESTBED_get_statistics (TOTAL_PEERS, testbed_handles,
327                                             NULL, NULL,
328                                             stats_iterator, stats_cont, NULL);
329 }
330
331
332 /**
333  * @brief Finish profiler normally.
334  *
335  * @param cls Closure (unused).
336  * @param tc Task context.
337  */
338 static void
339 finish_profiler (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
340 {
341   if ((GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason) != 0)
342     return;
343
344   test_finished = GNUNET_YES;
345   show_end_data();
346   GNUNET_SCHEDULER_add_now (&collect_stats, NULL);
347 }
348
349
350
351 /**
352  * Transmit ready callback.
353  *
354  * @param cls Closure (peer).
355  * @param size Size of the tranmist buffer.
356  * @param buf Pointer to the beginning of the buffer.
357  *
358  * @return Number of bytes written to buf.
359  */
360 static size_t
361 tmt_rdy (void *cls, size_t size, void *buf);
362
363
364 /**
365  * @brief Send data to destination
366  *
367  * @param cls Closure (peer).
368  * @param tc Task context.
369  */
370 static void
371 ping (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
372 {
373   struct MeshPeer *peer = (struct MeshPeer *) cls;
374
375   peer->ping_task = GNUNET_SCHEDULER_NO_TASK;
376   if ((GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason) != 0)
377     return;
378
379   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "%u -> %u\n",
380               get_index (peer), get_index (peer->dest));
381
382   GNUNET_MESH_notify_transmit_ready (peer->ch, GNUNET_NO,
383                                      GNUNET_TIME_UNIT_FOREVER_REL,
384                                      size_payload, &tmt_rdy, peer);
385 }
386
387
388 /**
389  * Transmit ready callback
390  *
391  * @param cls Closure (peer).
392  * @param size Size of the buffer we have.
393  * @param buf Buffer to copy data to.
394  */
395 size_t
396 tmt_rdy (void *cls, size_t size, void *buf)
397 {
398   struct MeshPeer *peer = (struct MeshPeer *) cls;
399   struct GNUNET_MessageHeader *msg = buf;
400   uint32_t *data;
401   unsigned int s;
402
403   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "tmt_rdy called, filling buffer\n");
404   if (size < size_payload || NULL == buf)
405   {
406     GNUNET_break (ok >= ok_goal - 2);
407     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
408                 "size %u, buf %p, data_sent %u, data_received %u\n",
409                 size, buf, peer->data_sent, peer->data_received);
410     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "ok %u, ok goal %u\n", ok, ok_goal);
411
412     return 0;
413   }
414   msg->size = htons (size);
415   msg->type = htons ((long) cls);
416   data = (uint32_t *) &msg[1];
417   *data = htonl (peer->data_sent);
418   if (0 == peer->data_sent)
419   {
420     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sent: initializer\n");
421     s = 5;
422   }
423   else
424   {
425     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sent: msg %d\n", peer->data_sent);
426     s = 60;
427   }
428   peer->data_sent++;
429   peer->timestamp = GNUNET_TIME_absolute_get ();
430   peer->ping_task = GNUNET_SCHEDULER_add_delayed (delay_ms_rnd (s * 1000),
431                                                   &ping, peer);
432
433   return size_payload;
434 }
435
436
437 /**
438  * Function is called whenever a message is received.
439  *
440  * @param cls closure (peer #, set from GNUNET_MESH_connect)
441  * @param channel connection to the other end
442  * @param channel_ctx place to store local state associated with the channel
443  * @param message the actual message
444  * @return GNUNET_OK to keep the connection open,
445  *         GNUNET_SYSERR to close it (signal serious error)
446  */
447 int
448 data_callback (void *cls, struct GNUNET_MESH_Channel *channel,
449                void **channel_ctx,
450                const struct GNUNET_MessageHeader *message)
451 {
452   long n = (long) cls;
453   struct MeshPeer *peer;
454   struct GNUNET_TIME_Relative latency;
455
456   GNUNET_MESH_receive_done (channel);
457   peer = &peers[n];
458
459   GNUNET_assert (0 != peer->timestamp.abs_value_us);
460   latency = GNUNET_TIME_absolute_get_duration (peer->incoming->timestamp);
461   FPRINTF (stderr, "%u -> %ld latency: %s\n",
462            get_index (peer->incoming), n,
463            GNUNET_STRINGS_relative_time_to_string (latency, GNUNET_NO));
464   peer->timestamp.abs_value_us = 0;
465
466   return GNUNET_OK;
467 }
468
469
470 /**
471  * Handlers, for diverse services
472  */
473 static struct GNUNET_MESH_MessageHandler handlers[] = {
474   {&data_callback, 1, sizeof (struct GNUNET_MessageHeader)},
475   {NULL, 0, 0}
476 };
477
478
479 /**
480  * Method called whenever another peer has added us to a channel
481  * the other peer initiated.
482  *
483  * @param cls Closure.
484  * @param channel New handle to the channel.
485  * @param initiator Peer that started the channel.
486  * @param port Port this channel is connected to.
487  * @param options channel option flags
488  * @return Initial channel context for the channel
489  *         (can be NULL -- that's not an error).
490  */
491 static void *
492 incoming_channel (void *cls, struct GNUNET_MESH_Channel *channel,
493                  const struct GNUNET_PeerIdentity *initiator,
494                  uint32_t port, enum GNUNET_MESH_ChannelOption options)
495 {
496   long n = (long) cls;
497   struct MeshPeer *peer;
498
499   peer = GNUNET_CONTAINER_multipeermap_get (ids, initiator);
500   GNUNET_assert (NULL != peer);
501   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "%u <= %u\n", n, get_index (peer));
502   peers[n].incoming_ch = channel;
503
504   if (GNUNET_SCHEDULER_NO_TASK != disconnect_task)
505   {
506     GNUNET_SCHEDULER_cancel (disconnect_task);
507     disconnect_task = GNUNET_SCHEDULER_add_delayed (SHORT_TIME,
508                                                     &disconnect_mesh_peers,
509                                                     (void *) __LINE__);
510   }
511
512   return NULL;
513 }
514
515 /**
516  * Function called whenever an inbound channel is destroyed.  Should clean up
517  * any associated state.
518  *
519  * @param cls closure (set from GNUNET_MESH_connect)
520  * @param channel connection to the other end (henceforth invalid)
521  * @param channel_ctx place where local state associated
522  *                   with the channel is stored
523  */
524 static void
525 channel_cleaner (void *cls, const struct GNUNET_MESH_Channel *channel,
526                  void *channel_ctx)
527 {
528   long n = (long) cls;
529
530   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
531               "Incoming channel disconnected at peer %ld\n", n);
532   GNUNET_log (GNUNET_ERROR_TYPE_INFO, " ok: %d\n", ok);
533
534   if (GNUNET_SCHEDULER_NO_TASK != disconnect_task)
535   {
536     GNUNET_SCHEDULER_cancel (disconnect_task);
537     disconnect_task = GNUNET_SCHEDULER_add_now (&disconnect_mesh_peers,
538                                                 (void *) __LINE__);
539   }
540 }
541
542
543 /**
544  * START THE TESTCASE ITSELF, AS WE ARE CONNECTED TO THE MESH SERVICES.
545  *
546  * Testcase continues when the root receives confirmation of connected peers,
547  * on callback funtion ch.
548  *
549  * @param cls Closure (unsued).
550  * @param tc Task Context.
551  */
552 static void
553 do_test (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
554 {
555   enum GNUNET_MESH_ChannelOption flags;
556   unsigned long i;
557
558   if ((GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason) != 0)
559     return;
560
561   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Start profiler\n");
562
563   if (GNUNET_SCHEDULER_NO_TASK != disconnect_task)
564     GNUNET_SCHEDULER_cancel (disconnect_task);
565   disconnect_task = GNUNET_SCHEDULER_add_delayed (SHORT_TIME,
566                                                   &disconnect_mesh_peers,
567                                                   (void *) __LINE__);
568
569   flags = GNUNET_MESH_OPTION_DEFAULT;
570   for (i = 0; i < TOTAL_PEERS; i++)
571   {
572     unsigned int r;
573     r = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, TOTAL_PEERS);
574     peers[i].dest = &peers[r];
575     peers[i].ch = GNUNET_MESH_channel_create (peers[i].mesh, NULL,
576                                               &peers[i].dest->id,
577                                               1, flags);
578     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "%u => %u\n", i, r);
579     peers[i].ping_task = GNUNET_SCHEDULER_add_delayed (delay_ms_rnd (2000),
580                                                        &ping, &peers[i]);
581   }
582 }
583
584
585 /**
586  * Callback to be called when the requested peer information is available
587  *
588  * @param cls the closure from GNUNET_TESTBED_peer_get_information()
589  * @param op the operation this callback corresponds to
590  * @param pinfo the result; will be NULL if the operation has failed
591  * @param emsg error message if the operation has failed;
592  *             NULL if the operation is successfull
593  */
594 static void
595 peer_id_cb (void *cls,
596        struct GNUNET_TESTBED_Operation *op,
597        const struct GNUNET_TESTBED_PeerInformation *pinfo,
598        const char *emsg)
599 {
600   long n = (long) cls;
601
602   if (NULL == pinfo || NULL != emsg)
603   {
604     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "pi_cb: %s\n", emsg);
605     abort_test (__LINE__);
606     return;
607   }
608   peers[n].id = *(pinfo->result.id);
609   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, " %u  id: %s\n",
610               n, GNUNET_i2s (&peers[n].id));
611   GNUNET_break (GNUNET_OK ==
612                 GNUNET_CONTAINER_multipeermap_put (ids, &peers[n].id, &peers[n],
613                                                    GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST));
614   p_ids++;
615   if (p_ids < TOTAL_PEERS)
616     return;
617   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Got all IDs, starting profiler\n");
618   test_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
619                                             &do_test, NULL);
620 }
621
622 /**
623  * test main: start test when all peers are connected
624  *
625  * @param cls Closure.
626  * @param ctx Argument to give to GNUNET_MESH_TEST_cleanup on test end.
627  * @param num_peers Number of peers that are running.
628  * @param testbed_peers Array of peers.
629  * @param meshes Handle to each of the MESHs of the peers.
630  */
631 static void
632 tmain (void *cls,
633        struct GNUNET_MESH_TEST_Context *ctx,
634        unsigned int num_peers,
635        struct GNUNET_TESTBED_Peer **testbed_peers,
636        struct GNUNET_MESH_Handle **meshes)
637 {
638   unsigned long i;
639
640   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "test main\n");
641   ok = 0;
642   test_ctx = ctx;
643   GNUNET_assert (TOTAL_PEERS == num_peers);
644   peers_running = num_peers;
645   testbed_handles = testbed_handles;
646   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES,
647                                 &finish_profiler, NULL);
648   disconnect_task = GNUNET_SCHEDULER_add_delayed (SHORT_TIME,
649                                                   &disconnect_mesh_peers,
650                                                   (void *) __LINE__);
651   shutdown_handle = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
652                                                   &shutdown_task, NULL);
653   for (i = 0; i < TOTAL_PEERS; i++)
654   {
655     peers[i].mesh = meshes[i];
656     peers[i].op =
657       GNUNET_TESTBED_peer_get_information (testbed_handles[i],
658                                            GNUNET_TESTBED_PIT_IDENTITY,
659                                            &peer_id_cb, (void *) i);
660   }
661   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "requested peer ids\n");
662   /* Continues from pi_cb -> do_test */
663 }
664
665
666 /**
667  * Main: start profiler.
668  */
669 int
670 main (int argc, char *argv[])
671 {
672   static uint32_t ports[2];
673   const char *config_file;
674
675   config_file = "test_mesh.conf";
676
677   ids = GNUNET_CONTAINER_multipeermap_create (2 * TOTAL_PEERS, GNUNET_YES);
678   GNUNET_assert (NULL != ids);
679   p_ids = 0;
680   test_finished = GNUNET_NO;
681   ports[0] = 1;
682   ports[1] = 0;
683   GNUNET_MESH_TEST_run ("mesh-profiler", config_file, TOTAL_PEERS,
684                         &tmain, NULL, /* tmain cls */
685                         &incoming_channel, &channel_cleaner,
686                         handlers, ports);
687
688   if (ok_goal > ok)
689   {
690     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
691                 "FAILED! (%d/%d)\n", ok, ok_goal);
692     return 1;
693   }
694   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "success\n");
695   return 0;
696 }
697
698 /* end of test_mesh_small.c */
699