Home | History | Annotate | Download | only in testcurl
      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, &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
    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