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, &copyBuffer);
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