fix shutdown order
[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
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      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      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
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   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 /**
181  * Free @a req and data structures reachable from it.
182  *
183  * @param req request to free
184  */
185 static void
186 free_request (struct Request *req)
187 {
188   if (NULL != req->lr)
189     GNUNET_GNS_lookup_with_tld_cancel (req->lr);
190   GNUNET_free (req);
191 }
192
193
194 /**
195  * Function called with the result of a GNS resolution.
196  *
197  * @param cls closure with the `struct Request`
198  * @param gns_tld #GNUNET_YES if GNS lookup was attempted
199  * @param rd_count number of records in @a rd
200  * @param rd the records in reply
201  */
202 static void
203 process_result (void *cls,
204                 int gns_tld,
205                 uint32_t rd_count,
206                 const struct GNUNET_GNSRECORD_Data *rd)
207 {
208   struct Request *req = cls;
209
210   (void) gns_tld;
211   (void) rd_count;
212   (void) rd;
213   req->lr = NULL;
214   req->latency = GNUNET_TIME_absolute_get_duration (req->op_start_time);
215   GNUNET_CONTAINER_DLL_remove (act_head,
216                                act_tail,
217                                req);
218   GNUNET_CONTAINER_DLL_insert (succ_head,
219                                succ_tail,
220                                req);
221   replies[req->cat]++;
222   latency_sum[req->cat]
223     = GNUNET_TIME_relative_add (latency_sum[req->cat],
224                                 req->latency);
225 }
226
227
228 /**
229  * Process request from the queue.
230  *
231  * @param cls NULL
232  */
233 static void
234 process_queue (void *cls)
235 {
236   struct Request *req;
237   struct GNUNET_TIME_Relative duration;
238
239   (void) cls;
240   /* check for expired requests */
241   while (NULL != (req = act_head))
242   {
243     duration = GNUNET_TIME_absolute_get_duration (req->op_start_time);
244     if (duration.rel_value_us < timeout.rel_value_us)
245       break;
246     GNUNET_CONTAINER_DLL_remove (act_head,
247                                  act_tail,
248                                  req);
249     failures[req->cat]++;
250     free_request (req);
251   }
252   if (NULL == (req = todo_head))
253   {
254     struct GNUNET_TIME_Absolute at;
255
256     if (NULL == (req = act_head))
257     {
258       GNUNET_SCHEDULER_shutdown ();
259       return;
260     }
261     at = GNUNET_TIME_absolute_add (req->op_start_time,
262                                    timeout);
263     t = GNUNET_SCHEDULER_add_at (at,
264                                  &process_queue,
265                                  NULL);
266     return;
267   }
268   GNUNET_CONTAINER_DLL_remove (todo_head,
269                                todo_tail,
270                                req);
271   GNUNET_CONTAINER_DLL_insert_tail (act_head,
272                                     act_tail,
273                                     req);
274   lookups[req->cat]++;
275   req->op_start_time = GNUNET_TIME_absolute_get ();
276   req->lr = GNUNET_GNS_lookup_with_tld (gns,
277                                         req->hostname,
278                                         GNUNET_GNSRECORD_TYPE_ANY,
279                                         GNUNET_GNS_LO_DEFAULT,
280                                         &process_result,
281                                         req);
282   t = GNUNET_SCHEDULER_add_delayed (request_delay,
283                                     &process_queue,
284                                     NULL);
285 }
286
287
288 /**
289  * Compare two requests by latency for qsort().
290  *
291  * @param c1 pointer to `struct Request *`
292  * @param c2 pointer to `struct Request *`
293  * @return -1 if c1<c2, 1 if c1>c2, 0 if c1==c2.
294  */
295 static int
296 compare_req (const void *c1,
297              const void *c2)
298 {
299   const struct Request *r1 = *(void **) c1;
300   const struct Request *r2 = *(void **) c2;
301
302   if (r1->latency.rel_value_us < r2->latency.rel_value_us)
303     return -1;
304   if (r1->latency.rel_value_us > r2->latency.rel_value_us)
305     return 1;
306   return 0;
307 }
308
309
310 /**
311  * Output statistics, then clean up and terminate the process.
312  *
313  * @param cls NULL
314  */
315 static void
316 do_shutdown (void *cls)
317 {
318   struct Request *req;
319   struct Request **ra[RC_MAX];
320   unsigned int rp[RC_MAX];
321
322   (void) cls;
323   for (enum RequestCategory rc = 0;rc < RC_MAX;rc++)
324   {
325     ra[rc] = GNUNET_new_array (replies[rc],
326                                struct Request *);
327     rp[rc] = 0;
328   }
329   for (req = succ_head;NULL != req; req = req->next)
330   {
331     GNUNET_assert (rp[req->cat] < replies[req->cat]);
332     ra[req->cat][rp[req->cat]++] = req;
333   }
334   for (enum RequestCategory rc = 0;rc < RC_MAX;rc++)
335   {
336     unsigned int off;
337
338     if (0 == rp[rc])
339       continue;
340     qsort (ra[rc],
341            rp[rc],
342            sizeof (struct Request *),
343            &compare_req);
344     fprintf (stdout,
345              "Category %u\n",
346              rc);
347     latency_sum[off] = GNUNET_TIME_relative_divide (latency_sum[off],
348                                                     replies[rc]);
349     fprintf (stdout,
350              "\taverage: %s\n",
351              GNUNET_STRINGS_relative_time_to_string (latency_sum[off],
352                                                      GNUNET_YES));
353     off = rp[rc] * 50 / 100;
354     fprintf (stdout,
355              "\tmedian(50): %s\n",
356              GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
357                                                      GNUNET_YES));
358     off = rp[rc] * 75 / 100;
359     fprintf (stdout,
360              "\tquantile(75): %s\n",
361              GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
362                                                      GNUNET_YES));
363     off = rp[rc] * 90 / 100;
364     fprintf (stdout,
365              "\tquantile(90): %s\n",
366              GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
367                                                      GNUNET_YES));
368     off = rp[rc] * 99 / 100;
369     fprintf (stdout,
370              "\tquantile(99): %s\n",
371              GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
372                                                      GNUNET_YES));
373     fprintf (stdout,
374              "\tlookups: %u replies: %u failures: %u\n",
375              lookups[rc],
376              replies[rc],
377              failures[rc]);
378     GNUNET_free (ra[rc]);
379   }
380   if (NULL != t)
381   {
382     GNUNET_SCHEDULER_cancel (t);
383     t = NULL;
384   }
385   while (NULL != (req = act_head))
386   {
387     GNUNET_CONTAINER_DLL_remove (act_head,
388                                  act_tail,
389                                  req);
390     free_request (req);
391   }
392   while (NULL != (req = succ_head))
393   {
394     GNUNET_CONTAINER_DLL_remove (succ_head,
395                                  succ_tail,
396                                  req);
397     free_request (req);
398   }
399   while (NULL != (req = todo_head))
400   {
401     GNUNET_CONTAINER_DLL_remove (todo_head,
402                                  todo_tail,
403                                  req);
404     free_request (req);
405   }
406   if (NULL != gns)
407   {
408     GNUNET_GNS_disconnect (gns);
409     gns = NULL;
410   }
411 }
412
413
414 /**
415  * Add @a hostname to the list of requests to be made.
416  *
417  * @param hostname name to resolve
418  * @param cat category of the @a hostname
419  */
420 static void
421 queue (const char *hostname,
422        enum RequestCategory cat)
423 {
424   struct Request *req;
425   const char *dot;
426   size_t hlen;
427
428   dot = strchr (hostname,
429                 (unsigned char) '.');
430   if (NULL == dot)
431   {
432     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
433                 "Refusing invalid hostname `%s' (lacks '.')\n",
434                 hostname);
435     return;
436   }
437   hlen = strlen (hostname) + 1;
438   req = GNUNET_malloc (sizeof (struct Request) + hlen);
439   req->cat = cat;
440   req->hostname = (char *) &req[1];
441   memcpy (req->hostname,
442           hostname,
443           hlen);
444   GNUNET_CONTAINER_DLL_insert (todo_head,
445                                todo_tail,
446                                req);
447 }
448
449
450 /**
451  * Begin processing hostnames from stdin.
452  *
453  * @param cls NULL
454  */
455 static void
456 process_stdin (void *cls)
457 {
458   static struct GNUNET_TIME_Absolute last;
459   static uint64_t idot;
460   unsigned int cat;
461   char hn[256];
462   char in[270];
463
464   (void) cls;
465   t = NULL;
466   while (NULL !=
467          fgets (in,
468                 sizeof (in),
469                 stdin))
470   {
471     if (strlen(in) > 0)
472       hn[strlen(in)-1] = '\0'; /* eat newline */
473     if ( (2 != sscanf (in,
474                        "%u %255s",
475                        &cat,
476                        hn)) ||
477          (cat >= RC_MAX) )
478     {
479       fprintf (stderr,
480                "Malformed input line `%s', skipping\n",
481                in);
482       continue;
483     }
484     if (0 == idot)
485       last = GNUNET_TIME_absolute_get ();
486     idot++;
487     if (0 == idot % 100000)
488     {
489       struct GNUNET_TIME_Relative delta;
490
491       delta = GNUNET_TIME_absolute_get_duration (last);
492       last = GNUNET_TIME_absolute_get ();
493       fprintf (stderr,
494                "Read 10000 domain names in %s\n",
495                GNUNET_STRINGS_relative_time_to_string (delta,
496                                                        GNUNET_YES));
497     }
498     queue (hn,
499            (enum RequestCategory) cat);
500   }
501   fprintf (stderr,
502            "Done reading %llu domain names\n",
503            (unsigned long long) idot);
504   t = GNUNET_SCHEDULER_add_now (&process_queue,
505                                 NULL);
506 }
507
508
509 /**
510  * Process requests from the queue, then if the queue is
511  * not empty, try again.
512  *
513  * @param cls NULL
514  * @param args remaining command-line arguments
515  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
516  * @param cfg configuration
517  */
518 static void
519 run (void *cls,
520      char *const *args,
521      const char *cfgfile,
522      const struct GNUNET_CONFIGURATION_Handle *cfg)
523 {
524   (void) cls;
525   (void) args;
526   (void) cfgfile;
527   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
528                                  NULL);
529   gns = GNUNET_GNS_connect (cfg);
530   if (NULL == gns)
531   {
532     GNUNET_break (0);
533     GNUNET_SCHEDULER_shutdown ();
534     return;
535   }
536   t = GNUNET_SCHEDULER_add_now (&process_stdin,
537                                 NULL);
538 }
539
540
541 /**
542  * Call with list of names with numeric category to query.
543  *
544  * @param argc unused
545  * @param argv unused
546  * @return 0 on success
547  */
548 int
549 main (int argc,
550       char *const*argv)
551 {
552   int ret = 0;
553   struct GNUNET_GETOPT_CommandLineOption options[] = {
554     GNUNET_GETOPT_option_relative_time ('d',
555                                         "delay",
556                                         "RELATIVETIME",
557                                         gettext_noop ("how long to wait between queries"),
558                                         &request_delay),
559     GNUNET_GETOPT_option_relative_time ('t',
560                                         "timeout",
561                                         "RELATIVETIME",
562                                         gettext_noop ("how long to wait for an answer"),
563                                         &timeout),
564     GNUNET_GETOPT_OPTION_END
565   };
566
567   if (GNUNET_OK !=
568       GNUNET_STRINGS_get_utf8_args (argc, argv,
569                                     &argc, &argv))
570     return 2;
571   timeout = DEF_TIMEOUT;
572   request_delay = DEF_REQUEST_DELAY;
573   if (GNUNET_OK !=
574       GNUNET_PROGRAM_run (argc,
575                           argv,
576                           "gnunet-gns-benchmark",
577                           "resolve GNS names and measure performance",
578                           options,
579                           &run,
580                           NULL))
581     ret = 1;
582   GNUNET_free ((void*) argv);
583   return ret;
584 }
585
586 /* end of gnunet-gns-benchmark.c */