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
now()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
start_timer()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
stop(const char * desc)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
copyBuffer(void * ptr,size_t size,size_t nmemb,void * ctx)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
ahc_echo(void * cls,struct MHD_Connection * connection,const char * url,const char * method,const char * version,const char * upload_data,size_t * upload_data_size,void ** unused)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
testInternalGet(int port,int poll_flag)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
testMultithreadedGet(int port,int poll_flag)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
testMultithreadedPoolGet(int port,int poll_flag)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
testExternalGet(int port)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
main(int argc,char * const * argv)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