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 ¤cy)) 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 }