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_concurrent.c 23 * @brief benchmark concurrent GET operations 24 * Note that we run libcurl on the machine at the 25 * same time, so the execution time may be influenced 26 * by the concurrent activity; 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 * @author Christian Grothoff 33 */ 34 35 #include "MHD_config.h" 36 #include "platform.h" 37 #include <curl/curl.h> 38 #include <microhttpd.h> 39 #include <stdlib.h> 40 #include <string.h> 41 #include <time.h> 42 #include "gauger.h" 43 44 #if defined(CPU_COUNT) && (CPU_COUNT+0) < 2 45 #undef CPU_COUNT 46 #endif 47 #if !defined(CPU_COUNT) 48 #define CPU_COUNT 2 49 #endif 50 51 /** 52 * How many rounds of operations do we do for each 53 * test (total number of requests will be ROUNDS * PAR). 54 */ 55 #define ROUNDS 500 56 57 /** 58 * How many requests do we do in parallel? 59 */ 60 #define PAR CPU_COUNT 61 62 /** 63 * Do we use HTTP 1.1? 64 */ 65 static int oneone; 66 67 /** 68 * Response to return (re-used). 69 */ 70 static struct MHD_Response *response; 71 72 /** 73 * Time this round was started. 74 */ 75 static unsigned long long start_time; 76 77 78 /** 79 * Get the current timestamp 80 * 81 * @return current time in ms 82 */ 83 static unsigned long long 84 now () 85 { 86 struct timeval tv; 87 88 gettimeofday (&tv, NULL); 89 return (((unsigned long long) tv.tv_sec * 1000LL) + 90 ((unsigned long long) tv.tv_usec / 1000LL)); 91 } 92 93 94 /** 95 * Start the timer. 96 */ 97 static void 98 start_timer() 99 { 100 start_time = now (); 101 } 102 103 104 /** 105 * Stop the timer and report performance 106 * 107 * @param desc description of the threading mode we used 108 */ 109 static void 110 stop (const char *desc) 111 { 112 double rps = ((double) (PAR * ROUNDS * 1000)) / ((double) (now() - start_time)); 113 114 fprintf (stderr, 115 "Parallel GETs using %s: %f %s\n", 116 desc, 117 rps, 118 "requests/s"); 119 GAUGER (desc, 120 "Parallel GETs", 121 rps, 122 "requests/s"); 123 } 124 125 126 static size_t 127 copyBuffer (void *ptr, 128 size_t size, size_t nmemb, 129 void *ctx) 130 { 131 return size * nmemb; 132 } 133 134 static int 135 ahc_echo (void *cls, 136 struct MHD_Connection *connection, 137 const char *url, 138 const char *method, 139 const char *version, 140 const char *upload_data, size_t *upload_data_size, 141 void **unused) 142 { 143 static int ptr; 144 const char *me = cls; 145 int ret; 146 147 if (0 != strcmp (me, method)) 148 return MHD_NO; /* unexpected method */ 149 if (&ptr != *unused) 150 { 151 *unused = &ptr; 152 return MHD_YES; 153 } 154 *unused = NULL; 155 ret = MHD_queue_response (connection, MHD_HTTP_OK, response); 156 if (ret == MHD_NO) 157 abort (); 158 return ret; 159 } 160 161 162 static pid_t 163 do_gets (int port) 164 { 165 pid_t ret; 166 CURL *c; 167 CURLcode errornum; 168 unsigned int i; 169 unsigned int j; 170 pid_t par[PAR]; 171 char url[64]; 172 173 sprintf(url, "http://127.0.0.1:%d/hello_world", port); 174 175 ret = fork (); 176 if (ret == -1) abort (); 177 if (ret != 0) 178 return ret; 179 for (j=0;j<PAR;j++) 180 { 181 par[j] = fork (); 182 if (par[j] == 0) 183 { 184 for (i=0;i<ROUNDS;i++) 185 { 186 c = curl_easy_init (); 187 curl_easy_setopt (c, CURLOPT_URL, url); 188 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 189 curl_easy_setopt (c, CURLOPT_WRITEDATA, NULL); 190 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 191 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 192 if (oneone) 193 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 194 else 195 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 196 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 197 /* NOTE: use of CONNECTTIMEOUT without also 198 setting NOSIGNAL results in really weird 199 crashes on my system! */ 200 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 201 if (CURLE_OK != (errornum = curl_easy_perform (c))) 202 { 203 fprintf (stderr, 204 "curl_easy_perform failed: `%s'\n", 205 curl_easy_strerror (errornum)); 206 curl_easy_cleanup (c); 207 _exit (1); 208 } 209 curl_easy_cleanup (c); 210 } 211 _exit (0); 212 } 213 } 214 for (j=0;j<PAR;j++) 215 waitpid (par[j], NULL, 0); 216 _exit (0); 217 } 218 219 220 static void 221 join_gets (pid_t pid) 222 { 223 int status; 224 225 status = 1; 226 waitpid (pid, &status, 0); 227 if (0 != status) 228 abort (); 229 } 230 231 232 static int 233 testInternalGet (int port, int poll_flag) 234 { 235 struct MHD_Daemon *d; 236 237 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag, 238 port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); 239 if (d == NULL) 240 return 1; 241 start_timer (); 242 join_gets (do_gets (port)); 243 stop (poll_flag ? "internal poll" : "internal select"); 244 MHD_stop_daemon (d); 245 return 0; 246 } 247 248 249 static int 250 testMultithreadedGet (int port, int poll_flag) 251 { 252 struct MHD_Daemon *d; 253 254 d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG | poll_flag, 255 port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); 256 if (d == NULL) 257 return 16; 258 start_timer (); 259 join_gets (do_gets (port)); 260 stop (poll_flag ? "thread with poll" : "thread with select"); 261 MHD_stop_daemon (d); 262 return 0; 263 } 264 265 static int 266 testMultithreadedPoolGet (int port, int poll_flag) 267 { 268 struct MHD_Daemon *d; 269 270 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | poll_flag, 271 port, NULL, NULL, &ahc_echo, "GET", 272 MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END); 273 if (d == NULL) 274 return 16; 275 start_timer (); 276 join_gets (do_gets (port)); 277 stop (poll_flag ? "thread pool with poll" : "thread pool with select"); 278 MHD_stop_daemon (d); 279 return 0; 280 } 281 282 static int 283 testExternalGet (int port) 284 { 285 struct MHD_Daemon *d; 286 pid_t pid; 287 fd_set rs; 288 fd_set ws; 289 fd_set es; 290 MHD_socket max; 291 struct timeval tv; 292 MHD_UNSIGNED_LONG_LONG tt; 293 int tret; 294 295 d = MHD_start_daemon (MHD_USE_DEBUG, 296 port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); 297 if (d == NULL) 298 return 256; 299 start_timer (); 300 pid = do_gets (port); 301 while (0 == waitpid (pid, NULL, WNOHANG)) 302 { 303 max = 0; 304 FD_ZERO (&rs); 305 FD_ZERO (&ws); 306 FD_ZERO (&es); 307 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max)) 308 { 309 MHD_stop_daemon (d); 310 return 4096; 311 } 312 tret = MHD_get_timeout (d, &tt); 313 if (MHD_YES != tret) tt = 1; 314 tv.tv_sec = tt / 1000; 315 tv.tv_usec = 1000 * (tt % 1000); 316 if (-1 == select (max + 1, &rs, &ws, &es, &tv)) 317 { 318 if (EINTR == errno) 319 continue; 320 fprintf (stderr, 321 "select failed: %s\n", 322 strerror (errno)); 323 break; 324 } 325 MHD_run (d); 326 } 327 stop ("external select"); 328 MHD_stop_daemon (d); 329 return 0; 330 } 331 332 333 int 334 main (int argc, char *const *argv) 335 { 336 unsigned int errorCount = 0; 337 int port = 1081; 338 339 oneone = (NULL != strrchr (argv[0], (int) '/')) ? 340 (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0; 341 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 342 return 2; 343 response = MHD_create_response_from_buffer (strlen ("/hello_world"), 344 "/hello_world", 345 MHD_RESPMEM_MUST_COPY); 346 errorCount += testInternalGet (port++, 0); 347 errorCount += testMultithreadedGet (port++, 0); 348 errorCount += testMultithreadedPoolGet (port++, 0); 349 errorCount += testExternalGet (port++); 350 #ifndef WINDOWS 351 errorCount += testInternalGet (port++, MHD_USE_POLL); 352 errorCount += testMultithreadedGet (port++, MHD_USE_POLL); 353 errorCount += testMultithreadedPoolGet (port++, MHD_USE_POLL); 354 #endif 355 #if EPOLL_SUPPORT 356 errorCount += testInternalGet (port++, MHD_USE_EPOLL_LINUX_ONLY); 357 errorCount += testMultithreadedPoolGet (port++, MHD_USE_EPOLL_LINUX_ONLY); 358 #endif 359 MHD_destroy_response (response); 360 if (errorCount != 0) 361 fprintf (stderr, "Error (code: %u)\n", errorCount); 362 curl_global_cleanup (); 363 return errorCount != 0; /* 0 == pass */ 364 } 365