exchange

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

taler-exchange-benchmark.c (19272B)


      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-exchange-benchmark.c
     21  * @brief HTTP serving layer intended to perform crypto-work and
     22  * communication with the exchange
     23  * @author Marcello Stanisci
     24  * @author Christian Grothoff
     25  */
     26 #include "taler/platform.h"
     27 #include <gnunet/gnunet_util_lib.h>
     28 #include <microhttpd.h>
     29 #include <sys/resource.h>
     30 #include "taler/taler_util.h"
     31 #include "taler/taler_testing_lib.h"
     32 
     33 /**
     34  * The whole benchmark is a repetition of a "unit".  Each
     35  * unit is a array containing a withdraw+deposit operation,
     36  * and _possibly_ a refresh of the deposited coin.
     37  */
     38 #define UNITY_SIZE 6
     39 
     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  * How many coins we want to create per client and reserve.
     58  */
     59 static unsigned int howmany_coins = 1;
     60 
     61 /**
     62  * How many reserves we want to create per client.
     63  */
     64 static unsigned int howmany_reserves = 1;
     65 
     66 /**
     67  * Probability (in percent) of refreshing per spent coin.
     68  */
     69 static unsigned int refresh_rate = 10;
     70 
     71 /**
     72  * How many clients we want to create.
     73  */
     74 static unsigned int howmany_clients = 1;
     75 
     76 /**
     77  * Log level used during the run.
     78  */
     79 static char *loglev;
     80 
     81 /**
     82  * Log file.
     83  */
     84 static char *logfile;
     85 
     86 /**
     87  * Configuration.
     88  */
     89 static struct GNUNET_CONFIGURATION_Handle *cfg;
     90 
     91 /**
     92  * Should we create all of the reserves at the beginning?
     93  */
     94 static int reserves_first;
     95 
     96 /**
     97  * Are we running against 'fakebank'?
     98  */
     99 static int use_fakebank;
    100 
    101 /**
    102  * Section with the configuration data for the exchange
    103  * bank account.
    104  */
    105 static char *exchange_bank_section;
    106 
    107 /**
    108  * Currency used.
    109  */
    110 static char *currency;
    111 
    112 /**
    113  * Array of command labels.
    114  */
    115 static char **labels;
    116 
    117 /**
    118  * Length of #labels.
    119  */
    120 static unsigned int label_len;
    121 
    122 /**
    123  * Offset in #labels.
    124  */
    125 static unsigned int label_off;
    126 
    127 /**
    128  * Performance counters.
    129  */
    130 static struct TALER_TESTING_Timer timings[] = {
    131   { .prefix = "createreserve" },
    132   { .prefix = "withdraw" },
    133   { .prefix = "deposit" },
    134   { .prefix = "melt" },
    135   { .prefix = "reveal" },
    136   { .prefix = "link" },
    137   { .prefix = NULL }
    138 };
    139 
    140 
    141 /**
    142  * Add label to the #labels table and return it.
    143  *
    144  * @param label string to add to the table
    145  * @return same string, now stored in the table
    146  */
    147 static const char *
    148 add_label (char *label)
    149 {
    150   if (label_off == label_len)
    151     GNUNET_array_grow (labels,
    152                        label_len,
    153                        label_len * 2 + 4);
    154   labels[label_off++] = label;
    155   return label;
    156 }
    157 
    158 
    159 static struct TALER_TESTING_Command
    160 cmd_transfer_to_exchange (const char *label,
    161                           const char *amount)
    162 {
    163   return TALER_TESTING_cmd_admin_add_incoming_retry (
    164     TALER_TESTING_cmd_admin_add_incoming (label,
    165                                           amount,
    166                                           &cred.ba_admin,
    167                                           cred.user42_payto));
    168 }
    169 
    170 
    171 /**
    172  * Throw a weighted coin with @a probability.
    173  *
    174  * @param probability weight of the coin flip
    175  * @return #GNUNET_OK with @a probability,
    176  *         #GNUNET_NO with 1 - @a probability
    177  */
    178 static unsigned int
    179 eval_probability (float probability)
    180 {
    181   uint64_t random;
    182   float random_01;
    183 
    184   random = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
    185                                      UINT64_MAX);
    186   random_01 = (double) random / (double) UINT64_MAX;
    187   return (random_01 <= probability) ? GNUNET_OK : GNUNET_NO;
    188 }
    189 
    190 
    191 /**
    192  * Actual commands construction and execution.
    193  *
    194  * @param cls unused
    195  * @param is interpreter to run commands with
    196  */
    197 static void
    198 run (void *cls,
    199      struct TALER_TESTING_Interpreter *is)
    200 {
    201   struct TALER_Amount total_reserve_amount;
    202   struct TALER_Amount withdraw_fee;
    203   char *withdraw_fee_str;
    204   char *amount_5;
    205   char *amount_4;
    206   char *amount_1;
    207 
    208   (void) cls;
    209   all_commands = GNUNET_malloc_large (
    210     (1 /* exchange CMD */
    211      + howmany_reserves
    212      * (1 /* Withdraw block */
    213         + howmany_coins) /* All units */
    214      + 1 /* stat CMD */
    215      + 1 /* End CMD */) * sizeof (struct TALER_TESTING_Command));
    216   GNUNET_assert (NULL != all_commands);
    217   all_commands[0]
    218     = TALER_TESTING_cmd_get_exchange ("get-exchange",
    219                                       cred.cfg,
    220                                       NULL,
    221                                       true,
    222                                       true);
    223   GNUNET_asprintf (&amount_5, "%s:5", currency);
    224   GNUNET_asprintf (&amount_4, "%s:4", currency);
    225   GNUNET_asprintf (&amount_1, "%s:1", currency);
    226   GNUNET_assert (GNUNET_OK ==
    227                  TALER_amount_set_zero (currency,
    228                                         &total_reserve_amount));
    229   total_reserve_amount.value = 5 * howmany_coins;
    230   GNUNET_asprintf (&withdraw_fee_str,
    231                    "%s:0.1",
    232                    currency);
    233   GNUNET_assert (GNUNET_OK ==
    234                  TALER_string_to_amount (withdraw_fee_str,
    235                                          &withdraw_fee));
    236   for (unsigned int i = 0; i < howmany_coins; i++)
    237     GNUNET_assert (0 <=
    238                    TALER_amount_add (&total_reserve_amount,
    239                                      &total_reserve_amount,
    240                                      &withdraw_fee));
    241   for (unsigned int j = 0; j < howmany_reserves; j++)
    242   {
    243     char *create_reserve_label;
    244 
    245     GNUNET_asprintf (&create_reserve_label,
    246                      "createreserve-%u",
    247                      j);
    248     {
    249       struct TALER_TESTING_Command make_reserve[] = {
    250         cmd_transfer_to_exchange (add_label (create_reserve_label),
    251                                   TALER_amount2s (&total_reserve_amount)),
    252         TALER_TESTING_cmd_end ()
    253       };
    254       char *batch_label;
    255 
    256       GNUNET_asprintf (&batch_label,
    257                        "batch-start-%u",
    258                        j);
    259       all_commands[1 + (reserves_first
    260                         ? j
    261                         : j * (howmany_coins + 1))]
    262         = TALER_TESTING_cmd_batch (add_label (batch_label),
    263                                    make_reserve);
    264     }
    265     for (unsigned int i = 0; i < howmany_coins; i++)
    266     {
    267       char *withdraw_label;
    268       char *order_enc;
    269       struct TALER_TESTING_Command unit[UNITY_SIZE];
    270       char *unit_label;
    271       const char *wl;
    272 
    273       GNUNET_asprintf (&withdraw_label,
    274                        "withdraw-%u-%u",
    275                        i,
    276                        j);
    277       wl = add_label (withdraw_label);
    278       GNUNET_asprintf (&order_enc,
    279                        "{\"nonce\": %llu}",
    280                        ((unsigned long long) i)
    281                        + (howmany_coins * (unsigned long long) j));
    282       unit[0] =
    283         TALER_TESTING_cmd_withdraw_with_retry (
    284           TALER_TESTING_cmd_withdraw_amount (wl,
    285                                              create_reserve_label,
    286                                              amount_5,
    287                                              0,  /* age restriction off */
    288                                              MHD_HTTP_OK));
    289       unit[1] =
    290         TALER_TESTING_cmd_deposit_with_retry (
    291           TALER_TESTING_cmd_deposit ("deposit",
    292                                      wl,
    293                                      0,  /* Index of the one withdrawn coin in the traits.  */
    294                                      cred.user43_payto,
    295                                      add_label (order_enc),
    296                                      GNUNET_TIME_UNIT_ZERO,
    297                                      amount_1,
    298                                      MHD_HTTP_OK));
    299       if (eval_probability (refresh_rate / 100.0d))
    300       {
    301         char *melt_label;
    302         char *reveal_label;
    303         const char *ml;
    304         const char *rl;
    305 
    306         GNUNET_asprintf (&melt_label,
    307                          "melt-%u-%u",
    308                          i,
    309                          j);
    310         ml = add_label (melt_label);
    311         GNUNET_asprintf (&reveal_label,
    312                          "reveal-%u-%u",
    313                          i,
    314                          j);
    315         rl = add_label (reveal_label);
    316         unit[2] =
    317           TALER_TESTING_cmd_melt_with_retry (
    318             TALER_TESTING_cmd_melt (ml,
    319                                     wl,
    320                                     MHD_HTTP_OK,
    321                                     NULL));
    322         unit[3] =
    323           TALER_TESTING_cmd_melt_reveal_with_retry (
    324             TALER_TESTING_cmd_melt_reveal (rl,
    325                                            ml,
    326                                            MHD_HTTP_OK));
    327         unit[4] = TALER_TESTING_cmd_end ();
    328       }
    329       else
    330         unit[2] = TALER_TESTING_cmd_end ();
    331 
    332       GNUNET_asprintf (&unit_label,
    333                        "unit-%u-%u",
    334                        i,
    335                        j);
    336       all_commands[1 + (reserves_first
    337                         ? howmany_reserves + j * howmany_coins + i
    338                         : j * (howmany_coins + 1) + (1 + i))]
    339         = TALER_TESTING_cmd_batch (add_label (unit_label),
    340                                    unit);
    341     }
    342   }
    343   all_commands[1 + howmany_reserves * (1 + howmany_coins)]
    344     = TALER_TESTING_cmd_stat (timings);
    345   all_commands[1 + howmany_reserves * (1 + howmany_coins) + 1]
    346     = TALER_TESTING_cmd_end ();
    347   TALER_TESTING_run2 (is,
    348                       all_commands,
    349                       GNUNET_TIME_UNIT_FOREVER_REL); /* no timeout */
    350   GNUNET_free (amount_1);
    351   GNUNET_free (amount_4);
    352   GNUNET_free (amount_5);
    353   GNUNET_free (withdraw_fee_str);
    354 }
    355 
    356 
    357 /**
    358  * Print performance statistics for this process.
    359  */
    360 static void
    361 print_stats (void)
    362 {
    363   for (unsigned int i = 0; NULL != timings[i].prefix; i++)
    364   {
    365     char *total;
    366     char *latency;
    367 
    368     total = GNUNET_strdup (
    369       GNUNET_STRINGS_relative_time_to_string (timings[i].total_duration,
    370                                               true));
    371     latency = GNUNET_strdup (
    372       GNUNET_STRINGS_relative_time_to_string (timings[i].success_latency,
    373                                               true));
    374     fprintf (stderr,
    375              "%s-%d took %s in total with %s for latency for %u executions (%u repeats)\n",
    376              timings[i].prefix,
    377              (int) getpid (),
    378              total,
    379              latency,
    380              timings[i].num_commands,
    381              timings[i].num_retries);
    382     GNUNET_free (total);
    383     GNUNET_free (latency);
    384   }
    385 }
    386 
    387 
    388 /**
    389  * Run the benchmark in parallel in many (client) processes
    390  * and summarize result.
    391  *
    392  * @param main_cb main function to run per process
    393  * @param main_cb_cls closure for @a main_cb
    394  * @param config_file configuration file to use
    395  * @return #GNUNET_OK on success
    396  */
    397 static enum GNUNET_GenericReturnValue
    398 parallel_benchmark (TALER_TESTING_Main main_cb,
    399                     void *main_cb_cls,
    400                     const char *config_file)
    401 {
    402   enum GNUNET_GenericReturnValue result = GNUNET_OK;
    403   pid_t cpids[howmany_clients];
    404 
    405   if (1 == howmany_clients)
    406   {
    407     result = TALER_TESTING_loop (main_cb,
    408                                  main_cb_cls);
    409     print_stats ();
    410   }
    411   else
    412   {
    413     for (unsigned int i = 0; i<howmany_clients; i++)
    414     {
    415       if (0 == (cpids[i] = fork ()))
    416       {
    417         /* I am the child, do the work! */
    418         GNUNET_log_setup ("benchmark-worker",
    419                           loglev,
    420                           logfile);
    421         result = TALER_TESTING_loop (main_cb,
    422                                      main_cb_cls);
    423         print_stats ();
    424         if (GNUNET_OK != result)
    425           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    426                       "Failure in child process %u test suite!\n",
    427                       i);
    428         if (GNUNET_OK == result)
    429           exit (0);
    430         else
    431           exit (1);
    432       }
    433       if (-1 == cpids[i])
    434       {
    435         GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    436                              "fork");
    437         howmany_clients = i;
    438         result = GNUNET_SYSERR;
    439         break;
    440       }
    441       /* fork() success, continue starting more processes! */
    442     }
    443     /* collect all children */
    444     for (unsigned int i = 0; i<howmany_clients; i++)
    445     {
    446       int wstatus;
    447 
    448       waitpid (cpids[i],
    449                &wstatus,
    450                0);
    451       if ( (! WIFEXITED (wstatus)) ||
    452            (0 != WEXITSTATUS (wstatus)) )
    453       {
    454         GNUNET_break (0);
    455         result = GNUNET_SYSERR;
    456       }
    457     }
    458   }
    459   return result;
    460 }
    461 
    462 
    463 /**
    464  * The main function of the serve tool
    465  *
    466  * @param argc number of arguments from the command line
    467  * @param argv command line arguments
    468  * @return 0 ok, or `enum PaymentGeneratorError` on error
    469  */
    470 int
    471 main (int argc,
    472       char *const *argv)
    473 {
    474   struct GNUNET_GETOPT_CommandLineOption options[] = {
    475     GNUNET_GETOPT_option_mandatory (
    476       GNUNET_GETOPT_option_cfgfile (
    477         &cfg_filename)),
    478     GNUNET_GETOPT_option_version (
    479       PACKAGE_VERSION " " VCS_VERSION),
    480     GNUNET_GETOPT_option_flag (
    481       'f',
    482       "fakebank",
    483       "use fakebank for the banking system",
    484       &use_fakebank),
    485     GNUNET_GETOPT_option_flag (
    486       'F',
    487       "reserves-first",
    488       "should all reserves be created first, before starting normal operations",
    489       &reserves_first),
    490     GNUNET_GETOPT_option_help (
    491       TALER_EXCHANGE_project_data (),
    492       "Exchange benchmark"),
    493     GNUNET_GETOPT_option_string (
    494       'l',
    495       "logfile",
    496       "LF",
    497       "will log to file LF",
    498       &logfile),
    499     GNUNET_GETOPT_option_loglevel (
    500       &loglev),
    501     GNUNET_GETOPT_option_uint (
    502       'n',
    503       "coins-number",
    504       "CN",
    505       "How many coins we should instantiate per reserve",
    506       &howmany_coins),
    507     GNUNET_GETOPT_option_uint (
    508       'p',
    509       "parallelism",
    510       "NPROCS",
    511       "How many client processes we should run",
    512       &howmany_clients),
    513     GNUNET_GETOPT_option_uint (
    514       'r',
    515       "reserves",
    516       "NRESERVES",
    517       "How many reserves per client we should create",
    518       &howmany_reserves),
    519     GNUNET_GETOPT_option_uint (
    520       'R',
    521       "refresh-rate",
    522       "RATE",
    523       "Probability of refresh per coin (0-100)",
    524       &refresh_rate),
    525     GNUNET_GETOPT_option_string (
    526       'u',
    527       "exchange-account-section",
    528       "SECTION",
    529       "use exchange bank account configuration from the given SECTION",
    530       &exchange_bank_section),
    531     GNUNET_GETOPT_OPTION_END
    532   };
    533   enum GNUNET_GenericReturnValue result;
    534   struct GNUNET_TIME_Relative duration;
    535 
    536   unsetenv ("XDG_DATA_HOME");
    537   unsetenv ("XDG_CONFIG_HOME");
    538   if (0 >=
    539       (result = GNUNET_GETOPT_run ("taler-exchange-benchmark",
    540                                    options,
    541                                    argc,
    542                                    argv)))
    543   {
    544     GNUNET_free (cfg_filename);
    545     if (GNUNET_NO == result)
    546       return 0;
    547     return EXIT_INVALIDARGUMENT;
    548   }
    549   if (NULL == exchange_bank_section)
    550     exchange_bank_section = (char *) "exchange-account-1";
    551   if (NULL == loglev)
    552     loglev = (char *) "INFO";
    553   GNUNET_log_setup ("taler-exchange-benchmark",
    554                     loglev,
    555                     logfile);
    556   if (NULL == cfg_filename)
    557     cfg_filename = GNUNET_CONFIGURATION_default_filename (
    558       TALER_EXCHANGE_project_data ());
    559   if (NULL == cfg_filename)
    560   {
    561     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    562                 "Can't find default configuration file.\n");
    563     return EXIT_NOTCONFIGURED;
    564   }
    565   cfg = GNUNET_CONFIGURATION_create (TALER_EXCHANGE_project_data ());
    566   if (GNUNET_OK !=
    567       GNUNET_CONFIGURATION_load (cfg,
    568                                  cfg_filename))
    569   {
    570     TALER_LOG_ERROR ("Could not parse configuration\n");
    571     GNUNET_free (cfg_filename);
    572     return EXIT_NOTCONFIGURED;
    573   }
    574   if (GNUNET_OK !=
    575       TALER_config_get_currency (cfg,
    576                                  "exchange",
    577                                  &currency))
    578   {
    579     GNUNET_CONFIGURATION_destroy (cfg);
    580     GNUNET_free (cfg_filename);
    581     return EXIT_NOTCONFIGURED;
    582   }
    583   if (howmany_clients > 10240)
    584   {
    585     TALER_LOG_ERROR ("-p option value given is too large\n");
    586     return EXIT_INVALIDARGUMENT;
    587   }
    588   if (0 == howmany_clients)
    589   {
    590     TALER_LOG_ERROR ("-p option value must not be zero\n");
    591     GNUNET_free (cfg_filename);
    592     return EXIT_INVALIDARGUMENT;
    593   }
    594 
    595   if (GNUNET_OK !=
    596       TALER_TESTING_get_credentials (
    597         cfg_filename,
    598         exchange_bank_section,
    599         use_fakebank
    600         ? TALER_TESTING_BS_FAKEBANK
    601         : TALER_TESTING_BS_IBAN,
    602         &cred))
    603   {
    604     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    605                 "Required bank credentials not given in configuration\n");
    606     GNUNET_free (cfg_filename);
    607     return EXIT_NOTCONFIGURED;
    608   }
    609 
    610   {
    611     struct GNUNET_TIME_Absolute start_time;
    612 
    613     start_time = GNUNET_TIME_absolute_get ();
    614     result = parallel_benchmark (&run,
    615                                  NULL,
    616                                  cfg_filename);
    617     duration = GNUNET_TIME_absolute_get_duration (start_time);
    618   }
    619 
    620   if (GNUNET_OK == result)
    621   {
    622     struct rusage usage;
    623 
    624     GNUNET_assert (0 == getrusage (RUSAGE_CHILDREN,
    625                                    &usage));
    626     fprintf (stdout,
    627              "Executed (Withdraw=%u, Deposit=%u, Refresh~=%5.2f)"
    628              " * Reserve=%u * Parallel=%u, operations in %s\n",
    629              howmany_coins,
    630              howmany_coins,
    631              howmany_coins * (refresh_rate / 100.0d),
    632              howmany_reserves,
    633              howmany_clients,
    634              GNUNET_STRINGS_relative_time_to_string (
    635                duration,
    636                false));
    637     fprintf (stdout,
    638              "(approximately %s/coin)\n",
    639              GNUNET_STRINGS_relative_time_to_string (
    640                GNUNET_TIME_relative_divide (
    641                  duration,
    642                  (unsigned long long) howmany_coins
    643                  * howmany_reserves
    644                  * howmany_clients),
    645                true));
    646     fprintf (stdout,
    647              "RAW: %04u %04u %04u %16llu\n",
    648              howmany_coins,
    649              howmany_reserves,
    650              howmany_clients,
    651              (unsigned long long) duration.rel_value_us);
    652     fprintf (stdout,
    653              "cpu time: sys %llu user %llu\n",
    654              (unsigned long long) (usage.ru_stime.tv_sec * 1000 * 1000
    655                                    + usage.ru_stime.tv_usec),
    656              (unsigned long long) (usage.ru_utime.tv_sec * 1000 * 1000
    657                                    + usage.ru_utime.tv_usec));
    658   }
    659 
    660   for (unsigned int i = 0; i<label_off; i++)
    661     GNUNET_free (labels[i]);
    662   GNUNET_array_grow (labels,
    663                      label_len,
    664                      0);
    665   GNUNET_CONFIGURATION_destroy (cfg);
    666   GNUNET_free (cfg_filename);
    667   return (GNUNET_OK == result) ? 0 : result;
    668 }