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
now()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
start_timer()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
stop(const char * desc)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
copyBuffer(void * ptr,size_t size,size_t nmemb,void * ctx)127 copyBuffer (void *ptr,
128 size_t size, size_t nmemb,
129 void *ctx)
130 {
131 return size * nmemb;
132 }
133
134 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)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
do_gets(int port)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
join_gets(pid_t pid)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
testInternalGet(int port,int poll_flag)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
testMultithreadedGet(int port,int poll_flag)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
testMultithreadedPoolGet(int port,int poll_flag)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
testExternalGet(int port)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
main(int argc,char * const * argv)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