diff options
author | Aaron Ball <nullspoon@iohq.net> | 2015-10-05 08:37:27 -0600 |
---|---|---|
committer | Aaron Ball <nullspoon@iohq.net> | 2015-10-05 08:37:38 -0600 |
commit | 522efa1f37ada373c6ea6624ab6d00e0fcb53e2e (patch) | |
tree | 6efb3837410e1583bec0a7e12084c5c5721f1073 /src | |
parent | 7c9eea953af3f42553b75a4dabb81f4b40ae6540 (diff) | |
parent | 2f480e7c8dab41e4a637e7f86a972d41ee7e8042 (diff) | |
download | cnetbench-master.tar.gz cnetbench-master.tar.xz |
Diffstat (limited to 'src')
-rw-r--r-- | src/common.c | 59 | ||||
-rw-r--r-- | src/common.h | 26 | ||||
-rw-r--r-- | src/main.c | 287 | ||||
-rw-r--r-- | src/test.c | 141 | ||||
-rw-r--r-- | src/test.h | 43 |
5 files changed, 370 insertions, 186 deletions
diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..558dedf --- /dev/null +++ b/src/common.c @@ -0,0 +1,59 @@ +// +// Copyright (C) 2015 Aaron Ball <nullspoon@iohq.net> +// +// Cnetbench 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 3 of the License, or +// (at your option) any later version. +// +// Cnetbench 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 cnetbench. If not, see <http://www.gnu.org/licenses/>. +// +#include "common.h" + +// +// Watches an array of pids for their exit, and updates the output array +// exit_times with each pid's exit timeval struct timestamp (epoch with +// microseconds). +// +// Note that this function does not start the pids, but only watches them. To +// calculate runtime, external code needs to track when the pids were created. +// +// @param pids Array of pid_t pids to be watched +// @param count Number of pids in the pids array +// @param exit_times Array of timeval structs that will contain exit times for +// all of the pids in the array. Must be at least the size of +// the pids array or unpredictable behavior may result. +// +// @return int Overall status of all exited pids (if any fail exit, returns 1) +// +int time_pids(pid_t* pids, int count, struct timeval *exit_times) { + int i; + int waiting = 1; + + while(waiting != 0) { + waiting = 0; + for(i = 0; i < count; i++) { + // Pid with this index has exited, skip it. + if(pids[i] == 0) + continue; + + // 0 = state change (exit) + if(waitpid(pids[i], NULL, WNOHANG) != 0) { + // Child has exited + pids[i] = 0; + // Write the exit time + gettimeofday(&exit_times[i], NULL); + } else { + waiting = 1; + } + } + sleep(1); + } + return 0; +} diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..7fdb513 --- /dev/null +++ b/src/common.h @@ -0,0 +1,26 @@ +// +// Copyright (C) 2015 Aaron Ball <nullspoon@iohq.net> +// +// Cnetbench 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 3 of the License, or +// (at your option) any later version. +// +// Cnetbench 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 cnetbench. If not, see <http://www.gnu.org/licenses/>. +// +#include <stdlib.h> +// For sleep() +#include <unistd.h> +// For pid_t +#include <netinet/in.h> +// For timing functionality +#include <sys/time.h> +#include <wait.h> + +int time_pids(pid_t*, int, struct timeval*); @@ -16,162 +16,119 @@ // #include <stdlib.h> #include <stdio.h> -#include <sys/socket.h> -#include <arpa/inet.h> -#include <netinet/in.h> -#include <strings.h> -#include <unistd.h> -#include <sys/time.h> -#include <wait.h> +#include <string.h> + +#include "common.h" +#include "test.h" + + +void get_help() { + printf( + "\n" + "Cnetbench is a simple protocol-agnostic network benchmarking program.\n" + "Simply put, it is a fire-hydrant of data and sends as much data as is\n" + "allowed, and reports what those numbers are. This can be single stream\n" + "or multiple concurrent data streams.\n" + "\n" + "Usage:\n\n" + " cnetbench -d <destination> -p <port> [-m <megabytes>] [-c <stream_count>]" + "\n\n"); +} // -// Watches an array of pids for their exit, and updates the output array -// exit_times with each pid's exit timeval struct timestamp (epoch with -// microseconds). -// -// Note that this function does not start the pids, but only watches them. To -// calculate runtime, external code needs to track when the pids were created. -// -// @param pids Array of pid_t pids to be watched -// @param count Number of pids in the pids array -// @param exit_times Array of timeval structs that will contain exit times for -// all of the pids in the array. Must be at least the size of -// the pids array or unpredictable behavior may result. +// Prints test results in a friendly, human-readable format. // -// @return int Overall status of all exited pids (if any fail exit, returns 1) +// @param t_test* Test type containing test parameters +// @param start_times Array of child start times, ordered by child process index +// @param exit_times Array of child exit times, ordered by child process index // -int time_pids(pid_t* pids, int count, struct timeval *exit_times) { - int i; - int waiting = 1; - - while(waiting != 0) { - waiting = 0; - for(i = 0; i < count; i++) { - // Pid with this index has exited, skip it. - if(pids[i] == 0) - continue; - - // 0 = state change (exit) - if(waitpid(pids[i], NULL, WNOHANG) != 0) { - // Child has exited - pids[i] = 0; - // Write the exit time - gettimeofday(&exit_times[i], NULL); - } else { - waiting = 1; - } - } - sleep(1); - } - return 0; -} +void print_results(t_test *t, + struct timeval *start_times, + struct timeval *exit_times) { + // Clear a little space + printf("\n\n"); + long total_amount = t->amount * t->streams; + double total_time = 0; -// -// Performs a benchmark test on the specified destination/port, sending the -// specified amount of data. -// -// @param amount Megabytes to send -// @param dest Destination host to send data to -// @param port Destination port to send the data on -// -// @return int Succcess (0) or failure (1) of data send -// -int test(int amount, char *dest, int port) { - int socket_desc; - // This is 1 megabyte - long size = 1048576; - char *message = calloc(size, 1); - - // AF_INET: ipv4 (AF_INET6 would be ipv6) - // SOCK_STREAM: TCP - // 0: Protocol 0 (IP) - socket_desc = socket(AF_INET, SOCK_STREAM, 0); - - if(socket_desc == -1) { - printf("Error creating socket\n"); - return 1; - } + for(int i = 0; i < t->streams; i++) { + // calculate average transfer rate for this thread - // Set socket values - struct sockaddr_in server; - server.sin_addr.s_addr = inet_addr(dest); - server.sin_family = AF_INET; - server.sin_port = htons(port); + // Second difference, multiplied by 1000 to make room for microseconds + double time_s = 1000 * (exit_times[i].tv_sec - start_times[i].tv_sec); + // Microsecond difference + double time_ms = exit_times[i].tv_usec - start_times[i].tv_usec; + // Sum of microsecond and second differences + double time = (time_s + time_ms) / 1000; - if(connect(socket_desc , (struct sockaddr *)&server , sizeof(server)) < 0) { - printf("Connection error\n"); - return -1; - } - printf("Connected\n"); + // Append duration to the total duration of transfers. + total_time += time; - // Send some data - long i = 0; - while(i < amount) { - if(send(socket_desc, message, size, 0) < 0) { - return -1; - } - i++; + double avg = t->amount / time; + + printf("Fork %d/%d transferred %d MB in %.4f seconds, averaging %.2f MB/s\n", + i + 1, + t->streams, + t->amount, + time, + avg + ); } - free(message); - return 0; + printf("\n%d forks transferred a total of %ld MBs in %.2f parallel seconds\n", + t->streams, + total_amount, + total_time + ); } // -// Forks sub-processes off stream_count times, to test bandwidth to the -// specified destination host/port, sending the specified amount of data. -// -// Note that start_times and exit_times are two arrays that will receive start -// and exit timestamps for each pid for timing purposes. -// -// @param amount Amount of data in megabytes to send -// @param stream_count Number of data streams to test concurrently -// @param dest Destination host to send data to -// @param port Destination port to send data over -// @param start_times Output array of timeval start time timestamps -// @param exit_times Output array of timeval exit time timestamps -// -// @return int Success (0) or failure(1) -// -int spawn_tests(int amount, - int stream_count, - char* dest, - int port, - struct timeval *start_times, - struct timeval *exit_times) { - pid_t pids[stream_count]; - - pid_t pid; - int i = 0; - while(i < stream_count) { - // Fork - pid = fork(); - - if(pid == 0) { - // Execute the test - printf("Fork %d starting\n", i); - test(amount, dest, port); - exit(0); - } else if(pid > 0) { - // parent - pids[i] = pid; - // Set start time - gettimeofday(&start_times[i], NULL); - i++; +// Parses the program arguments and populates a t_test type with the values +// specified by the user. +// +// @param argc Number of arguments passed to the program (the main argc) +// @param argv Arguments passed to the program (the main argv) +// @param test T_test object to be populated +// +int parse_args(int argc, char *argv[], t_test *test) { + int i = 1; + + char server = 0; + char port = 0; + + while(i < argc) { + if(strcmp(argv[i], "-m") == 0 || strcmp(argv[i], "--megabytes") == 0) { + ++i; + test->amount = atoi(argv[i]); + } else if(strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--count") == 0) { + ++i; + test->streams = atoi(argv[i]); + } else if(strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--dest") == 0) { + ++i; + strcpy(test->server, argv[i]); + server = 1; + } else if(strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) { + ++i; + test->port = atoi(argv[i]); + port = 1; } else { - // failure - printf("%d: Failure\n", i); + get_help(); return 1; } + ++i; } - // Wait for all children to exit, and clock their exit time - time_pids(pids, stream_count, exit_times); + if(server == 0) { + printf("Please specify a destination server (-d,--dest) to test with.\n"); + return 1; + } + if(port == 0) { + printf("Please specify a port (-p,--port) to run tests over.\n"); + return 1; + } - printf("Fin!\n"); return 0; } @@ -180,63 +137,21 @@ int spawn_tests(int amount, // Ye olde main // int main(int argc, char *argv[]) { - int amount; - int stream_count; - char *server; - int port; - - if(argc != 5) { - printf("\nUsage:\n"); - printf(" %s <megabytes> <stream_count> <server-ip> <port>\n\n", argv[0]); - return 1; - } + t_test test; + // Initialize the test object with defaults + new_test(&test); // Parse program arguments - amount = atoi(argv[1]); - stream_count = atoi(argv[2]); - server = argv[3]; - port = atoi(argv[4]); + int arg_status = parse_args(argc, argv, &test); - struct timeval start_times[stream_count]; - struct timeval exit_times[stream_count]; - - spawn_tests(amount, stream_count, server, port, start_times, exit_times); - - // Clear a little space - printf("\n\n"); - - long total_amount = amount * stream_count; - double total_time = 0; - - for(int i = 0; i < stream_count; i++) { - // calculate average transfer rate for this thread - - // Second difference, multiplied by 1000 to make room for microseconds - double time_s = 1000 * (exit_times[i].tv_sec - start_times[i].tv_sec); - // Microsecond difference - double time_ms = exit_times[i].tv_usec - start_times[i].tv_usec; - // Sum of microsecond and second differences - double time = (time_s + time_ms) / 1000; + if(arg_status > 0) + return 1; - // Append duration to the total duration of transfers. - total_time += time; + struct timeval start_times[test.streams]; + struct timeval exit_times[test.streams]; - double avg = amount / time; + test_start_all(&test, start_times, exit_times); + print_results(&test, start_times, exit_times); - printf("Fork %d/%d transferred %d MB in %.4f seconds, averaging %.2f MB/s\n", - i + 1, - stream_count, - amount, - time, - avg - ); - } - - printf("\n%d forks transferred a total of %ld MBs in %.2f parallel seconds\n", - stream_count, - total_amount, - total_time - ); - return 0; } diff --git a/src/test.c b/src/test.c new file mode 100644 index 0000000..83015c1 --- /dev/null +++ b/src/test.c @@ -0,0 +1,141 @@ +// +// Copyright (C) 2015 Aaron Ball <nullspoon@iohq.net> +// +// Cnetbench 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 3 of the License, or +// (at your option) any later version. +// +// Cnetbench 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 cnetbench. If not, see <http://www.gnu.org/licenses/>. +// +#include "test.h" + +#ifndef cnetbench_test +#define cnetbench_test + +#define test_default_amount 64 +#define test_default_streams 1 + +void new_test(t_test *test) { + test->amount = test_default_amount; + test->streams = test_default_streams; +} + + +// +// Performs a benchmark test on the specified destination/port, sending the +// specified amount of data. +// +// @param amount Megabytes to send +// @param dest Destination host to send data to +// @param port Destination port to send the data on +// +// @return int Succcess (0) or failure (1) of data send +// +int test_execute(t_test* test) { + int socket_desc; + // This is 1 megabyte + long size = 1048576; + char *message = calloc(size, 1); + + // AF_INET: ipv4 (AF_INET6 would be ipv6) + // SOCK_STREAM: TCP + // 0: Protocol 0 (IP) + socket_desc = socket(AF_INET, SOCK_STREAM, 0); + + if(socket_desc == -1) { + printf("Error creating socket\n"); + return 1; + } + + // Set socket values + struct sockaddr_in server; + server.sin_addr.s_addr = inet_addr(test->server); + server.sin_family = AF_INET; + server.sin_port = htons(test->port); + + if(connect(socket_desc , (struct sockaddr *)&server , sizeof(server)) < 0) { + printf("Connection error\n"); + return -1; + } + printf("Connected\n"); + + // Send some data + long i = 0; + while(i < test->amount) { + if(send(socket_desc, message, size, 0) < 0) { + return -1; + } + i++; + } + + free(message); + return 0; +} + + +// +// Forks sub-processes off stream_count times, to test bandwidth to the +// specified destination host/port, sending the specified amount of data. +// +// Note that start_times and exit_times are two arrays that will receive start +// and exit timestamps for each pid for timing purposes. +// +// @param amount Amount of data in megabytes to send +// @param stream_count Number of data streams to test concurrently +// @param dest Destination host to send data to +// @param port Destination port to send data over +// @param start_times Output array of timeval start time timestamps +// @param exit_times Output array of timeval exit time timestamps +// +// @return int Success (0) or failure(1) +// +//int spawn_tests(int amount, +// int stream_count, +// char* dest, +// int port, +// struct timeval *start_times, +// struct timeval *exit_times) { +int test_start_all(t_test *test_def, + struct timeval *start_times, + struct timeval *exit_times) { + pid_t pids[test_def->streams]; + + pid_t pid; + int i = 0; + while(i < test_def->streams) { + // Fork + pid = fork(); + + if(pid == 0) { + // Execute the test + printf("Fork %d starting\n", i); + test_execute(test_def); + exit(0); + } else if(pid > 0) { + // parent + pids[i] = pid; + // Set start time + gettimeofday(&start_times[i], NULL); + i++; + } else { + // failure + printf("%d: Failure\n", i); + return 1; + } + } + + // Wait for all children to exit, and clock their exit time + time_pids(pids, test_def->streams, exit_times); + + printf("Fin!\n"); + return 0; +} + +#endif diff --git a/src/test.h b/src/test.h new file mode 100644 index 0000000..753109c --- /dev/null +++ b/src/test.h @@ -0,0 +1,43 @@ +// +// Copyright (C) 2015 Aaron Ball <nullspoon@iohq.net> +// +// Cnetbench 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 3 of the License, or +// (at your option) any later version. +// +// Cnetbench 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 cnetbench. If not, see <http://www.gnu.org/licenses/>. +// +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +// Networking-related tasks +#include <sys/socket.h> +#include <arpa/inet.h> +// For pid timing +#include <sys/time.h> +#include <wait.h> +// For fork() +#include <unistd.h> + +// for time_pids function +#include "common.h" + +typedef struct { + int amount; + int streams; + char server[256]; + int port; +} t_test; + +void new_test(t_test*); + +int test_execute(t_test*); + +int test_start_all(t_test*, struct timeval*, struct timeval*); |