perf_get_concurrent.c (15232B)
1 /* 2 This file is part of libmicrohttpd 3 Copyright (C) 2007, 2009, 2011 Christian Grothoff 4 Copyright (C) 2014-2022 Evgeny Grin (Karlson2k) 5 6 libmicrohttpd is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published 8 by the Free Software Foundation; either version 2, or (at your 9 option) any later version. 10 11 libmicrohttpd is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with libmicrohttpd; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 Boston, MA 02110-1301, USA. 20 */ 21 22 /** 23 * @file perf_get_concurrent.c 24 * @brief benchmark concurrent GET operations 25 * Note that we run libcurl on the machine at the 26 * same time, so the execution time may be influenced 27 * by the concurrent activity; it is quite possible 28 * that more time is spend with libcurl than with MHD, 29 * so the performance scores calculated with this code 30 * should NOT be used to compare with other HTTP servers 31 * (since MHD is actually better); only the relative 32 * scores between MHD versions are meaningful. 33 * @author Christian Grothoff 34 * @author Karlson2k (Evgeny Grin) 35 */ 36 37 #include "MHD_config.h" 38 #include "platform.h" 39 #include <curl/curl.h> 40 #include <microhttpd.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <time.h> 44 #include <pthread.h> 45 #include <errno.h> 46 #include "mhd_has_in_name.h" 47 48 /* Turn any MHD_PANIC() or failing mhd_assert() reached from this 49 test into a marked, classifiable test error (TESTING.md, P5). */ 50 #include "mhd_panic_tripwire.h" 51 52 #if defined(MHD_CPU_COUNT) && (MHD_CPU_COUNT + 0) < 2 53 #undef MHD_CPU_COUNT 54 #endif 55 #if ! defined(MHD_CPU_COUNT) 56 #define MHD_CPU_COUNT 2 57 #endif 58 59 /** 60 * How many rounds of operations do we do for each 61 * test (total number of requests will be ROUNDS * PAR). 62 * Ensure that free ports are not exhausted during test. 63 */ 64 #if MHD_CPU_COUNT > 8 65 #ifndef _WIN32 66 #define ROUNDS (1 + (30000 / 12) / MHD_CPU_COUNT) 67 #else /* _WIN32 */ 68 #define ROUNDS (1 + (3000 / 12) / MHD_CPU_COUNT) 69 #endif /* _WIN32 */ 70 #else 71 #define ROUNDS 500 72 #endif 73 74 /** 75 * How many requests do we do in parallel? 76 */ 77 #define PAR MHD_CPU_COUNT 78 79 /** 80 * Do we use HTTP 1.1? 81 */ 82 static int oneone; 83 84 /** 85 * Response to return (re-used). 86 */ 87 static struct MHD_Response *response; 88 89 /** 90 * Time this round was started. 91 */ 92 static unsigned long long start_time; 93 94 /** 95 * Set to 1 if the worker threads are done. 96 */ 97 static volatile int signal_done; 98 99 100 /** 101 * Get the current timestamp 102 * 103 * @return current time in ms 104 */ 105 static unsigned long long 106 now (void) 107 { 108 struct timeval tv; 109 110 gettimeofday (&tv, NULL); 111 return (((unsigned long long) tv.tv_sec * 1000LL) 112 + ((unsigned long long) tv.tv_usec / 1000LL)); 113 } 114 115 116 /** 117 * Start the timer. 118 */ 119 static void 120 start_timer (void) 121 { 122 start_time = now (); 123 } 124 125 126 /** 127 * Stop the timer and report performance 128 * 129 * @param desc description of the threading mode we used 130 */ 131 static void 132 stop (const char *desc) 133 { 134 double rps = ((double) (PAR * ROUNDS * 1000)) / ((double) (now () 135 - start_time)); 136 137 fprintf (stderr, 138 "Parallel GETs using %s: %f %s\n", 139 desc, 140 rps, 141 "requests/s"); 142 } 143 144 145 static size_t 146 copyBuffer (void *ptr, 147 size_t size, size_t nmemb, 148 void *ctx) 149 { 150 (void) ptr; (void) ctx; /* Unused. Silent compiler warning. */ 151 return size * nmemb; 152 } 153 154 155 static enum MHD_Result 156 ahc_echo (void *cls, 157 struct MHD_Connection *connection, 158 const char *url, 159 const char *method, 160 const char *version, 161 const char *upload_data, size_t *upload_data_size, 162 void **req_cls) 163 { 164 static int ptr; 165 enum MHD_Result ret; 166 (void) cls; 167 (void) url; (void) version; /* Unused. Silent compiler warning. */ 168 (void) upload_data; (void) upload_data_size; /* Unused. Silent compiler warning. */ 169 170 if (0 != strcmp (MHD_HTTP_METHOD_GET, method)) 171 return MHD_NO; /* unexpected method */ 172 if (&ptr != *req_cls) 173 { 174 *req_cls = &ptr; 175 return MHD_YES; 176 } 177 *req_cls = NULL; 178 ret = MHD_queue_response (connection, MHD_HTTP_OK, response); 179 if (ret == MHD_NO) 180 abort (); 181 return ret; 182 } 183 184 185 static void * 186 thread_gets (void *param) 187 { 188 CURL *c; 189 CURLcode errornum; 190 unsigned int i; 191 char *const url = (char *) param; 192 static char curl_err_marker[] = "curl error"; 193 194 c = curl_easy_init (); 195 curl_easy_setopt (c, CURLOPT_URL, url); 196 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 197 curl_easy_setopt (c, CURLOPT_WRITEDATA, NULL); 198 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); 199 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 200 if (oneone) 201 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 202 else 203 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 204 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 205 /* NOTE: use of CONNECTTIMEOUT without also 206 setting NOSIGNAL results in really weird 207 crashes on my system! */ 208 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); 209 for (i = 0; i < ROUNDS; i++) 210 { 211 if (CURLE_OK != (errornum = curl_easy_perform (c))) 212 { 213 fprintf (stderr, 214 "curl_easy_perform failed: `%s'\n", 215 curl_easy_strerror (errornum)); 216 curl_easy_cleanup (c); 217 return curl_err_marker; 218 } 219 } 220 curl_easy_cleanup (c); 221 222 return NULL; 223 } 224 225 226 static void * 227 do_gets (void *param) 228 { 229 int j; 230 pthread_t par[PAR]; 231 char url[64]; 232 uint16_t port = (uint16_t) (intptr_t) param; 233 char *err = NULL; 234 static char pthr_err_marker[] = "pthread_create error"; 235 236 snprintf (url, 237 sizeof (url), 238 "http://127.0.0.1:%u/hello_world", 239 (unsigned int) port); 240 for (j = 0; j < PAR; j++) 241 { 242 if (0 != pthread_create (&par[j], NULL, &thread_gets, (void *) url)) 243 { 244 for (j--; j >= 0; j--) 245 pthread_join (par[j], NULL); 246 return pthr_err_marker; 247 } 248 } 249 for (j = 0; j < PAR; j++) 250 { 251 char *ret_val; 252 if ((0 != pthread_join (par[j], (void **) &ret_val)) || 253 (NULL != ret_val) ) 254 err = ret_val; 255 } 256 signal_done = 1; 257 return err; 258 } 259 260 261 static unsigned int 262 testInternalGet (uint16_t port, uint32_t poll_flag) 263 { 264 struct MHD_Daemon *d; 265 const char *const test_desc = ((poll_flag & MHD_USE_AUTO) ? 266 "internal thread with 'auto'" : 267 (poll_flag & MHD_USE_POLL) ? 268 "internal thread with poll()" : 269 (poll_flag & MHD_USE_EPOLL) ? 270 "internal thread with epoll" : 271 "internal thread with select()"); 272 const char *ret_val; 273 274 if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) 275 port = 0; 276 277 signal_done = 0; 278 d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG 279 | (enum MHD_FLAG) poll_flag, 280 port, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END); 281 if (d == NULL) 282 return 1; 283 if (0 == port) 284 { 285 const union MHD_DaemonInfo *dinfo; 286 dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); 287 if ((NULL == dinfo) || (0 == dinfo->port) ) 288 { 289 MHD_stop_daemon (d); return 32; 290 } 291 port = dinfo->port; 292 } 293 start_timer (); 294 ret_val = do_gets ((void *) (intptr_t) port); 295 if (! ret_val) 296 stop (test_desc); 297 MHD_stop_daemon (d); 298 if (ret_val) 299 { 300 fprintf (stderr, 301 "Error performing %s test: %s\n", test_desc, ret_val); 302 return 4; 303 } 304 return 0; 305 } 306 307 308 static unsigned int 309 testMultithreadedGet (uint16_t port, uint32_t poll_flag) 310 { 311 struct MHD_Daemon *d; 312 const char *const test_desc = ((poll_flag & MHD_USE_AUTO) ? 313 "internal thread with 'auto' and thread per connection" 314 : 315 (poll_flag & MHD_USE_POLL) ? 316 "internal thread with poll() and thread per connection" 317 : 318 (poll_flag & MHD_USE_EPOLL) ? 319 "internal thread with epoll and thread per connection" 320 : 321 "internal thread with select() and thread per connection"); 322 const char *ret_val; 323 324 if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) 325 port = 0; 326 327 signal_done = 0; 328 d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION 329 | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG 330 | (enum MHD_FLAG) poll_flag, 331 port, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END); 332 if (d == NULL) 333 return 16; 334 if (0 == port) 335 { 336 const union MHD_DaemonInfo *dinfo; 337 dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); 338 if ((NULL == dinfo) || (0 == dinfo->port) ) 339 { 340 MHD_stop_daemon (d); return 32; 341 } 342 port = dinfo->port; 343 } 344 start_timer (); 345 ret_val = do_gets ((void *) (intptr_t) port); 346 if (! ret_val) 347 stop (test_desc); 348 MHD_stop_daemon (d); 349 if (ret_val) 350 { 351 fprintf (stderr, 352 "Error performing %s test: %s\n", test_desc, ret_val); 353 return 4; 354 } 355 return 0; 356 } 357 358 359 static unsigned int 360 testMultithreadedPoolGet (uint16_t port, uint32_t poll_flag) 361 { 362 struct MHD_Daemon *d; 363 const char *const test_desc = ((poll_flag & MHD_USE_AUTO) ? 364 "internal thread pool with 'auto'" : 365 (poll_flag & MHD_USE_POLL) ? 366 "internal thread pool with poll()" : 367 (poll_flag & MHD_USE_EPOLL) ? 368 "internal thread poll with epoll" : 369 "internal thread pool with select()"); 370 const char *ret_val; 371 372 if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) 373 port = 0; 374 375 signal_done = 0; 376 d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG 377 | (enum MHD_FLAG) poll_flag, 378 port, NULL, NULL, &ahc_echo, NULL, 379 MHD_OPTION_THREAD_POOL_SIZE, MHD_CPU_COUNT, 380 MHD_OPTION_END); 381 if (d == NULL) 382 return 16; 383 if (0 == port) 384 { 385 const union MHD_DaemonInfo *dinfo; 386 dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); 387 if ((NULL == dinfo) || (0 == dinfo->port) ) 388 { 389 MHD_stop_daemon (d); return 32; 390 } 391 port = dinfo->port; 392 } 393 start_timer (); 394 ret_val = do_gets ((void *) (intptr_t) port); 395 if (! ret_val) 396 stop (test_desc); 397 MHD_stop_daemon (d); 398 if (ret_val) 399 { 400 fprintf (stderr, 401 "Error performing %s test: %s\n", test_desc, ret_val); 402 return 4; 403 } 404 return 0; 405 } 406 407 408 static unsigned int 409 testExternalGet (uint16_t port) 410 { 411 struct MHD_Daemon *d; 412 pthread_t tid; 413 fd_set rs; 414 fd_set ws; 415 fd_set es; 416 MHD_socket max; 417 struct timeval tv; 418 uint64_t tt64; 419 char *ret_val; 420 int ret = 0; 421 422 if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) 423 port = 0; 424 425 signal_done = 0; 426 d = MHD_start_daemon (MHD_USE_ERROR_LOG, 427 port, NULL, NULL, &ahc_echo, NULL, 428 MHD_OPTION_APP_FD_SETSIZE, (int) FD_SETSIZE, 429 MHD_OPTION_END); 430 if (d == NULL) 431 return 256; 432 if (0 == port) 433 { 434 const union MHD_DaemonInfo *dinfo; 435 dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); 436 if ((NULL == dinfo) || (0 == dinfo->port) ) 437 { 438 MHD_stop_daemon (d); return 32; 439 } 440 port = dinfo->port; 441 } 442 if (0 != pthread_create (&tid, NULL, 443 &do_gets, (void *) (intptr_t) port)) 444 { 445 MHD_stop_daemon (d); 446 return 512; 447 } 448 start_timer (); 449 450 while (0 == signal_done) 451 { 452 max = 0; 453 FD_ZERO (&rs); 454 FD_ZERO (&ws); 455 FD_ZERO (&es); 456 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max)) 457 { 458 MHD_stop_daemon (d); 459 return 4096; 460 } 461 if (MHD_NO == MHD_get_timeout64 (d, &tt64)) 462 tt64 = 1; 463 #if ! defined(_WIN32) || defined(__CYGWIN__) 464 tv.tv_sec = (time_t) (tt64 / 1000); 465 #else /* Native W32 */ 466 tv.tv_sec = (long) (tt64 / 1000); 467 #endif /* Native W32 */ 468 tv.tv_usec = ((long) (tt64 % 1000)) * 1000; 469 if (-1 == select (max + 1, &rs, &ws, &es, &tv)) 470 { 471 #ifdef MHD_POSIX_SOCKETS 472 if (EINTR != errno) 473 { 474 fprintf (stderr, "Unexpected select() error: %d. Line: %d\n", 475 (int) errno, __LINE__); 476 fflush (stderr); 477 exit (99); 478 } 479 ret |= 1024; 480 break; 481 #else 482 if ((WSAEINVAL != WSAGetLastError ()) || 483 (0 != rs.fd_count) || (0 != ws.fd_count) || (0 != es.fd_count) ) 484 { 485 fprintf (stderr, "Unexpected select() error: %d. Line: %d\n", 486 (int) WSAGetLastError (), __LINE__); 487 fflush (stderr); 488 exit (99); 489 } 490 Sleep (1); 491 #endif 492 } 493 MHD_run_from_select (d, &rs, &ws, &es); 494 } 495 496 stop ("external select"); 497 MHD_stop_daemon (d); 498 if ((0 != pthread_join (tid, (void **) &ret_val)) || 499 (NULL != ret_val) ) 500 { 501 fprintf (stderr, 502 "%s\n", ret_val); 503 ret |= 8; 504 } 505 if (ret) 506 fprintf (stderr, "Error performing test.\n"); 507 return 0; 508 } 509 510 511 int 512 main (int argc, char *const *argv) 513 { 514 unsigned int errorCount = 0; 515 uint16_t port = 1100; 516 (void) argc; /* Unused. Silent compiler warning. */ 517 518 if ((NULL == argv) || (0 == argv[0])) 519 return 99; 520 oneone = has_in_name (argv[0], "11"); 521 if (oneone) 522 port += 15; 523 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 524 return 2; 525 response = MHD_create_response_from_buffer_copy (strlen ("/hello_world"), 526 "/hello_world"); 527 errorCount += testInternalGet (port++, 0); 528 errorCount += testMultithreadedGet (port++, 0); 529 errorCount += testMultithreadedPoolGet (port++, 0); 530 errorCount += testExternalGet (port++); 531 errorCount += testInternalGet (port++, MHD_USE_AUTO); 532 errorCount += testMultithreadedGet (port++, MHD_USE_AUTO); 533 errorCount += testMultithreadedPoolGet (port++, MHD_USE_AUTO); 534 if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_POLL)) 535 { 536 errorCount += testInternalGet (port++, MHD_USE_POLL); 537 errorCount += testMultithreadedGet (port++, MHD_USE_POLL); 538 errorCount += testMultithreadedPoolGet (port++, MHD_USE_POLL); 539 } 540 if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_EPOLL)) 541 { 542 errorCount += testInternalGet (port++, MHD_USE_EPOLL); 543 errorCount += testMultithreadedPoolGet (port++, MHD_USE_EPOLL); 544 } 545 MHD_destroy_response (response); 546 if (errorCount != 0) 547 fprintf (stderr, "Error (code: %u)\n", errorCount); 548 curl_global_cleanup (); 549 return errorCount != 0; /* 0 == pass */ 550 }