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