Merge branch 'master' of ssh://gnunet.org/gnunet
[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   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 /**
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  * Number of requests we have concurrently active.
181  */
182 static unsigned int active_cnt;
183
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                                         GNUNET_GNSRECORD_TYPE_ANY,
298                                         GNUNET_GNS_LO_DEFAULT,
299                                         &process_result,
300                                         req);
301   t = GNUNET_SCHEDULER_add_delayed (request_delay,
302                                     &process_queue,
303                                     NULL);
304 }
305
306
307 /**
308  * Compare two requests by latency for qsort().
309  *
310  * @param c1 pointer to `struct Request *`
311  * @param c2 pointer to `struct Request *`
312  * @return -1 if c1<c2, 1 if c1>c2, 0 if c1==c2.
313  */
314 static int
315 compare_req (const void *c1,
316              const void *c2)
317 {
318   const struct Request *r1 = *(void **) c1;
319   const struct Request *r2 = *(void **) c2;
320
321   if (r1->latency.rel_value_us < r2->latency.rel_value_us)
322     return -1;
323   if (r1->latency.rel_value_us > r2->latency.rel_value_us)
324     return 1;
325   return 0;
326 }
327
328
329 /**
330  * Output statistics, then clean up and terminate the process.
331  *
332  * @param cls NULL
333  */
334 static void
335 do_shutdown (void *cls)
336 {
337   struct Request *req;
338   struct Request **ra[RC_MAX];
339   unsigned int rp[RC_MAX];
340
341   (void) cls;
342   for (enum RequestCategory rc = 0;rc < RC_MAX;rc++)
343   {
344     ra[rc] = GNUNET_new_array (replies[rc],
345                                struct Request *);
346     rp[rc] = 0;
347   }
348   for (req = succ_head;NULL != req; req = req->next)
349   {
350     GNUNET_assert (rp[req->cat] < replies[req->cat]);
351     ra[req->cat][rp[req->cat]++] = req;
352   }
353   for (enum RequestCategory rc = 0;rc < RC_MAX;rc++)
354   {
355     unsigned int off;
356
357     fprintf (stdout,
358              "Category %u\n",
359              rc);
360     fprintf (stdout,
361              "\tlookups: %u replies: %u failures: %u\n",
362              lookups[rc],
363              replies[rc],
364              failures[rc]);
365     if (0 == rp[rc])
366       continue;
367     qsort (ra[rc],
368            rp[rc],
369            sizeof (struct Request *),
370            &compare_req);
371     latency_sum[rc] = GNUNET_TIME_relative_divide (latency_sum[rc],
372                                                    replies[rc]);
373     fprintf (stdout,
374              "\taverage: %s\n",
375              GNUNET_STRINGS_relative_time_to_string (latency_sum[rc],
376                                                      GNUNET_YES));
377     off = rp[rc] * 50 / 100;
378     fprintf (stdout,
379              "\tmedian(50): %s\n",
380              GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
381                                                      GNUNET_YES));
382     off = rp[rc] * 75 / 100;
383     fprintf (stdout,
384              "\tquantile(75): %s\n",
385              GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
386                                                      GNUNET_YES));
387     off = rp[rc] * 90 / 100;
388     fprintf (stdout,
389              "\tquantile(90): %s\n",
390              GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
391                                                      GNUNET_YES));
392     off = rp[rc] * 99 / 100;
393     fprintf (stdout,
394              "\tquantile(99): %s\n",
395              GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
396                                                      GNUNET_YES));
397     GNUNET_free (ra[rc]);
398   }
399   if (NULL != t)
400   {
401     GNUNET_SCHEDULER_cancel (t);
402     t = NULL;
403   }
404   while (NULL != (req = act_head))
405   {
406     GNUNET_CONTAINER_DLL_remove (act_head,
407                                  act_tail,
408                                  req);
409     free_request (req);
410   }
411   while (NULL != (req = succ_head))
412   {
413     GNUNET_CONTAINER_DLL_remove (succ_head,
414                                  succ_tail,
415                                  req);
416     free_request (req);
417   }
418   while (NULL != (req = todo_head))
419   {
420     GNUNET_CONTAINER_DLL_remove (todo_head,
421                                  todo_tail,
422                                  req);
423     free_request (req);
424   }
425   if (NULL != gns)
426   {
427     GNUNET_GNS_disconnect (gns);
428     gns = NULL;
429   }
430 }
431
432
433 /**
434  * Add @a hostname to the list of requests to be made.
435  *
436  * @param hostname name to resolve
437  * @param cat category of the @a hostname
438  */
439 static void
440 queue (const char *hostname,
441        enum RequestCategory cat)
442 {
443   struct Request *req;
444   const char *dot;
445   size_t hlen;
446
447   dot = strchr (hostname,
448                 (unsigned char) '.');
449   if (NULL == dot)
450   {
451     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
452                 "Refusing invalid hostname `%s' (lacks '.')\n",
453                 hostname);
454     return;
455   }
456   hlen = strlen (hostname) + 1;
457   req = GNUNET_malloc (sizeof (struct Request) + hlen);
458   req->cat = cat;
459   req->hostname = (char *) &req[1];
460   GNUNET_memcpy (&req[1],
461                  hostname,
462                  hlen);
463   GNUNET_CONTAINER_DLL_insert (todo_head,
464                                todo_tail,
465                                req);
466 }
467
468
469 /**
470  * Begin processing hostnames from stdin.
471  *
472  * @param cls NULL
473  */
474 static void
475 process_stdin (void *cls)
476 {
477   static struct GNUNET_TIME_Absolute last;
478   static uint64_t idot;
479   unsigned int cat;
480   char hn[256];
481   char in[270];
482
483   (void) cls;
484   t = NULL;
485   while (NULL !=
486          fgets (in,
487                 sizeof (in),
488                 stdin))
489   {
490     if (strlen(in) > 0)
491       hn[strlen(in)-1] = '\0'; /* eat newline */
492     if ( (2 != sscanf (in,
493                        "%u %255s",
494                        &cat,
495                        hn)) ||
496          (cat >= RC_MAX) )
497     {
498       fprintf (stderr,
499                "Malformed input line `%s', skipping\n",
500                in);
501       continue;
502     }
503     if (0 == idot)
504       last = GNUNET_TIME_absolute_get ();
505     idot++;
506     if (0 == idot % 100000)
507     {
508       struct GNUNET_TIME_Relative delta;
509
510       delta = GNUNET_TIME_absolute_get_duration (last);
511       last = GNUNET_TIME_absolute_get ();
512       fprintf (stderr,
513                "Read 100000 domain names in %s\n",
514                GNUNET_STRINGS_relative_time_to_string (delta,
515                                                        GNUNET_YES));
516     }
517     queue (hn,
518            (enum RequestCategory) cat);
519   }
520   fprintf (stderr,
521            "Done reading %llu domain names\n",
522            (unsigned long long) idot);
523   t = GNUNET_SCHEDULER_add_now (&process_queue,
524                                 NULL);
525 }
526
527
528 /**
529  * Process requests from the queue, then if the queue is
530  * not empty, try again.
531  *
532  * @param cls NULL
533  * @param args remaining command-line arguments
534  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
535  * @param cfg configuration
536  */
537 static void
538 run (void *cls,
539      char *const *args,
540      const char *cfgfile,
541      const struct GNUNET_CONFIGURATION_Handle *cfg)
542 {
543   (void) cls;
544   (void) args;
545   (void) cfgfile;
546   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
547                                  NULL);
548   gns = GNUNET_GNS_connect (cfg);
549   if (NULL == gns)
550   {
551     GNUNET_break (0);
552     GNUNET_SCHEDULER_shutdown ();
553     return;
554   }
555   t = GNUNET_SCHEDULER_add_now (&process_stdin,
556                                 NULL);
557 }
558
559
560 /**
561  * Call with list of names with numeric category to query.
562  *
563  * @param argc unused
564  * @param argv unused
565  * @return 0 on success
566  */
567 int
568 main (int argc,
569       char *const*argv)
570 {
571   int ret = 0;
572   struct GNUNET_GETOPT_CommandLineOption options[] = {
573     GNUNET_GETOPT_option_relative_time ('d',
574                                         "delay",
575                                         "RELATIVETIME",
576                                         gettext_noop ("how long to wait between queries"),
577                                         &request_delay),
578     GNUNET_GETOPT_option_relative_time ('t',
579                                         "timeout",
580                                         "RELATIVETIME",
581                                         gettext_noop ("how long to wait for an answer"),
582                                         &timeout),
583     GNUNET_GETOPT_OPTION_END
584   };
585
586   if (GNUNET_OK !=
587       GNUNET_STRINGS_get_utf8_args (argc, argv,
588                                     &argc, &argv))
589     return 2;
590   timeout = DEF_TIMEOUT;
591   request_delay = DEF_REQUEST_DELAY;
592   if (GNUNET_OK !=
593       GNUNET_PROGRAM_run (argc,
594                           argv,
595                           "gnunet-gns-benchmark",
596                           "resolve GNS names and measure performance",
597                           options,
598                           &run,
599                           NULL))
600     ret = 1;
601   GNUNET_free ((void*) argv);
602   return ret;
603 }
604
605 /* end of gnunet-gns-benchmark.c */