diff --git a/Makefile b/Makefile index 59be3de0..884f514b 100644 --- a/Makefile +++ b/Makefile @@ -77,8 +77,8 @@ else include config-unix.mk endif -TESTS=test/echo-server.c test/test-*.c -BENCHMARKS=test/echo-server.c test/dns-server.c test/benchmark-*.c +TESTS=test/blackhole-server.c test/echo-server.c test/test-*.c +BENCHMARKS=test/blackhole-server.c test/echo-server.c test/dns-server.c test/benchmark-*.c all: uv.a test/run-tests$(E) test/run-benchmarks$(E) diff --git a/test/benchmark-list.h b/test/benchmark-list.h index 0e5467c7..0a72fb98 100644 --- a/test/benchmark-list.h +++ b/test/benchmark-list.h @@ -21,6 +21,7 @@ BENCHMARK_DECLARE (sizes) BENCHMARK_DECLARE (ping_pongs) +BENCHMARK_DECLARE (tcp_write_batch) BENCHMARK_DECLARE (tcp4_pound_100) BENCHMARK_DECLARE (tcp4_pound_1000) BENCHMARK_DECLARE (pipe_pound_100) @@ -42,6 +43,7 @@ BENCHMARK_DECLARE (udp_packet_storm_1000v1000) BENCHMARK_DECLARE (gethostbyname) BENCHMARK_DECLARE (getaddrinfo) BENCHMARK_DECLARE (spawn) +HELPER_DECLARE (tcp4_blackhole_server) HELPER_DECLARE (tcp_pump_server) HELPER_DECLARE (pipe_pump_server) HELPER_DECLARE (tcp4_echo_server) @@ -54,6 +56,9 @@ TASK_LIST_START BENCHMARK_ENTRY (ping_pongs) BENCHMARK_HELPER (ping_pongs, tcp4_echo_server) + BENCHMARK_ENTRY (tcp_write_batch) + BENCHMARK_HELPER (tcp_write_batch, tcp4_blackhole_server) + BENCHMARK_ENTRY (tcp_pump100_client) BENCHMARK_HELPER (tcp_pump100_client, tcp_pump_server) diff --git a/test/benchmark-tcp-write-batch.c b/test/benchmark-tcp-write-batch.c new file mode 100644 index 00000000..77bb0191 --- /dev/null +++ b/test/benchmark-tcp-write-batch.c @@ -0,0 +1,144 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "task.h" + +#include +#include +#include + +#define WRITE_REQ_DATA "Hello, world." +#define NUM_WRITE_REQS (1000 * 1000) + +#define container_of(ptr, type, member) \ + ((type *) ((char *) (ptr) - offsetof(type, member))) + +typedef struct { + uv_write_t req; + uv_buf_t buf; +} write_req; + + +static write_req* write_reqs; +static uv_tcp_t tcp_client; +static uv_connect_t connect_req; +static uv_shutdown_t shutdown_req; + +static int shutdown_cb_called = 0; +static int connect_cb_called = 0; +static int write_cb_called = 0; +static int close_cb_called = 0; + +static void connect_cb(uv_connect_t* req, int status); +static void write_cb(uv_write_t* req, int status); +static void shutdown_cb(uv_shutdown_t* req, int status); +static void close_cb(uv_handle_t* handle); + + +static void connect_cb(uv_connect_t* req, int status) { + write_req* w; + int i; + int r; + + ASSERT(req->handle == (uv_stream_t*)&tcp_client); + + for (i = 0; i < NUM_WRITE_REQS; i++) { + w = &write_reqs[i]; + r = uv_write(&w->req, req->handle, &w->buf, 1, write_cb); + ASSERT(r == 0); + } + + r = uv_shutdown(&shutdown_req, req->handle, shutdown_cb); + ASSERT(r == 0); + + connect_cb_called++; +} + + +static void write_cb(uv_write_t* req, int status) { + ASSERT(req != NULL); + ASSERT(status == 0); + write_cb_called++; +} + + +static void shutdown_cb(uv_shutdown_t* req, int status) { + ASSERT(req->handle == (uv_stream_t*)&tcp_client); + ASSERT(req->handle->write_queue_size == 0); + + uv_close((uv_handle_t*)req->handle, close_cb); + free(write_reqs); + + shutdown_cb_called++; +} + + +static void close_cb(uv_handle_t* handle) { + ASSERT(handle == (uv_handle_t*)&tcp_client); + close_cb_called++; +} + + +BENCHMARK_IMPL(tcp_write_batch) { + struct sockaddr_in addr; + uv_loop_t* loop; + uint64_t start; + uint64_t stop; + int i; + int r; + + write_reqs = malloc(sizeof(*write_reqs) * NUM_WRITE_REQS); + ASSERT(write_reqs != NULL); + + /* Prepare the data to write out. */ + for (i = 0; i < NUM_WRITE_REQS; i++) { + write_reqs[i].buf = uv_buf_init(WRITE_REQ_DATA, + sizeof(WRITE_REQ_DATA) - 1); + } + + loop = uv_default_loop(); + addr = uv_ip4_addr("127.0.0.1", TEST_PORT); + + r = uv_tcp_init(loop, &tcp_client); + ASSERT(r == 0); + + r = uv_tcp_connect(&connect_req, &tcp_client, addr, connect_cb); + ASSERT(r == 0); + + start = uv_hrtime(); + + r = uv_run(loop); + ASSERT(r == 0); + + stop = uv_hrtime(); + + ASSERT(connect_cb_called == 1); + ASSERT(write_cb_called == NUM_WRITE_REQS); + ASSERT(shutdown_cb_called == 1); + ASSERT(close_cb_called == 1); + + printf("%ld write requests in %.2fs.\n", + (long)NUM_WRITE_REQS, + (stop - start) / 10e8); + + return 0; +} diff --git a/test/blackhole-server.c b/test/blackhole-server.c new file mode 100644 index 00000000..85a0efc2 --- /dev/null +++ b/test/blackhole-server.c @@ -0,0 +1,122 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "task.h" + +#include +#include +#include + +#define container_of(ptr, type, member) \ + ((type *) ((char *) (ptr) - offsetof(type, member))) + +typedef struct { + uv_tcp_t handle; + uv_shutdown_t shutdown_req; +} conn_rec; + +static uv_tcp_t tcp_server; + +static void connection_cb(uv_stream_t* stream, int status); +static uv_buf_t alloc_cb(uv_handle_t* handle, size_t suggested_size); +static void read_cb(uv_stream_t* stream, ssize_t nread, uv_buf_t buf); +static void shutdown_cb(uv_shutdown_t* req, int status); +static void close_cb(uv_handle_t* handle); + + +static void connection_cb(uv_stream_t* stream, int status) { + conn_rec* conn; + int r; + + ASSERT(status == 0); + ASSERT(stream == (uv_stream_t*)&tcp_server); + + conn = malloc(sizeof *conn); + ASSERT(conn != NULL); + + r = uv_tcp_init(stream->loop, &conn->handle); + ASSERT(r == 0); + + r = uv_accept(stream, (uv_stream_t*)&conn->handle); + ASSERT(r == 0); + + r = uv_read_start((uv_stream_t*)&conn->handle, alloc_cb, read_cb); + ASSERT(r == 0); +} + + +static uv_buf_t alloc_cb(uv_handle_t* handle, size_t suggested_size) { + static char buf[65536]; + return uv_buf_init(buf, sizeof buf); +} + + +static void read_cb(uv_stream_t* stream, ssize_t nread, uv_buf_t buf) { + conn_rec* conn; + int r; + + if (nread >= 0) + return; + + ASSERT(uv_last_error(stream->loop).code == UV_EOF); + + conn = container_of(stream, conn_rec, handle); + + r = uv_shutdown(&conn->shutdown_req, stream, shutdown_cb); + ASSERT(r == 0); +} + + +static void shutdown_cb(uv_shutdown_t* req, int status) { + conn_rec* conn = container_of(req, conn_rec, shutdown_req); + uv_close((uv_handle_t*)&conn->handle, close_cb); +} + + +static void close_cb(uv_handle_t* handle) { + conn_rec* conn = container_of(handle, conn_rec, handle); + free(conn); +} + + +HELPER_IMPL(tcp4_blackhole_server) { + struct sockaddr_in addr; + uv_loop_t* loop; + int r; + + loop = uv_default_loop(); + addr = uv_ip4_addr("127.0.0.1", TEST_PORT); + + r = uv_tcp_init(loop, &tcp_server); + ASSERT(r == 0); + + r = uv_tcp_bind(&tcp_server, addr); + ASSERT(r == 0); + + r = uv_listen((uv_stream_t*)&tcp_server, 128, connection_cb); + ASSERT(r == 0); + + r = uv_run(loop); + ASSERT(0 && "Blackhole server dropped out of event loop."); + + return 0; +} diff --git a/uv.gyp b/uv.gyp index 43d21ff9..1328c12c 100644 --- a/uv.gyp +++ b/uv.gyp @@ -240,6 +240,7 @@ 'type': 'executable', 'dependencies': [ 'uv' ], 'sources': [ + 'test/blackhole-server.c', 'test/echo-server.c', 'test/run-tests.c', 'test/runner.c', @@ -326,9 +327,11 @@ 'test/benchmark-pump.c', 'test/benchmark-sizes.c', 'test/benchmark-spawn.c', + 'test/benchmark-tcp-write-batch.c', 'test/benchmark-udp-packet-storm.c', 'test/dns-server.c', 'test/echo-server.c', + 'test/blackhole-server.c', 'test/run-benchmarks.c', 'test/runner.c', 'test/runner.h',