/*
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 util/benchmark.c
* @brief benchmarking for various operations
* @author Florian Dold
*/
#include "platform.h"
#include "gnunet_util_lib.h"
#include "benchmark.h"
#include
#include
/**
* Thread-local storage key for the benchmark data.
*/
static pthread_key_t key;
/**
* One-time initialization marker for key.
*/
static pthread_once_t key_once = PTHREAD_ONCE_INIT;
/**
* Write benchmark data to a file.
*
* @param bd the benchmark data
*/
static void
write_benchmark_data (struct BenchmarkData *bd)
{
struct GNUNET_DISK_FileHandle *fh;
pid_t pid = getpid ();
pid_t tid = syscall (SYS_gettid);
char *benchmark_dir;
char *s;
benchmark_dir = getenv ("GNUNET_BENCHMARK_DIR");
if (NULL == benchmark_dir)
return;
if (GNUNET_OK != GNUNET_DISK_directory_create (benchmark_dir))
{
GNUNET_break (0);
return;
}
GNUNET_asprintf (&s, "%s/gnunet-benchmark-ops-%s-%llu-%llu.txt",
benchmark_dir,
(pid == tid) ? "main" : "thread",
(unsigned long long) pid,
(unsigned long long) tid);
fh = GNUNET_DISK_file_open (s,
(GNUNET_DISK_OPEN_WRITE
| GNUNET_DISK_OPEN_TRUNCATE
| GNUNET_DISK_OPEN_CREATE),
(GNUNET_DISK_PERM_USER_READ
| GNUNET_DISK_PERM_USER_WRITE));
GNUNET_assert (NULL != fh);
GNUNET_free (s);
#define WRITE_BENCHMARK_OP(opname) do { \
GNUNET_asprintf (&s, "op " #opname " count %llu time_us %llu\n", \
(unsigned long long) bd->opname ## _count, \
(unsigned long long) bd->opname ## _time.rel_value_us); \
GNUNET_assert (GNUNET_SYSERR != GNUNET_DISK_file_write_blocking (fh, s, \
strlen ( \
s))); \
GNUNET_free (s); \
} while (0)
WRITE_BENCHMARK_OP (ecc_ecdh);
WRITE_BENCHMARK_OP (ecdh_eddsa);
WRITE_BENCHMARK_OP (ecdhe_key_create);
WRITE_BENCHMARK_OP (ecdhe_key_get_public);
WRITE_BENCHMARK_OP (ecdsa_ecdh);
WRITE_BENCHMARK_OP (ecdsa_key_create);
WRITE_BENCHMARK_OP (ecdsa_key_get_public);
WRITE_BENCHMARK_OP (ecdsa_sign);
WRITE_BENCHMARK_OP (ecdsa_verify);
WRITE_BENCHMARK_OP (eddsa_ecdh);
WRITE_BENCHMARK_OP (eddsa_key_create);
WRITE_BENCHMARK_OP (eddsa_key_get_public);
WRITE_BENCHMARK_OP (eddsa_sign);
WRITE_BENCHMARK_OP (eddsa_verify);
WRITE_BENCHMARK_OP (hash);
WRITE_BENCHMARK_OP (hash_context_finish);
WRITE_BENCHMARK_OP (hash_context_read);
WRITE_BENCHMARK_OP (hash_context_start);
WRITE_BENCHMARK_OP (hkdf);
WRITE_BENCHMARK_OP (rsa_blind);
WRITE_BENCHMARK_OP (rsa_private_key_create);
WRITE_BENCHMARK_OP (rsa_private_key_get_public);
WRITE_BENCHMARK_OP (rsa_sign_blinded);
WRITE_BENCHMARK_OP (rsa_unblind);
WRITE_BENCHMARK_OP (rsa_verify);
#undef WRITE_BENCHMARK_OP
GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_close (fh));
GNUNET_asprintf (&s, "%s/gnunet-benchmark-urls-%s-%llu-%llu.txt",
benchmark_dir,
(pid == tid) ? "main" : "thread",
(unsigned long long) pid,
(unsigned long long) tid);
fh = GNUNET_DISK_file_open (s,
(GNUNET_DISK_OPEN_WRITE
| GNUNET_DISK_OPEN_TRUNCATE
| GNUNET_DISK_OPEN_CREATE),
(GNUNET_DISK_PERM_USER_READ
| GNUNET_DISK_PERM_USER_WRITE));
GNUNET_assert (NULL != fh);
GNUNET_free (s);
for (unsigned int i = 0; i < bd->urd_len; i++)
{
struct UrlRequestData *urd = &bd->urd[i];
GNUNET_asprintf (&s,
"url %s status %u count %llu time_us %llu time_us_max %llu bytes_sent %llu bytes_received %llu\n",
urd->request_url,
urd->status,
(unsigned long long) urd->count,
(unsigned long long) urd->time.rel_value_us,
(unsigned long long) urd->time_max.rel_value_us,
(unsigned long long) urd->bytes_sent,
(unsigned long long) urd->bytes_received);
GNUNET_assert (GNUNET_SYSERR != GNUNET_DISK_file_write_blocking (fh, s,
strlen (
s)));
GNUNET_free (s);
}
GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_close (fh));
}
/**
* Called when the main thread exits and benchmark data for it was created.
*/
static void
main_thread_destructor ()
{
struct BenchmarkData *bd;
bd = pthread_getspecific (key);
if (NULL != bd)
write_benchmark_data (bd);
}
/**
* Called when a thread exits and benchmark data for it was created.
*
* @param cls closure
*/
static void
thread_destructor (void *cls)
{
struct BenchmarkData *bd = cls;
// main thread will be handled by atexit
if (getpid () == (pid_t) syscall (SYS_gettid))
return;
GNUNET_assert (NULL != bd);
write_benchmark_data (bd);
}
/**
* Initialize the thread-local variable key for benchmark data.
*/
static void
make_key ()
{
(void) pthread_key_create (&key, &thread_destructor);
}
/**
* Acquire the benchmark data for the current thread, allocate if necessary.
* Installs handler to collect the benchmark data on thread termination.
*
* @return benchmark data for the current thread
*/
struct BenchmarkData *
get_benchmark_data (void)
{
struct BenchmarkData *bd;
(void) pthread_once (&key_once, &make_key);
if (NULL == (bd = pthread_getspecific (key)))
{
bd = GNUNET_new (struct BenchmarkData);
(void) pthread_setspecific (key, bd);
if (getpid () == (pid_t) syscall (SYS_gettid))
{
// We're the main thread!
atexit (main_thread_destructor);
}
}
return bd;
}
/**
* Get benchmark data for a URL. If the URL is too long, it's truncated
* before looking up the correspoding benchmark data.
*
* Statistics are bucketed by URL and status code.
*
* @param url url to get request data for
* @param status http status code
*/
struct UrlRequestData *
get_url_benchmark_data (char *url, unsigned int status)
{
char trunc[MAX_BENCHMARK_URL_LEN];
struct BenchmarkData *bd;
if (NULL == url)
{
/* Should not happen unless curl barfs */
GNUNET_break (0);
url = "";
}
memcpy (trunc, url, MAX_BENCHMARK_URL_LEN);
trunc[MAX_BENCHMARK_URL_LEN - 1] = 0;
/* We're not interested in what's after the query string */
for (size_t i = 0; i < strlen (trunc); i++)
{
if (trunc[i] == '?')
{
trunc[i] = 0;
break;
}
}
bd = get_benchmark_data ();
GNUNET_assert (bd->urd_len <= bd->urd_capacity);
for (unsigned int i = 0; i < bd->urd_len; i++)
{
if ((0 == strcmp (trunc, bd->urd[i].request_url)) &&
(bd->urd[i].status == status))
return &bd->urd[i];
}
{
struct UrlRequestData urd = { 0 };
memcpy (&urd.request_url, trunc, MAX_BENCHMARK_URL_LEN);
urd.status = status;
if (bd->urd_len == bd->urd_capacity)
{
bd->urd_capacity = 2 * (bd->urd_capacity + 1);
bd->urd = GNUNET_realloc (bd->urd, bd->urd_capacity * sizeof(struct
UrlRequestData));
}
bd->urd[bd->urd_len++] = urd;
return &bd->urd[bd->urd_len - 1];
}
}