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