aboutsummaryrefslogtreecommitdiff
path: root/src/gns/gnunet-gns-benchmark.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gns/gnunet-gns-benchmark.c')
-rw-r--r--src/gns/gnunet-gns-benchmark.c613
1 files changed, 613 insertions, 0 deletions
diff --git a/src/gns/gnunet-gns-benchmark.c b/src/gns/gnunet-gns-benchmark.c
new file mode 100644
index 000000000..6ed4dfe6c
--- /dev/null
+++ b/src/gns/gnunet-gns-benchmark.c
@@ -0,0 +1,613 @@
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 it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your 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 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17*/
18/**
19 * @file src/gns/gnunet-gns-benchmark.c
20 * @brief issue many queries to GNS and compute performance statistics
21 * @author Christian Grothoff
22 */
23#include "platform.h"
24#include <gnunet_util_lib.h>
25#include <gnunet_gnsrecord_lib.h>
26#include <gnunet_gns_service.h>
27
28
29/**
30 * How long do we wait at least between requests by default?
31 */
32#define DEF_REQUEST_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 1)
33
34/**
35 * How long do we wait until we consider a request failed by default?
36 */
37#define DEF_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 1)
38
39
40/**
41 * We distinguish between different categories of
42 * requests, for which we track statistics separately.
43 * However, this process does not change how it acts
44 * based on the category.
45 */
46enum RequestCategory
47{
48 RC_SHARED = 0,
49 RC_PRIVATE = 1,
50 /**
51 * Must be last and match number of categories.
52 */
53 RC_MAX = 2
54};
55
56
57/**
58 * Request we should make. We keep this struct in memory per request,
59 * thus optimizing it is crucial for the overall memory consumption of
60 * the zone importer.
61 */
62struct Request
63{
64
65 /**
66 * Active requests are kept in a DLL.
67 */
68 struct Request *next;
69
70 /**
71 * Active requests are kept in a DLL.
72 */
73 struct Request *prev;
74
75 /**
76 * Socket used to make the request, NULL if not active.
77 */
78 struct GNUNET_GNS_LookupWithTldRequest *lr;
79
80 /**
81 * Hostname we are resolving, allocated at the end of
82 * this struct (optimizing memory consumption by reducing
83 * total number of allocations).
84 */
85 const char *hostname;
86
87 /**
88 * While we are fetching the record, the value is set to the
89 * starting time of the GNS operation.
90 */
91 struct GNUNET_TIME_Absolute op_start_time;
92
93 /**
94 * Observed latency, set once we got a reply.
95 */
96 struct GNUNET_TIME_Relative latency;
97
98 /**
99 * Category of the request.
100 */
101 enum RequestCategory cat;
102
103};
104
105
106/**
107 * GNS handle.
108 */
109static struct GNUNET_GNS_Handle *gns;
110
111/**
112 * Number of lookups we performed overall per category.
113 */
114static unsigned int lookups[RC_MAX];
115
116/**
117 * Number of replies we got per category.
118 */
119static unsigned int replies[RC_MAX];
120
121/**
122 * Number of replies we got per category.
123 */
124static unsigned int failures[RC_MAX];
125
126/**
127 * Sum of the observed latencies of successful queries,
128 * per category.
129 */
130static struct GNUNET_TIME_Relative latency_sum[RC_MAX];
131
132/**
133 * Active requests are kept in a DLL.
134 */
135static struct Request *act_head;
136
137/**
138 * Active requests are kept in a DLL.
139 */
140static struct Request *act_tail;
141
142/**
143 * Completed successful requests are kept in a DLL.
144 */
145static struct Request *succ_head;
146
147/**
148 * Completed successful requests are kept in a DLL.
149 */
150static struct Request *succ_tail;
151
152/**
153 * Yet to be started requests are kept in a DLL.
154 */
155static struct Request *todo_head;
156
157/**
158 * Yet to be started requests are kept in a DLL.
159 */
160static struct Request *todo_tail;
161
162/**
163 * Main task.
164 */
165static struct GNUNET_SCHEDULER_Task *t;
166
167/**
168 * Delay between requests.
169 */
170static struct GNUNET_TIME_Relative request_delay;
171
172/**
173 * Timeout for requests.
174 */
175static struct GNUNET_TIME_Relative timeout;
176
177/**
178 * Number of requests we have concurrently active.
179 */
180static unsigned int active_cnt;
181
182/**
183 * Look for GNS2DNS records specifically?
184 */
185static int g2d;
186
187/**
188 * Free @a req and data structures reachable from it.
189 *
190 * @param req request to free
191 */
192static void
193free_request (struct Request *req)
194{
195 if (NULL != req->lr)
196 GNUNET_GNS_lookup_with_tld_cancel (req->lr);
197 GNUNET_free (req);
198}
199
200
201/**
202 * Function called with the result of a GNS resolution.
203 *
204 * @param cls closure with the `struct Request`
205 * @param gns_tld #GNUNET_YES if GNS lookup was attempted
206 * @param rd_count number of records in @a rd
207 * @param rd the records in reply
208 */
209static void
210process_result (void *cls,
211 int gns_tld,
212 uint32_t rd_count,
213 const struct GNUNET_GNSRECORD_Data *rd)
214{
215 struct Request *req = cls;
216
217 (void) gns_tld;
218 (void) rd_count;
219 (void) rd;
220 active_cnt--;
221 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
222 "Got response for request `%s'\n",
223 req->hostname);
224 req->lr = NULL;
225 req->latency = GNUNET_TIME_absolute_get_duration (req->op_start_time);
226 GNUNET_CONTAINER_DLL_remove (act_head,
227 act_tail,
228 req);
229 GNUNET_CONTAINER_DLL_insert (succ_head,
230 succ_tail,
231 req);
232 replies[req->cat]++;
233 latency_sum[req->cat]
234 = GNUNET_TIME_relative_add (latency_sum[req->cat],
235 req->latency);
236}
237
238
239/**
240 * Process request from the queue.
241 *
242 * @param cls NULL
243 */
244static void
245process_queue (void *cls)
246{
247 struct Request *req;
248 struct GNUNET_TIME_Relative duration;
249
250 (void) cls;
251 t = NULL;
252 /* check for expired requests */
253 while (NULL != (req = act_head))
254 {
255 duration = GNUNET_TIME_absolute_get_duration (req->op_start_time);
256 if (duration.rel_value_us < timeout.rel_value_us)
257 break;
258 GNUNET_CONTAINER_DLL_remove (act_head,
259 act_tail,
260 req);
261 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
262 "Failing request `%s' due to timeout\n",
263 req->hostname);
264 failures[req->cat]++;
265 active_cnt--;
266 free_request (req);
267 }
268 if (NULL == (req = todo_head))
269 {
270 struct GNUNET_TIME_Absolute at;
271
272 if (NULL == (req = act_head))
273 {
274 GNUNET_SCHEDULER_shutdown ();
275 return;
276 }
277 at = GNUNET_TIME_absolute_add (req->op_start_time,
278 timeout);
279 t = GNUNET_SCHEDULER_add_at (at,
280 &process_queue,
281 NULL);
282 return;
283 }
284 GNUNET_CONTAINER_DLL_remove (todo_head,
285 todo_tail,
286 req);
287 GNUNET_CONTAINER_DLL_insert_tail (act_head,
288 act_tail,
289 req);
290 lookups[req->cat]++;
291 active_cnt++;
292 req->op_start_time = GNUNET_TIME_absolute_get ();
293 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
294 "Starting request `%s' (%u in parallel)\n",
295 req->hostname,
296 active_cnt);
297 req->lr = GNUNET_GNS_lookup_with_tld (gns,
298 req->hostname,
299 g2d
300 ? GNUNET_GNSRECORD_TYPE_GNS2DNS
301 : GNUNET_GNSRECORD_TYPE_ANY,
302 GNUNET_GNS_LO_DEFAULT,
303 &process_result,
304 req);
305 t = GNUNET_SCHEDULER_add_delayed (request_delay,
306 &process_queue,
307 NULL);
308}
309
310
311/**
312 * Compare two requests by latency for qsort().
313 *
314 * @param c1 pointer to `struct Request *`
315 * @param c2 pointer to `struct Request *`
316 * @return -1 if c1<c2, 1 if c1>c2, 0 if c1==c2.
317 */
318static int
319compare_req (const void *c1,
320 const void *c2)
321{
322 const struct Request *r1 = *(void **) c1;
323 const struct Request *r2 = *(void **) c2;
324
325 if (r1->latency.rel_value_us < r2->latency.rel_value_us)
326 return -1;
327 if (r1->latency.rel_value_us > r2->latency.rel_value_us)
328 return 1;
329 return 0;
330}
331
332
333/**
334 * Output statistics, then clean up and terminate the process.
335 *
336 * @param cls NULL
337 */
338static void
339do_shutdown (void *cls)
340{
341 struct Request *req;
342 struct Request **ra[RC_MAX];
343 unsigned int rp[RC_MAX];
344
345 (void) cls;
346 for (enum RequestCategory rc = 0;rc < RC_MAX;rc++)
347 {
348 ra[rc] = GNUNET_new_array (replies[rc],
349 struct Request *);
350 rp[rc] = 0;
351 }
352 for (req = succ_head;NULL != req; req = req->next)
353 {
354 GNUNET_assert (rp[req->cat] < replies[req->cat]);
355 ra[req->cat][rp[req->cat]++] = req;
356 }
357 for (enum RequestCategory rc = 0;rc < RC_MAX;rc++)
358 {
359 unsigned int off;
360
361 fprintf (stdout,
362 "Category %u\n",
363 rc);
364 fprintf (stdout,
365 "\tlookups: %u replies: %u failures: %u\n",
366 lookups[rc],
367 replies[rc],
368 failures[rc]);
369 if (0 == rp[rc])
370 continue;
371 qsort (ra[rc],
372 rp[rc],
373 sizeof (struct Request *),
374 &compare_req);
375 latency_sum[rc] = GNUNET_TIME_relative_divide (latency_sum[rc],
376 replies[rc]);
377 fprintf (stdout,
378 "\taverage: %s\n",
379 GNUNET_STRINGS_relative_time_to_string (latency_sum[rc],
380 GNUNET_YES));
381 off = rp[rc] * 50 / 100;
382 fprintf (stdout,
383 "\tmedian(50): %s\n",
384 GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
385 GNUNET_YES));
386 off = rp[rc] * 75 / 100;
387 fprintf (stdout,
388 "\tquantile(75): %s\n",
389 GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
390 GNUNET_YES));
391 off = rp[rc] * 90 / 100;
392 fprintf (stdout,
393 "\tquantile(90): %s\n",
394 GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
395 GNUNET_YES));
396 off = rp[rc] * 99 / 100;
397 fprintf (stdout,
398 "\tquantile(99): %s\n",
399 GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
400 GNUNET_YES));
401 GNUNET_free (ra[rc]);
402 }
403 if (NULL != t)
404 {
405 GNUNET_SCHEDULER_cancel (t);
406 t = NULL;
407 }
408 while (NULL != (req = act_head))
409 {
410 GNUNET_CONTAINER_DLL_remove (act_head,
411 act_tail,
412 req);
413 free_request (req);
414 }
415 while (NULL != (req = succ_head))
416 {
417 GNUNET_CONTAINER_DLL_remove (succ_head,
418 succ_tail,
419 req);
420 free_request (req);
421 }
422 while (NULL != (req = todo_head))
423 {
424 GNUNET_CONTAINER_DLL_remove (todo_head,
425 todo_tail,
426 req);
427 free_request (req);
428 }
429 if (NULL != gns)
430 {
431 GNUNET_GNS_disconnect (gns);
432 gns = NULL;
433 }
434}
435
436
437/**
438 * Add @a hostname to the list of requests to be made.
439 *
440 * @param hostname name to resolve
441 * @param cat category of the @a hostname
442 */
443static void
444queue (const char *hostname,
445 enum RequestCategory cat)
446{
447 struct Request *req;
448 const char *dot;
449 size_t hlen;
450
451 dot = strchr (hostname,
452 (unsigned char) '.');
453 if (NULL == dot)
454 {
455 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
456 "Refusing invalid hostname `%s' (lacks '.')\n",
457 hostname);
458 return;
459 }
460 hlen = strlen (hostname) + 1;
461 req = GNUNET_malloc (sizeof (struct Request) + hlen);
462 req->cat = cat;
463 req->hostname = (char *) &req[1];
464 GNUNET_memcpy (&req[1],
465 hostname,
466 hlen);
467 GNUNET_CONTAINER_DLL_insert (todo_head,
468 todo_tail,
469 req);
470}
471
472
473/**
474 * Begin processing hostnames from stdin.
475 *
476 * @param cls NULL
477 */
478static void
479process_stdin (void *cls)
480{
481 static struct GNUNET_TIME_Absolute last;
482 static uint64_t idot;
483 unsigned int cat;
484 char hn[256];
485 char in[270];
486
487 (void) cls;
488 t = NULL;
489 while (NULL !=
490 fgets (in,
491 sizeof (in),
492 stdin))
493 {
494 if (strlen(in) > 0)
495 hn[strlen(in)-1] = '\0'; /* eat newline */
496 if ( (2 != sscanf (in,
497 "%u %255s",
498 &cat,
499 hn)) ||
500 (cat >= RC_MAX) )
501 {
502 fprintf (stderr,
503 "Malformed input line `%s', skipping\n",
504 in);
505 continue;
506 }
507 if (0 == idot)
508 last = GNUNET_TIME_absolute_get ();
509 idot++;
510 if (0 == idot % 100000)
511 {
512 struct GNUNET_TIME_Relative delta;
513
514 delta = GNUNET_TIME_absolute_get_duration (last);
515 last = GNUNET_TIME_absolute_get ();
516 fprintf (stderr,
517 "Read 100000 domain names in %s\n",
518 GNUNET_STRINGS_relative_time_to_string (delta,
519 GNUNET_YES));
520 }
521 queue (hn,
522 (enum RequestCategory) cat);
523 }
524 fprintf (stderr,
525 "Done reading %llu domain names\n",
526 (unsigned long long) idot);
527 t = GNUNET_SCHEDULER_add_now (&process_queue,
528 NULL);
529}
530
531
532/**
533 * Process requests from the queue, then if the queue is
534 * not empty, try again.
535 *
536 * @param cls NULL
537 * @param args remaining command-line arguments
538 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
539 * @param cfg configuration
540 */
541static void
542run (void *cls,
543 char *const *args,
544 const char *cfgfile,
545 const struct GNUNET_CONFIGURATION_Handle *cfg)
546{
547 (void) cls;
548 (void) args;
549 (void) cfgfile;
550 GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
551 NULL);
552 gns = GNUNET_GNS_connect (cfg);
553 if (NULL == gns)
554 {
555 GNUNET_break (0);
556 GNUNET_SCHEDULER_shutdown ();
557 return;
558 }
559 t = GNUNET_SCHEDULER_add_now (&process_stdin,
560 NULL);
561}
562
563
564/**
565 * Call with list of names with numeric category to query.
566 *
567 * @param argc unused
568 * @param argv unused
569 * @return 0 on success
570 */
571int
572main (int argc,
573 char *const*argv)
574{
575 int ret = 0;
576 struct GNUNET_GETOPT_CommandLineOption options[] = {
577 GNUNET_GETOPT_option_relative_time ('d',
578 "delay",
579 "RELATIVETIME",
580 gettext_noop ("how long to wait between queries"),
581 &request_delay),
582 GNUNET_GETOPT_option_relative_time ('t',
583 "timeout",
584 "RELATIVETIME",
585 gettext_noop ("how long to wait for an answer"),
586 &timeout),
587 GNUNET_GETOPT_option_flag ('2',
588 "g2d",
589 gettext_noop ("look for GNS2DNS records instead of ANY"),
590 &g2d),
591 GNUNET_GETOPT_OPTION_END
592 };
593
594 if (GNUNET_OK !=
595 GNUNET_STRINGS_get_utf8_args (argc, argv,
596 &argc, &argv))
597 return 2;
598 timeout = DEF_TIMEOUT;
599 request_delay = DEF_REQUEST_DELAY;
600 if (GNUNET_OK !=
601 GNUNET_PROGRAM_run (argc,
602 argv,
603 "gnunet-gns-benchmark",
604 "resolve GNS names and measure performance",
605 options,
606 &run,
607 NULL))
608 ret = 1;
609 GNUNET_free ((void*) argv);
610 return ret;
611}
612
613/* end of gnunet-gns-benchmark.c */