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