libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

perf_get_concurrent.c (15065B)


      1 /*
      2      This file is part of libmicrohttpd
      3      Copyright (C) 2007, 2009, 2011 Christian Grothoff
      4      Copyright (C) 2014-2022 Evgeny Grin (Karlson2k)
      5 
      6      libmicrohttpd is free software; you can redistribute it and/or modify
      7      it under the terms of the GNU General Public License as published
      8      by the Free Software Foundation; either version 2, or (at your
      9      option) any later version.
     10 
     11      libmicrohttpd is distributed in the hope that it will be useful, but
     12      WITHOUT ANY WARRANTY; without even the implied warranty of
     13      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14      General Public License for more details.
     15 
     16      You should have received a copy of the GNU General Public License
     17      along with libmicrohttpd; see the file COPYING.  If not, write to the
     18      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     19      Boston, MA 02110-1301, USA.
     20 */
     21 
     22 /**
     23  * @file perf_get_concurrent.c
     24  * @brief benchmark concurrent GET operations
     25  *        Note that we run libcurl on the machine at the
     26  *        same time, so the execution time may be influenced
     27  *        by the concurrent activity; it is quite possible
     28  *        that more time is spend with libcurl than with MHD,
     29  *        so the performance scores calculated with this code
     30  *        should NOT be used to compare with other HTTP servers
     31  *        (since MHD is actually better); only the relative
     32  *        scores between MHD versions are meaningful.
     33  * @author Christian Grothoff
     34  * @author Karlson2k (Evgeny Grin)
     35  */
     36 
     37 #include "MHD_config.h"
     38 #include "platform.h"
     39 #include <curl/curl.h>
     40 #include <microhttpd.h>
     41 #include <stdlib.h>
     42 #include <string.h>
     43 #include <time.h>
     44 #include <pthread.h>
     45 #include <errno.h>
     46 #include "mhd_has_in_name.h"
     47 
     48 #if defined(MHD_CPU_COUNT) && (MHD_CPU_COUNT + 0) < 2
     49 #undef MHD_CPU_COUNT
     50 #endif
     51 #if ! defined(MHD_CPU_COUNT)
     52 #define MHD_CPU_COUNT 2
     53 #endif
     54 
     55 /**
     56  * How many rounds of operations do we do for each
     57  * test (total number of requests will be ROUNDS * PAR).
     58  * Ensure that free ports are not exhausted during test.
     59  */
     60 #if MHD_CPU_COUNT > 8
     61 #ifndef _WIN32
     62 #define ROUNDS (1 + (30000 / 12) / MHD_CPU_COUNT)
     63 #else /* _WIN32 */
     64 #define ROUNDS (1 + (3000 / 12) / MHD_CPU_COUNT)
     65 #endif /* _WIN32 */
     66 #else
     67 #define ROUNDS 500
     68 #endif
     69 
     70 /**
     71  * How many requests do we do in parallel?
     72  */
     73 #define PAR MHD_CPU_COUNT
     74 
     75 /**
     76  * Do we use HTTP 1.1?
     77  */
     78 static int oneone;
     79 
     80 /**
     81  * Response to return (re-used).
     82  */
     83 static struct MHD_Response *response;
     84 
     85 /**
     86  * Time this round was started.
     87  */
     88 static unsigned long long start_time;
     89 
     90 /**
     91  * Set to 1 if the worker threads are done.
     92  */
     93 static volatile int signal_done;
     94 
     95 
     96 /**
     97  * Get the current timestamp
     98  *
     99  * @return current time in ms
    100  */
    101 static unsigned long long
    102 now (void)
    103 {
    104   struct timeval tv;
    105 
    106   gettimeofday (&tv, NULL);
    107   return (((unsigned long long) tv.tv_sec * 1000LL)
    108           + ((unsigned long long) tv.tv_usec / 1000LL));
    109 }
    110 
    111 
    112 /**
    113  * Start the timer.
    114  */
    115 static void
    116 start_timer (void)
    117 {
    118   start_time = now ();
    119 }
    120 
    121 
    122 /**
    123  * Stop the timer and report performance
    124  *
    125  * @param desc description of the threading mode we used
    126  */
    127 static void
    128 stop (const char *desc)
    129 {
    130   double rps = ((double) (PAR * ROUNDS * 1000)) / ((double) (now ()
    131                                                              - start_time));
    132 
    133   fprintf (stderr,
    134            "Parallel GETs using %s: %f %s\n",
    135            desc,
    136            rps,
    137            "requests/s");
    138 }
    139 
    140 
    141 static size_t
    142 copyBuffer (void *ptr,
    143             size_t size, size_t nmemb,
    144             void *ctx)
    145 {
    146   (void) ptr; (void) ctx;          /* Unused. Silent compiler warning. */
    147   return size * nmemb;
    148 }
    149 
    150 
    151 static enum MHD_Result
    152 ahc_echo (void *cls,
    153           struct MHD_Connection *connection,
    154           const char *url,
    155           const char *method,
    156           const char *version,
    157           const char *upload_data, size_t *upload_data_size,
    158           void **req_cls)
    159 {
    160   static int ptr;
    161   enum MHD_Result ret;
    162   (void) cls;
    163   (void) url; (void) version;                      /* Unused. Silent compiler warning. */
    164   (void) upload_data; (void) upload_data_size;     /* Unused. Silent compiler warning. */
    165 
    166   if (0 != strcmp (MHD_HTTP_METHOD_GET, method))
    167     return MHD_NO;              /* unexpected method */
    168   if (&ptr != *req_cls)
    169   {
    170     *req_cls = &ptr;
    171     return MHD_YES;
    172   }
    173   *req_cls = NULL;
    174   ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    175   if (ret == MHD_NO)
    176     abort ();
    177   return ret;
    178 }
    179 
    180 
    181 static void *
    182 thread_gets (void *param)
    183 {
    184   CURL *c;
    185   CURLcode errornum;
    186   unsigned int i;
    187   char *const url = (char *) param;
    188   static char curl_err_marker[] = "curl error";
    189 
    190   c = curl_easy_init ();
    191   curl_easy_setopt (c, CURLOPT_URL, url);
    192   curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
    193   curl_easy_setopt (c, CURLOPT_WRITEDATA, NULL);
    194   curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L);
    195   curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
    196   if (oneone)
    197     curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
    198   else
    199     curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
    200   curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
    201   /* NOTE: use of CONNECTTIMEOUT without also
    202      setting NOSIGNAL results in really weird
    203      crashes on my system! */
    204   curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L);
    205   for (i = 0; i < ROUNDS; i++)
    206   {
    207     if (CURLE_OK != (errornum = curl_easy_perform (c)))
    208     {
    209       fprintf (stderr,
    210                "curl_easy_perform failed: `%s'\n",
    211                curl_easy_strerror (errornum));
    212       curl_easy_cleanup (c);
    213       return curl_err_marker;
    214     }
    215   }
    216   curl_easy_cleanup (c);
    217 
    218   return NULL;
    219 }
    220 
    221 
    222 static void *
    223 do_gets (void *param)
    224 {
    225   int j;
    226   pthread_t par[PAR];
    227   char url[64];
    228   uint16_t port = (uint16_t) (intptr_t) param;
    229   char *err = NULL;
    230   static char pthr_err_marker[] = "pthread_create error";
    231 
    232   snprintf (url,
    233             sizeof (url),
    234             "http://127.0.0.1:%u/hello_world",
    235             (unsigned int) port);
    236   for (j = 0; j < PAR; j++)
    237   {
    238     if (0 != pthread_create (&par[j], NULL, &thread_gets, (void *) url))
    239     {
    240       for (j--; j >= 0; j--)
    241         pthread_join (par[j], NULL);
    242       return pthr_err_marker;
    243     }
    244   }
    245   for (j = 0; j < PAR; j++)
    246   {
    247     char *ret_val;
    248     if ((0 != pthread_join (par[j], (void **) &ret_val)) ||
    249         (NULL != ret_val) )
    250       err = ret_val;
    251   }
    252   signal_done = 1;
    253   return err;
    254 }
    255 
    256 
    257 static unsigned int
    258 testInternalGet (uint16_t port, uint32_t poll_flag)
    259 {
    260   struct MHD_Daemon *d;
    261   const char *const test_desc = ((poll_flag & MHD_USE_AUTO) ?
    262                                  "internal thread with 'auto'" :
    263                                  (poll_flag & MHD_USE_POLL) ?
    264                                  "internal thread with poll()" :
    265                                  (poll_flag & MHD_USE_EPOLL) ?
    266                                  "internal thread with epoll" :
    267                                  "internal thread with select()");
    268   const char *ret_val;
    269 
    270   if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
    271     port = 0;
    272 
    273   signal_done = 0;
    274   d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG
    275                         | (enum MHD_FLAG) poll_flag,
    276                         port, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END);
    277   if (d == NULL)
    278     return 1;
    279   if (0 == port)
    280   {
    281     const union MHD_DaemonInfo *dinfo;
    282     dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
    283     if ((NULL == dinfo) || (0 == dinfo->port) )
    284     {
    285       MHD_stop_daemon (d); return 32;
    286     }
    287     port = dinfo->port;
    288   }
    289   start_timer ();
    290   ret_val = do_gets ((void *) (intptr_t) port);
    291   if (! ret_val)
    292     stop (test_desc);
    293   MHD_stop_daemon (d);
    294   if (ret_val)
    295   {
    296     fprintf (stderr,
    297              "Error performing %s test: %s\n", test_desc, ret_val);
    298     return 4;
    299   }
    300   return 0;
    301 }
    302 
    303 
    304 static unsigned int
    305 testMultithreadedGet (uint16_t port, uint32_t poll_flag)
    306 {
    307   struct MHD_Daemon *d;
    308   const char *const test_desc = ((poll_flag & MHD_USE_AUTO) ?
    309                                  "internal thread with 'auto' and thread per connection"
    310                                  :
    311                                  (poll_flag & MHD_USE_POLL) ?
    312                                  "internal thread with poll() and thread per connection"
    313                                  :
    314                                  (poll_flag & MHD_USE_EPOLL) ?
    315                                  "internal thread with epoll and thread per connection"
    316                                  :
    317                                  "internal thread with select() and thread per connection");
    318   const char *ret_val;
    319 
    320   if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
    321     port = 0;
    322 
    323   signal_done = 0;
    324   d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION
    325                         | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG
    326                         | (enum MHD_FLAG) poll_flag,
    327                         port, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END);
    328   if (d == NULL)
    329     return 16;
    330   if (0 == port)
    331   {
    332     const union MHD_DaemonInfo *dinfo;
    333     dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
    334     if ((NULL == dinfo) || (0 == dinfo->port) )
    335     {
    336       MHD_stop_daemon (d); return 32;
    337     }
    338     port = dinfo->port;
    339   }
    340   start_timer ();
    341   ret_val = do_gets ((void *) (intptr_t) port);
    342   if (! ret_val)
    343     stop (test_desc);
    344   MHD_stop_daemon (d);
    345   if (ret_val)
    346   {
    347     fprintf (stderr,
    348              "Error performing %s test: %s\n", test_desc, ret_val);
    349     return 4;
    350   }
    351   return 0;
    352 }
    353 
    354 
    355 static unsigned int
    356 testMultithreadedPoolGet (uint16_t port, uint32_t poll_flag)
    357 {
    358   struct MHD_Daemon *d;
    359   const char *const test_desc = ((poll_flag & MHD_USE_AUTO) ?
    360                                  "internal thread pool with 'auto'" :
    361                                  (poll_flag & MHD_USE_POLL) ?
    362                                  "internal thread pool with poll()" :
    363                                  (poll_flag & MHD_USE_EPOLL) ?
    364                                  "internal thread poll with epoll" :
    365                                  "internal thread pool with select()");
    366   const char *ret_val;
    367 
    368   if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
    369     port = 0;
    370 
    371   signal_done = 0;
    372   d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG
    373                         | (enum MHD_FLAG) poll_flag,
    374                         port, NULL, NULL, &ahc_echo, NULL,
    375                         MHD_OPTION_THREAD_POOL_SIZE, MHD_CPU_COUNT,
    376                         MHD_OPTION_END);
    377   if (d == NULL)
    378     return 16;
    379   if (0 == port)
    380   {
    381     const union MHD_DaemonInfo *dinfo;
    382     dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
    383     if ((NULL == dinfo) || (0 == dinfo->port) )
    384     {
    385       MHD_stop_daemon (d); return 32;
    386     }
    387     port = dinfo->port;
    388   }
    389   start_timer ();
    390   ret_val = do_gets ((void *) (intptr_t) port);
    391   if (! ret_val)
    392     stop (test_desc);
    393   MHD_stop_daemon (d);
    394   if (ret_val)
    395   {
    396     fprintf (stderr,
    397              "Error performing %s test: %s\n", test_desc, ret_val);
    398     return 4;
    399   }
    400   return 0;
    401 }
    402 
    403 
    404 static unsigned int
    405 testExternalGet (uint16_t port)
    406 {
    407   struct MHD_Daemon *d;
    408   pthread_t tid;
    409   fd_set rs;
    410   fd_set ws;
    411   fd_set es;
    412   MHD_socket max;
    413   struct timeval tv;
    414   uint64_t tt64;
    415   char *ret_val;
    416   int ret = 0;
    417 
    418   if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
    419     port = 0;
    420 
    421   signal_done = 0;
    422   d = MHD_start_daemon (MHD_USE_ERROR_LOG,
    423                         port, NULL, NULL, &ahc_echo, NULL,
    424                         MHD_OPTION_APP_FD_SETSIZE, (int) FD_SETSIZE,
    425                         MHD_OPTION_END);
    426   if (d == NULL)
    427     return 256;
    428   if (0 == port)
    429   {
    430     const union MHD_DaemonInfo *dinfo;
    431     dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
    432     if ((NULL == dinfo) || (0 == dinfo->port) )
    433     {
    434       MHD_stop_daemon (d); return 32;
    435     }
    436     port = dinfo->port;
    437   }
    438   if (0 != pthread_create (&tid, NULL,
    439                            &do_gets, (void *) (intptr_t) port))
    440   {
    441     MHD_stop_daemon (d);
    442     return 512;
    443   }
    444   start_timer ();
    445 
    446   while (0 == signal_done)
    447   {
    448     max = 0;
    449     FD_ZERO (&rs);
    450     FD_ZERO (&ws);
    451     FD_ZERO (&es);
    452     if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
    453     {
    454       MHD_stop_daemon (d);
    455       return 4096;
    456     }
    457     if (MHD_NO == MHD_get_timeout64 (d, &tt64))
    458       tt64 = 1;
    459 #if ! defined(_WIN32) || defined(__CYGWIN__)
    460     tv.tv_sec = (time_t) (tt64 / 1000);
    461 #else  /* Native W32 */
    462     tv.tv_sec = (long) (tt64 / 1000);
    463 #endif /* Native W32 */
    464     tv.tv_usec = ((long) (tt64 % 1000)) * 1000;
    465     if (-1 == select (max + 1, &rs, &ws, &es, &tv))
    466     {
    467 #ifdef MHD_POSIX_SOCKETS
    468       if (EINTR != errno)
    469       {
    470         fprintf (stderr, "Unexpected select() error: %d. Line: %d\n",
    471                  (int) errno, __LINE__);
    472         fflush (stderr);
    473         exit (99);
    474       }
    475       ret |= 1024;
    476       break;
    477 #else
    478       if ((WSAEINVAL != WSAGetLastError ()) ||
    479           (0 != rs.fd_count) || (0 != ws.fd_count) || (0 != es.fd_count) )
    480       {
    481         fprintf (stderr, "Unexpected select() error: %d. Line: %d\n",
    482                  (int) WSAGetLastError (), __LINE__);
    483         fflush (stderr);
    484         exit (99);
    485       }
    486       Sleep (1);
    487 #endif
    488     }
    489     MHD_run_from_select (d, &rs, &ws, &es);
    490   }
    491 
    492   stop ("external select");
    493   MHD_stop_daemon (d);
    494   if ((0 != pthread_join (tid, (void **) &ret_val)) ||
    495       (NULL != ret_val) )
    496   {
    497     fprintf (stderr,
    498              "%s\n", ret_val);
    499     ret |= 8;
    500   }
    501   if (ret)
    502     fprintf (stderr, "Error performing test.\n");
    503   return 0;
    504 }
    505 
    506 
    507 int
    508 main (int argc, char *const *argv)
    509 {
    510   unsigned int errorCount = 0;
    511   uint16_t port = 1100;
    512   (void) argc;   /* Unused. Silent compiler warning. */
    513 
    514   if ((NULL == argv) || (0 == argv[0]))
    515     return 99;
    516   oneone = has_in_name (argv[0], "11");
    517   if (oneone)
    518     port += 15;
    519   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
    520     return 2;
    521   response = MHD_create_response_from_buffer_copy (strlen ("/hello_world"),
    522                                                    "/hello_world");
    523   errorCount += testInternalGet (port++, 0);
    524   errorCount += testMultithreadedGet (port++, 0);
    525   errorCount += testMultithreadedPoolGet (port++, 0);
    526   errorCount += testExternalGet (port++);
    527   errorCount += testInternalGet (port++, MHD_USE_AUTO);
    528   errorCount += testMultithreadedGet (port++, MHD_USE_AUTO);
    529   errorCount += testMultithreadedPoolGet (port++, MHD_USE_AUTO);
    530   if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_POLL))
    531   {
    532     errorCount += testInternalGet (port++, MHD_USE_POLL);
    533     errorCount += testMultithreadedGet (port++, MHD_USE_POLL);
    534     errorCount += testMultithreadedPoolGet (port++, MHD_USE_POLL);
    535   }
    536   if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_EPOLL))
    537   {
    538     errorCount += testInternalGet (port++, MHD_USE_EPOLL);
    539     errorCount += testMultithreadedPoolGet (port++, MHD_USE_EPOLL);
    540   }
    541   MHD_destroy_response (response);
    542   if (errorCount != 0)
    543     fprintf (stderr, "Error (code: %u)\n", errorCount);
    544   curl_global_cleanup ();
    545   return errorCount != 0;       /* 0 == pass */
    546 }