1 /* 2 This file is part of libmicrohttpd 3 Copyright (C) 2007, 2009, 2011 Christian Grothoff 4 5 libmicrohttpd is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 2, or (at your 8 option) any later version. 9 10 libmicrohttpd 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 libmicrohttpd; see the file COPYING. If not, write to the 17 Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18 Boston, MA 02111-1307, USA. 19 */ 20 21 /** 22 * @file perf_get.c 23 * @brief benchmark simple GET operations (sequential access). 24 * Note that we run libcurl in the same process at the 25 * same time, so the execution time given is the combined 26 * time for both MHD and libcurl; it is quite possible 27 * that more time is spend with libcurl than with MHD, 28 * so the performance scores calculated with this code 29 * should NOT be used to compare with other HTTP servers 30 * (since MHD is actually better); only the relative 31 * scores between MHD versions are meaningful. 32 * Furthermore, this code ONLY tests MHD processing 33 * a single request at a time. This is again 34 * not universally meaningful (i.e. when comparing 35 * multithreaded vs. single-threaded or select/poll). 36 * @author Christian Grothoff 37 */ 38 39 #include "MHD_config.h" 40 #include "platform.h" 41 #include <curl/curl.h> 42 #include <microhttpd.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #include <time.h> 46 #include "gauger.h" 47 48 #ifndef WINDOWS 49 #include <unistd.h> 50 #include <sys/socket.h> 51 #endif 52 53 #if defined(CPU_COUNT) && (CPU_COUNT+0) < 2 54 #undef CPU_COUNT 55 #endif 56 #if !defined(CPU_COUNT) 57 #define CPU_COUNT 2 58 #endif 59 60 /** 61 * How many rounds of operations do we do for each 62 * test? 63 */ 64 #define ROUNDS 500 65 66 /** 67 * Do we use HTTP 1.1? 68 */ 69 static int oneone; 70 71 /** 72 * Response to return (re-used). 73 */ 74 static struct MHD_Response *response; 75 76 /** 77 * Time this round was started. 78 */ 79 static unsigned long long start_time; 80 81 82 /** 83 * Get the current timestamp 84 * 85 * @return current time in ms 86 */ 87 static unsigned long long 88 now () 89 { 90 struct timeval tv; 91 92 gettimeofday (&tv, NULL); 93 return (((unsigned long long) tv.tv_sec * 1000LL) + 94 ((unsigned long long) tv.tv_usec / 1000LL)); 95 } 96 97 98 /** 99 * Start the timer. 100 */ 101 static void 102 start_timer() 103 { 104 start_time = now (); 105 } 106 107 108 /** 109 * Stop the timer and report performance 110 * 111 * @param desc description of the threading mode we used 112 */ 113 static void 114 stop (const char *desc) 115 { 116 double rps = ((double) (ROUNDS * 1000)) / ((double) (now() - start_time)); 117 118 fprintf (stderr, 119 "Sequential GETs using %s: %f %s\n", 120 desc, 121 rps, 122 "requests/s"); 123 GAUGER (desc, 124 "Sequential GETs", 125 rps, 126 "requests/s"); 127 } 128 129 130 struct CBC 131 { 132 char *buf; 133 size_t pos; 134 size_t size; 135 }; 136 137 138 static size_t 139 copyBuffer (void *ptr, 140 size_t size, size_t nmemb, 141 void *ctx) 142 { 143 struct CBC *cbc = ctx; 144 145 if (cbc->pos + size * nmemb > cbc->size) 146 return 0; /* overflow */ 147 memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb); 148 cbc->pos += size * nmemb; 149 return size * nmemb; 150 } 151 152 static int 153 ahc_echo (void *cls, 154 struct MHD_Connection *connection, 155 const char *url, 156 const char *method, 157 const char *version, 158 const char *upload_data, size_t *upload_data_size, 159 void **unused) 160 { 161 static int ptr; 162 const char *me = cls; 163 int ret; 164 165 if (0 != strcmp (me, method)) 166 return MHD_NO; /* unexpected method */ 167 if (&ptr != *unused) 168 { 169 *unused = &ptr; 170 return MHD_YES; 171 } 172 *unused = NULL; 173 ret = MHD_queue_response (connection, MHD_HTTP_OK, response); 174 if (ret == MHD_NO) 175 abort (); 176 return ret; 177 } 178 179 180 static int 181 testInternalGet (int port, int poll_flag) 182 { 183 struct MHD_Daemon *d; 184 CURL *c; 185 char buf[2048]; 186 struct CBC cbc; 187 CURLcode errornum; 188 unsigned int i; 189 char url[64]; 190 191 sprintf(url, "http://127.0.0.1:%d/hello_world", port); 192 193 cbc.buf = buf; 194 cbc.size = 2048; 195 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag, 196 port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); 197 if (d == NULL) 198 return 1; 199 start_timer (); 200 for (i=0;i<ROUNDS;i++) 201 { 202 cbc.pos = 0; 203 c = curl_easy_init (); 204 curl_easy_setopt (c, CURLOPT_URL, url); 205 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 206 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 207 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 208 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 209 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 210 if (oneone) 211 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 212 else 213 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 214 /* NOTE: use of CONNECTTIMEOUT without also 215 setting NOSIGNAL results in really weird 216 crashes on my system!*/ 217 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 218 if (CURLE_OK != (errornum = curl_easy_perform (c))) 219 { 220 fprintf (stderr, 221 "curl_easy_perform failed: `%s'\n", 222 curl_easy_strerror (errornum)); 223 curl_easy_cleanup (c); 224 MHD_stop_daemon (d); 225 return 2; 226 } 227 curl_easy_cleanup (c); 228 } 229 stop (poll_flag == MHD_USE_POLL ? "internal poll" : 230 poll_flag == MHD_USE_EPOLL_LINUX_ONLY ? "internal epoll" : "internal select"); 231 MHD_stop_daemon (d); 232 if (cbc.pos != strlen ("/hello_world")) 233 return 4; 234 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 235 return 8; 236 return 0; 237 } 238 239 240 static int 241 testMultithreadedGet (int port, int poll_flag) 242 { 243 struct MHD_Daemon *d; 244 CURL *c; 245 char buf[2048]; 246 struct CBC cbc; 247 CURLcode errornum; 248 unsigned int i; 249 char url[64]; 250 251 sprintf(url, "http://127.0.0.1:%d/hello_world", port); 252 253 cbc.buf = buf; 254 cbc.size = 2048; 255 d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG | poll_flag, 256 port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); 257 if (d == NULL) 258 return 16; 259 start_timer (); 260 for (i=0;i<ROUNDS;i++) 261 { 262 cbc.pos = 0; 263 c = curl_easy_init (); 264 curl_easy_setopt (c, CURLOPT_URL, url); 265 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 266 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 267 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 268 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 269 if (oneone) 270 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 271 else 272 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 273 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 274 /* NOTE: use of CONNECTTIMEOUT without also 275 setting NOSIGNAL results in really weird 276 crashes on my system! */ 277 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 278 if (CURLE_OK != (errornum = curl_easy_perform (c))) 279 { 280 fprintf (stderr, 281 "curl_easy_perform failed: `%s'\n", 282 curl_easy_strerror (errornum)); 283 curl_easy_cleanup (c); 284 MHD_stop_daemon (d); 285 return 32; 286 } 287 curl_easy_cleanup (c); 288 } 289 stop ((poll_flag & MHD_USE_POLL) ? "thread with poll" : 290 (poll_flag & MHD_USE_EPOLL_LINUX_ONLY) ? "thread with epoll" : "thread with select"); 291 MHD_stop_daemon (d); 292 if (cbc.pos != strlen ("/hello_world")) 293 return 64; 294 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 295 return 128; 296 return 0; 297 } 298 299 static int 300 testMultithreadedPoolGet (int port, int poll_flag) 301 { 302 struct MHD_Daemon *d; 303 CURL *c; 304 char buf[2048]; 305 struct CBC cbc; 306 CURLcode errornum; 307 unsigned int i; 308 char url[64]; 309 310 sprintf(url, "http://127.0.0.1:%d/hello_world", port); 311 312 cbc.buf = buf; 313 cbc.size = 2048; 314 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag, 315 port, NULL, NULL, &ahc_echo, "GET", 316 MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END); 317 if (d == NULL) 318 return 16; 319 start_timer (); 320 for (i=0;i<ROUNDS;i++) 321 { 322 cbc.pos = 0; 323 c = curl_easy_init (); 324 curl_easy_setopt (c, CURLOPT_URL, url); 325 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 326 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 327 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 328 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 329 if (oneone) 330 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 331 else 332 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 333 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 334 /* NOTE: use of CONNECTTIMEOUT without also 335 setting NOSIGNAL results in really weird 336 crashes on my system!*/ 337 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 338 if (CURLE_OK != (errornum = curl_easy_perform (c))) 339 { 340 fprintf (stderr, 341 "curl_easy_perform failed: `%s'\n", 342 curl_easy_strerror (errornum)); 343 curl_easy_cleanup (c); 344 MHD_stop_daemon (d); 345 return 32; 346 } 347 curl_easy_cleanup (c); 348 } 349 stop (0 != (poll_flag & MHD_USE_POLL) ? "thread pool with poll" : 350 0 != (poll_flag & MHD_USE_EPOLL_LINUX_ONLY) ? "thread pool with epoll" : "thread pool with select"); 351 MHD_stop_daemon (d); 352 if (cbc.pos != strlen ("/hello_world")) 353 return 64; 354 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 355 return 128; 356 return 0; 357 } 358 359 static int 360 testExternalGet (int port) 361 { 362 struct MHD_Daemon *d; 363 CURL *c; 364 char buf[2048]; 365 struct CBC cbc; 366 CURLM *multi; 367 CURLMcode mret; 368 fd_set rs; 369 fd_set ws; 370 fd_set es; 371 MHD_socket max; 372 int running; 373 struct CURLMsg *msg; 374 time_t start; 375 struct timeval tv; 376 unsigned int i; 377 char url[64]; 378 379 sprintf(url, "http://127.0.0.1:%d/hello_world", port); 380 381 multi = NULL; 382 cbc.buf = buf; 383 cbc.size = 2048; 384 d = MHD_start_daemon (MHD_USE_DEBUG, 385 port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); 386 if (d == NULL) 387 return 256; 388 start_timer (); 389 multi = curl_multi_init (); 390 if (multi == NULL) 391 { 392 MHD_stop_daemon (d); 393 return 512; 394 } 395 for (i=0;i<ROUNDS;i++) 396 { 397 cbc.pos = 0; 398 c = curl_easy_init (); 399 curl_easy_setopt (c, CURLOPT_URL, url); 400 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 401 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 402 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 403 if (oneone) 404 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 405 else 406 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 407 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 408 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 409 /* NOTE: use of CONNECTTIMEOUT without also 410 setting NOSIGNAL results in really weird 411 crashes on my system! */ 412 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 413 mret = curl_multi_add_handle (multi, c); 414 if (mret != CURLM_OK) 415 { 416 curl_multi_cleanup (multi); 417 curl_easy_cleanup (c); 418 MHD_stop_daemon (d); 419 return 1024; 420 } 421 start = time (NULL); 422 while ((time (NULL) - start < 5) && (c != NULL)) 423 { 424 max = 0; 425 FD_ZERO (&rs); 426 FD_ZERO (&ws); 427 FD_ZERO (&es); 428 curl_multi_perform (multi, &running); 429 mret = curl_multi_fdset (multi, &rs, &ws, &es, &max); 430 if (mret != CURLM_OK) 431 { 432 curl_multi_remove_handle (multi, c); 433 curl_multi_cleanup (multi); 434 curl_easy_cleanup (c); 435 MHD_stop_daemon (d); 436 return 2048; 437 } 438 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max)) 439 { 440 curl_multi_remove_handle (multi, c); 441 curl_multi_cleanup (multi); 442 curl_easy_cleanup (c); 443 MHD_stop_daemon (d); 444 return 4096; 445 } 446 tv.tv_sec = 0; 447 tv.tv_usec = 1000; 448 select (max + 1, &rs, &ws, &es, &tv); 449 curl_multi_perform (multi, &running); 450 if (running == 0) 451 { 452 msg = curl_multi_info_read (multi, &running); 453 if (msg == NULL) 454 break; 455 if (msg->msg == CURLMSG_DONE) 456 { 457 if (msg->data.result != CURLE_OK) 458 printf ("%s failed at %s:%d: `%s'\n", 459 "curl_multi_perform", 460 __FILE__, 461 __LINE__, curl_easy_strerror (msg->data.result)); 462 curl_multi_remove_handle (multi, c); 463 curl_easy_cleanup (c); 464 c = NULL; 465 } 466 } 467 /* two possibilities here; as select sets are 468 tiny, this makes virtually no difference 469 in actual runtime right now, even though the 470 number of select calls is virtually cut in half 471 (and 'select' is the most expensive of our system 472 calls according to 'strace') */ 473 if (0) 474 MHD_run (d); 475 else 476 MHD_run_from_select (d, &rs, &ws, &es); 477 } 478 if (NULL != c) 479 { 480 curl_multi_remove_handle (multi, c); 481 curl_easy_cleanup (c); 482 fprintf (stderr, "Timeout!?\n"); 483 } 484 } 485 stop ("external select"); 486 if (multi != NULL) 487 { 488 curl_multi_cleanup (multi); 489 } 490 MHD_stop_daemon (d); 491 if (cbc.pos != strlen ("/hello_world")) 492 return 8192; 493 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 494 return 16384; 495 return 0; 496 } 497 498 499 int 500 main (int argc, char *const *argv) 501 { 502 unsigned int errorCount = 0; 503 int port = 1081; 504 505 oneone = (NULL != strrchr (argv[0], (int) '/')) ? 506 (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0; 507 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 508 return 2; 509 response = MHD_create_response_from_buffer (strlen ("/hello_world"), 510 "/hello_world", 511 MHD_RESPMEM_MUST_COPY); 512 errorCount += testExternalGet (port++); 513 errorCount += testInternalGet (port++, 0); 514 errorCount += testMultithreadedGet (port++, 0); 515 errorCount += testMultithreadedPoolGet (port++, 0); 516 if (MHD_YES == MHD_is_feature_supported(MHD_FEATURE_POLL)) 517 { 518 errorCount += testInternalGet(port++, MHD_USE_POLL); 519 errorCount += testMultithreadedGet(port++, MHD_USE_POLL); 520 errorCount += testMultithreadedPoolGet(port++, MHD_USE_POLL); 521 } 522 if (MHD_YES == MHD_is_feature_supported(MHD_FEATURE_EPOLL)) 523 { 524 errorCount += testInternalGet(port++, MHD_USE_EPOLL_LINUX_ONLY); 525 errorCount += testMultithreadedPoolGet(port++, MHD_USE_EPOLL_LINUX_ONLY); 526 } 527 MHD_destroy_response (response); 528 if (errorCount != 0) 529 fprintf (stderr, "Error (code: %u)\n", errorCount); 530 curl_global_cleanup (); 531 return errorCount != 0; /* 0 == pass */ 532 } 533