From fdf095c6efae2e6c59712996ca380bc499229370 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 19 Jun 2021 13:59:03 +0200 Subject: initial version of taler-bank-benchmark --- src/bank-lib/fakebank.c | 1 - src/benchmark/.gitignore | 1 + src/benchmark/Makefile.am | 16 + src/benchmark/taler-bank-benchmark.c | 716 +++++++++++++++++++++++++++++++++++ src/include/taler_testing_lib.h | 8 +- 5 files changed, 737 insertions(+), 5 deletions(-) create mode 100644 src/benchmark/.gitignore create mode 100644 src/benchmark/taler-bank-benchmark.c diff --git a/src/bank-lib/fakebank.c b/src/bank-lib/fakebank.c index e091adf54..978253b57 100644 --- a/src/bank-lib/fakebank.c +++ b/src/bank-lib/fakebank.c @@ -21,7 +21,6 @@ * @brief library that fakes being a Taler bank for testcases * @author Christian Grothoff */ -// TOOD: pass test suite... // TODO: support long polling // TODO: support adding WAD transfers // TODO: adapt taler-exchange-benchmark to profile bank API diff --git a/src/benchmark/.gitignore b/src/benchmark/.gitignore new file mode 100644 index 000000000..9757b376b --- /dev/null +++ b/src/benchmark/.gitignore @@ -0,0 +1 @@ +taler-bank-benchmark diff --git a/src/benchmark/Makefile.am b/src/benchmark/Makefile.am index af1f5b94c..89dc30061 100644 --- a/src/benchmark/Makefile.am +++ b/src/benchmark/Makefile.am @@ -11,8 +11,24 @@ if USE_COVERAGE endif bin_PROGRAMS = \ + taler-bank-benchmark \ taler-exchange-benchmark +taler_bank_benchmark_SOURCES = \ + taler-bank-benchmark.c +taler_bank_benchmark_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/testing/libtalertesting.la \ + $(top_builddir)/src/bank-lib/libtalerfakebank.la \ + $(top_builddir)/src/bank-lib/libtalerbank.la \ + -lgnunetjson \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson \ + $(XLIB) + taler_exchange_benchmark_SOURCES = \ taler-exchange-benchmark.c taler_exchange_benchmark_LDADD = \ diff --git a/src/benchmark/taler-bank-benchmark.c b/src/benchmark/taler-bank-benchmark.c new file mode 100644 index 000000000..ad46f86d9 --- /dev/null +++ b/src/benchmark/taler-bank-benchmark.c @@ -0,0 +1,716 @@ +/* + This file is part of TALER + (C) 2014-2021 Taler Systems SA + + TALER 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, or + (at your option) any later version. + + TALER 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file benchmark/taler-bank-benchmark.c + * @brief code to benchmark only the 'bank' and the 'taler-exchange-wirewatch' tool + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_json_lib.h" +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" +#include "taler_error_codes.h" + + +/* Error codes. */ +enum BenchmarkError +{ + MISSING_BANK_URL, + FAILED_TO_LAUNCH_BANK, + BAD_CLI_ARG, + BAD_CONFIG_FILE, + NO_CONFIG_FILE_GIVEN +}; + + +/** + * What mode should the benchmark run in? + */ +enum BenchmarkMode +{ + /** + * Run as client against the bank. + */ + MODE_CLIENT = 1, + + /** + * Run the bank. + */ + MODE_BANK = 2, + + /** + * Run both, for a local benchmark. + */ + MODE_BOTH = 3, +}; + + +/** + * Hold information regarding which bank has the exchange account. + */ +static struct TALER_BANK_AuthenticationData exchange_bank_account; + +/** + * Time snapshot taken right before executing the CMDs. + */ +static struct GNUNET_TIME_Absolute start_time; + +/** + * Benchmark duration time taken right after the CMD interpreter + * returns. + */ +static struct GNUNET_TIME_Relative duration; + +/** + * Array of all the commands the benchmark is running. + */ +static struct TALER_TESTING_Command *all_commands; + +/** + * Name of our configuration file. + */ +static char *cfg_filename; + +/** + * Use the fakebank instead of LibEuFin. + * NOTE: LibEuFin not yet supported! Set + * to 0 once we do support it! + */ +static int use_fakebank = 1; + +/** + * How many reserves we want to create per client. + */ +static unsigned int howmany_reserves = 1; + +/** + * How many clients we want to create. + */ +static unsigned int howmany_clients = 1; + +/** + * Log level used during the run. + */ +static char *loglev; + +/** + * Log file. + */ +static char *logfile; + +/** + * Benchmarking mode (run as client, exchange, both) as string. + */ +static char *mode_str; + +/** + * Benchmarking mode (run as client, bank, both). + */ +static enum BenchmarkMode mode; + +/** + * Don't kill exchange/fakebank/wirewatch until + * requested by the user explicitly. + */ +static int linger; + +/** + * Configuration. + */ +static struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Currency used. + */ +static char *currency; + +/** + * Array of command labels. + */ +static char **labels; + +/** + * Length of #labels. + */ +static unsigned int label_len; + +/** + * Offset in #labels. + */ +static unsigned int label_off; + +/** + * Performance counters. + */ +static struct TALER_TESTING_Timer timings[] = { + { .prefix = "createreserve" }, + { .prefix = NULL } +}; + + +/** + * Add label to the #labels table and return it. + * + * @param label string to add to the table + * @return same string, now stored in the table + */ +const char * +add_label (char *label) +{ + if (label_off == label_len) + GNUNET_array_grow (labels, + label_len, + label_len * 2 + 4); + labels[label_off++] = label; + return label; +} + + +/** + * Print performance statistics for this process. + */ +static void +print_stats (void) +{ + for (unsigned int i = 0; NULL != timings[i].prefix; i++) + { + char *total; + char *latency; + + total = GNUNET_strdup ( + GNUNET_STRINGS_relative_time_to_string (timings[i].total_duration, + GNUNET_YES)); + latency = GNUNET_strdup ( + GNUNET_STRINGS_relative_time_to_string (timings[i].success_latency, + GNUNET_YES)); + fprintf (stderr, + "%s-%d took %s in total with %s for latency for %u executions (%u repeats)\n", + timings[i].prefix, + (int) getpid (), + total, + latency, + timings[i].num_commands, + timings[i].num_retries); + GNUNET_free (total); + GNUNET_free (latency); + } +} + + +/** + * Decide which exchange account is going to be used to address a wire + * transfer to. Used at withdrawal time. + * + * @param cls closure + * @param section section name. + */ +static void +pick_exchange_account_cb (void *cls, + const char *section) +{ + const char **s = cls; + + if (0 == strncasecmp ("exchange-account-", + section, + strlen ("exchange-account-"))) + *s = section; +} + + +/** + * Actual commands construction and execution. + * + * @param cls unused + * @param is interpreter to run commands with + */ +static void +run (void *cls, + struct TALER_TESTING_Interpreter *is) +{ + struct TALER_Amount total_reserve_amount; + char *user_payto_uri; + + (void) cls; + // FIXME: vary user accounts more... + GNUNET_assert (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (cfg, + "benchmark", + "USER_PAYTO_URI", + &user_payto_uri)); + all_commands = GNUNET_new_array (howmany_reserves + + 1 /* stat CMD */ + + 1 /* End CMD */, + struct TALER_TESTING_Command); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_reserve_amount)); + total_reserve_amount.value = 5; + for (unsigned int j = 0; j < howmany_reserves; j++) + { + char create_reserve_label[32]; + + GNUNET_snprintf (create_reserve_label, + sizeof (create_reserve_label), + "createreserve-%u", + j); + all_commands[j] + = TALER_TESTING_cmd_admin_add_incoming_retry ( + TALER_TESTING_cmd_admin_add_incoming (add_label ( + create_reserve_label), + TALER_amount2s ( + &total_reserve_amount), + &exchange_bank_account, + user_payto_uri)); + } + GNUNET_free (user_payto_uri); + all_commands[howmany_reserves] + = TALER_TESTING_cmd_stat (timings); + all_commands[howmany_reserves + 1] + = TALER_TESTING_cmd_end (); + TALER_TESTING_run2 (is, + all_commands, + GNUNET_TIME_UNIT_FOREVER_REL); /* no timeout */ +} + + +/** + * Starts #howmany_clients workers to run the client logic from #run(). + */ +static enum GNUNET_GenericReturnValue +launch_clients (void) +{ + enum GNUNET_GenericReturnValue result = GNUNET_OK; + pid_t cpids[howmany_clients]; + + start_time = GNUNET_TIME_absolute_get (); + if (1 == howmany_clients) + { + /* do everything in this process */ + result = TALER_TESTING_setup (&run, + NULL, + cfg, + NULL, + GNUNET_NO); + print_stats (); + return result; + } + /* start work processes */ + for (unsigned int i = 0; i= + (result = GNUNET_GETOPT_run ("taler-bank-benchmark", + options, + argc, + argv))) + { + GNUNET_free (cfg_filename); + if (GNUNET_NO == result) + return 0; + return BAD_CLI_ARG; + } + GNUNET_log_setup ("taler-bank-benchmark", + NULL == loglev ? "INFO" : loglev, + logfile); + if (NULL == mode_str) + mode = MODE_BOTH; + else if (0 == strcasecmp (mode_str, + "bank")) + mode = MODE_BANK; + else if (0 == strcasecmp (mode_str, + "client")) + mode = MODE_CLIENT; + else if (0 == strcasecmp (mode_str, + "both")) + mode = MODE_BOTH; + else + { + TALER_LOG_ERROR ("Unknown mode given: '%s'\n", + mode_str); + GNUNET_free (cfg_filename); + return BAD_CONFIG_FILE; + } + if (NULL == cfg_filename) + cfg_filename = GNUNET_strdup ( + GNUNET_OS_project_data_get ()->user_config_file); + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != + GNUNET_CONFIGURATION_load (cfg, + cfg_filename)) + { + TALER_LOG_ERROR ("Could not parse configuration\n"); + GNUNET_free (cfg_filename); + return BAD_CONFIG_FILE; + } + if (GNUNET_OK != + TALER_config_get_currency (cfg, + ¤cy)) + { + GNUNET_CONFIGURATION_destroy (cfg); + GNUNET_free (cfg_filename); + return BAD_CONFIG_FILE; + } + if (MODE_BANK != mode) + { + if (howmany_clients > 10240) + { + TALER_LOG_ERROR ("-p option value given is too large\n"); + return BAD_CLI_ARG; + } + if (0 == howmany_clients) + { + TALER_LOG_ERROR ("-p option value must not be zero\n"); + GNUNET_free (cfg_filename); + return BAD_CLI_ARG; + } + } + { + const char *bank_details_section; + + GNUNET_CONFIGURATION_iterate_sections (cfg, + &pick_exchange_account_cb, + &bank_details_section); + if (NULL == bank_details_section) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Missing specification of bank account in configuration\n"); + GNUNET_free (cfg_filename); + return BAD_CONFIG_FILE; + } + if (GNUNET_OK != + TALER_BANK_auth_parse_cfg (cfg, + bank_details_section, + &exchange_bank_account)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Configuration fails to provide exchange bank details in section `%s'\n", + bank_details_section); + GNUNET_free (cfg_filename); + return BAD_CONFIG_FILE; + } + } + result = parallel_benchmark (); + GNUNET_CONFIGURATION_destroy (cfg); + GNUNET_free (cfg_filename); + + if (MODE_BANK == mode) + { + /* If we're the bank, we're done now. No need to print results. */ + return (GNUNET_OK == result) ? 0 : result; + } + duration = GNUNET_TIME_absolute_get_duration (start_time); + if (GNUNET_OK == result) + { + struct rusage usage; + + GNUNET_assert (0 == getrusage (RUSAGE_CHILDREN, + &usage)); + fprintf (stdout, + "Executed Reserve=%u * Parallel=%u, operations in %s\n", + howmany_reserves, + howmany_clients, + GNUNET_STRINGS_relative_time_to_string (duration, + GNUNET_NO)); + fprintf (stdout, + "RAW: %04u %04u %16llu\n", + howmany_reserves, + howmany_clients, + (unsigned long long) duration.rel_value_us); + fprintf (stdout, + "CPU time: sys %llu user %llu\n", \ + (unsigned long long) (usage.ru_stime.tv_sec * 1000 * 1000 + + usage.ru_stime.tv_usec), + (unsigned long long) (usage.ru_utime.tv_sec * 1000 * 1000 + + usage.ru_utime.tv_usec)); + } + for (unsigned int i = 0; i