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