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