/* 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]; } }