diff options
Diffstat (limited to 'src/gns/gnunet-gns-benchmark.c')
-rw-r--r-- | src/gns/gnunet-gns-benchmark.c | 613 |
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 | */ | ||
46 | enum 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 | */ | ||
62 | struct 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 | */ | ||
109 | static struct GNUNET_GNS_Handle *gns; | ||
110 | |||
111 | /** | ||
112 | * Number of lookups we performed overall per category. | ||
113 | */ | ||
114 | static unsigned int lookups[RC_MAX]; | ||
115 | |||
116 | /** | ||
117 | * Number of replies we got per category. | ||
118 | */ | ||
119 | static unsigned int replies[RC_MAX]; | ||
120 | |||
121 | /** | ||
122 | * Number of replies we got per category. | ||
123 | */ | ||
124 | static unsigned int failures[RC_MAX]; | ||
125 | |||
126 | /** | ||
127 | * Sum of the observed latencies of successful queries, | ||
128 | * per category. | ||
129 | */ | ||
130 | static struct GNUNET_TIME_Relative latency_sum[RC_MAX]; | ||
131 | |||
132 | /** | ||
133 | * Active requests are kept in a DLL. | ||
134 | */ | ||
135 | static struct Request *act_head; | ||
136 | |||
137 | /** | ||
138 | * Active requests are kept in a DLL. | ||
139 | */ | ||
140 | static struct Request *act_tail; | ||
141 | |||
142 | /** | ||
143 | * Completed successful requests are kept in a DLL. | ||
144 | */ | ||
145 | static struct Request *succ_head; | ||
146 | |||
147 | /** | ||
148 | * Completed successful requests are kept in a DLL. | ||
149 | */ | ||
150 | static struct Request *succ_tail; | ||
151 | |||
152 | /** | ||
153 | * Yet to be started requests are kept in a DLL. | ||
154 | */ | ||
155 | static struct Request *todo_head; | ||
156 | |||
157 | /** | ||
158 | * Yet to be started requests are kept in a DLL. | ||
159 | */ | ||
160 | static struct Request *todo_tail; | ||
161 | |||
162 | /** | ||
163 | * Main task. | ||
164 | */ | ||
165 | static struct GNUNET_SCHEDULER_Task *t; | ||
166 | |||
167 | /** | ||
168 | * Delay between requests. | ||
169 | */ | ||
170 | static struct GNUNET_TIME_Relative request_delay; | ||
171 | |||
172 | /** | ||
173 | * Timeout for requests. | ||
174 | */ | ||
175 | static struct GNUNET_TIME_Relative timeout; | ||
176 | |||
177 | /** | ||
178 | * Number of requests we have concurrently active. | ||
179 | */ | ||
180 | static unsigned int active_cnt; | ||
181 | |||
182 | /** | ||
183 | * Look for GNS2DNS records specifically? | ||
184 | */ | ||
185 | static int g2d; | ||
186 | |||
187 | /** | ||
188 | * Free @a req and data structures reachable from it. | ||
189 | * | ||
190 | * @param req request to free | ||
191 | */ | ||
192 | static void | ||
193 | free_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 | */ | ||
209 | static void | ||
210 | process_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 | */ | ||
244 | static void | ||
245 | process_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 | */ | ||
318 | static int | ||
319 | compare_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 | */ | ||
338 | static void | ||
339 | do_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 | */ | ||
443 | static void | ||
444 | queue (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 | */ | ||
478 | static void | ||
479 | process_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 | */ | ||
541 | static void | ||
542 | run (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 | */ | ||
571 | int | ||
572 | main (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 */ | ||