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


      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_OS_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_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
    332                                             NULL, NULL, NULL,
    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     if (NULL == wirewatch[w])
    342     {
    343       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    344                   "Failed to launch wirewatch, aborting benchmark\n");
    345       for (unsigned int x = 0; x<w; x++)
    346       {
    347         GNUNET_break (0 ==
    348                       GNUNET_OS_process_kill (wirewatch[x],
    349                                               SIGTERM));
    350         GNUNET_break (GNUNET_OK ==
    351                       GNUNET_OS_process_wait (wirewatch[x]));
    352         GNUNET_OS_process_destroy (wirewatch[x]);
    353         wirewatch[x] = NULL;
    354       }
    355       return GNUNET_SYSERR;
    356     }
    357   }
    358   result = launch_clients ();
    359   /* Ensure wirewatch runs to completion! */
    360   if (0 != start_wirewatch)
    361   {
    362     /* replace ONE of the wirewatchers with one that is in test-mode */
    363     GNUNET_break (0 ==
    364                   GNUNET_OS_process_kill (wirewatch[0],
    365                                           SIGTERM));
    366     GNUNET_break (GNUNET_OK ==
    367                   GNUNET_OS_process_wait (wirewatch[0]));
    368     GNUNET_OS_process_destroy (wirewatch[0]);
    369     wirewatch[0] = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
    370                                             NULL, NULL, NULL,
    371                                             "taler-exchange-wirewatch",
    372                                             "taler-exchange-wirewatch",
    373                                             "-c", cfg_filename,
    374                                             "-a", exchange_bank_section,
    375                                             "-S", SHARD_SIZE,
    376                                             "-t",
    377                                             (NULL != loglev) ? "-L" : NULL,
    378                                             loglev,
    379                                             NULL);
    380     /* wait for it to finish! */
    381     GNUNET_break (GNUNET_OK ==
    382                   GNUNET_OS_process_wait (wirewatch[0]));
    383     GNUNET_OS_process_destroy (wirewatch[0]);
    384     wirewatch[0] = NULL;
    385     /* Then stop the rest, which should basically also be finished */
    386     for (unsigned int w = 1; w<start_wirewatch; w++)
    387     {
    388       GNUNET_break (0 ==
    389                     GNUNET_OS_process_kill (wirewatch[w],
    390                                             SIGTERM));
    391       GNUNET_break (GNUNET_OK ==
    392                     GNUNET_OS_process_wait (wirewatch[w]));
    393       GNUNET_OS_process_destroy (wirewatch[w]);
    394     }
    395 
    396     /* But be extra sure we did finish all shards by doing one more */
    397     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    398                 "Shard check phase\n");
    399     wirewatch[0] = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
    400                                             NULL, NULL, NULL,
    401                                             "taler-exchange-wirewatch",
    402                                             "taler-exchange-wirewatch",
    403                                             "-c", cfg_filename,
    404                                             "-a", exchange_bank_section,
    405                                             "-S", SHARD_SIZE,
    406                                             "-t",
    407                                             (NULL != loglev) ? "-L" : NULL,
    408                                             loglev,
    409                                             NULL);
    410     /* wait for it to finish! */
    411     GNUNET_break (GNUNET_OK ==
    412                   GNUNET_OS_process_wait (wirewatch[0]));
    413     GNUNET_OS_process_destroy (wirewatch[0]);
    414     wirewatch[0] = NULL;
    415   }
    416 
    417   return result;
    418 }
    419 
    420 
    421 /**
    422  * The main function of the serve tool
    423  *
    424  * @param argc number of arguments from the command line
    425  * @param argv command line arguments
    426  * @return 0 ok, or `enum PaymentGeneratorError` on error
    427  */
    428 int
    429 main (int argc,
    430       char *const *argv)
    431 {
    432   enum GNUNET_GenericReturnValue result;
    433   struct GNUNET_GETOPT_CommandLineOption options[] = {
    434     GNUNET_GETOPT_option_mandatory (
    435       GNUNET_GETOPT_option_cfgfile (&cfg_filename)),
    436     GNUNET_GETOPT_option_flag ('f',
    437                                "fakebank",
    438                                "we are using fakebank",
    439                                &use_fakebank),
    440     GNUNET_GETOPT_option_help (TALER_EXCHANGE_project_data (),
    441                                "taler-bank benchmark"),
    442     GNUNET_GETOPT_option_string ('l',
    443                                  "logfile",
    444                                  "LF",
    445                                  "will log to file LF",
    446                                  &logfile),
    447     GNUNET_GETOPT_option_loglevel (&loglev),
    448     GNUNET_GETOPT_option_uint ('p',
    449                                "worker-parallelism",
    450                                "NPROCS",
    451                                "How many client processes we should run",
    452                                &howmany_clients),
    453     GNUNET_GETOPT_option_uint ('r',
    454                                "reserves",
    455                                "NRESERVES",
    456                                "How many reserves per client we should create",
    457                                &howmany_reserves),
    458     GNUNET_GETOPT_option_mandatory (
    459       GNUNET_GETOPT_option_string (
    460         'u',
    461         "exchange-account-section",
    462         "SECTION",
    463         "use exchange bank account configuration from the given SECTION",
    464         &exchange_bank_section)),
    465     GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION),
    466     GNUNET_GETOPT_option_verbose (&verbose),
    467     GNUNET_GETOPT_option_uint ('w',
    468                                "wirewatch",
    469                                "NPROC",
    470                                "run NPROC taler-exchange-wirewatch processes",
    471                                &start_wirewatch),
    472     GNUNET_GETOPT_OPTION_END
    473   };
    474   struct GNUNET_TIME_Relative duration;
    475 
    476   unsetenv ("XDG_DATA_HOME");
    477   unsetenv ("XDG_CONFIG_HOME");
    478   if (0 >=
    479       (result = GNUNET_GETOPT_run ("taler-bank-benchmark",
    480                                    options,
    481                                    argc,
    482                                    argv)))
    483   {
    484     GNUNET_free (cfg_filename);
    485     if (GNUNET_NO == result)
    486       return 0;
    487     return EXIT_INVALIDARGUMENT;
    488   }
    489   if (NULL == exchange_bank_section)
    490     exchange_bank_section = (char *) "exchange-account-1";
    491   if (NULL == loglev)
    492     loglev = (char *) "INFO";
    493   GNUNET_log_setup ("taler-bank-benchmark",
    494                     loglev,
    495                     logfile);
    496   cfg = GNUNET_CONFIGURATION_create (TALER_EXCHANGE_project_data ());
    497   if (GNUNET_OK !=
    498       GNUNET_CONFIGURATION_load (cfg,
    499                                  cfg_filename))
    500   {
    501     TALER_LOG_ERROR ("Could not parse configuration\n");
    502     GNUNET_free (cfg_filename);
    503     return EXIT_NOTCONFIGURED;
    504   }
    505   if (GNUNET_OK !=
    506       TALER_config_get_currency (cfg,
    507                                  "exchange",
    508                                  &currency))
    509   {
    510     GNUNET_CONFIGURATION_destroy (cfg);
    511     GNUNET_free (cfg_filename);
    512     return EXIT_NOTCONFIGURED;
    513   }
    514 
    515   if (GNUNET_OK !=
    516       TALER_TESTING_get_credentials (
    517         cfg_filename,
    518         exchange_bank_section,
    519         use_fakebank
    520         ? TALER_TESTING_BS_FAKEBANK
    521         : TALER_TESTING_BS_IBAN,
    522         &cred))
    523   {
    524     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    525                 "Required bank credentials not given in configuration\n");
    526     GNUNET_free (cfg_filename);
    527     return EXIT_NOTCONFIGURED;
    528   }
    529 
    530   {
    531     struct GNUNET_TIME_Absolute start_time;
    532 
    533     start_time = GNUNET_TIME_absolute_get ();
    534     result = parallel_benchmark ();
    535     duration = GNUNET_TIME_absolute_get_duration (start_time);
    536   }
    537 
    538   if (GNUNET_OK == result)
    539   {
    540     struct rusage usage;
    541     unsigned long long tps;
    542 
    543     GNUNET_assert (0 == getrusage (RUSAGE_CHILDREN,
    544                                    &usage));
    545     fprintf (stdout,
    546              "Executed Reserve=%u * Parallel=%u, operations in %s\n",
    547              howmany_reserves,
    548              howmany_clients,
    549              GNUNET_STRINGS_relative_time_to_string (duration,
    550                                                      true));
    551     if (! GNUNET_TIME_relative_is_zero (duration))
    552     {
    553       tps = ((unsigned long long) howmany_reserves) * howmany_clients * 1000LLU
    554             / (duration.rel_value_us / 1000LL);
    555       fprintf (stdout,
    556                "RAW: %04u %04u %16llu (%llu TPS)\n",
    557                howmany_reserves,
    558                howmany_clients,
    559                (unsigned long long) duration.rel_value_us,
    560                tps);
    561     }
    562     fprintf (stdout,
    563              "CPU time: sys %llu user %llu\n",
    564              (unsigned long long) (usage.ru_stime.tv_sec * 1000 * 1000
    565                                    + usage.ru_stime.tv_usec),
    566              (unsigned long long) (usage.ru_utime.tv_sec * 1000 * 1000
    567                                    + usage.ru_utime.tv_usec));
    568   }
    569   for (unsigned int i = 0; i<label_off; i++)
    570     GNUNET_free (labels[i]);
    571   GNUNET_array_grow (labels,
    572                      label_len,
    573                      0);
    574   GNUNET_CONFIGURATION_destroy (cfg);
    575   GNUNET_free (cfg_filename);
    576   return (GNUNET_OK == result) ? 0 : result;
    577 }