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