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