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