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