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