/*
This file is part of GNUnet
Copyright (C) 2018 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
GNUnet is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
SPDX-License-Identifier: AGPL3.0-or-later
*/
/**
* @file src/gns/gnunet-gns-benchmark.c
* @brief issue many queries to GNS and compute performance statistics
* @author Christian Grothoff
*/
#include "platform.h"
#include
#include
#include
/**
* How long do we wait at least between requests by default?
*/
#define DEF_REQUEST_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 1)
/**
* How long do we wait until we consider a request failed by default?
*/
#define DEF_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 1)
/**
* We distinguish between different categories of
* requests, for which we track statistics separately.
* However, this process does not change how it acts
* based on the category.
*/
enum RequestCategory
{
RC_SHARED = 0,
RC_PRIVATE = 1,
/**
* Must be last and match number of categories.
*/
RC_MAX = 2
};
/**
* Request we should make. We keep this struct in memory per request,
* thus optimizing it is crucial for the overall memory consumption of
* the zone importer.
*/
struct Request
{
/**
* Active requests are kept in a DLL.
*/
struct Request *next;
/**
* Active requests are kept in a DLL.
*/
struct Request *prev;
/**
* Socket used to make the request, NULL if not active.
*/
struct GNUNET_GNS_LookupWithTldRequest *lr;
/**
* Hostname we are resolving, allocated at the end of
* this struct (optimizing memory consumption by reducing
* total number of allocations).
*/
const char *hostname;
/**
* While we are fetching the record, the value is set to the
* starting time of the GNS operation.
*/
struct GNUNET_TIME_Absolute op_start_time;
/**
* Observed latency, set once we got a reply.
*/
struct GNUNET_TIME_Relative latency;
/**
* Category of the request.
*/
enum RequestCategory cat;
};
/**
* GNS handle.
*/
static struct GNUNET_GNS_Handle *gns;
/**
* Number of lookups we performed overall per category.
*/
static unsigned int lookups[RC_MAX];
/**
* Number of replies we got per category.
*/
static unsigned int replies[RC_MAX];
/**
* Number of replies we got per category.
*/
static unsigned int failures[RC_MAX];
/**
* Sum of the observed latencies of successful queries,
* per category.
*/
static struct GNUNET_TIME_Relative latency_sum[RC_MAX];
/**
* Active requests are kept in a DLL.
*/
static struct Request *act_head;
/**
* Active requests are kept in a DLL.
*/
static struct Request *act_tail;
/**
* Completed successful requests are kept in a DLL.
*/
static struct Request *succ_head;
/**
* Completed successful requests are kept in a DLL.
*/
static struct Request *succ_tail;
/**
* Yet to be started requests are kept in a DLL.
*/
static struct Request *todo_head;
/**
* Yet to be started requests are kept in a DLL.
*/
static struct Request *todo_tail;
/**
* Main task.
*/
static struct GNUNET_SCHEDULER_Task *t;
/**
* Delay between requests.
*/
static struct GNUNET_TIME_Relative request_delay;
/**
* Timeout for requests.
*/
static struct GNUNET_TIME_Relative timeout;
/**
* Number of requests we have concurrently active.
*/
static unsigned int active_cnt;
/**
* Look for GNS2DNS records specifically?
*/
static int g2d;
/**
* Free @a req and data structures reachable from it.
*
* @param req request to free
*/
static void
free_request (struct Request *req)
{
if (NULL != req->lr)
GNUNET_GNS_lookup_with_tld_cancel (req->lr);
GNUNET_free (req);
}
/**
* Function called with the result of a GNS resolution.
*
* @param cls closure with the `struct Request`
* @param gns_tld #GNUNET_YES if GNS lookup was attempted
* @param rd_count number of records in @a rd
* @param rd the records in reply
*/
static void
process_result (void *cls,
int gns_tld,
uint32_t rd_count,
const struct GNUNET_GNSRECORD_Data *rd)
{
struct Request *req = cls;
(void) gns_tld;
(void) rd_count;
(void) rd;
active_cnt--;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Got response for request `%s'\n",
req->hostname);
req->lr = NULL;
req->latency = GNUNET_TIME_absolute_get_duration (req->op_start_time);
GNUNET_CONTAINER_DLL_remove (act_head,
act_tail,
req);
GNUNET_CONTAINER_DLL_insert (succ_head,
succ_tail,
req);
replies[req->cat]++;
latency_sum[req->cat]
= GNUNET_TIME_relative_add (latency_sum[req->cat],
req->latency);
}
/**
* Process request from the queue.
*
* @param cls NULL
*/
static void
process_queue (void *cls)
{
struct Request *req;
struct GNUNET_TIME_Relative duration;
(void) cls;
t = NULL;
/* check for expired requests */
while (NULL != (req = act_head))
{
duration = GNUNET_TIME_absolute_get_duration (req->op_start_time);
if (duration.rel_value_us < timeout.rel_value_us)
break;
GNUNET_CONTAINER_DLL_remove (act_head,
act_tail,
req);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Failing request `%s' due to timeout\n",
req->hostname);
failures[req->cat]++;
active_cnt--;
free_request (req);
}
if (NULL == (req = todo_head))
{
struct GNUNET_TIME_Absolute at;
if (NULL == (req = act_head))
{
GNUNET_SCHEDULER_shutdown ();
return;
}
at = GNUNET_TIME_absolute_add (req->op_start_time,
timeout);
t = GNUNET_SCHEDULER_add_at (at,
&process_queue,
NULL);
return;
}
GNUNET_CONTAINER_DLL_remove (todo_head,
todo_tail,
req);
GNUNET_CONTAINER_DLL_insert_tail (act_head,
act_tail,
req);
lookups[req->cat]++;
active_cnt++;
req->op_start_time = GNUNET_TIME_absolute_get ();
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Starting request `%s' (%u in parallel)\n",
req->hostname,
active_cnt);
req->lr = GNUNET_GNS_lookup_with_tld (gns,
req->hostname,
g2d
? GNUNET_GNSRECORD_TYPE_GNS2DNS
: GNUNET_GNSRECORD_TYPE_ANY,
GNUNET_GNS_LO_DEFAULT,
&process_result,
req);
t = GNUNET_SCHEDULER_add_delayed (request_delay,
&process_queue,
NULL);
}
/**
* Compare two requests by latency for qsort().
*
* @param c1 pointer to `struct Request *`
* @param c2 pointer to `struct Request *`
* @return -1 if c1c2, 0 if c1==c2.
*/
static int
compare_req (const void *c1,
const void *c2)
{
const struct Request *r1 = *(void **) c1;
const struct Request *r2 = *(void **) c2;
if (r1->latency.rel_value_us < r2->latency.rel_value_us)
return -1;
if (r1->latency.rel_value_us > r2->latency.rel_value_us)
return 1;
return 0;
}
/**
* Output statistics, then clean up and terminate the process.
*
* @param cls NULL
*/
static void
do_shutdown (void *cls)
{
struct Request *req;
struct Request **ra[RC_MAX];
unsigned int rp[RC_MAX];
(void) cls;
for (enum RequestCategory rc = 0;rc < RC_MAX;rc++)
{
ra[rc] = GNUNET_new_array (replies[rc],
struct Request *);
rp[rc] = 0;
}
for (req = succ_head;NULL != req; req = req->next)
{
GNUNET_assert (rp[req->cat] < replies[req->cat]);
ra[req->cat][rp[req->cat]++] = req;
}
for (enum RequestCategory rc = 0;rc < RC_MAX;rc++)
{
unsigned int off;
fprintf (stdout,
"Category %u\n",
rc);
fprintf (stdout,
"\tlookups: %u replies: %u failures: %u\n",
lookups[rc],
replies[rc],
failures[rc]);
if (0 == rp[rc])
continue;
qsort (ra[rc],
rp[rc],
sizeof (struct Request *),
&compare_req);
latency_sum[rc] = GNUNET_TIME_relative_divide (latency_sum[rc],
replies[rc]);
fprintf (stdout,
"\taverage: %s\n",
GNUNET_STRINGS_relative_time_to_string (latency_sum[rc],
GNUNET_YES));
off = rp[rc] * 50 / 100;
fprintf (stdout,
"\tmedian(50): %s\n",
GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
GNUNET_YES));
off = rp[rc] * 75 / 100;
fprintf (stdout,
"\tquantile(75): %s\n",
GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
GNUNET_YES));
off = rp[rc] * 90 / 100;
fprintf (stdout,
"\tquantile(90): %s\n",
GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
GNUNET_YES));
off = rp[rc] * 99 / 100;
fprintf (stdout,
"\tquantile(99): %s\n",
GNUNET_STRINGS_relative_time_to_string (ra[rc][off]->latency,
GNUNET_YES));
GNUNET_free (ra[rc]);
}
if (NULL != t)
{
GNUNET_SCHEDULER_cancel (t);
t = NULL;
}
while (NULL != (req = act_head))
{
GNUNET_CONTAINER_DLL_remove (act_head,
act_tail,
req);
free_request (req);
}
while (NULL != (req = succ_head))
{
GNUNET_CONTAINER_DLL_remove (succ_head,
succ_tail,
req);
free_request (req);
}
while (NULL != (req = todo_head))
{
GNUNET_CONTAINER_DLL_remove (todo_head,
todo_tail,
req);
free_request (req);
}
if (NULL != gns)
{
GNUNET_GNS_disconnect (gns);
gns = NULL;
}
}
/**
* Add @a hostname to the list of requests to be made.
*
* @param hostname name to resolve
* @param cat category of the @a hostname
*/
static void
queue (const char *hostname,
enum RequestCategory cat)
{
struct Request *req;
const char *dot;
size_t hlen;
dot = strchr (hostname,
(unsigned char) '.');
if (NULL == dot)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Refusing invalid hostname `%s' (lacks '.')\n",
hostname);
return;
}
hlen = strlen (hostname) + 1;
req = GNUNET_malloc (sizeof (struct Request) + hlen);
req->cat = cat;
req->hostname = (char *) &req[1];
GNUNET_memcpy (&req[1],
hostname,
hlen);
GNUNET_CONTAINER_DLL_insert (todo_head,
todo_tail,
req);
}
/**
* Begin processing hostnames from stdin.
*
* @param cls NULL
*/
static void
process_stdin (void *cls)
{
static struct GNUNET_TIME_Absolute last;
static uint64_t idot;
unsigned int cat;
char hn[256];
char in[270];
(void) cls;
t = NULL;
while (NULL !=
fgets (in,
sizeof (in),
stdin))
{
if (strlen(in) > 0)
hn[strlen(in)-1] = '\0'; /* eat newline */
if ( (2 != sscanf (in,
"%u %255s",
&cat,
hn)) ||
(cat >= RC_MAX) )
{
fprintf (stderr,
"Malformed input line `%s', skipping\n",
in);
continue;
}
if (0 == idot)
last = GNUNET_TIME_absolute_get ();
idot++;
if (0 == idot % 100000)
{
struct GNUNET_TIME_Relative delta;
delta = GNUNET_TIME_absolute_get_duration (last);
last = GNUNET_TIME_absolute_get ();
fprintf (stderr,
"Read 100000 domain names in %s\n",
GNUNET_STRINGS_relative_time_to_string (delta,
GNUNET_YES));
}
queue (hn,
(enum RequestCategory) cat);
}
fprintf (stderr,
"Done reading %llu domain names\n",
(unsigned long long) idot);
t = GNUNET_SCHEDULER_add_now (&process_queue,
NULL);
}
/**
* Process requests from the queue, then if the queue is
* not empty, try again.
*
* @param cls NULL
* @param args remaining command-line arguments
* @param cfgfile name of the configuration file used (for saving, can be NULL!)
* @param cfg configuration
*/
static void
run (void *cls,
char *const *args,
const char *cfgfile,
const struct GNUNET_CONFIGURATION_Handle *cfg)
{
(void) cls;
(void) args;
(void) cfgfile;
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
NULL);
gns = GNUNET_GNS_connect (cfg);
if (NULL == gns)
{
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
return;
}
t = GNUNET_SCHEDULER_add_now (&process_stdin,
NULL);
}
/**
* Call with list of names with numeric category to query.
*
* @param argc unused
* @param argv unused
* @return 0 on success
*/
int
main (int argc,
char *const*argv)
{
int ret = 0;
struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_option_relative_time ('d',
"delay",
"RELATIVETIME",
gettext_noop ("how long to wait between queries"),
&request_delay),
GNUNET_GETOPT_option_relative_time ('t',
"timeout",
"RELATIVETIME",
gettext_noop ("how long to wait for an answer"),
&timeout),
GNUNET_GETOPT_option_flag ('2',
"g2d",
gettext_noop ("look for GNS2DNS records instead of ANY"),
&g2d),
GNUNET_GETOPT_OPTION_END
};
if (GNUNET_OK !=
GNUNET_STRINGS_get_utf8_args (argc, argv,
&argc, &argv))
return 2;
timeout = DEF_TIMEOUT;
request_delay = DEF_REQUEST_DELAY;
if (GNUNET_OK !=
GNUNET_PROGRAM_run (argc,
argv,
"gnunet-gns-benchmark",
"resolve GNS names and measure performance",
options,
&run,
NULL))
ret = 1;
GNUNET_free ((void*) argv);
return ret;
}
/* end of gnunet-gns-benchmark.c */