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 (18395B)


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