From 05fa4879dd8c70156fd98eabed4634098b0feecb Mon Sep 17 00:00:00 2001 From: dimitri staessens Date: Sun, 30 Oct 2016 10:53:04 +0100 Subject: tools: Add operf tool This tool allows bidirectional bandwidth measurement between a client and server application. The server reflects all traffic back to the client. The traffic can be capped at a certain rate or set to flood. --- src/tools/CMakeLists.txt | 1 + src/tools/operf/CMakeLists.txt | 21 ++++ src/tools/operf/operf.c | 179 +++++++++++++++++++++++++++++ src/tools/operf/operf_client.c | 248 +++++++++++++++++++++++++++++++++++++++++ src/tools/operf/operf_server.c | 179 +++++++++++++++++++++++++++++ 5 files changed, 628 insertions(+) create mode 100644 src/tools/operf/CMakeLists.txt create mode 100644 src/tools/operf/operf.c create mode 100644 src/tools/operf/operf_client.c create mode 100644 src/tools/operf/operf_server.c (limited to 'src/tools') diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index e8c24557..e8181d5f 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -2,3 +2,4 @@ add_subdirectory(irm) add_subdirectory(echo) add_subdirectory(cbr) add_subdirectory(oping) +add_subdirectory(operf) diff --git a/src/tools/operf/CMakeLists.txt b/src/tools/operf/CMakeLists.txt new file mode 100644 index 00000000..b63d24ee --- /dev/null +++ b/src/tools/operf/CMakeLists.txt @@ -0,0 +1,21 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +include_directories(${CMAKE_SOURCE_DIR}/include) +include_directories(${CMAKE_BINARY_DIR}/include) + +find_library(LIBM_LIBRARIES m) +if(NOT LIBM_LIBRARIES) + message(FATAL_ERROR "libm not found") +endif() + +set(SOURCE_FILES + # Add source files here + operf.c +) + +add_executable(operf ${SOURCE_FILES}) + +target_link_libraries(operf LINK_PUBLIC ${LIBM_LIBRARIES} ouroboros) + +install(TARGETS operf RUNTIME DESTINATION usr/bin) diff --git a/src/tools/operf/operf.c b/src/tools/operf/operf.c new file mode 100644 index 00000000..b52109cf --- /dev/null +++ b/src/tools/operf/operf.c @@ -0,0 +1,179 @@ +/* + * Ouroboros - Copyright (C) 2016 + * + * Ouroboros perf application + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define _POSIX_C_SOURCE 199506L + +#include +#include + +#include +#include +#include +#include +#include + +#define OPERF_BUF_SIZE (1024 * 1024) + +#define OPERF_MAX_FLOWS 256 + +struct c { + char * s_apn; + int size; + long rate; + bool flood; + int duration; + + size_t sent; + size_t rcvd; + + flow_set_t * flows; + fqueue_t * fq; + + pthread_t reader_pt; + pthread_t writer_pt; +} client; + +struct s { + struct timespec times[OPERF_MAX_FLOWS]; + flow_set_t * flows; + fqueue_t * fq; + pthread_mutex_t lock; + + uint8_t buffer[OPERF_BUF_SIZE]; + ssize_t timeout; + + pthread_t cleaner_pt; + pthread_t accept_pt; + pthread_t server_pt; +} server; + +#include "operf_client.c" +#include "operf_server.c" + +static void usage(void) +{ + printf("Usage: operf [OPTION]...\n" + "Measures bandwidth between a client and a server\n" + " -l, --listen Run in server mode\n" + "\n" + " -n, --server-apn Name of the operf server\n" + " -d, --duration Test duration (s, default 60)\n" + " -r, --rate Rate (b/s)\n" + " -s, --size Payload size (B, default 1500)\n" + " -f, --flood Send SDUs as fast as possible\n" + " --help Display this help text and exit\n"); +} + +int main(int argc, char ** argv) +{ + int ret = -1; + char * rem = NULL; + bool serv = false; + char ** argv_dup = argv; + + argc--; + argv++; + + client.s_apn = NULL; + client.size = 1500; + client.duration = 60000; + server.timeout = 1000; /* ms */ + client.rate = 1000000; + client.flood = false; + + while (argc > 0) { + if (strcmp(*argv, "-n") == 0 || + strcmp(*argv, "--server_apn") == 0) { + client.s_apn = *(++argv); + --argc; + } else if (strcmp(*argv, "-s") == 0 || + strcmp(*argv, "--size") == 0) { + client.size = strtol(*(++argv), &rem, 10); + --argc; + } else if (strcmp(*argv, "-d") == 0 || + strcmp(*argv, "--duration") == 0) { + client.duration = strtol(*(++argv), &rem, 10) * 1000; + --argc; + } else if (strcmp(*argv, "-r") == 0 || + strcmp(*argv, "--rate") == 0) { + client.rate = strtol(*(++argv), &rem, 10); + if (*rem == 'k') + client.rate *= 1000; + if (*rem == 'M') + client.rate *= MILLION; + if (*rem == 'G') + client.rate *= BILLION; + --argc; + } else if (strcmp(*argv, "-f") == 0 || + strcmp(*argv, "--flood") == 0) { + client.flood = true; + } else if (strcmp(*argv, "-l") == 0 || + strcmp(*argv, "--listen") == 0) { + serv = true; + } else { + usage(); + exit(EXIT_SUCCESS); + } + argc--; + argv++; + } + + if (serv) { + if (ap_init(argv_dup[0])) { + printf("Failed to init AP.\n"); + exit(EXIT_FAILURE); + } + + ret = server_main(); + } else { + if (ap_init(NULL)) { + printf("Failed to init AP.\n"); + exit(EXIT_FAILURE); + } + + if (client.s_apn == NULL) { + printf("No server specified.\n"); + usage(); + exit(EXIT_SUCCESS); + } + if (client.size > OPERF_BUF_SIZE) { + printf("Packet size truncated to %d bytes.\n", + OPERF_BUF_SIZE); + client.size = OPERF_BUF_SIZE; + } + + if (client.size < 64) { + printf("Packet size set to 64 bytes.\n"); + client.size = 64; + } + + ret = client_main(); + } + + ap_fini(); + + if (ret < 0) + exit(EXIT_FAILURE); + + exit(EXIT_SUCCESS); +} diff --git a/src/tools/operf/operf_client.c b/src/tools/operf/operf_client.c new file mode 100644 index 00000000..1f6226d4 --- /dev/null +++ b/src/tools/operf/operf_client.c @@ -0,0 +1,248 @@ +/* + * Ouroboros - Copyright (C) 2016 + * + * Ouroboros ping application + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +#ifdef __FreeBSD__ +#define __XSI_VISIBLE 500 +#endif + +#include +#include +#include +#include +#include +#include +#include + +void shutdown_client(int signo, siginfo_t * info, void * c) +{ + (void) info; + (void) c; + + switch(signo) { + case SIGINT: + case SIGTERM: + case SIGHUP: + pthread_cancel(client.reader_pt); + pthread_cancel(client.writer_pt); + default: + return; + } +} + +void * reader(void * o) +{ + struct timespec timeout = {2, 0}; + + char buf[OPERF_BUF_SIZE]; + int fd = 0; + int msg_len = 0; + + (void) o; + + /* FIXME: use flow timeout option once we have it */ + while (flow_event_wait(client.flows, client.fq, &timeout) != -ETIMEDOUT) + while ((fd = fqueue_next(client.fq)) >= 0) { + msg_len = flow_read(fd, buf, OPERF_BUF_SIZE); + if (msg_len != client.size) { + printf("Invalid message on fd %d.\n", fd); + continue; + } + + ++client.rcvd; + } + + return (void *) 0; +} + +void * writer(void * o) +{ + int * fdp = (int *) o; + long gap = client.size * 8.0 * (BILLION / (double) client.rate); + + struct timespec now; + struct timespec start; + struct timespec intv = {(gap / BILLION), gap % BILLION}; + + char * buf = malloc(client.size); + if (buf == NULL) + return (void *) -ENOMEM; + + if (fdp == NULL) + return (void *) -EINVAL; + + memset(buf, 0, client.size); + + if (client.flood) + printf("Flooding %s with %d byte SDUs for %d seconds.\n\n", + client.s_apn, client.size, client.duration / 1000); + else + printf("Sending %d byte SDUs for %d s to %s at %.3lf Mb/s.\n\n", + client.size, client.duration / 1000, client.s_apn, + client.rate / (double) MILLION); + + clock_gettime(CLOCK_REALTIME, &start); + clock_gettime(CLOCK_REALTIME, &now); + + pthread_cleanup_push((void (*) (void *)) free, buf); + + if (client.flood) { + while (ts_diff_ms(&start, &now) < client.duration) { + if (flow_write(*fdp, buf, client.size) == -1) { + printf("Failed to send SDU.\n"); + flow_dealloc(*fdp); + free(buf); + return (void *) -1; + } + + ++client.sent; + + clock_gettime(CLOCK_REALTIME, &now); + } + } else { + while (ts_diff_ms(&start, &now) < client.duration) { + if (flow_write(*fdp, buf, client.size) == -1) { + printf("Failed to send SDU.\n"); + flow_dealloc(*fdp); + free(buf); + return (void *) -1; + } + + ++client.sent; + + nanosleep(&intv, NULL); + + clock_gettime(CLOCK_REALTIME, &now); + } + } + + pthread_cleanup_pop(true); + + printf("Test finished.\n"); + + return (void *) 0; +} + +static int client_init(void) +{ + client.flows = flow_set_create(); + if (client.flows == NULL) + return -ENOMEM; + + client.fq = fqueue_create(); + if (client.fq == NULL) { + flow_set_destroy(client.flows); + return -ENOMEM; + } + + client.sent = 0; + client.rcvd = 0; + + return 0; +} + +void client_fini(void) +{ + if (client.flows != NULL) + flow_set_destroy(client.flows); + + if (client.fq != NULL) + fqueue_destroy(client.fq); +} + +int client_main(void) +{ + struct sigaction sig_act; + + struct timespec tic; + struct timespec toc; + + int fd; + + memset(&sig_act, 0, sizeof sig_act); + sig_act.sa_sigaction = &shutdown_client; + sig_act.sa_flags = 0; + + if (sigaction(SIGINT, &sig_act, NULL) || + sigaction(SIGTERM, &sig_act, NULL) || + sigaction(SIGHUP, &sig_act, NULL) || + sigaction(SIGPIPE, &sig_act, NULL)) { + printf("Failed to install sighandler.\n"); + return -1; + } + + if (client_init()) { + printf("Failed to initialize client.\n"); + return -1; + } + + fd = flow_alloc(client.s_apn, NULL, NULL); + if (fd < 0) { + flow_set_destroy(client.flows); + fqueue_destroy(client.fq); + printf("Failed to allocate flow.\n"); + return -1; + } + + flow_set_add(client.flows, fd); + + if (flow_alloc_res(fd)) { + printf("Flow allocation refused.\n"); + flow_set_del(client.flows, fd); + flow_dealloc(fd); + client_fini(); + return -1; + } + + clock_gettime(CLOCK_REALTIME, &tic); + + pthread_create(&client.reader_pt, NULL, reader, NULL); + pthread_create(&client.writer_pt, NULL, writer, &fd); + + pthread_join(client.writer_pt, NULL); + + clock_gettime(CLOCK_REALTIME, &toc); + + pthread_join(client.reader_pt, NULL); + + printf("\n"); + printf("--- %s perf statistics ---\n", client.s_apn); + printf("%ld SDUs transmitted, ", client.sent); + printf("%ld received, ", client.rcvd); + printf("%ld%% packet loss, ", client.sent == 0 ? 0 : + 100 - ((100 * client.rcvd) / client.sent)); + printf("time: %.3f ms, ", ts_diff_us(&tic, &toc) / 1000.0); + printf("bandwidth: %.3lf Mb/s.\n", + (client.rcvd * client.size * 8) + / (double) ts_diff_us(&tic, &toc)); + + flow_set_del(client.flows, fd); + + flow_dealloc(fd); + + client_fini(); + + return 0; +} diff --git a/src/tools/operf/operf_server.c b/src/tools/operf/operf_server.c new file mode 100644 index 00000000..4eb93879 --- /dev/null +++ b/src/tools/operf/operf_server.c @@ -0,0 +1,179 @@ +/* + * Ouroboros - Copyright (C) 2016 + * + * Ouroboros perf application + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifdef __FreeBSD__ +#define __XSI_VISIBLE 500 +#endif + +#include +#include +#include + +void shutdown_server(int signo, siginfo_t * info, void * c) +{ + (void) info; + (void) c; + + switch(signo) { + case SIGINT: + case SIGTERM: + case SIGHUP: + pthread_cancel(server.accept_pt); + default: + return; + } +} + +void * cleaner_thread(void * o) +{ + int i = 0; + struct timespec now = {0, 0}; + + (void) o; + + while (true) { + clock_gettime(CLOCK_REALTIME, &now); + pthread_mutex_lock(&server.lock); + for (i = 0; i < OPERF_MAX_FLOWS; ++i) + if (flow_set_has(server.flows, i) && + ts_diff_ms(&server.times[i], &now) + > server.timeout) { + printf("Flow %d timed out.\n", i); + flow_set_del(server.flows, i); + flow_dealloc(i); + } + + pthread_mutex_unlock(&server.lock); + sleep(1); + } +} + +void * server_thread(void *o) +{ + int msg_len = 0; + struct timespec timeout = {0, 100 * MILLION}; + struct timespec now = {0, 0}; + int fd; + + (void) o; + + while (flow_event_wait(server.flows, server.fq, &timeout)) + while ((fd = fqueue_next(server.fq)) >= 0) { + msg_len = flow_read(fd, server.buffer, OPERF_BUF_SIZE); + if (msg_len < 0) + continue; + + clock_gettime(CLOCK_REALTIME, &now); + + pthread_mutex_lock(&server.lock); + server.times[fd] = now; + pthread_mutex_unlock(&server.lock); + + if (flow_write(fd, server.buffer, msg_len) < 0) { + printf("Error writing to flow (fd %d).\n", fd); + flow_dealloc(fd); + } + } + + return (void *) 0; +} + +void * accept_thread(void * o) +{ + int fd = 0; + struct timespec now = {0, 0}; + struct qos_spec qs; + + (void) o; + + printf("Ouroboros perf server started.\n"); + + while (true) { + fd = flow_accept(NULL, &qs); + if (fd < 0) { + printf("Failed to accept flow.\n"); + break; + } + + printf("New flow %d.\n", fd); + + if (flow_alloc_resp(fd, 0)) { + printf("Failed to give an allocate response.\n"); + flow_dealloc(fd); + continue; + } + + clock_gettime(CLOCK_REALTIME, &now); + + pthread_mutex_lock(&server.lock); + flow_set_add(server.flows, fd); + server.times[fd] = now; + pthread_mutex_unlock(&server.lock); + } + + return (void *) 0; +} + +int server_main(void) +{ + struct sigaction sig_act; + + memset(&sig_act, 0, sizeof sig_act); + sig_act.sa_sigaction = &shutdown_server; + sig_act.sa_flags = 0; + + if (sigaction(SIGINT, &sig_act, NULL) || + sigaction(SIGTERM, &sig_act, NULL) || + sigaction(SIGHUP, &sig_act, NULL) || + sigaction(SIGPIPE, &sig_act, NULL)) { + printf("Failed to install sighandler.\n"); + return -1; + } + + server.flows = flow_set_create(); + if (server.flows == NULL) + return 0; + + server.fq = fqueue_create(); + if (server.fq == NULL) { + flow_set_destroy(server.flows); + return -1; + } + + pthread_create(&server.cleaner_pt, NULL, cleaner_thread, NULL); + pthread_create(&server.accept_pt, NULL, accept_thread, NULL); + pthread_create(&server.server_pt, NULL, server_thread, NULL); + + pthread_join(server.accept_pt, NULL); + + pthread_cancel(server.server_pt); + pthread_cancel(server.cleaner_pt); + + flow_set_destroy(server.flows); + fqueue_destroy(server.fq); + + pthread_join(server.server_pt, NULL); + pthread_join(server.cleaner_pt, NULL); + + return 0; +} -- cgit v1.2.3