/* This file is part of GNUnet. Copyright (C) 2013, 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 namestore/perf_namestore_api_zone_iteration.c * @brief testcase for zone iteration functionality: iterate all zones * @author Christian Grothoff */ #include "platform.h" #include "gnunet_namestore_service.h" #include "gnunet_testing_lib.h" #include "namestore.h" #include "gnunet_dnsparser_lib.h" #define TEST_RECORD_TYPE GNUNET_DNSPARSER_TYPE_TXT /** * A #BENCHMARK_SIZE of 1000 takes less than a minute on a reasonably * modern system, so 30 minutes should be OK even for very, very * slow systems. */ #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 30) /** * The runtime of the benchmark is expected to be linear * for the iteration phase with a *good* database. The FLAT * database uses a quadratic retrieval algorithm, * hence it should be quadratic in the size. */ #define BENCHMARK_SIZE 1000 /** * Maximum record size */ #define MAX_REC_SIZE 500 /** * How big are the blocks we fetch? Note that the first block is * always just 1 record set per current API. Smaller block * sizes will make quadratic iteration-by-offset penalties * more pronounced. */ #define BLOCK_SIZE 100 static struct GNUNET_NAMESTORE_Handle *nsh; static struct GNUNET_SCHEDULER_Task *timeout_task; static struct GNUNET_SCHEDULER_Task *t; static struct GNUNET_CRYPTO_EcdsaPrivateKey privkey; static struct GNUNET_NAMESTORE_ZoneIterator *zi; static struct GNUNET_NAMESTORE_QueueEntry *qe; static int res; static unsigned int off; static unsigned int left_until_next; static uint8_t seen[1 + BENCHMARK_SIZE / 8]; static struct GNUNET_TIME_Absolute start; /** * Terminate everything * * @param cls NULL */ static void end (void *cls) { (void) cls; if (NULL != qe) { GNUNET_NAMESTORE_cancel (qe); qe = NULL; } if (NULL != zi) { GNUNET_NAMESTORE_zone_iteration_stop (zi); zi = NULL; } if (NULL != nsh) { GNUNET_NAMESTORE_disconnect (nsh); nsh = NULL; } if (NULL != t) { GNUNET_SCHEDULER_cancel (t); t = NULL; } if (NULL != timeout_task) { GNUNET_SCHEDULER_cancel (timeout_task); timeout_task = NULL; } } /** * End with timeout. As this is a benchmark, we do not * fail hard but return "skipped". */ static void timeout (void *cls) { (void) cls; timeout_task = NULL; GNUNET_SCHEDULER_shutdown (); res = 77; } static struct GNUNET_GNSRECORD_Data * create_record (unsigned int count) { struct GNUNET_GNSRECORD_Data *rd; rd = GNUNET_malloc (count + sizeof(struct GNUNET_GNSRECORD_Data)); rd->expiration_time = GNUNET_TIME_relative_to_absolute ( GNUNET_TIME_UNIT_HOURS).abs_value_us; rd->record_type = TEST_RECORD_TYPE; rd->data_size = count; rd->data = (void *) &rd[1]; rd->flags = 0; memset (&rd[1], 'a', count); return rd; } static void zone_end (void *cls) { struct GNUNET_TIME_Relative delay; zi = NULL; delay = GNUNET_TIME_absolute_get_duration (start); fprintf (stdout, "Iterating over %u records took %s\n", off, GNUNET_STRINGS_relative_time_to_string (delay, GNUNET_YES)); if (BENCHMARK_SIZE == off) { res = 0; } else { GNUNET_break (0); res = 1; } GNUNET_SCHEDULER_shutdown (); } static void fail_cb (void *cls) { zi = NULL; res = 2; GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); } static void zone_proc (void *cls, const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone, const char *label, unsigned int rd_count, const struct GNUNET_GNSRECORD_Data *rd) { struct GNUNET_GNSRECORD_Data *wrd; unsigned int xoff; GNUNET_assert (NULL != zone); if (1 != sscanf (label, "l%u", &xoff)) { res = 3; GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); return; } if ((xoff > BENCHMARK_SIZE) || (0 != (seen[xoff / 8] & (1U << (xoff % 8))))) { res = 3; GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); return; } seen[xoff / 8] |= (1U << (xoff % 8)); wrd = create_record (xoff % MAX_REC_SIZE); if ((rd->record_type != wrd->record_type) || (rd->data_size != wrd->data_size) || (rd->flags != wrd->flags)) { res = 4; GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); GNUNET_free (wrd); return; } if (0 != memcmp (rd->data, wrd->data, wrd->data_size)) { res = 4; GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); GNUNET_free (wrd); return; } GNUNET_free (wrd); if (0 != GNUNET_memcmp (zone, &privkey)) { res = 5; GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); return; } off++; left_until_next--; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Obtained record %u, expecting %u more until asking for mor explicitly\n", off, left_until_next); if (0 == left_until_next) { left_until_next = BLOCK_SIZE; GNUNET_NAMESTORE_zone_iterator_next (zi, left_until_next); } } static void publish_record (void *cls); static void put_cont (void *cls, int32_t success, const char *emsg) { (void) cls; qe = NULL; if (GNUNET_OK != success) { GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); return; } t = GNUNET_SCHEDULER_add_now (&publish_record, NULL); } static void publish_record (void *cls) { struct GNUNET_GNSRECORD_Data *rd; char *label; (void) cls; t = NULL; if (BENCHMARK_SIZE == off) { struct GNUNET_TIME_Relative delay; delay = GNUNET_TIME_absolute_get_duration (start); fprintf (stdout, "Inserting %u records took %s\n", off, GNUNET_STRINGS_relative_time_to_string (delay, GNUNET_YES)); start = GNUNET_TIME_absolute_get (); off = 0; left_until_next = 1; zi = GNUNET_NAMESTORE_zone_iteration_start (nsh, NULL, &fail_cb, NULL, &zone_proc, NULL, &zone_end, NULL); GNUNET_assert (NULL != zi); return; } rd = create_record ((++off) % MAX_REC_SIZE); GNUNET_asprintf (&label, "l%u", off); qe = GNUNET_NAMESTORE_records_store (nsh, &privkey, label, 1, rd, &put_cont, NULL); GNUNET_free (label); GNUNET_free (rd); } static void run (void *cls, const struct GNUNET_CONFIGURATION_Handle *cfg, struct GNUNET_TESTING_Peer *peer) { GNUNET_SCHEDULER_add_shutdown (&end, NULL); timeout_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT, &timeout, NULL); nsh = GNUNET_NAMESTORE_connect (cfg); GNUNET_assert (NULL != nsh); GNUNET_CRYPTO_ecdsa_key_create (&privkey); start = GNUNET_TIME_absolute_get (); t = GNUNET_SCHEDULER_add_now (&publish_record, NULL); } #include "test_common.c" int main (int argc, char *argv[]) { const char *plugin_name; char *cfg_name; SETUP_CFG (plugin_name, cfg_name); res = 1; if (0 != GNUNET_TESTING_peer_run ("perf-namestore-api-zone-iteration", cfg_name, &run, NULL)) { res = 1; } GNUNET_DISK_purge_cfg_dir (cfg_name, "GNUNET_TEST_HOME"); GNUNET_free (cfg_name); return res; } /* end of perf_namestore_api_zone_iteration.c */