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