2 This file is part of GNUnet
3 Copyright (C) 2018 GNUnet e.V.
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.
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.
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.
21 * @file src/gns/gnunet-gns-benchmark.c
22 * @brief issue many queries to GNS and compute performance statistics
23 * @author Christian Grothoff
26 #include <gnunet_util_lib.h>
27 #include <gnunet_gnsrecord_lib.h>
28 #include <gnunet_gns_service.h>
32 * How long do we wait at least between requests by default?
34 #define DEF_REQUEST_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 1)
37 * How long do we wait until we consider a request failed by default?
39 #define DEF_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 1)
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.
53 * Must be last and match number of categories.
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
68 * Active requests are kept in a DLL.
73 * Active requests are kept in a DLL.
78 * Socket used to make the request, NULL if not active.
80 struct GNUNET_GNS_LookupWithTldRequest *lr;
83 * Hostname we are resolving, allocated at the end of
84 * this struct (optimizing memory consumption by reducing
85 * total number of allocations).
90 * While we are fetching the record, the value is set to the
91 * starting time of the GNS operation.
93 struct GNUNET_TIME_Absolute op_start_time;
96 * Observed latency, set once we got a reply.
98 struct GNUNET_TIME_Relative latency;
101 * Category of the request.
103 enum RequestCategory cat;
111 static struct GNUNET_GNS_Handle *gns;
114 * Number of lookups we performed overall per category.
116 static unsigned int lookups[RC_MAX];
119 * Number of replies we got per category.
121 static unsigned int replies[RC_MAX];
124 * Number of replies we got per category.
126 static unsigned int failures[RC_MAX];
129 * Sum of the observed latencies of successful queries,
132 static struct GNUNET_TIME_Relative latency_sum[RC_MAX];
135 * Active requests are kept in a DLL.
137 static struct Request *act_head;
140 * Active requests are kept in a DLL.
142 static struct Request *act_tail;
145 * Completed successful requests are kept in a DLL.
147 static struct Request *succ_head;
150 * Completed successful requests are kept in a DLL.
152 static struct Request *succ_tail;
155 * Yet to be started requests are kept in a DLL.
157 static struct Request *todo_head;
160 * Yet to be started requests are kept in a DLL.
162 static struct Request *todo_tail;
167 static struct GNUNET_SCHEDULER_Task *t;
170 * Delay between requests.
172 static struct GNUNET_TIME_Relative request_delay;
175 * Timeout for requests.
177 static struct GNUNET_TIME_Relative timeout;
181 * Free @a req and data structures reachable from it.
183 * @param req request to free
186 free_request (struct Request *req)
189 GNUNET_GNS_lookup_with_tld_cancel (req->lr);
195 * Function called with the result of a GNS resolution.
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
203 process_result (void *cls,
206 const struct GNUNET_GNSRECORD_Data *rd)
208 struct Request *req = cls;
214 req->latency = GNUNET_TIME_absolute_get_duration (req->op_start_time);
215 GNUNET_CONTAINER_DLL_remove (act_head,
218 GNUNET_CONTAINER_DLL_insert (succ_head,
222 latency_sum[req->cat]
223 = GNUNET_TIME_relative_add (latency_sum[req->cat],
229 * Process request from the queue.
234 process_queue (void *cls)
237 struct GNUNET_TIME_Relative duration;
241 /* check for expired requests */
242 while (NULL != (req = act_head))
244 duration = GNUNET_TIME_absolute_get_duration (req->op_start_time);
245 if (duration.rel_value_us < timeout.rel_value_us)
247 GNUNET_GNS_lookup_with_tld_cancel (req->lr);
248 GNUNET_CONTAINER_DLL_remove (act_head,
251 failures[req->cat]++;
254 if (NULL == (req = todo_head))
256 struct GNUNET_TIME_Absolute at;
258 if (NULL == (req = act_head))
260 GNUNET_SCHEDULER_shutdown ();
263 at = GNUNET_TIME_absolute_add (req->op_start_time,
265 t = GNUNET_SCHEDULER_add_at (at,
270 GNUNET_CONTAINER_DLL_remove (todo_head,
273 GNUNET_CONTAINER_DLL_insert_tail (act_head,
277 req->op_start_time = GNUNET_TIME_absolute_get ();
278 req->lr = GNUNET_GNS_lookup_with_tld (gns,
280 GNUNET_GNSRECORD_TYPE_ANY,
281 GNUNET_GNS_LO_DEFAULT,
284 t = GNUNET_SCHEDULER_add_delayed (request_delay,
291 * Compare two requests by latency for qsort().
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.
298 compare_req (const void *c1,
301 const struct Request *r1 = *(void **) c1;
302 const struct Request *r2 = *(void **) c2;
304 if (r1->latency.rel_value_us < r2->latency.rel_value_us)
306 if (r1->latency.rel_value_us > r2->latency.rel_value_us)
313 * Output statistics, then clean up and terminate the process.
318 do_shutdown (void *cls)
321 struct Request **ra[RC_MAX];
322 unsigned int rp[RC_MAX];
325 for (enum RequestCategory rc = 0;rc < RC_MAX;rc++)
327 ra[rc] = GNUNET_new_array (replies[rc],
331 for (req = succ_head;NULL != req; req = req->next)
333 GNUNET_assert (rp[req->cat] < replies[req->cat]);
334 ra[req->cat][rp[req->cat]++] = req;
336 for (enum RequestCategory rc = 0;rc < RC_MAX;rc++)
344 "\tlookups: %u replies: %u failures: %u\n",
352 sizeof (struct Request *),
354 latency_sum[rc] = GNUNET_TIME_relative_divide (latency_sum[rc],
358 GNUNET_STRINGS_relative_time_to_string (latency_sum[rc],
360 off = rp[rc] * 50 / 100;
362 "\tmedian(50): %s\n",
363 GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
365 off = rp[rc] * 75 / 100;
367 "\tquantile(75): %s\n",
368 GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
370 off = rp[rc] * 90 / 100;
372 "\tquantile(90): %s\n",
373 GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
375 off = rp[rc] * 99 / 100;
377 "\tquantile(99): %s\n",
378 GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
380 GNUNET_free (ra[rc]);
384 GNUNET_SCHEDULER_cancel (t);
387 while (NULL != (req = act_head))
389 GNUNET_CONTAINER_DLL_remove (act_head,
394 while (NULL != (req = succ_head))
396 GNUNET_CONTAINER_DLL_remove (succ_head,
401 while (NULL != (req = todo_head))
403 GNUNET_CONTAINER_DLL_remove (todo_head,
410 GNUNET_GNS_disconnect (gns);
417 * Add @a hostname to the list of requests to be made.
419 * @param hostname name to resolve
420 * @param cat category of the @a hostname
423 queue (const char *hostname,
424 enum RequestCategory cat)
430 dot = strchr (hostname,
431 (unsigned char) '.');
434 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
435 "Refusing invalid hostname `%s' (lacks '.')\n",
439 hlen = strlen (hostname) + 1;
440 req = GNUNET_malloc (sizeof (struct Request) + hlen);
442 req->hostname = (char *) &req[1];
443 GNUNET_memcpy (req->hostname,
446 GNUNET_CONTAINER_DLL_insert (todo_head,
453 * Begin processing hostnames from stdin.
458 process_stdin (void *cls)
460 static struct GNUNET_TIME_Absolute last;
461 static uint64_t idot;
474 hn[strlen(in)-1] = '\0'; /* eat newline */
475 if ( (2 != sscanf (in,
482 "Malformed input line `%s', skipping\n",
487 last = GNUNET_TIME_absolute_get ();
489 if (0 == idot % 100000)
491 struct GNUNET_TIME_Relative delta;
493 delta = GNUNET_TIME_absolute_get_duration (last);
494 last = GNUNET_TIME_absolute_get ();
496 "Read 100000 domain names in %s\n",
497 GNUNET_STRINGS_relative_time_to_string (delta,
501 (enum RequestCategory) cat);
504 "Done reading %llu domain names\n",
505 (unsigned long long) idot);
506 t = GNUNET_SCHEDULER_add_now (&process_queue,
512 * Process requests from the queue, then if the queue is
513 * not empty, try again.
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
524 const struct GNUNET_CONFIGURATION_Handle *cfg)
529 GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
531 gns = GNUNET_GNS_connect (cfg);
535 GNUNET_SCHEDULER_shutdown ();
538 t = GNUNET_SCHEDULER_add_now (&process_stdin,
544 * Call with list of names with numeric category to query.
548 * @return 0 on success
555 struct GNUNET_GETOPT_CommandLineOption options[] = {
556 GNUNET_GETOPT_option_relative_time ('d',
559 gettext_noop ("how long to wait between queries"),
561 GNUNET_GETOPT_option_relative_time ('t',
564 gettext_noop ("how long to wait for an answer"),
566 GNUNET_GETOPT_OPTION_END
570 GNUNET_STRINGS_get_utf8_args (argc, argv,
573 timeout = DEF_TIMEOUT;
574 request_delay = DEF_REQUEST_DELAY;
576 GNUNET_PROGRAM_run (argc,
578 "gnunet-gns-benchmark",
579 "resolve GNS names and measure performance",
584 GNUNET_free ((void*) argv);
588 /* end of gnunet-gns-benchmark.c */