taler-exchange-benchmark.c (19185B)
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 "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 (UINT64_MAX); 185 random_01 = (double) random / (double) UINT64_MAX; 186 return (random_01 <= probability) ? GNUNET_OK : GNUNET_NO; 187 } 188 189 190 /** 191 * Actual commands construction and execution. 192 * 193 * @param cls unused 194 * @param is interpreter to run commands with 195 */ 196 static void 197 run (void *cls, 198 struct TALER_TESTING_Interpreter *is) 199 { 200 struct TALER_Amount total_reserve_amount; 201 struct TALER_Amount withdraw_fee; 202 char *withdraw_fee_str; 203 char *amount_5; 204 char *amount_4; 205 char *amount_1; 206 207 (void) cls; 208 all_commands = GNUNET_malloc_large ( 209 (1 /* exchange CMD */ 210 + howmany_reserves 211 * (1 /* Withdraw block */ 212 + howmany_coins) /* All units */ 213 + 1 /* stat CMD */ 214 + 1 /* End CMD */) * sizeof (struct TALER_TESTING_Command)); 215 GNUNET_assert (NULL != all_commands); 216 all_commands[0] 217 = TALER_TESTING_cmd_get_exchange ("get-exchange", 218 cred.cfg, 219 NULL, 220 true, 221 true); 222 GNUNET_asprintf (&amount_5, "%s:5", currency); 223 GNUNET_asprintf (&amount_4, "%s:4", currency); 224 GNUNET_asprintf (&amount_1, "%s:1", currency); 225 GNUNET_assert (GNUNET_OK == 226 TALER_amount_set_zero (currency, 227 &total_reserve_amount)); 228 total_reserve_amount.value = 5 * howmany_coins; 229 GNUNET_asprintf (&withdraw_fee_str, 230 "%s:0.1", 231 currency); 232 GNUNET_assert (GNUNET_OK == 233 TALER_string_to_amount (withdraw_fee_str, 234 &withdraw_fee)); 235 for (unsigned int i = 0; i < howmany_coins; i++) 236 GNUNET_assert (0 <= 237 TALER_amount_add (&total_reserve_amount, 238 &total_reserve_amount, 239 &withdraw_fee)); 240 for (unsigned int j = 0; j < howmany_reserves; j++) 241 { 242 char *create_reserve_label; 243 244 GNUNET_asprintf (&create_reserve_label, 245 "createreserve-%u", 246 j); 247 { 248 struct TALER_TESTING_Command make_reserve[] = { 249 cmd_transfer_to_exchange (add_label (create_reserve_label), 250 TALER_amount2s (&total_reserve_amount)), 251 TALER_TESTING_cmd_end () 252 }; 253 char *batch_label; 254 255 GNUNET_asprintf (&batch_label, 256 "batch-start-%u", 257 j); 258 all_commands[1 + (reserves_first 259 ? j 260 : j * (howmany_coins + 1))] 261 = TALER_TESTING_cmd_batch (add_label (batch_label), 262 make_reserve); 263 } 264 for (unsigned int i = 0; i < howmany_coins; i++) 265 { 266 char *withdraw_label; 267 char *order_enc; 268 struct TALER_TESTING_Command unit[UNITY_SIZE]; 269 char *unit_label; 270 const char *wl; 271 272 GNUNET_asprintf (&withdraw_label, 273 "withdraw-%u-%u", 274 i, 275 j); 276 wl = add_label (withdraw_label); 277 GNUNET_asprintf (&order_enc, 278 "{\"nonce\": %llu}", 279 ((unsigned long long) i) 280 + (howmany_coins * (unsigned long long) j)); 281 unit[0] = 282 TALER_TESTING_cmd_withdraw_with_retry ( 283 TALER_TESTING_cmd_withdraw_amount (wl, 284 create_reserve_label, 285 amount_5, 286 0, /* age restriction off */ 287 MHD_HTTP_OK)); 288 unit[1] = 289 TALER_TESTING_cmd_deposit_with_retry ( 290 TALER_TESTING_cmd_deposit ("deposit", 291 wl, 292 0, /* Index of the one withdrawn coin in the traits. */ 293 cred.user43_payto, 294 add_label (order_enc), 295 GNUNET_TIME_UNIT_ZERO, 296 amount_1, 297 MHD_HTTP_OK)); 298 if (eval_probability (refresh_rate / 100.0d)) 299 { 300 char *melt_label; 301 char *reveal_label; 302 const char *ml; 303 const char *rl; 304 305 GNUNET_asprintf (&melt_label, 306 "melt-%u-%u", 307 i, 308 j); 309 ml = add_label (melt_label); 310 GNUNET_asprintf (&reveal_label, 311 "reveal-%u-%u", 312 i, 313 j); 314 rl = add_label (reveal_label); 315 unit[2] = 316 TALER_TESTING_cmd_melt_with_retry ( 317 TALER_TESTING_cmd_melt (ml, 318 wl, 319 MHD_HTTP_OK, 320 NULL)); 321 unit[3] = 322 TALER_TESTING_cmd_melt_reveal_with_retry ( 323 TALER_TESTING_cmd_melt_reveal (rl, 324 ml, 325 MHD_HTTP_OK)); 326 unit[4] = TALER_TESTING_cmd_end (); 327 } 328 else 329 unit[2] = TALER_TESTING_cmd_end (); 330 331 GNUNET_asprintf (&unit_label, 332 "unit-%u-%u", 333 i, 334 j); 335 all_commands[1 + (reserves_first 336 ? howmany_reserves + j * howmany_coins + i 337 : j * (howmany_coins + 1) + (1 + i))] 338 = TALER_TESTING_cmd_batch (add_label (unit_label), 339 unit); 340 } 341 } 342 all_commands[1 + howmany_reserves * (1 + howmany_coins)] 343 = TALER_TESTING_cmd_stat (timings); 344 all_commands[1 + howmany_reserves * (1 + howmany_coins) + 1] 345 = TALER_TESTING_cmd_end (); 346 TALER_TESTING_run2 (is, 347 all_commands, 348 GNUNET_TIME_UNIT_FOREVER_REL); /* no timeout */ 349 GNUNET_free (amount_1); 350 GNUNET_free (amount_4); 351 GNUNET_free (amount_5); 352 GNUNET_free (withdraw_fee_str); 353 } 354 355 356 /** 357 * Print performance statistics for this process. 358 */ 359 static void 360 print_stats (void) 361 { 362 for (unsigned int i = 0; NULL != timings[i].prefix; i++) 363 { 364 char *total; 365 char *latency; 366 367 total = GNUNET_strdup ( 368 GNUNET_STRINGS_relative_time_to_string (timings[i].total_duration, 369 true)); 370 latency = GNUNET_strdup ( 371 GNUNET_STRINGS_relative_time_to_string (timings[i].success_latency, 372 true)); 373 fprintf (stderr, 374 "%s-%d took %s in total with %s for latency for %u executions (%u repeats)\n", 375 timings[i].prefix, 376 (int) getpid (), 377 total, 378 latency, 379 timings[i].num_commands, 380 timings[i].num_retries); 381 GNUNET_free (total); 382 GNUNET_free (latency); 383 } 384 } 385 386 387 /** 388 * Run the benchmark in parallel in many (client) processes 389 * and summarize result. 390 * 391 * @param main_cb main function to run per process 392 * @param main_cb_cls closure for @a main_cb 393 * @param config_file configuration file to use 394 * @return #GNUNET_OK on success 395 */ 396 static enum GNUNET_GenericReturnValue 397 parallel_benchmark (TALER_TESTING_Main main_cb, 398 void *main_cb_cls, 399 const char *config_file) 400 { 401 enum GNUNET_GenericReturnValue result = GNUNET_OK; 402 pid_t cpids[howmany_clients]; 403 404 if (1 == howmany_clients) 405 { 406 result = TALER_TESTING_loop (main_cb, 407 main_cb_cls); 408 print_stats (); 409 } 410 else 411 { 412 for (unsigned int i = 0; i<howmany_clients; i++) 413 { 414 if (0 == (cpids[i] = fork ())) 415 { 416 /* I am the child, do the work! */ 417 GNUNET_log_setup ("benchmark-worker", 418 loglev, 419 logfile); 420 result = TALER_TESTING_loop (main_cb, 421 main_cb_cls); 422 print_stats (); 423 if (GNUNET_OK != result) 424 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 425 "Failure in child process %u test suite!\n", 426 i); 427 if (GNUNET_OK == result) 428 exit (0); 429 else 430 exit (1); 431 } 432 if (-1 == cpids[i]) 433 { 434 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 435 "fork"); 436 howmany_clients = i; 437 result = GNUNET_SYSERR; 438 break; 439 } 440 /* fork() success, continue starting more processes! */ 441 } 442 /* collect all children */ 443 for (unsigned int i = 0; i<howmany_clients; i++) 444 { 445 int wstatus; 446 447 waitpid (cpids[i], 448 &wstatus, 449 0); 450 if ( (! WIFEXITED (wstatus)) || 451 (0 != WEXITSTATUS (wstatus)) ) 452 { 453 GNUNET_break (0); 454 result = GNUNET_SYSERR; 455 } 456 } 457 } 458 return result; 459 } 460 461 462 /** 463 * The main function of the serve tool 464 * 465 * @param argc number of arguments from the command line 466 * @param argv command line arguments 467 * @return 0 ok, or `enum PaymentGeneratorError` on error 468 */ 469 int 470 main (int argc, 471 char *const *argv) 472 { 473 struct GNUNET_GETOPT_CommandLineOption options[] = { 474 GNUNET_GETOPT_option_mandatory ( 475 GNUNET_GETOPT_option_cfgfile ( 476 &cfg_filename)), 477 GNUNET_GETOPT_option_version ( 478 PACKAGE_VERSION), 479 GNUNET_GETOPT_option_flag ( 480 'f', 481 "fakebank", 482 "use fakebank for the banking system", 483 &use_fakebank), 484 GNUNET_GETOPT_option_flag ( 485 'F', 486 "reserves-first", 487 "should all reserves be created first, before starting normal operations", 488 &reserves_first), 489 GNUNET_GETOPT_option_help ( 490 TALER_EXCHANGE_project_data (), 491 "Exchange benchmark"), 492 GNUNET_GETOPT_option_string ( 493 'l', 494 "logfile", 495 "LF", 496 "will log to file LF", 497 &logfile), 498 GNUNET_GETOPT_option_loglevel ( 499 &loglev), 500 GNUNET_GETOPT_option_uint ( 501 'n', 502 "coins-number", 503 "CN", 504 "How many coins we should instantiate per reserve", 505 &howmany_coins), 506 GNUNET_GETOPT_option_uint ( 507 'p', 508 "parallelism", 509 "NPROCS", 510 "How many client processes we should run", 511 &howmany_clients), 512 GNUNET_GETOPT_option_uint ( 513 'r', 514 "reserves", 515 "NRESERVES", 516 "How many reserves per client we should create", 517 &howmany_reserves), 518 GNUNET_GETOPT_option_uint ( 519 'R', 520 "refresh-rate", 521 "RATE", 522 "Probability of refresh per coin (0-100)", 523 &refresh_rate), 524 GNUNET_GETOPT_option_string ( 525 'u', 526 "exchange-account-section", 527 "SECTION", 528 "use exchange bank account configuration from the given SECTION", 529 &exchange_bank_section), 530 GNUNET_GETOPT_OPTION_END 531 }; 532 enum GNUNET_GenericReturnValue result; 533 struct GNUNET_TIME_Relative duration; 534 535 unsetenv ("XDG_DATA_HOME"); 536 unsetenv ("XDG_CONFIG_HOME"); 537 if (0 >= 538 (result = GNUNET_GETOPT_run ("taler-exchange-benchmark", 539 options, 540 argc, 541 argv))) 542 { 543 GNUNET_free (cfg_filename); 544 if (GNUNET_NO == result) 545 return 0; 546 return EXIT_INVALIDARGUMENT; 547 } 548 if (NULL == exchange_bank_section) 549 exchange_bank_section = (char *) "exchange-account-1"; 550 if (NULL == loglev) 551 loglev = (char *) "INFO"; 552 GNUNET_log_setup ("taler-exchange-benchmark", 553 loglev, 554 logfile); 555 if (NULL == cfg_filename) 556 cfg_filename = GNUNET_CONFIGURATION_default_filename ( 557 TALER_EXCHANGE_project_data ()); 558 if (NULL == cfg_filename) 559 { 560 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 561 "Can't find default configuration file.\n"); 562 return EXIT_NOTCONFIGURED; 563 } 564 cfg = GNUNET_CONFIGURATION_create (TALER_EXCHANGE_project_data ()); 565 if (GNUNET_OK != 566 GNUNET_CONFIGURATION_load (cfg, 567 cfg_filename)) 568 { 569 TALER_LOG_ERROR ("Could not parse configuration\n"); 570 GNUNET_free (cfg_filename); 571 return EXIT_NOTCONFIGURED; 572 } 573 if (GNUNET_OK != 574 TALER_config_get_currency (cfg, 575 "exchange", 576 ¤cy)) 577 { 578 GNUNET_CONFIGURATION_destroy (cfg); 579 GNUNET_free (cfg_filename); 580 return EXIT_NOTCONFIGURED; 581 } 582 if (howmany_clients > 10240) 583 { 584 TALER_LOG_ERROR ("-p option value given is too large\n"); 585 return EXIT_INVALIDARGUMENT; 586 } 587 if (0 == howmany_clients) 588 { 589 TALER_LOG_ERROR ("-p option value must not be zero\n"); 590 GNUNET_free (cfg_filename); 591 return EXIT_INVALIDARGUMENT; 592 } 593 594 if (GNUNET_OK != 595 TALER_TESTING_get_credentials ( 596 cfg_filename, 597 exchange_bank_section, 598 use_fakebank 599 ? TALER_TESTING_BS_FAKEBANK 600 : TALER_TESTING_BS_IBAN, 601 &cred)) 602 { 603 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 604 "Required bank credentials not given in configuration\n"); 605 GNUNET_free (cfg_filename); 606 return EXIT_NOTCONFIGURED; 607 } 608 609 { 610 struct GNUNET_TIME_Absolute start_time; 611 612 start_time = GNUNET_TIME_absolute_get (); 613 result = parallel_benchmark (&run, 614 NULL, 615 cfg_filename); 616 duration = GNUNET_TIME_absolute_get_duration (start_time); 617 } 618 619 if (GNUNET_OK == result) 620 { 621 struct rusage usage; 622 623 GNUNET_assert (0 == getrusage (RUSAGE_CHILDREN, 624 &usage)); 625 fprintf (stdout, 626 "Executed (Withdraw=%u, Deposit=%u, Refresh~=%5.2f)" 627 " * Reserve=%u * Parallel=%u, operations in %s\n", 628 howmany_coins, 629 howmany_coins, 630 howmany_coins * (refresh_rate / 100.0d), 631 howmany_reserves, 632 howmany_clients, 633 GNUNET_STRINGS_relative_time_to_string ( 634 duration, 635 false)); 636 fprintf (stdout, 637 "(approximately %s/coin)\n", 638 GNUNET_STRINGS_relative_time_to_string ( 639 GNUNET_TIME_relative_divide ( 640 duration, 641 (unsigned long long) howmany_coins 642 * howmany_reserves 643 * howmany_clients), 644 true)); 645 fprintf (stdout, 646 "RAW: %04u %04u %04u %16llu\n", 647 howmany_coins, 648 howmany_reserves, 649 howmany_clients, 650 (unsigned long long) duration.rel_value_us); 651 fprintf (stdout, 652 "cpu time: sys %llu user %llu\n", 653 (unsigned long long) (usage.ru_stime.tv_sec * 1000 * 1000 654 + usage.ru_stime.tv_usec), 655 (unsigned long long) (usage.ru_utime.tv_sec * 1000 * 1000 656 + usage.ru_utime.tv_usec)); 657 } 658 659 for (unsigned int i = 0; i<label_off; i++) 660 GNUNET_free (labels[i]); 661 GNUNET_array_grow (labels, 662 label_len, 663 0); 664 GNUNET_CONFIGURATION_destroy (cfg); 665 GNUNET_free (cfg_filename); 666 return (GNUNET_OK == result) ? 0 : result; 667 }