exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-bank-benchmark.c (18341B)


      1 /*
      2   This file is part of TALER
      3   (C) 2014-2023 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it
      6   under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation; either version 3, or
      8   (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13   General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public License
     16   along with TALER; see the file COPYING.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file benchmark/taler-bank-benchmark.c
     21  * @brief code to benchmark only the 'bank' and the 'taler-exchange-wirewatch' tool
     22  * @author Marcello Stanisci
     23  * @author Christian Grothoff
     24  */
     25 // FIXME: support use of more than one 'client' bank account
     26 // FIXME: add taler-exchange-transfer to simulate outgoing payments
     27 #include "platform.h"
     28 #include <gnunet/gnunet_util_lib.h>
     29 #include <microhttpd.h>
     30 #include <sys/resource.h>
     31 #include "taler/taler_util.h"
     32 #include "taler/taler_json_lib.h"
     33 #include "taler/taler_bank_service.h"
     34 #include "exchangedb_lib.h"
     35 #include "taler/taler_fakebank_lib.h"
     36 #include "taler/taler_testing_lib.h"
     37 #include "taler/taler_error_codes.h"
     38 
     39 #define SHARD_SIZE "1024"
     40 
     41 /**
     42  * Credentials to use for the benchmark.
     43  */
     44 static struct TALER_TESTING_Credentials cred;
     45 
     46 /**
     47  * Array of all the commands the benchmark is running.
     48  */
     49 static struct TALER_TESTING_Command *all_commands;
     50 
     51 /**
     52  * Name of our configuration file.
     53  */
     54 static char *cfg_filename;
     55 
     56 /**
     57  * Use the fakebank instead of LibEuFin.
     58  */
     59 static int use_fakebank;
     60 
     61 /**
     62  * Verbosity level.
     63  */
     64 static unsigned int verbose;
     65 
     66 /**
     67  * How many reserves we want to create per client.
     68  */
     69 static unsigned int howmany_reserves = 1;
     70 
     71 /**
     72  * How many clients we want to create.
     73  */
     74 static unsigned int howmany_clients = 1;
     75 
     76 /**
     77  * How many wirewatch processes do we want to create.
     78  */
     79 static unsigned int start_wirewatch;
     80 
     81 /**
     82  * Log level used during the run.
     83  */
     84 static char *loglev;
     85 
     86 /**
     87  * Log file.
     88  */
     89 static char *logfile;
     90 
     91 /**
     92  * Configuration.
     93  */
     94 static struct GNUNET_CONFIGURATION_Handle *cfg;
     95 
     96 /**
     97  * Section with the configuration data for the exchange
     98  * bank account.
     99  */
    100 static char *exchange_bank_section;
    101 
    102 /**
    103  * Currency used.
    104  */
    105 static char *currency;
    106 
    107 /**
    108  * Array of command labels.
    109  */
    110 static char **labels;
    111 
    112 /**
    113  * Length of #labels.
    114  */
    115 static unsigned int label_len;
    116 
    117 /**
    118  * Offset in #labels.
    119  */
    120 static unsigned int label_off;
    121 
    122 /**
    123  * Performance counters.
    124  */
    125 static struct TALER_TESTING_Timer timings[] = {
    126   { .prefix = "createreserve" },
    127   { .prefix = NULL }
    128 };
    129 
    130 
    131 /**
    132  * Add label to the #labels table and return it.
    133  *
    134  * @param label string to add to the table
    135  * @return same string, now stored in the table
    136  */
    137 static const char *
    138 add_label (char *label)
    139 {
    140   if (label_off == label_len)
    141     GNUNET_array_grow (labels,
    142                        label_len,
    143                        label_len * 2 + 4);
    144   labels[label_off++] = label;
    145   return label;
    146 }
    147 
    148 
    149 /**
    150  * Print performance statistics for this process.
    151  */
    152 static void
    153 print_stats (void)
    154 {
    155   for (unsigned int i = 0; NULL != timings[i].prefix; i++)
    156   {
    157     char *total;
    158     char *latency;
    159 
    160     total = GNUNET_strdup (
    161       GNUNET_STRINGS_relative_time_to_string (timings[i].total_duration,
    162                                               true));
    163     latency = GNUNET_strdup (
    164       GNUNET_STRINGS_relative_time_to_string (timings[i].success_latency,
    165                                               true));
    166     fprintf (stderr,
    167              "%s-%d took %s in total with %s for latency for %u executions (%u repeats)\n",
    168              timings[i].prefix,
    169              (int) getpid (),
    170              total,
    171              latency,
    172              timings[i].num_commands,
    173              timings[i].num_retries);
    174     GNUNET_free (total);
    175     GNUNET_free (latency);
    176   }
    177 }
    178 
    179 
    180 /**
    181  * Actual commands construction and execution.
    182  *
    183  * @param cls unused
    184  * @param is interpreter to run commands with
    185  */
    186 static void
    187 run (void *cls,
    188      struct TALER_TESTING_Interpreter *is)
    189 {
    190   char *total_reserve_amount;
    191   size_t len;
    192 
    193   (void) cls;
    194   len = howmany_reserves + 2;
    195   all_commands = GNUNET_malloc_large ((1 + len)
    196                                       * sizeof (struct TALER_TESTING_Command));
    197   GNUNET_assert (NULL != all_commands);
    198   all_commands[0]
    199     = TALER_TESTING_cmd_get_exchange ("get-exchange",
    200                                       cred.cfg,
    201                                       NULL,
    202                                       true,
    203                                       true);
    204 
    205   GNUNET_asprintf (&total_reserve_amount,
    206                    "%s:5",
    207                    currency);
    208   for (unsigned int j = 0; j < howmany_reserves; j++)
    209   {
    210     char *create_reserve_label;
    211 
    212     GNUNET_asprintf (&create_reserve_label,
    213                      "createreserve-%u",
    214                      j);
    215     // FIXME: vary user accounts more...
    216     all_commands[1 + j]
    217       = TALER_TESTING_cmd_admin_add_incoming_retry (
    218           TALER_TESTING_cmd_admin_add_incoming (add_label (
    219                                                   create_reserve_label),
    220                                                 total_reserve_amount,
    221                                                 &cred.ba_admin,
    222                                                 cred.user42_payto));
    223   }
    224   GNUNET_free (total_reserve_amount);
    225   all_commands[1 + howmany_reserves]
    226     = TALER_TESTING_cmd_stat (timings);
    227   all_commands[1 + howmany_reserves + 1]
    228     = TALER_TESTING_cmd_end ();
    229   TALER_TESTING_run2 (is,
    230                       all_commands,
    231                       GNUNET_TIME_UNIT_FOREVER_REL); /* no timeout */
    232 }
    233 
    234 
    235 /**
    236  * Starts #howmany_clients workers to run the client logic from #run().
    237  */
    238 static enum GNUNET_GenericReturnValue
    239 launch_clients (void)
    240 {
    241   enum GNUNET_GenericReturnValue result = GNUNET_OK;
    242   pid_t cpids[howmany_clients];
    243 
    244   if (1 == howmany_clients)
    245   {
    246     /* do everything in this process */
    247     result = TALER_TESTING_loop (&run,
    248                                  NULL);
    249     if (verbose)
    250       print_stats ();
    251     return result;
    252   }
    253   /* start work processes */
    254   for (unsigned int i = 0; i<howmany_clients; i++)
    255   {
    256     if (0 == (cpids[i] = fork ()))
    257     {
    258       /* I am the child, do the work! */
    259       GNUNET_log_setup ("benchmark-worker",
    260                         NULL == loglev ? "INFO" : loglev,
    261                         logfile);
    262       result = TALER_TESTING_loop (&run,
    263                                    NULL);
    264       if (verbose)
    265         print_stats ();
    266       if (GNUNET_OK != result)
    267         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    268                     "Failure in child process test suite!\n");
    269       if (GNUNET_OK == result)
    270         exit (0);
    271       else
    272         exit (1);
    273     }
    274     if (-1 == cpids[i])
    275     {
    276       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    277                            "fork");
    278       howmany_clients = i;
    279       result = GNUNET_SYSERR;
    280       break;
    281     }
    282     /* fork() success, continue starting more processes! */
    283   }
    284   /* collect all children */
    285   for (unsigned int i = 0; i<howmany_clients; i++)
    286   {
    287     int wstatus;
    288 
    289 again:
    290     if (cpids[i] !=
    291         waitpid (cpids[i],
    292                  &wstatus,
    293                  0))
    294     {
    295       if (EINTR == errno)
    296         goto again;
    297       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    298                            "waitpid");
    299       return GNUNET_SYSERR;
    300     }
    301     if ( (! WIFEXITED (wstatus)) ||
    302          (0 != WEXITSTATUS (wstatus)) )
    303     {
    304       GNUNET_break (0);
    305       result = GNUNET_SYSERR;
    306     }
    307   }
    308   return result;
    309 }
    310 
    311 
    312 /**
    313  * Run the benchmark in parallel in many (client) processes
    314  * and summarize result.
    315  *
    316  * @return #GNUNET_OK on success
    317  */
    318 static enum GNUNET_GenericReturnValue
    319 parallel_benchmark (void)
    320 {
    321   enum GNUNET_GenericReturnValue result = GNUNET_OK;
    322   struct GNUNET_Process *wirewatch[GNUNET_NZL (start_wirewatch)];
    323 
    324   memset (wirewatch,
    325           0,
    326           sizeof (wirewatch));
    327   /* start exchange wirewatch */
    328   for (unsigned int w = 0; w<start_wirewatch; w++)
    329   {
    330     wirewatch[w] = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ALL);
    331     if (GNUNET_OK !=
    332         GNUNET_process_run_command_va (wirewatch[w],
    333                                        "taler-exchange-wirewatch",
    334                                        "taler-exchange-wirewatch",
    335                                        "-c", cfg_filename,
    336                                        "-a", exchange_bank_section,
    337                                        "-S", SHARD_SIZE,
    338                                        (NULL != loglev) ? "-L" : NULL,
    339                                        loglev,
    340                                        NULL))
    341     {
    342       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    343                   "Failed to launch wirewatch, aborting benchmark\n");
    344       GNUNET_process_destroy (wirewatch[w]);
    345       for (unsigned int x = 0; x<w; x++)
    346       {
    347         GNUNET_break (GNUNET_OK ==
    348                       GNUNET_process_kill (wirewatch[x],
    349                                            SIGTERM));
    350         GNUNET_break (GNUNET_OK ==
    351                       GNUNET_process_wait (wirewatch[x],
    352                                            true,
    353                                            NULL,
    354                                            NULL));
    355         GNUNET_process_destroy (wirewatch[x]);
    356         wirewatch[x] = NULL;
    357       }
    358       return GNUNET_SYSERR;
    359     }
    360   }
    361   result = launch_clients ();
    362   /* Ensure wirewatch runs to completion! */
    363   if (0 != start_wirewatch)
    364   {
    365     /* replace ONE of the wirewatchers with one that is in test-mode */
    366     GNUNET_break (GNUNET_OK ==
    367                   GNUNET_process_kill (wirewatch[0],
    368                                        SIGTERM));
    369     GNUNET_break (GNUNET_OK ==
    370                   GNUNET_process_wait (wirewatch[0],
    371                                        true,
    372                                        NULL,
    373                                        NULL));
    374     GNUNET_process_destroy (wirewatch[0]);
    375     wirewatch[0] = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ALL);
    376     if (GNUNET_OK ==
    377         GNUNET_process_run_command_va (wirewatch[0],
    378                                        "taler-exchange-wirewatch",
    379                                        "taler-exchange-wirewatch",
    380                                        "-c", cfg_filename,
    381                                        "-a", exchange_bank_section,
    382                                        "-S", SHARD_SIZE,
    383                                        "-t",
    384                                        (NULL != loglev) ? "-L" : NULL,
    385                                        loglev,
    386                                        NULL))
    387     {
    388       /* wait for it to finish! */
    389       GNUNET_break (GNUNET_OK ==
    390                     GNUNET_process_wait (wirewatch[0],
    391                                          true,
    392                                          NULL,
    393                                          NULL));
    394     }
    395     GNUNET_process_destroy (wirewatch[0]);
    396     wirewatch[0] = NULL;
    397     /* Then stop the rest, which should basically also be finished */
    398     for (unsigned int w = 1; w<start_wirewatch; w++)
    399     {
    400       GNUNET_break (GNUNET_OK ==
    401                     GNUNET_process_kill (wirewatch[w],
    402                                          SIGTERM));
    403       GNUNET_break (GNUNET_OK ==
    404                     GNUNET_process_wait (wirewatch[w],
    405                                          true,
    406                                          NULL,
    407                                          NULL));
    408       GNUNET_process_destroy (wirewatch[w]);
    409     }
    410 
    411     /* But be extra sure we did finish all shards by doing one more */
    412     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    413                 "Shard check phase\n");
    414     wirewatch[0] = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ALL);
    415     if (GNUNET_OK ==
    416         GNUNET_process_run_command_va (wirewatch[0],
    417                                        "taler-exchange-wirewatch",
    418                                        "taler-exchange-wirewatch",
    419                                        "-c", cfg_filename,
    420                                        "-a", exchange_bank_section,
    421                                        "-S", SHARD_SIZE,
    422                                        "-t",
    423                                        (NULL != loglev) ? "-L" : NULL,
    424                                        loglev,
    425                                        NULL))
    426     {
    427       /* wait for it to finish! */
    428       GNUNET_break (GNUNET_OK ==
    429                     GNUNET_process_wait (wirewatch[0],
    430                                          true,
    431                                          NULL,
    432                                          NULL));
    433     }
    434     GNUNET_process_destroy (wirewatch[0]);
    435     wirewatch[0] = NULL;
    436   }
    437 
    438   return result;
    439 }
    440 
    441 
    442 /**
    443  * The main function of the serve tool
    444  *
    445  * @param argc number of arguments from the command line
    446  * @param argv command line arguments
    447  * @return 0 ok, or `enum PaymentGeneratorError` on error
    448  */
    449 int
    450 main (int argc,
    451       char *const *argv)
    452 {
    453   enum GNUNET_GenericReturnValue result;
    454   struct GNUNET_GETOPT_CommandLineOption options[] = {
    455     GNUNET_GETOPT_option_mandatory (
    456       GNUNET_GETOPT_option_cfgfile (&cfg_filename)),
    457     GNUNET_GETOPT_option_flag ('f',
    458                                "fakebank",
    459                                "we are using fakebank",
    460                                &use_fakebank),
    461     GNUNET_GETOPT_option_help (TALER_EXCHANGE_project_data (),
    462                                "taler-bank benchmark"),
    463     GNUNET_GETOPT_option_string ('l',
    464                                  "logfile",
    465                                  "LF",
    466                                  "will log to file LF",
    467                                  &logfile),
    468     GNUNET_GETOPT_option_loglevel (&loglev),
    469     GNUNET_GETOPT_option_uint ('p',
    470                                "worker-parallelism",
    471                                "NPROCS",
    472                                "How many client processes we should run",
    473                                &howmany_clients),
    474     GNUNET_GETOPT_option_uint ('r',
    475                                "reserves",
    476                                "NRESERVES",
    477                                "How many reserves per client we should create",
    478                                &howmany_reserves),
    479     GNUNET_GETOPT_option_mandatory (
    480       GNUNET_GETOPT_option_string (
    481         'u',
    482         "exchange-account-section",
    483         "SECTION",
    484         "use exchange bank account configuration from the given SECTION",
    485         &exchange_bank_section)),
    486     GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION),
    487     GNUNET_GETOPT_option_verbose (&verbose),
    488     GNUNET_GETOPT_option_uint ('w',
    489                                "wirewatch",
    490                                "NPROC",
    491                                "run NPROC taler-exchange-wirewatch processes",
    492                                &start_wirewatch),
    493     GNUNET_GETOPT_OPTION_END
    494   };
    495   struct GNUNET_TIME_Relative duration;
    496 
    497   unsetenv ("XDG_DATA_HOME");
    498   unsetenv ("XDG_CONFIG_HOME");
    499   if (0 >=
    500       (result = GNUNET_GETOPT_run ("taler-bank-benchmark",
    501                                    options,
    502                                    argc,
    503                                    argv)))
    504   {
    505     GNUNET_free (cfg_filename);
    506     if (GNUNET_NO == result)
    507       return 0;
    508     return EXIT_INVALIDARGUMENT;
    509   }
    510   if (NULL == exchange_bank_section)
    511     exchange_bank_section = (char *) "exchange-account-1";
    512   if (NULL == loglev)
    513     loglev = (char *) "INFO";
    514   GNUNET_log_setup ("taler-bank-benchmark",
    515                     loglev,
    516                     logfile);
    517   cfg = GNUNET_CONFIGURATION_create (TALER_EXCHANGE_project_data ());
    518   if (GNUNET_OK !=
    519       GNUNET_CONFIGURATION_load (cfg,
    520                                  cfg_filename))
    521   {
    522     TALER_LOG_ERROR ("Could not parse configuration\n");
    523     GNUNET_free (cfg_filename);
    524     return EXIT_NOTCONFIGURED;
    525   }
    526   if (GNUNET_OK !=
    527       TALER_config_get_currency (cfg,
    528                                  "exchange",
    529                                  &currency))
    530   {
    531     GNUNET_CONFIGURATION_destroy (cfg);
    532     GNUNET_free (cfg_filename);
    533     return EXIT_NOTCONFIGURED;
    534   }
    535 
    536   if (GNUNET_OK !=
    537       TALER_TESTING_get_credentials (
    538         cfg_filename,
    539         exchange_bank_section,
    540         use_fakebank
    541         ? TALER_TESTING_BS_FAKEBANK
    542         : TALER_TESTING_BS_IBAN,
    543         &cred))
    544   {
    545     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    546                 "Required bank credentials not given in configuration\n");
    547     GNUNET_free (cfg_filename);
    548     return EXIT_NOTCONFIGURED;
    549   }
    550 
    551   {
    552     struct GNUNET_TIME_Absolute start_time;
    553 
    554     start_time = GNUNET_TIME_absolute_get ();
    555     result = parallel_benchmark ();
    556     duration = GNUNET_TIME_absolute_get_duration (start_time);
    557   }
    558 
    559   if (GNUNET_OK == result)
    560   {
    561     struct rusage usage;
    562     unsigned long long tps;
    563 
    564     GNUNET_assert (0 == getrusage (RUSAGE_CHILDREN,
    565                                    &usage));
    566     fprintf (stdout,
    567              "Executed Reserve=%u * Parallel=%u, operations in %s\n",
    568              howmany_reserves,
    569              howmany_clients,
    570              GNUNET_STRINGS_relative_time_to_string (duration,
    571                                                      true));
    572     if (! GNUNET_TIME_relative_is_zero (duration))
    573     {
    574       tps = ((unsigned long long) howmany_reserves) * howmany_clients * 1000LLU
    575             / (duration.rel_value_us / 1000LL);
    576       fprintf (stdout,
    577                "RAW: %04u %04u %16llu (%llu TPS)\n",
    578                howmany_reserves,
    579                howmany_clients,
    580                (unsigned long long) duration.rel_value_us,
    581                tps);
    582     }
    583     fprintf (stdout,
    584              "CPU time: sys %llu user %llu\n",
    585              (unsigned long long) (usage.ru_stime.tv_sec * 1000 * 1000
    586                                    + usage.ru_stime.tv_usec),
    587              (unsigned long long) (usage.ru_utime.tv_sec * 1000 * 1000
    588                                    + usage.ru_utime.tv_usec));
    589   }
    590   for (unsigned int i = 0; i<label_off; i++)
    591     GNUNET_free (labels[i]);
    592   GNUNET_array_grow (labels,
    593                      label_len,
    594                      0);
    595   GNUNET_CONFIGURATION_destroy (cfg);
    596   GNUNET_free (cfg_filename);
    597   return (GNUNET_OK == result) ? 0 : result;
    598 }