replace printf() with GNUNET_log()
[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      SPDX-License-Identifier: AGPL3.0-or-later
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
36
37 struct Iteration
38 {
39   struct Iteration *next;
40   struct Iteration *prev;
41   struct GNUNET_TIME_Absolute start;
42   struct GNUNET_TIME_Absolute end;
43
44   struct GNUNET_TIME_Relative dur;
45
46   /* Transmission rate for this iteration in KB/s */
47   float rate;
48
49   unsigned int msgs_sent;
50 };
51
52
53 /**
54  * Timeout for a connections
55  */
56 #define CONNECT_TIMEOUT \
57   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,
220                  _ ("%llu B in %llu ms == %.2f KB/s!\n"),
221                  ((long long unsigned int) benchmark_count * benchmark_size),
222                  ((long long unsigned int) icur->dur.rel_value_us / 1000),
223                  (float) icur->rate);
224
225       avg_duration += icur->dur.rel_value_us / (1000);
226       avg_rate += icur->rate;
227       iterations++;
228     }
229     if (0 == iterations)
230       iterations = 1;   /* avoid division by zero */
231     /* Calculate average rate */
232     avg_rate /= iterations;
233     /* Calculate average duration */
234     avg_duration /= iterations;
235
236     stddev_rate = 0;
237     stddev_duration = 0;
238     inext = ihead;
239     while (NULL != (icur = inext))
240     {
241       inext = icur->next;
242       stddev_rate += ((icur->rate - avg_rate) * (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     /* Calculate standard deviation rate */
247     stddev_rate = stddev_rate / iterations;
248     stddev_rate = sqrtf (stddev_rate);
249
250     /* Calculate standard deviation duration */
251     stddev_duration = stddev_duration / iterations;
252     stddev_duration = sqrtf (stddev_duration);
253
254     /* Output */
255     fprintf (stdout,
256              "%u;%u;%llu;%llu;%.2f;%.2f",
257              benchmark_count,
258              benchmark_size,
259              avg_duration,
260              (unsigned long long) stddev_duration,
261              avg_rate,
262              stddev_rate);
263
264     inext = ihead;
265     while (NULL != (icur = inext))
266     {
267       inext = icur->next;
268       GNUNET_CONTAINER_DLL_remove (ihead, itail, icur);
269
270       fprintf (stdout,
271                ";%llu;%.2f",
272                (long long unsigned int) (icur->dur.rel_value_us / 1000),
273                icur->rate);
274
275       GNUNET_free (icur);
276     }
277   }
278 #if 0
279   if (benchmark_receive)
280   {
281     duration = GNUNET_TIME_absolute_get_duration (start_time);
282     fprintf (stdout,
283              "Received %llu bytes/s (%llu bytes in %s)\n",
284              1000LL * 1000LL * traffic_received / (1 + duration.rel_value_us),
285              traffic_received,
286              GNUNET_STRINGS_relative_time_to_string (duration, GNUNET_YES));
287   }
288 #endif
289   fprintf (stdout, "\n");
290 }
291
292
293 static void
294 iteration_done ();
295
296
297 /**
298  * Function called to notify a client about the socket
299  * begin ready to queue more data.  @a buf will be
300  * NULL and @a size zero if the socket was closed for
301  * writing in the meantime.
302  *
303  * @param cls closure
304  * @param size number of bytes available in @a buf
305  * @param buf where the callee should write the message
306  * @return number of bytes written to @a buf
307  */
308 static void
309 send_msg (void *cls)
310 {
311   struct GNUNET_MQ_Envelope *env;
312   struct GNUNET_MessageHeader *m;
313
314   if (NULL == mq)
315     return;
316   env = GNUNET_MQ_msg_extra (m, benchmark_size, GNUNET_MESSAGE_TYPE_DUMMY);
317   memset (&m[1], 52, benchmark_size - sizeof(struct GNUNET_MessageHeader));
318
319   if (itail->msgs_sent < benchmark_count)
320   {
321     GNUNET_MQ_notify_sent (env, &send_msg, NULL);
322   }
323   else
324   {
325     iteration_done ();
326   }
327   GNUNET_MQ_send (mq, env);
328   if ((verbosity > 0) && (0 == itail->msgs_sent % 10))
329     fprintf (stdout, ".");
330 }
331
332
333 static void
334 iteration_start ()
335 {
336   struct Iteration *icur;
337
338   ret = 0;
339   if (! benchmark_send)
340     return;
341   benchmark_running = GNUNET_YES;
342   icur = GNUNET_new (struct Iteration);
343   GNUNET_CONTAINER_DLL_insert_tail (ihead, itail, icur);
344   icur->start = GNUNET_TIME_absolute_get ();
345   if (verbosity > 0)
346     fprintf (
347       stdout,
348       "\nStarting benchmark, starting to send %u messages in %u byte blocks\n",
349       benchmark_count,
350       benchmark_size);
351   send_msg (NULL);
352 }
353
354
355 static void
356 iteration_done ()
357 {
358   static int it_count = 0;
359
360   it_count++;
361   itail->dur = GNUNET_TIME_absolute_get_duration (itail->start);
362   if (it_count == benchmark_iterations)
363   {
364     benchmark_running = GNUNET_NO;
365     GNUNET_SCHEDULER_shutdown ();
366     return;
367   }
368   iteration_start ();
369 }
370
371
372 /**
373  * Function called to notify transport users that another
374  * peer connected to us.
375  *
376  * @param cls closure
377  * @param peer the peer that connected
378  * @param m message queue for transmissions
379  * @return NULL
380  */
381 static void *
382 notify_connect (void *cls,
383                 const struct GNUNET_PeerIdentity *peer,
384                 struct GNUNET_MQ_Handle *m)
385 {
386   if (0 != memcmp (&pid, peer, sizeof(struct GNUNET_PeerIdentity)))
387   {
388     fprintf (stdout, "Connected to different peer `%s'\n", GNUNET_i2s (&pid));
389     return NULL;
390   }
391
392   if (verbosity > 0)
393     fprintf (stdout, "Successfully connected to `%s'\n", GNUNET_i2s (&pid));
394   mq = m;
395   iteration_start ();
396   return NULL;
397 }
398
399
400 /**
401  * Function called to notify transport users that another
402  * peer disconnected from us.
403  *
404  * @param cls closure
405  * @param peer the peer that disconnected
406  * @param internal_cls NULL
407  */
408 static void
409 notify_disconnect (void *cls,
410                    const struct GNUNET_PeerIdentity *peer,
411                    void *internal_cls)
412 {
413   if (0 != memcmp (&pid, peer, sizeof(struct GNUNET_PeerIdentity)))
414     return;
415   mq = NULL;
416   if (GNUNET_YES == benchmark_running)
417   {
418     fprintf (stdout,
419              "Disconnected from peer `%s' while benchmarking\n",
420              GNUNET_i2s (&pid));
421     return;
422   }
423 }
424
425
426 /**
427  * Function called by the transport for each received message.
428  *
429  * @param cls closure
430  * @param message the message
431  * @return #GNUNET_OK
432  */
433 static int
434 check_dummy (void *cls, const struct GNUNET_MessageHeader *message)
435 {
436   return GNUNET_OK; /* all messages are fine */
437 }
438
439
440 /**
441  * Function called by the transport for each received message.
442  *
443  * @param cls closure
444  * @param message the message
445  */
446 static void
447 handle_dummy (void *cls, const struct GNUNET_MessageHeader *message)
448 {
449   if (! benchmark_receive)
450     return;
451   if (verbosity > 0)
452     fprintf (stdout,
453              "Received %u bytes\n",
454              (unsigned int) ntohs (message->size));
455 }
456
457
458 static int
459 blacklist_cb (void *cls, const struct GNUNET_PeerIdentity *peer)
460 {
461   if (0 != memcmp (&pid, peer, sizeof(struct GNUNET_PeerIdentity)))
462   {
463     if (verbosity > 0)
464       fprintf (stdout, "Denying connection to `%s'\n", GNUNET_i2s (peer));
465     return GNUNET_SYSERR;
466   }
467   return GNUNET_OK;
468 }
469
470
471 /**
472  * Main function that will be run by the scheduler.
473  *
474  * @param cls closure
475  * @param args remaining command-line arguments
476  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
477  * @param mycfg configuration
478  */
479 static void
480 run (void *cls,
481      char *const *args,
482      const char *cfgfile,
483      const struct GNUNET_CONFIGURATION_Handle *mycfg)
484 {
485   struct GNUNET_MQ_MessageHandler handlers[] =
486   { GNUNET_MQ_hd_var_size (dummy,
487                            GNUNET_MESSAGE_TYPE_DUMMY,
488                            struct GNUNET_MessageHeader,
489                            NULL),
490     GNUNET_MQ_handler_end () };
491
492   cfg = (struct GNUNET_CONFIGURATION_Handle *) mycfg;
493
494   ret = 1;
495   if (GNUNET_MAX_MESSAGE_SIZE <= benchmark_size)
496   {
497     fprintf (stderr, "Message size too big!\n");
498     return;
499   }
500
501   if (NULL == cpid)
502   {
503     fprintf (stderr, "No peer identity given\n");
504     return;
505   }
506   if (GNUNET_OK != GNUNET_CRYPTO_eddsa_public_key_from_string (cpid,
507                                                                strlen (cpid),
508                                                                &pid.public_key))
509   {
510     fprintf (stderr, "Failed to parse peer identity `%s'\n", cpid);
511     return;
512   }
513   if (1 == benchmark_send)
514   {
515     if (verbosity > 0)
516       fprintf (stderr,
517                "Trying to send %u messages with size %u to peer `%s'\n",
518                benchmark_count,
519                benchmark_size,
520                GNUNET_i2s (&pid));
521   }
522   else if (1 == benchmark_receive)
523   {
524     fprintf (stderr,
525              "Trying to receive messages from peer `%s'\n",
526              GNUNET_i2s (&pid));
527   }
528   else
529   {
530     fprintf (stderr, "No operation given\n");
531     return;
532   }
533
534   ats = GNUNET_ATS_connectivity_init (cfg);
535   if (NULL == ats)
536   {
537     fprintf (stderr, "Failed to connect to ATS service\n");
538     ret = 1;
539     return;
540   }
541
542   handle = GNUNET_TRANSPORT_core_connect (cfg,
543                                           NULL,
544                                           handlers,
545                                           NULL,
546                                           &notify_connect,
547                                           &notify_disconnect,
548                                           NULL);
549   if (NULL == handle)
550   {
551     fprintf (stderr, "Failed to connect to transport service\n");
552     GNUNET_ATS_connectivity_done (ats);
553     ats = NULL;
554     ret = 1;
555     return;
556   }
557
558   bl_handle = GNUNET_TRANSPORT_blacklist (cfg, &blacklist_cb, NULL);
559   ats_sh = GNUNET_ATS_connectivity_suggest (ats, &pid, 1);
560   GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
561 }
562
563
564 int
565 main (int argc, char *const *argv)
566 {
567   int res;
568
569   benchmark_count = DEFAULT_MESSAGE_COUNT;
570   benchmark_size = DEFAULT_MESSAGE_SIZE;
571   benchmark_iterations = DEFAULT_ITERATION_COUNT;
572   benchmark_running = GNUNET_NO;
573
574   struct GNUNET_GETOPT_CommandLineOption options[] = {
575     GNUNET_GETOPT_option_flag ('s',
576                                "send",
577                                gettext_noop ("send data to peer"),
578                                &benchmark_send),
579     GNUNET_GETOPT_option_flag ('r',
580                                "receive",
581                                gettext_noop ("receive data from peer"),
582                                &benchmark_receive),
583     GNUNET_GETOPT_option_uint ('i',
584                                "iterations",
585                                NULL,
586                                gettext_noop ("iterations"),
587                                &benchmark_iterations),
588     GNUNET_GETOPT_option_uint ('n',
589                                "number",
590                                NULL,
591                                gettext_noop ("number of messages to send"),
592                                &benchmark_count),
593     GNUNET_GETOPT_option_uint ('m',
594                                "messagesize",
595                                NULL,
596                                gettext_noop ("message size to use"),
597                                &benchmark_size),
598     GNUNET_GETOPT_option_string ('p',
599                                  "peer",
600                                  "PEER",
601                                  gettext_noop ("peer identity"),
602                                  &cpid),
603     GNUNET_GETOPT_option_verbose (&verbosity),
604     GNUNET_GETOPT_OPTION_END
605   };
606
607   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
608     return 2;
609
610   res =
611     GNUNET_PROGRAM_run (argc,
612                         argv,
613                         "gnunet-transport",
614                         gettext_noop ("Direct access to transport service."),
615                         options,
616                         &run,
617                         NULL);
618   GNUNET_free ((void *) argv);
619   if (GNUNET_OK == res)
620     return ret;
621   return 1;
622 }
623
624
625 /* end of gnunet-transport-profiler.c */