Merge branch 'master' of git+ssh://gnunet.org/gnunet
[oweals/gnunet.git] / src / gns / gnunet-gns-benchmark.c
1 /*
2      This file is part of GNUnet
3      Copyright (C) 2018 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  * @file src/gns/gnunet-gns-benchmark.c
22  * @brief issue many queries to GNS and compute performance statistics
23  * @author Christian Grothoff
24  */
25 #include "platform.h"
26 #include <gnunet_util_lib.h>
27 #include <gnunet_gnsrecord_lib.h>
28 #include <gnunet_gns_service.h>
29
30
31 /**
32  * How long do we wait at least between requests by default?
33  */
34 #define DEF_REQUEST_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 1)
35
36 /**
37  * How long do we wait until we consider a request failed by default?
38  */
39 #define DEF_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 1)
40
41
42 /**
43  * We distinguish between different categories of
44  * requests, for which we track statistics separately.
45  * However, this process does not change how it acts
46  * based on the category.
47  */
48 enum RequestCategory
49 {
50   RC_SHARED = 0,
51   RC_PRIVATE = 1,
52   /**
53    * Must be last and match number of categories.
54    */
55   RC_MAX = 2
56 };
57
58
59 /**
60  * Request we should make.  We keep this struct in memory per request,
61  * thus optimizing it is crucial for the overall memory consumption of
62  * the zone importer.
63  */
64 struct Request
65 {
66
67   /**
68    * Active requests are kept in a DLL.
69    */
70   struct Request *next;
71
72   /**
73    * Active requests are kept in a DLL.
74    */
75   struct Request *prev;
76
77   /**
78    * Socket used to make the request, NULL if not active.
79    */
80   struct GNUNET_GNS_LookupWithTldRequest *lr;
81
82   /**
83    * Hostname we are resolving, allocated at the end of
84    * this struct (optimizing memory consumption by reducing
85    * total number of allocations).
86    */
87   const char *hostname;
88
89   /**
90    * While we are fetching the record, the value is set to the
91    * starting time of the GNS operation.
92    */
93   struct GNUNET_TIME_Absolute op_start_time;
94
95   /**
96    * Observed latency, set once we got a reply.
97    */
98   struct GNUNET_TIME_Relative latency;
99
100   /**
101    * Category of the request.
102    */
103   enum RequestCategory cat;
104
105 };
106
107
108 /**
109  * GNS handle.
110  */
111 static struct GNUNET_GNS_Handle *gns;
112
113 /**
114  * Number of lookups we performed overall per category.
115  */
116 static unsigned int lookups[RC_MAX];
117
118 /**
119  * Number of replies we got per category.
120  */
121 static unsigned int replies[RC_MAX];
122
123 /**
124  * Number of replies we got per category.
125  */
126 static unsigned int failures[RC_MAX];
127
128 /**
129  * Sum of the observed latencies of successful queries,
130  * per category.
131  */
132 static struct GNUNET_TIME_Relative latency_sum[RC_MAX];
133
134 /**
135  * Active requests are kept in a DLL.
136  */
137 static struct Request *act_head;
138
139 /**
140  * Active requests are kept in a DLL.
141  */
142 static struct Request *act_tail;
143
144 /**
145  * Completed successful requests are kept in a DLL.
146  */
147 static struct Request *succ_head;
148
149 /**
150  * Completed successful requests are kept in a DLL.
151  */
152 static struct Request *succ_tail;
153
154 /**
155  * Yet to be started requests are kept in a DLL.
156  */
157 static struct Request *todo_head;
158
159 /**
160  * Yet to be started requests are kept in a DLL.
161  */
162 static struct Request *todo_tail;
163
164 /**
165  * Main task.
166  */
167 static struct GNUNET_SCHEDULER_Task *t;
168
169 /**
170  * Delay between requests.
171  */
172 static struct GNUNET_TIME_Relative request_delay;
173
174 /**
175  * Timeout for requests.
176  */
177 static struct GNUNET_TIME_Relative timeout;
178
179 /**
180  * Number of requests we have concurrently active.
181  */
182 static unsigned int active_cnt;
183
184 /**
185  * Look for GNS2DNS records specifically?
186  */
187 static int g2d;
188
189 /**
190  * Free @a req and data structures reachable from it.
191  *
192  * @param req request to free
193  */
194 static void
195 free_request (struct Request *req)
196 {
197   if (NULL != req->lr)
198     GNUNET_GNS_lookup_with_tld_cancel (req->lr);
199   GNUNET_free (req);
200 }
201
202
203 /**
204  * Function called with the result of a GNS resolution.
205  *
206  * @param cls closure with the `struct Request`
207  * @param gns_tld #GNUNET_YES if GNS lookup was attempted
208  * @param rd_count number of records in @a rd
209  * @param rd the records in reply
210  */
211 static void
212 process_result (void *cls,
213                 int gns_tld,
214                 uint32_t rd_count,
215                 const struct GNUNET_GNSRECORD_Data *rd)
216 {
217   struct Request *req = cls;
218
219   (void) gns_tld;
220   (void) rd_count;
221   (void) rd;
222   active_cnt--;
223   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
224               "Got response for request `%s'\n",
225               req->hostname);
226   req->lr = NULL;
227   req->latency = GNUNET_TIME_absolute_get_duration (req->op_start_time);
228   GNUNET_CONTAINER_DLL_remove (act_head,
229                                act_tail,
230                                req);
231   GNUNET_CONTAINER_DLL_insert (succ_head,
232                                succ_tail,
233                                req);
234   replies[req->cat]++;
235   latency_sum[req->cat]
236     = GNUNET_TIME_relative_add (latency_sum[req->cat],
237                                 req->latency);
238 }
239
240
241 /**
242  * Process request from the queue.
243  *
244  * @param cls NULL
245  */
246 static void
247 process_queue (void *cls)
248 {
249   struct Request *req;
250   struct GNUNET_TIME_Relative duration;
251
252   (void) cls;
253   t = NULL;
254   /* check for expired requests */
255   while (NULL != (req = act_head))
256   {
257     duration = GNUNET_TIME_absolute_get_duration (req->op_start_time);
258     if (duration.rel_value_us < timeout.rel_value_us)
259       break;
260     GNUNET_CONTAINER_DLL_remove (act_head,
261                                  act_tail,
262                                  req);
263     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
264                 "Failing request `%s' due to timeout\n",
265                 req->hostname);
266     failures[req->cat]++;
267     active_cnt--;
268     free_request (req);
269   }
270   if (NULL == (req = todo_head))
271   {
272     struct GNUNET_TIME_Absolute at;
273
274     if (NULL == (req = act_head))
275     {
276       GNUNET_SCHEDULER_shutdown ();
277       return;
278     }
279     at = GNUNET_TIME_absolute_add (req->op_start_time,
280                                    timeout);
281     t = GNUNET_SCHEDULER_add_at (at,
282                                  &process_queue,
283                                  NULL);
284     return;
285   }
286   GNUNET_CONTAINER_DLL_remove (todo_head,
287                                todo_tail,
288                                req);
289   GNUNET_CONTAINER_DLL_insert_tail (act_head,
290                                     act_tail,
291                                     req);
292   lookups[req->cat]++;
293   active_cnt++;
294   req->op_start_time = GNUNET_TIME_absolute_get ();
295   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
296               "Starting request `%s' (%u in parallel)\n",
297               req->hostname,
298               active_cnt);
299   req->lr = GNUNET_GNS_lookup_with_tld (gns,
300                                         req->hostname,
301                                         g2d
302                                         ? GNUNET_GNSRECORD_TYPE_GNS2DNS
303                                         : GNUNET_GNSRECORD_TYPE_ANY,
304                                         GNUNET_GNS_LO_DEFAULT,
305                                         &process_result,
306                                         req);
307   t = GNUNET_SCHEDULER_add_delayed (request_delay,
308                                     &process_queue,
309                                     NULL);
310 }
311
312
313 /**
314  * Compare two requests by latency for qsort().
315  *
316  * @param c1 pointer to `struct Request *`
317  * @param c2 pointer to `struct Request *`
318  * @return -1 if c1<c2, 1 if c1>c2, 0 if c1==c2.
319  */
320 static int
321 compare_req (const void *c1,
322              const void *c2)
323 {
324   const struct Request *r1 = *(void **) c1;
325   const struct Request *r2 = *(void **) c2;
326
327   if (r1->latency.rel_value_us < r2->latency.rel_value_us)
328     return -1;
329   if (r1->latency.rel_value_us > r2->latency.rel_value_us)
330     return 1;
331   return 0;
332 }
333
334
335 /**
336  * Output statistics, then clean up and terminate the process.
337  *
338  * @param cls NULL
339  */
340 static void
341 do_shutdown (void *cls)
342 {
343   struct Request *req;
344   struct Request **ra[RC_MAX];
345   unsigned int rp[RC_MAX];
346
347   (void) cls;
348   for (enum RequestCategory rc = 0;rc < RC_MAX;rc++)
349   {
350     ra[rc] = GNUNET_new_array (replies[rc],
351                                struct Request *);
352     rp[rc] = 0;
353   }
354   for (req = succ_head;NULL != req; req = req->next)
355   {
356     GNUNET_assert (rp[req->cat] < replies[req->cat]);
357     ra[req->cat][rp[req->cat]++] = req;
358   }
359   for (enum RequestCategory rc = 0;rc < RC_MAX;rc++)
360   {
361     unsigned int off;
362
363     fprintf (stdout,
364              "Category %u\n",
365              rc);
366     fprintf (stdout,
367              "\tlookups: %u replies: %u failures: %u\n",
368              lookups[rc],
369              replies[rc],
370              failures[rc]);
371     if (0 == rp[rc])
372       continue;
373     qsort (ra[rc],
374            rp[rc],
375            sizeof (struct Request *),
376            &compare_req);
377     latency_sum[rc] = GNUNET_TIME_relative_divide (latency_sum[rc],
378                                                    replies[rc]);
379     fprintf (stdout,
380              "\taverage: %s\n",
381              GNUNET_STRINGS_relative_time_to_string (latency_sum[rc],
382                                                      GNUNET_YES));
383     off = rp[rc] * 50 / 100;
384     fprintf (stdout,
385              "\tmedian(50): %s\n",
386              GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
387                                                      GNUNET_YES));
388     off = rp[rc] * 75 / 100;
389     fprintf (stdout,
390              "\tquantile(75): %s\n",
391              GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
392                                                      GNUNET_YES));
393     off = rp[rc] * 90 / 100;
394     fprintf (stdout,
395              "\tquantile(90): %s\n",
396              GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
397                                                      GNUNET_YES));
398     off = rp[rc] * 99 / 100;
399     fprintf (stdout,
400              "\tquantile(99): %s\n",
401              GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
402                                                      GNUNET_YES));
403     GNUNET_free (ra[rc]);
404   }
405   if (NULL != t)
406   {
407     GNUNET_SCHEDULER_cancel (t);
408     t = NULL;
409   }
410   while (NULL != (req = act_head))
411   {
412     GNUNET_CONTAINER_DLL_remove (act_head,
413                                  act_tail,
414                                  req);
415     free_request (req);
416   }
417   while (NULL != (req = succ_head))
418   {
419     GNUNET_CONTAINER_DLL_remove (succ_head,
420                                  succ_tail,
421                                  req);
422     free_request (req);
423   }
424   while (NULL != (req = todo_head))
425   {
426     GNUNET_CONTAINER_DLL_remove (todo_head,
427                                  todo_tail,
428                                  req);
429     free_request (req);
430   }
431   if (NULL != gns)
432   {
433     GNUNET_GNS_disconnect (gns);
434     gns = NULL;
435   }
436 }
437
438
439 /**
440  * Add @a hostname to the list of requests to be made.
441  *
442  * @param hostname name to resolve
443  * @param cat category of the @a hostname
444  */
445 static void
446 queue (const char *hostname,
447        enum RequestCategory cat)
448 {
449   struct Request *req;
450   const char *dot;
451   size_t hlen;
452
453   dot = strchr (hostname,
454                 (unsigned char) '.');
455   if (NULL == dot)
456   {
457     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
458                 "Refusing invalid hostname `%s' (lacks '.')\n",
459                 hostname);
460     return;
461   }
462   hlen = strlen (hostname) + 1;
463   req = GNUNET_malloc (sizeof (struct Request) + hlen);
464   req->cat = cat;
465   req->hostname = (char *) &req[1];
466   GNUNET_memcpy (&req[1],
467                  hostname,
468                  hlen);
469   GNUNET_CONTAINER_DLL_insert (todo_head,
470                                todo_tail,
471                                req);
472 }
473
474
475 /**
476  * Begin processing hostnames from stdin.
477  *
478  * @param cls NULL
479  */
480 static void
481 process_stdin (void *cls)
482 {
483   static struct GNUNET_TIME_Absolute last;
484   static uint64_t idot;
485   unsigned int cat;
486   char hn[256];
487   char in[270];
488
489   (void) cls;
490   t = NULL;
491   while (NULL !=
492          fgets (in,
493                 sizeof (in),
494                 stdin))
495   {
496     if (strlen(in) > 0)
497       hn[strlen(in)-1] = '\0'; /* eat newline */
498     if ( (2 != sscanf (in,
499                        "%u %255s",
500                        &cat,
501                        hn)) ||
502          (cat >= RC_MAX) )
503     {
504       fprintf (stderr,
505                "Malformed input line `%s', skipping\n",
506                in);
507       continue;
508     }
509     if (0 == idot)
510       last = GNUNET_TIME_absolute_get ();
511     idot++;
512     if (0 == idot % 100000)
513     {
514       struct GNUNET_TIME_Relative delta;
515
516       delta = GNUNET_TIME_absolute_get_duration (last);
517       last = GNUNET_TIME_absolute_get ();
518       fprintf (stderr,
519                "Read 100000 domain names in %s\n",
520                GNUNET_STRINGS_relative_time_to_string (delta,
521                                                        GNUNET_YES));
522     }
523     queue (hn,
524            (enum RequestCategory) cat);
525   }
526   fprintf (stderr,
527            "Done reading %llu domain names\n",
528            (unsigned long long) idot);
529   t = GNUNET_SCHEDULER_add_now (&process_queue,
530                                 NULL);
531 }
532
533
534 /**
535  * Process requests from the queue, then if the queue is
536  * not empty, try again.
537  *
538  * @param cls NULL
539  * @param args remaining command-line arguments
540  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
541  * @param cfg configuration
542  */
543 static void
544 run (void *cls,
545      char *const *args,
546      const char *cfgfile,
547      const struct GNUNET_CONFIGURATION_Handle *cfg)
548 {
549   (void) cls;
550   (void) args;
551   (void) cfgfile;
552   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
553                                  NULL);
554   gns = GNUNET_GNS_connect (cfg);
555   if (NULL == gns)
556   {
557     GNUNET_break (0);
558     GNUNET_SCHEDULER_shutdown ();
559     return;
560   }
561   t = GNUNET_SCHEDULER_add_now (&process_stdin,
562                                 NULL);
563 }
564
565
566 /**
567  * Call with list of names with numeric category to query.
568  *
569  * @param argc unused
570  * @param argv unused
571  * @return 0 on success
572  */
573 int
574 main (int argc,
575       char *const*argv)
576 {
577   int ret = 0;
578   struct GNUNET_GETOPT_CommandLineOption options[] = {
579     GNUNET_GETOPT_option_relative_time ('d',
580                                         "delay",
581                                         "RELATIVETIME",
582                                         gettext_noop ("how long to wait between queries"),
583                                         &request_delay),
584     GNUNET_GETOPT_option_relative_time ('t',
585                                         "timeout",
586                                         "RELATIVETIME",
587                                         gettext_noop ("how long to wait for an answer"),
588                                         &timeout),
589     GNUNET_GETOPT_option_flag ('2',
590                                "g2d",
591                                gettext_noop ("look for GNS2DNS records instead of ANY"),
592                                &g2d),
593     GNUNET_GETOPT_OPTION_END
594   };
595
596   if (GNUNET_OK !=
597       GNUNET_STRINGS_get_utf8_args (argc, argv,
598                                     &argc, &argv))
599     return 2;
600   timeout = DEF_TIMEOUT;
601   request_delay = DEF_REQUEST_DELAY;
602   if (GNUNET_OK !=
603       GNUNET_PROGRAM_run (argc,
604                           argv,
605                           "gnunet-gns-benchmark",
606                           "resolve GNS names and measure performance",
607                           options,
608                           &run,
609                           NULL))
610     ret = 1;
611   GNUNET_free ((void*) argv);
612   return ret;
613 }
614
615 /* end of gnunet-gns-benchmark.c */