From bc9aec8395edb2ae9ce219892a1e99a6e114860c Mon Sep 17 00:00:00 2001 From: Garrett D'Amore Date: Thu, 5 Jan 2017 12:38:09 -0800 Subject: Add initial performanced tests. --- CMakeLists.txt | 1 + perf/CMakeLists.txt | 43 ++++++++++ perf/perf.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 269 insertions(+) create mode 100644 perf/CMakeLists.txt create mode 100644 perf/perf.c diff --git a/CMakeLists.txt b/CMakeLists.txt index b8d41fc2..05633e71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -210,6 +210,7 @@ if (NNG_TESTS) enable_testing() set(all_tests, "") add_subdirectory (tests) + add_subdirectory (perf) endif() # Build the tools diff --git a/perf/CMakeLists.txt b/perf/CMakeLists.txt new file mode 100644 index 00000000..44c4c607 --- /dev/null +++ b/perf/CMakeLists.txt @@ -0,0 +1,43 @@ +# +# Copyright (c) 2012 Martin Sustrik All rights reserved. +# Copyright (c) 2013 GoPivotal, Inc. All rights reserved. +# Copyright (c) 2015-2016 Jack R. Dunaway. All rights reserved. +# Copyright 2016 Garrett D'Amore +# Copyright 2016 Franklin "Snaipe" Mathieu +# +# 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. +# + +# Build unit tests. + +include_directories(AFTER SYSTEM ${PROJECT_SOURCE_DIR}/src) + +if (NNG_TESTS) + macro (add_nng_perf NAME) + add_executable (${NAME} perf.c) + target_link_libraries (${NAME} ${PROJECT_NAME}) + endmacro (add_nng_perf) + +else () + macro (add_nng_perf NAME) + endmacro (add_nng_perf) +endif () + +add_nng_perf(remote_lat) +add_nng_perf(local_lat) diff --git a/perf/perf.c b/perf/perf.c new file mode 100644 index 00000000..169a2119 --- /dev/null +++ b/perf/perf.c @@ -0,0 +1,225 @@ +// +// Copyright 2016 Garrett D'Amore +// +// This software is supplied under the terms of the MIT License, a +// copy of which should be located in the distribution where this +// file was obtained (LICENSE.txt). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +#include "nng.h" + +#include +#include +#include +#include +#include + +// We steal access to the clock and thread functions so that we can +// work on Windows too. These functions are *not* part of nng's public +// API, so don't be lazy like this! All nni_ symbols are subject to +// change without notice, and not part of the stable API or ABI. +#include "core/nng_impl.h" + +static void latency_client(const char *, int, int); +static void latency_server(const char *, int, int); +static void do_remote_lat(int argc, char **argv); +static void do_local_lat(int argc, char **argv); +static void die(const char *, ...); + +// perf implements the same performance tests found in the standard +// nanomsg & mangos performance tests. As with mangos, the decision +// about which test to run is determined by the program name (ARGV[0}]) +// that it is run under. +// +// Options are: +// +// - remote_lat - remote latency side (client, aka latency_client) +// - local_lat - local latency side (server, aka latency_server) +// - local_thr - local throughput side +// - remote_thr - remote throughput side +// - inproc_lat - inproc latency +// - inproc_thr - inproc throughput +// + +int +main(int argc, char **argv) +{ + char *prog; + + // Allow -m or whatever to override argv[0]. + if ((argc >= 3) && (strcmp(argv[1], "-m") == 0)) { + prog = argv[1]; + argv += 3; + argc -= 3; + } else { + if (((prog = strrchr(argv[0], '/')) != NULL) || + ((prog = strrchr(argv[0], '\\')) != NULL) || + ((prog = strrchr(argv[0], ':')) != NULL)) { + prog++; + } else { + prog = argv[0]; + } + argc--; + argv++; + } + if ((strcmp(prog, "remote_lat") == 0) || + (strcmp(prog, "latency_client") == 0)) { + do_remote_lat(argc, argv); + } else if ((strcmp(prog, "local_lat") == 0) || + (strcmp(prog, "latency_server") == 0)) { + do_local_lat(argc, argv); + } else { + die("Unknown program mode? Use -m ."); + } +} + + +static void +die(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + exit(2); +} + + +static int +parse_int(const char *arg, const char *what) +{ + long val; + char *eptr; + + val = strtol(arg, &eptr, 10); + // Must be a postive number less than around a billion. + if ((val < 0) || (val > (1<<30)) || (*eptr != 0) || (eptr == arg)) { + die("Invalid %s", what); + } + return ((int) val); +} + + +void +do_local_lat(int argc, char **argv) +{ + long int msgsize; + long int trips; + + if (argc != 3) { + die("Usage: local_lat "); + } + + msgsize = parse_int(argv[1], "message size"); + trips = parse_int(argv[2], "round-trips"); + + latency_server(argv[0], msgsize, trips); +} + + +void +do_remote_lat(int argc, char **argv) +{ + long int msgsize; + long int trips; + + if (argc != 3) { + die("Usage: remote_lat "); + } + + msgsize = parse_int(argv[1], "message size"); + trips = parse_int(argv[2], "round-trips"); + + latency_client(argv[0], msgsize, trips); +} + + +void +latency_client(const char *addr, int msgsize, int trips) +{ + nng_socket *s; + nng_msg *msg; + nni_time start, end; + int rv; + int i; + float total; + float latency; + + if ((rv = nng_open(&s, NNG_PROTO_PAIR)) != 0) { + die("nng_socket: %s", nng_strerror(rv)); + } + + // XXX: set no delay + // XXX: other options (TLS in the future?, Linger?) + + if ((rv = nng_dial(s, addr, NULL, NNG_FLAG_SYNCH)) != 0) { + die("nng_dial: %s", nng_strerror(rv)); + } + + if (nng_msg_alloc(&msg, msgsize) != 0) { + die("nng_msg_alloc: %s", nng_strerror(rv)); + } + + start = nni_clock(); + for (i = 0; i < trips; i++) { + if ((rv = nng_sendmsg(s, msg, 0)) != 0) { + die("nng_sendmsg: %s", nng_strerror(rv)); + } + + if ((rv = nng_recvmsg(s, &msg, 0)) != 0) { + die("nng_recvmsg: %s", nng_strerror(rv)); + } + } + end = nni_clock(); + + nni_msg_free(msg); + + total = (end - start) / 1.0; + latency = (total / (trips * 2)); + printf("total time: %.3f [s]\n", total / 1000000.0); + printf("message size: %d [B]\n", msgsize); + printf("round trip count: %d\n", trips); + printf("average latency: %.3f [us]\n", latency); +} + + +void +latency_server(const char *addr, int msgsize, int trips) +{ + nng_socket *s; + nng_msg *msg; + int rv; + int i; + size_t len; + + if ((rv = nng_open(&s, NNG_PROTO_PAIR)) != 0) { + die("nng_socket: %s", nng_strerror(rv)); + } + + // XXX: set no delay + // XXX: other options (TLS in the future?, Linger?) + + if ((rv = nng_listen(s, addr, NULL, NNG_FLAG_SYNCH)) != 0) { + die("nng_listen: %s", nng_strerror(rv)); + } + + for (i = 0; i < trips; i++) { + if ((rv = nng_recvmsg(s, &msg, 0)) != 0) { + die("nng_recvmsg: %s", nng_strerror(rv)); + } + nng_msg_body(msg, &len); + if (len != msgsize) { + die("wrong message size: %d != %d", len, msgsize); + } + if ((rv = nng_sendmsg(s, msg, 0)) != 0) { + die("nng_sendmsg: %s", nng_strerror(rv)); + } + } + + // Wait a bit for things to drain... linger should do this. + // 100ms ought to be enough. + nni_usleep(100000); +} -- cgit v1.2.3-70-g09d2