diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/bank-lib/fakebank.c | 1 | ||||
| -rw-r--r-- | src/benchmark/.gitignore | 1 | ||||
| -rw-r--r-- | src/benchmark/Makefile.am | 16 | ||||
| -rw-r--r-- | src/benchmark/taler-bank-benchmark.c | 716 | ||||
| -rw-r--r-- | src/include/taler_testing_lib.h | 8 | 
5 files changed, 737 insertions, 5 deletions
diff --git a/src/bank-lib/fakebank.c b/src/bank-lib/fakebank.c index e091adf5..978253b5 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 <christian@grothoff.org>   */ -// 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 00000000..9757b376 --- /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 af1f5b94..89dc3006 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 00000000..ad46f86d --- /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 <http://www.gnu.org/licenses/> +*/ +/** + * @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 <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include <sys/resource.h> +#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<howmany_clients; i++) +  { +    if (0 == (cpids[i] = fork ())) +    { +      /* I am the child, do the work! */ +      GNUNET_log_setup ("benchmark-worker", +                        NULL == loglev ? "INFO" : loglev, +                        logfile); +      result = TALER_TESTING_setup (&run, +                                    NULL, +                                    cfg, +                                    NULL, +                                    GNUNET_NO); +      print_stats (); +      if (GNUNET_OK != result) +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Failure in child process test suite!\n"); +      if (GNUNET_OK == result) +        exit (0); +      else +        exit (1); +    } +    if (-1 == cpids[i]) +    { +      GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, +                           "fork"); +      howmany_clients = i; +      result = GNUNET_SYSERR; +      break; +    } +    /* fork() success, continue starting more processes! */ +  } +  /* collect all children */ +  for (unsigned int i = 0; i<howmany_clients; i++) +  { +    int wstatus; + +    waitpid (cpids[i], +             &wstatus, +             0); +    if ( (! WIFEXITED (wstatus)) || +         (0 != WEXITSTATUS (wstatus)) ) +    { +      GNUNET_break (0); +      result = GNUNET_SYSERR; +    } +  } +  return result; +} + + +/** + * Stop the fakebank. + * + * @param cls fakebank handle + */ +static void +stop_fakebank (void *cls) +{ +  struct TALER_FAKEBANK_Handle *fakebank = cls; + +  TALER_FAKEBANK_stop (fakebank); +} + + +/** + * Start the fakebank. + * + * @param cls NULL + */ +static void +launch_fakebank (void *cls) +{ +  struct TALER_FAKEBANK_Handle *fakebank; + +  (void) cls; +  fakebank +    = TALER_TESTING_run_fakebank (exchange_bank_account.wire_gateway_url, +                                  currency); +  if (NULL == fakebank) +  { +    GNUNET_break (0); +    return; +  } +  GNUNET_SCHEDULER_add_shutdown (&stop_fakebank, +                                 fakebank); +} + + +/** + * Run the benchmark in parallel in many (client) processes + * and summarize result. + * + * @return #GNUNET_OK on success + */ +static int +parallel_benchmark (void) +{ +  enum GNUNET_GenericReturnValue result = GNUNET_OK; +  pid_t fakebank = -1; +  struct GNUNET_OS_Process *bankd = NULL; +  struct GNUNET_OS_Process *wirewatch = NULL; + +  if ( (MODE_BANK == mode) || +       (MODE_BOTH == mode) ) +  { +    if (use_fakebank) +    { +      /* start fakebank */ +      fakebank = fork (); +      if (0 == fakebank) +      { +        GNUNET_log_setup ("benchmark-fakebank", +                          NULL == loglev ? "INFO" : loglev, +                          logfile); +        GNUNET_SCHEDULER_run (&launch_fakebank, +                              NULL); +        exit (0); +      } +      if (-1 == fakebank) +      { +        GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, +                             "fork"); +        return GNUNET_SYSERR; +      } +      /* wait for fakebank to be ready */ +      sleep (1); +    } +    else +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "FIXME: launching LibEuFin not yet supported\n"); +      bankd = NULL; // FIXME +      return GNUNET_SYSERR; +    } + +    /* start exchange wirewatch */ +    wirewatch = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL, +                                         NULL, NULL, NULL, +                                         "taler-exchange-wirewatch", +                                         "taler-exchange-wirewatch", +                                         "-c", cfg_filename, +                                         NULL); +    if (-1 != fakebank) +    { +      int wstatus; + +      kill (fakebank, +            SIGTERM); +      waitpid (fakebank, +               &wstatus, +               0); +    } +    if (NULL != bankd) +    { +      GNUNET_OS_process_kill (bankd, +                              SIGTERM); +      GNUNET_OS_process_destroy (bankd); +    } +  } + +  if ( (MODE_CLIENT == mode) || +       (MODE_BOTH == mode) ) +    result = launch_clients (); +  if ( (GNUNET_YES == linger) || +       (MODE_BANK == mode) ) +  { +    printf ("Press ENTER to stop!\n"); +    (void) getchar (); +  } + +  if ( (MODE_BANK == mode) || +       (MODE_BOTH == mode) ) +  { +    GNUNET_assert (NULL != wirewatch); +    /* stop wirewatch */ +    GNUNET_break (0 == +                  GNUNET_OS_process_kill (wirewatch, +                                          SIGTERM)); +    GNUNET_break (GNUNET_OK == +                  GNUNET_OS_process_wait (wirewatch)); +    GNUNET_OS_process_destroy (wirewatch); + +    /* stop fakebank */ +    if (-1 != fakebank) +    { +      int wstatus; + +      kill (fakebank, +            SIGTERM); +      waitpid (fakebank, +               &wstatus, +               0); +      if ( (! WIFEXITED (wstatus)) || +           (0 != WEXITSTATUS (wstatus)) ) +      { +        GNUNET_break (0); +        result = GNUNET_SYSERR; +      } +    } +    if (NULL != bankd) +    { +      GNUNET_OS_process_kill (bankd, +                              SIGTERM); +      GNUNET_OS_process_destroy (bankd); +    } +  } +  return result; +} + + +/** + * The main function of the serve tool + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, or `enum PaymentGeneratorError` on error + */ +int +main (int argc, +      char *const *argv) +{ +  enum GNUNET_GenericReturnValue result; +  struct GNUNET_GETOPT_CommandLineOption options[] = { +    GNUNET_GETOPT_option_mandatory ( +      GNUNET_GETOPT_option_cfgfile (&cfg_filename)), +#if FIXME_SUPPORT_LIBEUFIN +    GNUNET_GETOPT_option_flag ('f', +                               "fakebank", +                               "start a fakebank instead of the Python bank", +                               &use_fakebank), +#endif +    GNUNET_GETOPT_option_help ("taler-bank benchmark"), +    GNUNET_GETOPT_option_flag ('K', +                               "linger", +                               "linger around until key press", +                               &linger), +    GNUNET_GETOPT_option_string ('l', +                                 "logfile", +                                 "LF", +                                 "will log to file LF", +                                 &logfile), +    GNUNET_GETOPT_option_loglevel (&loglev), +    GNUNET_GETOPT_option_string ('m', +                                 "mode", +                                 "MODE", +                                 "run as bank, client or both", +                                 &mode_str), +    GNUNET_GETOPT_option_uint ('p', +                               "parallelism", +                               "NPROCS", +                               "How many client processes we should run", +                               &howmany_clients), +    GNUNET_GETOPT_option_uint ('r', +                               "reserves", +                               "NRESERVES", +                               "How many reserves per client we should create", +                               &howmany_reserves), +    GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION), +    GNUNET_GETOPT_OPTION_END +  }; + +  unsetenv ("XDG_DATA_HOME"); +  unsetenv ("XDG_CONFIG_HOME"); +  if (0 >= +      (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<label_off; i++) +    GNUNET_free (labels[i]); +  GNUNET_array_grow (labels, +                     label_len, +                     0); +  return (GNUNET_OK == result) ? 0 : result; +} diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index f15a5f76..b35e393d 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -714,12 +714,12 @@ typedef void   * @param exchanged exchange process handle: will be put in the   *        state as some commands - e.g. revoke - need to send   *        signal to it, for example to let it know to reload the - *        key state.. if NULL, the interpreter will run without + *        key state. If NULL, the interpreter will run without   *        trying to connect to the exchange first. - * @param exchange_connect GNUNET_YES if the test should connect - *        to the exchange, GNUNET_NO otherwise + * @param exchange_connect #GNUNET_YES if the test should connect + *        to the exchange, #GNUNET_NO otherwise   * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise. - *         non-GNUNET_OK codes are #GNUNET_SYSERR most of the + *         non-#GNUNET_OK codes are #GNUNET_SYSERR most of the   *         times.   */  int  | 
