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