Merge branch 'master' of gnunet.org:gnunet
[oweals/gnunet.git] / src / transport / gnunet-transport-profiler.c
1 /*
2  This file is part of GNUnet.
3  Copyright (C) 2011-2016 GNUnet e.V.
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., 51 Franklin Street, Fifth Floor,
18  Boston, MA 02110-1301, USA.
19  */
20
21 /**
22  * @file src/transport/gnunet-transport-profiler.c
23  * @brief Tool to help benchmark the transport subsystem.
24  * @author Christian Grothoff
25  * @author Nathan Evans
26  *
27  * This utility can be used to benchmark a transport mechanism for
28  * GNUnet.
29  */
30 #include "platform.h"
31 #include "gnunet_util_lib.h"
32 #include "gnunet_protocols.h"
33 #include "gnunet_ats_service.h"
34 #include "gnunet_transport_service.h"
35 #include "gnunet_transport_core_service.h"
36
37
38 struct Iteration
39 {
40   struct Iteration *next;
41   struct Iteration *prev;
42   struct GNUNET_TIME_Absolute start;
43   struct GNUNET_TIME_Absolute end;
44
45   struct GNUNET_TIME_Relative dur;
46
47   /* Transmission rate for this iteration in KB/s */
48   float rate;
49
50   unsigned int msgs_sent;
51 };
52
53
54 /**
55  * Timeout for a connections
56  */
57 #define CONNECT_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)
58
59 /**
60  * Benchmarking block size in bye
61  */
62 #define DEFAULT_MESSAGE_SIZE 1024
63
64 /**
65  * Benchmarking message count
66  */
67 #define DEFAULT_MESSAGE_COUNT 1024
68
69 /**
70  * Benchmarking iteration count
71  */
72 #define DEFAULT_ITERATION_COUNT 1
73
74 /**
75  * Option -s.
76  */
77 static int benchmark_send;
78
79 /**
80  * Option -b.
81  */
82 static int benchmark_receive;
83
84 /**
85  * Option -n.
86  */
87 static unsigned int benchmark_count;
88
89 /**
90  * Option -i.
91  */
92 static unsigned int benchmark_iterations;
93
94 /**
95  * Option -m.
96  */
97 static unsigned int benchmark_size;
98
99 /**
100  * Benchmark running
101  */
102 static unsigned int benchmark_running;
103
104 /**
105  * Which peer should we connect to?
106  */
107 static char *cpid;
108
109 /**
110  * Handle to transport service.
111  */
112 static struct GNUNET_TRANSPORT_CoreHandle *handle;
113
114 /**
115  * Handle to ATS service.
116  */
117 static struct GNUNET_ATS_ConnectivityHandle *ats;
118
119 /**
120  * Configuration handle
121  */
122 static struct GNUNET_CONFIGURATION_Handle *cfg;
123
124 /**
125  * Try_connect handle
126  */
127 static struct GNUNET_ATS_ConnectivitySuggestHandle *ats_sh;
128
129 static struct Iteration *ihead;
130
131 static struct Iteration *itail;
132
133 /**
134  * Global return value (0 success).
135  */
136 static int ret;
137
138 /**
139  * Handle for transmissions.
140  */
141 static struct GNUNET_MQ_Handle *mq;
142
143 static struct GNUNET_TRANSPORT_Blacklist *bl_handle;
144
145 /**
146  * Identity of the peer we transmit to / connect to.
147  * (equivalent to 'cpid' string).
148  */
149 static struct GNUNET_PeerIdentity pid;
150
151 /**
152  * Selected level of verbosity.
153  */
154 static unsigned int verbosity;
155
156
157 /**
158  * Task run in monitor mode when the user presses CTRL-C to abort.
159  * Stops monitoring activity.
160  *
161  * @param cls NULL
162  */
163 static void
164 shutdown_task (void *cls)
165 {
166   struct Iteration *icur;
167   struct Iteration *inext;
168
169   unsigned int iterations;
170
171   unsigned long long avg_duration;
172   float avg_rate;
173   float stddev_rate;
174   float stddev_duration;
175
176   if (NULL != ats_sh)
177   {
178     GNUNET_ATS_connectivity_suggest_cancel (ats_sh);
179     ats_sh = NULL;
180   }
181   if (NULL != bl_handle)
182   {
183     GNUNET_TRANSPORT_blacklist_cancel (bl_handle);
184     bl_handle = NULL;
185   }
186   if (NULL != ats)
187   {
188     GNUNET_ATS_connectivity_done (ats);
189     ats = NULL;
190   }
191   if (NULL != handle)
192   {
193     GNUNET_TRANSPORT_core_disconnect (handle);
194     handle = NULL;
195   }
196
197   if (verbosity > 0)
198     FPRINTF (stdout, "\n");
199
200   /* Output format:
201    * All time values in ms
202    * Rate in KB/s
203    * #messages;#messagesize;#avg_dur;#avg_rate;#duration_i0;#duration_i0;... */
204
205   if (benchmark_send)
206   {
207     /* First iteration to calculcate avg and stddev */
208     iterations = 0;
209     avg_duration = 0;
210     avg_rate = 0.0;
211
212     inext = ihead;
213     while (NULL != (icur = inext))
214     {
215       inext = icur->next;
216       icur->rate = ((benchmark_count * benchmark_size) / 1024) /
217           ((float) icur->dur.rel_value_us / (1000 * 1000));
218       if (verbosity > 0)
219         FPRINTF (stdout, _("%llu B in %llu ms == %.2f KB/s!\n"),
220             ((long long unsigned int) benchmark_count * benchmark_size),
221             ((long long unsigned int) icur->dur.rel_value_us / 1000),
222             (float) icur->rate);
223
224       avg_duration += icur->dur.rel_value_us / (1000);
225       avg_rate  += icur->rate;
226       iterations++;
227     }
228     if (0 == iterations)
229       iterations = 1; /* avoid division by zero */
230     /* Calculate average rate */
231     avg_rate /= iterations;
232     /* Calculate average duration */
233     avg_duration /= iterations;
234
235     stddev_rate = 0;
236     stddev_duration = 0;
237     inext = ihead;
238     while (NULL != (icur = inext))
239     {
240       inext = icur->next;
241       stddev_rate += ((icur->rate-avg_rate) *
242           (icur->rate-avg_rate));
243       stddev_duration += (((icur->dur.rel_value_us / 1000) - avg_duration) *
244           ((icur->dur.rel_value_us / 1000) - avg_duration));
245
246     }
247     /* Calculate standard deviation rate */
248     stddev_rate = stddev_rate / iterations;
249     stddev_rate = sqrtf(stddev_rate);
250
251     /* Calculate standard deviation duration */
252     stddev_duration = stddev_duration / iterations;
253     stddev_duration = sqrtf(stddev_duration);
254
255     /* Output */
256     FPRINTF (stdout,
257              "%u;%u;%llu;%llu;%.2f;%.2f",
258              benchmark_count,
259              benchmark_size,
260              avg_duration,
261              (unsigned long long) stddev_duration,
262              avg_rate,
263              stddev_rate);
264
265     inext = ihead;
266     while (NULL != (icur = inext))
267     {
268       inext = icur->next;
269       GNUNET_CONTAINER_DLL_remove (ihead,
270                                    itail,
271                                    icur);
272
273       FPRINTF (stdout,
274                ";%llu;%.2f",
275                (long long unsigned int) (icur->dur.rel_value_us / 1000),
276                icur->rate);
277
278       GNUNET_free (icur);
279     }
280   }
281 #if 0
282   if (benchmark_receive)
283   {
284     duration = GNUNET_TIME_absolute_get_duration (start_time);
285     FPRINTF (stdout,
286              "Received %llu bytes/s (%llu bytes in %s)\n",
287              1000LL * 1000LL * traffic_received / (1 + duration.rel_value_us),
288              traffic_received,
289              GNUNET_STRINGS_relative_time_to_string (duration, GNUNET_YES));
290   }
291 #endif
292   FPRINTF (stdout, "\n");
293 }
294
295
296 static void
297 iteration_done ();
298
299
300 /**
301  * Function called to notify a client about the socket
302  * begin ready to queue more data.  @a buf will be
303  * NULL and @a size zero if the socket was closed for
304  * writing in the meantime.
305  *
306  * @param cls closure
307  * @param size number of bytes available in @a buf
308  * @param buf where the callee should write the message
309  * @return number of bytes written to @a buf
310  */
311 static void
312 send_msg (void *cls)
313 {
314   struct GNUNET_MQ_Envelope *env;
315   struct GNUNET_MessageHeader *m;
316
317   if (NULL == mq)
318     return;
319   env = GNUNET_MQ_msg_extra (m,
320                              benchmark_size,
321                              GNUNET_MESSAGE_TYPE_DUMMY);
322   memset (&m[1],
323           52,
324           benchmark_size - sizeof(struct GNUNET_MessageHeader));
325   
326   if (itail->msgs_sent < benchmark_count)
327   {
328     GNUNET_MQ_notify_sent (env,
329                            &send_msg,
330                            NULL);
331   }
332   else
333   {
334     iteration_done ();
335   }
336   GNUNET_MQ_send (mq,
337                   env);
338   if ( (verbosity > 0) &&
339        (0 == itail->msgs_sent % 10) )
340     FPRINTF (stdout, ".");
341 }
342
343
344 static void
345 iteration_start ()
346 {
347   struct Iteration *icur;
348
349   ret = 0;
350   if (! benchmark_send)
351     return;
352   benchmark_running = GNUNET_YES;
353   icur = GNUNET_new (struct Iteration);
354   GNUNET_CONTAINER_DLL_insert_tail (ihead,
355                                     itail,
356                                     icur);
357   icur->start = GNUNET_TIME_absolute_get();
358   if (verbosity > 0)
359     FPRINTF (stdout,
360              "\nStarting benchmark, starting to send %u messages in %u byte blocks\n",
361              benchmark_count,
362              benchmark_size);
363   send_msg (NULL);
364 }
365
366
367 static void
368 iteration_done ()
369 {
370   static int it_count = 0;
371
372   it_count++;
373   itail->dur = GNUNET_TIME_absolute_get_duration (itail->start);
374   if (it_count == benchmark_iterations)
375   {
376     benchmark_running = GNUNET_NO;
377     GNUNET_SCHEDULER_shutdown ();
378     return;
379   }
380   iteration_start ();
381 }
382
383
384 /**
385  * Function called to notify transport users that another
386  * peer connected to us.
387  *
388  * @param cls closure
389  * @param peer the peer that connected
390  * @param m message queue for transmissions
391  * @return NULL
392  */
393 static void *
394 notify_connect (void *cls,
395                 const struct GNUNET_PeerIdentity *peer,
396                 struct GNUNET_MQ_Handle *m)
397 {
398   if (0 != memcmp (&pid,
399                    peer,
400                    sizeof(struct GNUNET_PeerIdentity)))
401   {
402     FPRINTF (stdout,
403              "Connected to different peer `%s'\n",
404              GNUNET_i2s (&pid));
405     return NULL;
406   }
407
408   if (verbosity > 0)
409     FPRINTF (stdout,
410              "Successfully connected to `%s'\n",
411              GNUNET_i2s (&pid));
412   mq = m;
413   iteration_start ();
414   return NULL;
415 }
416
417
418 /**
419  * Function called to notify transport users that another
420  * peer disconnected from us.
421  *
422  * @param cls closure
423  * @param peer the peer that disconnected
424  * @param internal_cls NULL
425  */
426 static void
427 notify_disconnect (void *cls,
428                    const struct GNUNET_PeerIdentity *peer,
429                    void *internal_cls)
430 {
431   if (0 != memcmp (&pid,
432                    peer,
433                    sizeof(struct GNUNET_PeerIdentity)))
434     return;
435   mq = NULL;
436   if (GNUNET_YES == benchmark_running)
437   {
438     FPRINTF (stdout,
439              "Disconnected from peer `%s' while benchmarking\n",
440              GNUNET_i2s (&pid));
441     return;
442   }
443 }
444
445
446 /**
447  * Function called by the transport for each received message.
448  *
449  * @param cls closure
450  * @param message the message
451  * @return #GNUNET_OK
452  */
453 static int
454 check_dummy (void *cls,
455              const struct GNUNET_MessageHeader *message)
456 {
457   return GNUNET_OK; /* all messages are fine */
458 }
459
460
461 /**
462  * Function called by the transport for each received message.
463  *
464  * @param cls closure
465  * @param message the message
466  */
467 static void
468 handle_dummy (void *cls,
469               const struct GNUNET_MessageHeader *message)
470 {
471   if (! benchmark_receive)
472     return;
473   if (verbosity > 0)
474     FPRINTF (stdout,
475              "Received %u bytes\n",
476              (unsigned int) ntohs (message->size));
477 }
478
479
480 static int
481 blacklist_cb (void *cls,
482               const struct GNUNET_PeerIdentity *peer)
483 {
484   if (0 != memcmp (&pid,
485                    peer,
486                    sizeof(struct GNUNET_PeerIdentity)))
487   {
488     if (verbosity > 0)
489       FPRINTF (stdout,
490                "Denying connection to `%s'\n",
491                GNUNET_i2s (peer));
492     return GNUNET_SYSERR;
493   }
494   return GNUNET_OK;
495 }
496
497
498 /**
499  * Main function that will be run by the scheduler.
500  *
501  * @param cls closure
502  * @param args remaining command-line arguments
503  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
504  * @param mycfg configuration
505  */
506 static void
507 run (void *cls,
508      char *const *args,
509      const char *cfgfile,
510      const struct GNUNET_CONFIGURATION_Handle *mycfg)
511 {
512   struct GNUNET_MQ_MessageHandler handlers[] = {
513     GNUNET_MQ_hd_var_size (dummy,
514                            GNUNET_MESSAGE_TYPE_DUMMY,
515                            struct GNUNET_MessageHeader,
516                            NULL),
517     GNUNET_MQ_handler_end ()
518   };
519   
520   cfg = (struct GNUNET_CONFIGURATION_Handle *) mycfg;
521
522   ret = 1;
523   if (GNUNET_MAX_MESSAGE_SIZE <= benchmark_size)
524   {
525     FPRINTF (stderr,
526              "Message size too big!\n");
527     return;
528   }
529
530   if (NULL == cpid)
531   {
532     FPRINTF (stderr,
533              "No peer identity given\n");
534     return;
535   }
536   if (GNUNET_OK !=
537       GNUNET_CRYPTO_eddsa_public_key_from_string (cpid,
538                                                   strlen (cpid),
539                                                   &pid.public_key))
540   {
541     FPRINTF (stderr,
542              "Failed to parse peer identity `%s'\n",
543              cpid);
544     return;
545   }
546   if (1 == benchmark_send)
547   {
548     if (verbosity > 0)
549       FPRINTF (stderr,
550                "Trying to send %u messages with size %u to peer `%s'\n",
551                benchmark_count, benchmark_size,
552                GNUNET_i2s (&pid));
553   }
554   else if (1 == benchmark_receive)
555   {
556     FPRINTF (stderr,
557              "Trying to receive messages from peer `%s'\n",
558              GNUNET_i2s (&pid));
559   }
560   else
561   {
562     FPRINTF (stderr,
563              "No operation given\n");
564     return;
565   }
566
567   ats = GNUNET_ATS_connectivity_init (cfg);
568   if (NULL == ats)
569   {
570     FPRINTF (stderr,
571              "Failed to connect to ATS service\n");
572     ret = 1;
573     return;
574   }
575
576   handle = GNUNET_TRANSPORT_core_connect (cfg,
577                                           NULL,
578                                           handlers,
579                                           NULL,
580                                           &notify_connect,
581                                           &notify_disconnect,
582                                           NULL);
583   if (NULL == handle)
584   {
585     FPRINTF (stderr,
586              "Failed to connect to transport service\n");
587     GNUNET_ATS_connectivity_done (ats);
588     ats = NULL;
589     ret = 1;
590     return;
591   }
592
593   bl_handle = GNUNET_TRANSPORT_blacklist (cfg,
594                                           &blacklist_cb,
595                                           NULL);
596   ats_sh = GNUNET_ATS_connectivity_suggest (ats,
597                                             &pid,
598                                             1);
599   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
600                                  NULL);
601 }
602
603
604 int
605 main (int argc, char * const *argv)
606 {
607   int res;
608   benchmark_count = DEFAULT_MESSAGE_COUNT;
609   benchmark_size = DEFAULT_MESSAGE_SIZE;
610   benchmark_iterations = DEFAULT_ITERATION_COUNT;
611   benchmark_running = GNUNET_NO;
612
613   struct GNUNET_GETOPT_CommandLineOption options[] = {
614
615     GNUNET_GETOPT_OPTION_SET_ONE ('s',
616                                   "send",
617                                   gettext_noop ("send data to peer"),
618                                   &benchmark_send),
619     GNUNET_GETOPT_OPTION_SET_ONE ('r',
620                                   "receive",
621                                   gettext_noop ("receive data from peer"),
622                                   &benchmark_receive),
623     GNUNET_GETOPT_OPTION_SET_UINT ('i',
624                                    "iterations",
625                                    NULL,
626                                    gettext_noop ("iterations"),
627                                    &benchmark_iterations),
628     GNUNET_GETOPT_OPTION_SET_UINT ('n',
629                                    "number",
630                                    NULL,
631                                    gettext_noop ("number of messages to send"),
632                                    &benchmark_count),
633     GNUNET_GETOPT_OPTION_SET_UINT ('m',
634                                    "messagesize",
635                                    NULL,
636                                    gettext_noop ("message size to use"),
637                                    &benchmark_size),
638     GNUNET_GETOPT_OPTION_STRING ('p',
639                                  "peer",
640                                  "PEER",
641                                  gettext_noop ("peer identity"),
642                                  &cpid),
643     GNUNET_GETOPT_OPTION_VERBOSE (&verbosity),
644     GNUNET_GETOPT_OPTION_END
645   };
646
647   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
648     return 2;
649
650   res = GNUNET_PROGRAM_run (argc, argv,
651                             "gnunet-transport",
652                             gettext_noop ("Direct access to transport service."),
653                             options,
654                             &run, NULL);
655   GNUNET_free((void *) argv);
656   if (GNUNET_OK == res)
657     return ret;
658   return 1;
659 }
660
661 /* end of gnunet-transport-profiler.c */