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