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 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.
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.
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/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
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.
48 enum RequestCategory {
52 * Must be last and match number of categories.
59 * Request we should make. We keep this struct in memory per request,
60 * thus optimizing it is crucial for the overall memory consumption of
65 * Active requests are kept in a DLL.
70 * Active requests are kept in a DLL.
75 * Socket used to make the request, NULL if not active.
77 struct GNUNET_GNS_LookupWithTldRequest *lr;
80 * Hostname we are resolving, allocated at the end of
81 * this struct (optimizing memory consumption by reducing
82 * total number of allocations).
87 * While we are fetching the record, the value is set to the
88 * starting time of the GNS operation.
90 struct GNUNET_TIME_Absolute op_start_time;
93 * Observed latency, set once we got a reply.
95 struct GNUNET_TIME_Relative latency;
98 * Category of the request.
100 enum RequestCategory cat;
107 static struct GNUNET_GNS_Handle *gns;
110 * Number of lookups we performed overall per category.
112 static unsigned int lookups[RC_MAX];
115 * Number of replies we got per category.
117 static unsigned int replies[RC_MAX];
120 * Number of replies we got per category.
122 static unsigned int failures[RC_MAX];
125 * Sum of the observed latencies of successful queries,
128 static struct GNUNET_TIME_Relative latency_sum[RC_MAX];
131 * Active requests are kept in a DLL.
133 static struct Request *act_head;
136 * Active requests are kept in a DLL.
138 static struct Request *act_tail;
141 * Completed successful requests are kept in a DLL.
143 static struct Request *succ_head;
146 * Completed successful requests are kept in a DLL.
148 static struct Request *succ_tail;
151 * Yet to be started requests are kept in a DLL.
153 static struct Request *todo_head;
156 * Yet to be started requests are kept in a DLL.
158 static struct Request *todo_tail;
163 static struct GNUNET_SCHEDULER_Task *t;
166 * Delay between requests.
168 static struct GNUNET_TIME_Relative request_delay;
171 * Timeout for requests.
173 static struct GNUNET_TIME_Relative timeout;
176 * Number of requests we have concurrently active.
178 static unsigned int active_cnt;
181 * Look for GNS2DNS records specifically?
186 * Free @a req and data structures reachable from it.
188 * @param req request to free
191 free_request(struct Request *req)
194 GNUNET_GNS_lookup_with_tld_cancel(req->lr);
200 * Function called with the result of a GNS resolution.
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
208 process_result(void *cls,
211 const struct GNUNET_GNSRECORD_Data *rd)
213 struct Request *req = cls;
219 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
220 "Got response for request `%s'\n",
223 req->latency = GNUNET_TIME_absolute_get_duration(req->op_start_time);
224 GNUNET_CONTAINER_DLL_remove(act_head,
227 GNUNET_CONTAINER_DLL_insert(succ_head,
231 latency_sum[req->cat]
232 = GNUNET_TIME_relative_add(latency_sum[req->cat],
238 * Process request from the queue.
243 process_queue(void *cls)
246 struct GNUNET_TIME_Relative duration;
250 /* check for expired requests */
251 while (NULL != (req = act_head))
253 duration = GNUNET_TIME_absolute_get_duration(req->op_start_time);
254 if (duration.rel_value_us < timeout.rel_value_us)
256 GNUNET_CONTAINER_DLL_remove(act_head,
259 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
260 "Failing request `%s' due to timeout\n",
262 failures[req->cat]++;
266 if (NULL == (req = todo_head))
268 struct GNUNET_TIME_Absolute at;
270 if (NULL == (req = act_head))
272 GNUNET_SCHEDULER_shutdown();
275 at = GNUNET_TIME_absolute_add(req->op_start_time,
277 t = GNUNET_SCHEDULER_add_at(at,
282 GNUNET_CONTAINER_DLL_remove(todo_head,
285 GNUNET_CONTAINER_DLL_insert_tail(act_head,
290 req->op_start_time = GNUNET_TIME_absolute_get();
291 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
292 "Starting request `%s' (%u in parallel)\n",
295 req->lr = GNUNET_GNS_lookup_with_tld(gns,
298 ? GNUNET_GNSRECORD_TYPE_GNS2DNS
299 : GNUNET_GNSRECORD_TYPE_ANY,
300 GNUNET_GNS_LO_DEFAULT,
303 t = GNUNET_SCHEDULER_add_delayed(request_delay,
310 * Compare two requests by latency for qsort().
312 * @param c1 pointer to `struct Request *`
313 * @param c2 pointer to `struct Request *`
314 * @return -1 if c1<c2, 1 if c1>c2, 0 if c1==c2.
317 compare_req(const void *c1,
320 const struct Request *r1 = *(void **)c1;
321 const struct Request *r2 = *(void **)c2;
323 if (r1->latency.rel_value_us < r2->latency.rel_value_us)
325 if (r1->latency.rel_value_us > r2->latency.rel_value_us)
332 * Output statistics, then clean up and terminate the process.
337 do_shutdown(void *cls)
340 struct Request **ra[RC_MAX];
341 unsigned int rp[RC_MAX];
344 for (enum RequestCategory rc = 0; rc < RC_MAX; rc++)
346 ra[rc] = GNUNET_new_array(replies[rc],
350 for (req = succ_head; NULL != req; req = req->next)
352 GNUNET_assert(rp[req->cat] < replies[req->cat]);
353 ra[req->cat][rp[req->cat]++] = req;
355 for (enum RequestCategory rc = 0; rc < RC_MAX; rc++)
363 "\tlookups: %u replies: %u failures: %u\n",
371 sizeof(struct Request *),
373 latency_sum[rc] = GNUNET_TIME_relative_divide(latency_sum[rc],
377 GNUNET_STRINGS_relative_time_to_string(latency_sum[rc],
379 off = rp[rc] * 50 / 100;
381 "\tmedian(50): %s\n",
382 GNUNET_STRINGS_relative_time_to_string(ra[rc][off]->latency,
384 off = rp[rc] * 75 / 100;
386 "\tquantile(75): %s\n",
387 GNUNET_STRINGS_relative_time_to_string(ra[rc][off]->latency,
389 off = rp[rc] * 90 / 100;
391 "\tquantile(90): %s\n",
392 GNUNET_STRINGS_relative_time_to_string(ra[rc][off]->latency,
394 off = rp[rc] * 99 / 100;
396 "\tquantile(99): %s\n",
397 GNUNET_STRINGS_relative_time_to_string(ra[rc][off]->latency,
403 GNUNET_SCHEDULER_cancel(t);
406 while (NULL != (req = act_head))
408 GNUNET_CONTAINER_DLL_remove(act_head,
413 while (NULL != (req = succ_head))
415 GNUNET_CONTAINER_DLL_remove(succ_head,
420 while (NULL != (req = todo_head))
422 GNUNET_CONTAINER_DLL_remove(todo_head,
429 GNUNET_GNS_disconnect(gns);
436 * Add @a hostname to the list of requests to be made.
438 * @param hostname name to resolve
439 * @param cat category of the @a hostname
442 queue(const char *hostname,
443 enum RequestCategory cat)
449 dot = strchr(hostname,
453 GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
454 "Refusing invalid hostname `%s' (lacks '.')\n",
458 hlen = strlen(hostname) + 1;
459 req = GNUNET_malloc(sizeof(struct Request) + hlen);
461 req->hostname = (char *)&req[1];
462 GNUNET_memcpy(&req[1],
465 GNUNET_CONTAINER_DLL_insert(todo_head,
472 * Begin processing hostnames from stdin.
477 process_stdin(void *cls)
479 static struct GNUNET_TIME_Absolute last;
480 static uint64_t idot;
493 hn[strlen(in) - 1] = '\0'; /* eat newline */
501 "Malformed input line `%s', skipping\n",
506 last = GNUNET_TIME_absolute_get();
508 if (0 == idot % 100000)
510 struct GNUNET_TIME_Relative delta;
512 delta = GNUNET_TIME_absolute_get_duration(last);
513 last = GNUNET_TIME_absolute_get();
515 "Read 100000 domain names in %s\n",
516 GNUNET_STRINGS_relative_time_to_string(delta,
520 (enum RequestCategory)cat);
523 "Done reading %llu domain names\n",
524 (unsigned long long)idot);
525 t = GNUNET_SCHEDULER_add_now(&process_queue,
531 * Process requests from the queue, then if the queue is
532 * not empty, try again.
535 * @param args remaining command-line arguments
536 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
537 * @param cfg configuration
543 const struct GNUNET_CONFIGURATION_Handle *cfg)
548 GNUNET_SCHEDULER_add_shutdown(&do_shutdown,
550 gns = GNUNET_GNS_connect(cfg);
554 GNUNET_SCHEDULER_shutdown();
557 t = GNUNET_SCHEDULER_add_now(&process_stdin,
563 * Call with list of names with numeric category to query.
567 * @return 0 on success
574 struct GNUNET_GETOPT_CommandLineOption options[] = {
575 GNUNET_GETOPT_option_relative_time('d',
578 gettext_noop("how long to wait between queries"),
580 GNUNET_GETOPT_option_relative_time('t',
583 gettext_noop("how long to wait for an answer"),
585 GNUNET_GETOPT_option_flag('2',
587 gettext_noop("look for GNS2DNS records instead of ANY"),
589 GNUNET_GETOPT_OPTION_END
593 GNUNET_STRINGS_get_utf8_args(argc, argv,
596 timeout = DEF_TIMEOUT;
597 request_delay = DEF_REQUEST_DELAY;
599 GNUNET_PROGRAM_run(argc,
601 "gnunet-gns-benchmark",
602 "resolve GNS names and measure performance",
607 GNUNET_free((void*)argv);
611 /* end of gnunet-gns-benchmark.c */