/* 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 */ // TODO: // - use more than one 'client' bank account // - also add taler-exchange-transfer to simulate outgoing payments #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_exchangedb_lib.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 const struct TALER_EXCHANGEDB_AccountInfo *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; /** * Dummy keepalive task. */ static struct GNUNET_SCHEDULER_Task *keepalive; /** * 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; /** * Launch taler-exchange-wirewatch. */ static int start_wirewatch; /** * Verbosity level. */ static unsigned int verbose; /** * Size of the transaction history the fakebank * should keep in RAM. */ static unsigned long long history_size = 65536; /** * 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; /** * How many bank worker threads do we want to create. */ static unsigned int howmany_threads; /** * 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); } } /** * 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) { char *total_reserve_amount; size_t len; (void) cls; len = howmany_reserves + 2; all_commands = GNUNET_new_array (len, struct TALER_TESTING_Command); GNUNET_asprintf (&total_reserve_amount, "%s:5", currency); for (unsigned int j = 0; j < howmany_reserves; j++) { char *create_reserve_label; char *user_payto_uri; // FIXME: vary user accounts more... GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (cfg, "benchmark", "USER_PAYTO_URI", &user_payto_uri)); GNUNET_asprintf (&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), total_reserve_amount, exchange_bank_account->auth, add_label (user_payto_uri))); } GNUNET_free (total_reserve_amount); 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); if (verbose) 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 (history_size < 10) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "History size too small, this can hardly work\n"); return BAD_CLI_ARG; } 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_CONFIGURATION_default_filename (); if (NULL == cfg_filename) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Can't find default configuration file.\n"); return EXIT_NOTCONFIGURED; } 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; } } if (GNUNET_OK != TALER_EXCHANGEDB_load_accounts (cfg, TALER_EXCHANGEDB_ALO_AUTHDATA | TALER_EXCHANGEDB_ALO_CREDIT)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Configuration fails to provide exchange bank details\n"); GNUNET_free (cfg_filename); return BAD_CONFIG_FILE; } exchange_bank_account = TALER_EXCHANGEDB_find_account_by_method ("x-taler-bank"); if (NULL == exchange_bank_account) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "No bank account for `x-taler-bank` given in configuration\n"); GNUNET_free (cfg_filename); return BAD_CONFIG_FILE; } result = parallel_benchmark (); TALER_EXCHANGEDB_unload_accounts (); 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; } if (GNUNET_OK == result) { struct rusage usage; unsigned long long tps; 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_YES)); tps = ((unsigned long long) howmany_reserves) * howmany_clients * 1000LLU / (duration.rel_value_us / 1000LL); fprintf (stdout, "RAW: %04u %04u %16llu (%llu TPS)\n", howmany_reserves, howmany_clients, (unsigned long long) duration.rel_value_us, tps); 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