Home | History | Annotate | Line # | Download | only in test
      1      1.1  christos /* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
      2      1.1  christos  *
      3      1.1  christos  * Permission is hereby granted, free of charge, to any person obtaining a copy
      4      1.1  christos  * of this software and associated documentation files (the "Software"), to
      5      1.1  christos  * deal in the Software without restriction, including without limitation the
      6      1.1  christos  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
      7      1.1  christos  * sell copies of the Software, and to permit persons to whom the Software is
      8      1.1  christos  * furnished to do so, subject to the following conditions:
      9      1.1  christos  *
     10      1.1  christos  * The above copyright notice and this permission notice shall be included in
     11      1.1  christos  * all copies or substantial portions of the Software.
     12      1.1  christos  *
     13      1.1  christos  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     14      1.1  christos  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     15      1.1  christos  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     16      1.1  christos  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     17      1.1  christos  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
     18      1.1  christos  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
     19      1.1  christos  * IN THE SOFTWARE.
     20      1.1  christos  */
     21      1.1  christos 
     22      1.1  christos #include "task.h"
     23      1.1  christos #include "uv.h"
     24      1.1  christos 
     25      1.1  christos #define IPC_PIPE_NAME TEST_PIPENAME
     26      1.1  christos #define NUM_CONNECTS  (250 * 1000)
     27      1.1  christos 
     28      1.1  christos union stream_handle {
     29      1.1  christos   uv_pipe_t pipe;
     30      1.1  christos   uv_tcp_t tcp;
     31      1.1  christos };
     32      1.1  christos 
     33      1.1  christos /* Use as (uv_stream_t *) &handle_storage -- it's kind of clunky but it
     34      1.1  christos  * avoids aliasing warnings.
     35      1.1  christos  */
     36      1.1  christos typedef unsigned char handle_storage_t[sizeof(union stream_handle)];
     37      1.1  christos 
     38      1.1  christos /* Used for passing around the listen handle, not part of the benchmark proper.
     39      1.1  christos  * We have an overabundance of server types here. It works like this:
     40      1.1  christos  *
     41      1.1  christos  *  1. The main thread starts an IPC pipe server.
     42      1.1  christos  *  2. The worker threads connect to the IPC server and obtain a listen handle.
     43      1.1  christos  *  3. The worker threads start accepting requests on the listen handle.
     44      1.1  christos  *  4. The main thread starts connecting repeatedly.
     45      1.1  christos  *
     46      1.1  christos  * Step #4 should perhaps be farmed out over several threads.
     47      1.1  christos  */
     48      1.1  christos struct ipc_server_ctx {
     49      1.1  christos   handle_storage_t server_handle;
     50      1.1  christos   unsigned int num_connects;
     51      1.1  christos   uv_pipe_t ipc_pipe;
     52      1.1  christos };
     53      1.1  christos 
     54      1.1  christos struct ipc_peer_ctx {
     55      1.1  christos   handle_storage_t peer_handle;
     56      1.1  christos   uv_write_t write_req;
     57      1.1  christos };
     58      1.1  christos 
     59      1.1  christos struct ipc_client_ctx {
     60      1.1  christos   uv_connect_t connect_req;
     61      1.1  christos   uv_stream_t* server_handle;
     62      1.1  christos   uv_pipe_t ipc_pipe;
     63      1.1  christos   char scratch[16];
     64      1.1  christos };
     65      1.1  christos 
     66      1.1  christos /* Used in the actual benchmark. */
     67      1.1  christos struct server_ctx {
     68      1.1  christos   handle_storage_t server_handle;
     69      1.1  christos   unsigned int num_connects;
     70      1.1  christos   uv_async_t async_handle;
     71      1.1  christos   uv_thread_t thread_id;
     72      1.1  christos   uv_sem_t semaphore;
     73      1.1  christos };
     74      1.1  christos 
     75      1.1  christos struct client_ctx {
     76      1.1  christos   handle_storage_t client_handle;
     77      1.1  christos   unsigned int num_connects;
     78      1.1  christos   uv_connect_t connect_req;
     79      1.1  christos   uv_idle_t idle_handle;
     80      1.1  christos };
     81      1.1  christos 
     82      1.1  christos static void ipc_connection_cb(uv_stream_t* ipc_pipe, int status);
     83      1.1  christos static void ipc_write_cb(uv_write_t* req, int status);
     84      1.1  christos static void ipc_close_cb(uv_handle_t* handle);
     85      1.1  christos static void ipc_connect_cb(uv_connect_t* req, int status);
     86      1.1  christos static void ipc_read_cb(uv_stream_t* handle,
     87      1.1  christos                         ssize_t nread,
     88      1.1  christos                         const uv_buf_t* buf);
     89      1.1  christos static void ipc_alloc_cb(uv_handle_t* handle,
     90      1.1  christos                          size_t suggested_size,
     91      1.1  christos                          uv_buf_t* buf);
     92      1.1  christos 
     93      1.1  christos static void sv_async_cb(uv_async_t* handle);
     94      1.1  christos static void sv_connection_cb(uv_stream_t* server_handle, int status);
     95      1.1  christos static void sv_read_cb(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf);
     96      1.1  christos static void sv_alloc_cb(uv_handle_t* handle,
     97      1.1  christos                         size_t suggested_size,
     98      1.1  christos                         uv_buf_t* buf);
     99      1.1  christos 
    100      1.1  christos static void cl_connect_cb(uv_connect_t* req, int status);
    101      1.1  christos static void cl_idle_cb(uv_idle_t* handle);
    102      1.1  christos static void cl_close_cb(uv_handle_t* handle);
    103      1.1  christos 
    104      1.1  christos static struct sockaddr_in listen_addr;
    105      1.1  christos 
    106      1.1  christos 
    107      1.1  christos static void ipc_connection_cb(uv_stream_t* ipc_pipe, int status) {
    108      1.1  christos   struct ipc_server_ctx* sc;
    109      1.1  christos   struct ipc_peer_ctx* pc;
    110      1.1  christos   uv_loop_t* loop;
    111      1.1  christos   uv_buf_t buf;
    112      1.1  christos 
    113      1.1  christos   loop = ipc_pipe->loop;
    114      1.1  christos   buf = uv_buf_init("PING", 4);
    115      1.1  christos   sc = container_of(ipc_pipe, struct ipc_server_ctx, ipc_pipe);
    116      1.1  christos   pc = calloc(1, sizeof(*pc));
    117  1.1.1.2  christos   ASSERT_NOT_NULL(pc);
    118      1.1  christos 
    119      1.1  christos   if (ipc_pipe->type == UV_TCP)
    120      1.1  christos     ASSERT(0 == uv_tcp_init(loop, (uv_tcp_t*) &pc->peer_handle));
    121      1.1  christos   else if (ipc_pipe->type == UV_NAMED_PIPE)
    122      1.1  christos     ASSERT(0 == uv_pipe_init(loop, (uv_pipe_t*) &pc->peer_handle, 1));
    123      1.1  christos   else
    124      1.1  christos     ASSERT(0);
    125      1.1  christos 
    126      1.1  christos   ASSERT(0 == uv_accept(ipc_pipe, (uv_stream_t*) &pc->peer_handle));
    127      1.1  christos   ASSERT(0 == uv_write2(&pc->write_req,
    128      1.1  christos                         (uv_stream_t*) &pc->peer_handle,
    129      1.1  christos                         &buf,
    130      1.1  christos                         1,
    131      1.1  christos                         (uv_stream_t*) &sc->server_handle,
    132      1.1  christos                         ipc_write_cb));
    133      1.1  christos 
    134      1.1  christos   if (--sc->num_connects == 0)
    135      1.1  christos     uv_close((uv_handle_t*) ipc_pipe, NULL);
    136      1.1  christos }
    137      1.1  christos 
    138      1.1  christos 
    139      1.1  christos static void ipc_write_cb(uv_write_t* req, int status) {
    140      1.1  christos   struct ipc_peer_ctx* ctx;
    141      1.1  christos   ctx = container_of(req, struct ipc_peer_ctx, write_req);
    142      1.1  christos   uv_close((uv_handle_t*) &ctx->peer_handle, ipc_close_cb);
    143      1.1  christos }
    144      1.1  christos 
    145      1.1  christos 
    146      1.1  christos static void ipc_close_cb(uv_handle_t* handle) {
    147      1.1  christos   struct ipc_peer_ctx* ctx;
    148      1.1  christos   ctx = container_of(handle, struct ipc_peer_ctx, peer_handle);
    149      1.1  christos   free(ctx);
    150      1.1  christos }
    151      1.1  christos 
    152      1.1  christos 
    153      1.1  christos static void ipc_connect_cb(uv_connect_t* req, int status) {
    154      1.1  christos   struct ipc_client_ctx* ctx;
    155      1.1  christos   ctx = container_of(req, struct ipc_client_ctx, connect_req);
    156      1.1  christos   ASSERT(0 == status);
    157      1.1  christos   ASSERT(0 == uv_read_start((uv_stream_t*) &ctx->ipc_pipe,
    158      1.1  christos                             ipc_alloc_cb,
    159      1.1  christos                             ipc_read_cb));
    160      1.1  christos }
    161      1.1  christos 
    162      1.1  christos 
    163      1.1  christos static void ipc_alloc_cb(uv_handle_t* handle,
    164      1.1  christos                          size_t suggested_size,
    165      1.1  christos                          uv_buf_t* buf) {
    166      1.1  christos   struct ipc_client_ctx* ctx;
    167      1.1  christos   ctx = container_of(handle, struct ipc_client_ctx, ipc_pipe);
    168      1.1  christos   buf->base = ctx->scratch;
    169      1.1  christos   buf->len = sizeof(ctx->scratch);
    170      1.1  christos }
    171      1.1  christos 
    172      1.1  christos 
    173      1.1  christos static void ipc_read_cb(uv_stream_t* handle,
    174      1.1  christos                         ssize_t nread,
    175      1.1  christos                         const uv_buf_t* buf) {
    176      1.1  christos   struct ipc_client_ctx* ctx;
    177      1.1  christos   uv_loop_t* loop;
    178      1.1  christos   uv_handle_type type;
    179      1.1  christos   uv_pipe_t* ipc_pipe;
    180      1.1  christos 
    181      1.1  christos   ipc_pipe = (uv_pipe_t*) handle;
    182      1.1  christos   ctx = container_of(ipc_pipe, struct ipc_client_ctx, ipc_pipe);
    183      1.1  christos   loop = ipc_pipe->loop;
    184      1.1  christos 
    185      1.1  christos   ASSERT(1 == uv_pipe_pending_count(ipc_pipe));
    186      1.1  christos   type = uv_pipe_pending_type(ipc_pipe);
    187      1.1  christos   if (type == UV_TCP)
    188      1.1  christos     ASSERT(0 == uv_tcp_init(loop, (uv_tcp_t*) ctx->server_handle));
    189      1.1  christos   else if (type == UV_NAMED_PIPE)
    190      1.1  christos     ASSERT(0 == uv_pipe_init(loop, (uv_pipe_t*) ctx->server_handle, 0));
    191      1.1  christos   else
    192      1.1  christos     ASSERT(0);
    193      1.1  christos 
    194      1.1  christos   ASSERT(0 == uv_accept(handle, ctx->server_handle));
    195      1.1  christos   uv_close((uv_handle_t*) &ctx->ipc_pipe, NULL);
    196      1.1  christos }
    197      1.1  christos 
    198      1.1  christos 
    199      1.1  christos /* Set up an IPC pipe server that hands out listen sockets to the worker
    200      1.1  christos  * threads. It's kind of cumbersome for such a simple operation, maybe we
    201      1.1  christos  * should revive uv_import() and uv_export().
    202      1.1  christos  */
    203      1.1  christos static void send_listen_handles(uv_handle_type type,
    204      1.1  christos                                 unsigned int num_servers,
    205      1.1  christos                                 struct server_ctx* servers) {
    206      1.1  christos   struct ipc_server_ctx ctx;
    207      1.1  christos   uv_loop_t* loop;
    208      1.1  christos   unsigned int i;
    209      1.1  christos 
    210      1.1  christos   loop = uv_default_loop();
    211      1.1  christos   ctx.num_connects = num_servers;
    212      1.1  christos 
    213      1.1  christos   if (type == UV_TCP) {
    214      1.1  christos     ASSERT(0 == uv_tcp_init(loop, (uv_tcp_t*) &ctx.server_handle));
    215      1.1  christos     ASSERT(0 == uv_tcp_bind((uv_tcp_t*) &ctx.server_handle,
    216      1.1  christos                             (const struct sockaddr*) &listen_addr,
    217      1.1  christos                             0));
    218      1.1  christos   }
    219      1.1  christos   else
    220      1.1  christos     ASSERT(0);
    221      1.1  christos   /* We need to initialize this pipe with ipc=0 - this is not a uv_pipe we'll
    222      1.1  christos    * be sending handles over, it's just for listening for new connections.
    223      1.1  christos    * If we accept a connection then the connected pipe must be initialized
    224      1.1  christos    * with ipc=1.
    225      1.1  christos    */
    226      1.1  christos   ASSERT(0 == uv_pipe_init(loop, &ctx.ipc_pipe, 0));
    227      1.1  christos   ASSERT(0 == uv_pipe_bind(&ctx.ipc_pipe, IPC_PIPE_NAME));
    228      1.1  christos   ASSERT(0 == uv_listen((uv_stream_t*) &ctx.ipc_pipe, 128, ipc_connection_cb));
    229      1.1  christos 
    230      1.1  christos   for (i = 0; i < num_servers; i++)
    231      1.1  christos     uv_sem_post(&servers[i].semaphore);
    232      1.1  christos 
    233      1.1  christos   ASSERT(0 == uv_run(loop, UV_RUN_DEFAULT));
    234      1.1  christos   uv_close((uv_handle_t*) &ctx.server_handle, NULL);
    235      1.1  christos   ASSERT(0 == uv_run(loop, UV_RUN_DEFAULT));
    236      1.1  christos 
    237      1.1  christos   for (i = 0; i < num_servers; i++)
    238      1.1  christos     uv_sem_wait(&servers[i].semaphore);
    239      1.1  christos }
    240      1.1  christos 
    241      1.1  christos 
    242      1.1  christos static void get_listen_handle(uv_loop_t* loop, uv_stream_t* server_handle) {
    243      1.1  christos   struct ipc_client_ctx ctx;
    244      1.1  christos 
    245      1.1  christos   ctx.server_handle = server_handle;
    246      1.1  christos   ctx.server_handle->data = "server handle";
    247      1.1  christos 
    248      1.1  christos   ASSERT(0 == uv_pipe_init(loop, &ctx.ipc_pipe, 1));
    249      1.1  christos   uv_pipe_connect(&ctx.connect_req,
    250      1.1  christos                   &ctx.ipc_pipe,
    251      1.1  christos                   IPC_PIPE_NAME,
    252      1.1  christos                   ipc_connect_cb);
    253      1.1  christos   ASSERT(0 == uv_run(loop, UV_RUN_DEFAULT));
    254      1.1  christos }
    255      1.1  christos 
    256      1.1  christos 
    257      1.1  christos static void server_cb(void *arg) {
    258      1.1  christos   struct server_ctx *ctx;
    259      1.1  christos   uv_loop_t loop;
    260      1.1  christos 
    261      1.1  christos   ctx = arg;
    262      1.1  christos   ASSERT(0 == uv_loop_init(&loop));
    263      1.1  christos 
    264      1.1  christos   ASSERT(0 == uv_async_init(&loop, &ctx->async_handle, sv_async_cb));
    265      1.1  christos   uv_unref((uv_handle_t*) &ctx->async_handle);
    266      1.1  christos 
    267      1.1  christos   /* Wait until the main thread is ready. */
    268      1.1  christos   uv_sem_wait(&ctx->semaphore);
    269      1.1  christos   get_listen_handle(&loop, (uv_stream_t*) &ctx->server_handle);
    270      1.1  christos   uv_sem_post(&ctx->semaphore);
    271      1.1  christos 
    272      1.1  christos   /* Now start the actual benchmark. */
    273      1.1  christos   ASSERT(0 == uv_listen((uv_stream_t*) &ctx->server_handle,
    274      1.1  christos                         128,
    275      1.1  christos                         sv_connection_cb));
    276      1.1  christos   ASSERT(0 == uv_run(&loop, UV_RUN_DEFAULT));
    277      1.1  christos 
    278      1.1  christos   uv_loop_close(&loop);
    279      1.1  christos }
    280      1.1  christos 
    281      1.1  christos 
    282      1.1  christos static void sv_async_cb(uv_async_t* handle) {
    283      1.1  christos   struct server_ctx* ctx;
    284      1.1  christos   ctx = container_of(handle, struct server_ctx, async_handle);
    285      1.1  christos   uv_close((uv_handle_t*) &ctx->server_handle, NULL);
    286      1.1  christos   uv_close((uv_handle_t*) &ctx->async_handle, NULL);
    287      1.1  christos }
    288      1.1  christos 
    289      1.1  christos 
    290      1.1  christos static void sv_connection_cb(uv_stream_t* server_handle, int status) {
    291      1.1  christos   handle_storage_t* storage;
    292      1.1  christos   struct server_ctx* ctx;
    293      1.1  christos 
    294      1.1  christos   ctx = container_of(server_handle, struct server_ctx, server_handle);
    295      1.1  christos   ASSERT(status == 0);
    296      1.1  christos 
    297      1.1  christos   storage = malloc(sizeof(*storage));
    298  1.1.1.2  christos   ASSERT_NOT_NULL(storage);
    299      1.1  christos 
    300      1.1  christos   if (server_handle->type == UV_TCP)
    301      1.1  christos     ASSERT(0 == uv_tcp_init(server_handle->loop, (uv_tcp_t*) storage));
    302      1.1  christos   else if (server_handle->type == UV_NAMED_PIPE)
    303      1.1  christos     ASSERT(0 == uv_pipe_init(server_handle->loop, (uv_pipe_t*) storage, 0));
    304      1.1  christos   else
    305      1.1  christos     ASSERT(0);
    306      1.1  christos 
    307      1.1  christos   ASSERT(0 == uv_accept(server_handle, (uv_stream_t*) storage));
    308      1.1  christos   ASSERT(0 == uv_read_start((uv_stream_t*) storage, sv_alloc_cb, sv_read_cb));
    309      1.1  christos   ctx->num_connects++;
    310      1.1  christos }
    311      1.1  christos 
    312      1.1  christos 
    313      1.1  christos static void sv_alloc_cb(uv_handle_t* handle,
    314      1.1  christos                         size_t suggested_size,
    315      1.1  christos                         uv_buf_t* buf) {
    316      1.1  christos   static char slab[32];
    317      1.1  christos   buf->base = slab;
    318      1.1  christos   buf->len = sizeof(slab);
    319      1.1  christos }
    320      1.1  christos 
    321      1.1  christos 
    322      1.1  christos static void sv_read_cb(uv_stream_t* handle,
    323      1.1  christos                        ssize_t nread,
    324      1.1  christos                        const uv_buf_t* buf) {
    325      1.1  christos   ASSERT(nread == UV_EOF);
    326      1.1  christos   uv_close((uv_handle_t*) handle, (uv_close_cb) free);
    327      1.1  christos }
    328      1.1  christos 
    329      1.1  christos 
    330      1.1  christos static void cl_connect_cb(uv_connect_t* req, int status) {
    331      1.1  christos   struct client_ctx* ctx = container_of(req, struct client_ctx, connect_req);
    332      1.1  christos   uv_idle_start(&ctx->idle_handle, cl_idle_cb);
    333      1.1  christos   ASSERT(0 == status);
    334      1.1  christos }
    335      1.1  christos 
    336      1.1  christos 
    337      1.1  christos static void cl_idle_cb(uv_idle_t* handle) {
    338      1.1  christos   struct client_ctx* ctx = container_of(handle, struct client_ctx, idle_handle);
    339      1.1  christos   uv_close((uv_handle_t*) &ctx->client_handle, cl_close_cb);
    340      1.1  christos   uv_idle_stop(&ctx->idle_handle);
    341      1.1  christos }
    342      1.1  christos 
    343      1.1  christos 
    344      1.1  christos static void cl_close_cb(uv_handle_t* handle) {
    345      1.1  christos   struct client_ctx* ctx;
    346      1.1  christos 
    347      1.1  christos   ctx = container_of(handle, struct client_ctx, client_handle);
    348      1.1  christos 
    349      1.1  christos   if (--ctx->num_connects == 0) {
    350      1.1  christos     uv_close((uv_handle_t*) &ctx->idle_handle, NULL);
    351      1.1  christos     return;
    352      1.1  christos   }
    353      1.1  christos 
    354      1.1  christos   ASSERT(0 == uv_tcp_init(handle->loop, (uv_tcp_t*) &ctx->client_handle));
    355      1.1  christos   ASSERT(0 == uv_tcp_connect(&ctx->connect_req,
    356      1.1  christos                              (uv_tcp_t*) &ctx->client_handle,
    357      1.1  christos                              (const struct sockaddr*) &listen_addr,
    358      1.1  christos                              cl_connect_cb));
    359      1.1  christos }
    360      1.1  christos 
    361      1.1  christos 
    362      1.1  christos static int test_tcp(unsigned int num_servers, unsigned int num_clients) {
    363      1.1  christos   struct server_ctx* servers;
    364      1.1  christos   struct client_ctx* clients;
    365      1.1  christos   uv_loop_t* loop;
    366      1.1  christos   uv_tcp_t* handle;
    367      1.1  christos   unsigned int i;
    368      1.1  christos   double time;
    369      1.1  christos 
    370      1.1  christos   ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &listen_addr));
    371      1.1  christos   loop = uv_default_loop();
    372      1.1  christos 
    373      1.1  christos   servers = calloc(num_servers, sizeof(servers[0]));
    374      1.1  christos   clients = calloc(num_clients, sizeof(clients[0]));
    375  1.1.1.2  christos   ASSERT_NOT_NULL(servers);
    376  1.1.1.2  christos   ASSERT_NOT_NULL(clients);
    377      1.1  christos 
    378      1.1  christos   /* We're making the assumption here that from the perspective of the
    379      1.1  christos    * OS scheduler, threads are functionally equivalent to and interchangeable
    380      1.1  christos    * with full-blown processes.
    381      1.1  christos    */
    382      1.1  christos   for (i = 0; i < num_servers; i++) {
    383      1.1  christos     struct server_ctx* ctx = servers + i;
    384      1.1  christos     ASSERT(0 == uv_sem_init(&ctx->semaphore, 0));
    385      1.1  christos     ASSERT(0 == uv_thread_create(&ctx->thread_id, server_cb, ctx));
    386      1.1  christos   }
    387      1.1  christos 
    388      1.1  christos   send_listen_handles(UV_TCP, num_servers, servers);
    389      1.1  christos 
    390      1.1  christos   for (i = 0; i < num_clients; i++) {
    391      1.1  christos     struct client_ctx* ctx = clients + i;
    392      1.1  christos     ctx->num_connects = NUM_CONNECTS / num_clients;
    393      1.1  christos     handle = (uv_tcp_t*) &ctx->client_handle;
    394      1.1  christos     handle->data = "client handle";
    395      1.1  christos     ASSERT(0 == uv_tcp_init(loop, handle));
    396      1.1  christos     ASSERT(0 == uv_tcp_connect(&ctx->connect_req,
    397      1.1  christos                                handle,
    398      1.1  christos                                (const struct sockaddr*) &listen_addr,
    399      1.1  christos                                cl_connect_cb));
    400      1.1  christos     ASSERT(0 == uv_idle_init(loop, &ctx->idle_handle));
    401      1.1  christos   }
    402      1.1  christos 
    403      1.1  christos   {
    404      1.1  christos     uint64_t t = uv_hrtime();
    405      1.1  christos     ASSERT(0 == uv_run(loop, UV_RUN_DEFAULT));
    406      1.1  christos     t = uv_hrtime() - t;
    407      1.1  christos     time = t / 1e9;
    408      1.1  christos   }
    409      1.1  christos 
    410      1.1  christos   for (i = 0; i < num_servers; i++) {
    411      1.1  christos     struct server_ctx* ctx = servers + i;
    412      1.1  christos     uv_async_send(&ctx->async_handle);
    413      1.1  christos     ASSERT(0 == uv_thread_join(&ctx->thread_id));
    414      1.1  christos     uv_sem_destroy(&ctx->semaphore);
    415      1.1  christos   }
    416      1.1  christos 
    417      1.1  christos   printf("accept%u: %.0f accepts/sec (%u total)\n",
    418      1.1  christos          num_servers,
    419      1.1  christos          NUM_CONNECTS / time,
    420      1.1  christos          NUM_CONNECTS);
    421      1.1  christos 
    422      1.1  christos   for (i = 0; i < num_servers; i++) {
    423      1.1  christos     struct server_ctx* ctx = servers + i;
    424      1.1  christos     printf("  thread #%u: %.0f accepts/sec (%u total, %.1f%%)\n",
    425      1.1  christos            i,
    426      1.1  christos            ctx->num_connects / time,
    427      1.1  christos            ctx->num_connects,
    428      1.1  christos            ctx->num_connects * 100.0 / NUM_CONNECTS);
    429      1.1  christos   }
    430      1.1  christos 
    431      1.1  christos   free(clients);
    432      1.1  christos   free(servers);
    433      1.1  christos 
    434      1.1  christos   MAKE_VALGRIND_HAPPY();
    435      1.1  christos   return 0;
    436      1.1  christos }
    437      1.1  christos 
    438      1.1  christos 
    439      1.1  christos BENCHMARK_IMPL(tcp_multi_accept2) {
    440      1.1  christos   return test_tcp(2, 40);
    441      1.1  christos }
    442      1.1  christos 
    443      1.1  christos 
    444      1.1  christos BENCHMARK_IMPL(tcp_multi_accept4) {
    445      1.1  christos   return test_tcp(4, 40);
    446      1.1  christos }
    447      1.1  christos 
    448      1.1  christos 
    449      1.1  christos BENCHMARK_IMPL(tcp_multi_accept8) {
    450      1.1  christos   return test_tcp(8, 40);
    451      1.1  christos }
    452