1 //
2 // Copyright (C) 2015 Aaron Ball <nullspoon@iohq.net>
3 //
4 // Cnetbench is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Cnetbench is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with cnetbench. If not, see <http://www.gnu.org/licenses/>.
16 //
17 #include <stdlib.h>
18 #include <stdio.h>
19 #include <sys/socket.h>
20 #include <arpa/inet.h>
21 #include <netinet/in.h>
22 #include <strings.h>
23 #include <unistd.h>
24 #include <sys/time.h>
25 #include <wait.h>
26
27
28 //
29 // Watches an array of pids for their exit, and updates the output array
30 // exit_times with each pid's exit timeval struct timestamp (epoch with
31 // microseconds).
32 //
33 // Note that this function does not start the pids, but only watches them. To
34 // calculate runtime, external code needs to track when the pids were created.
35 //
36 // @param pids Array of pid_t pids to be watched
37 // @param count Number of pids in the pids array
38 // @param exit_times Array of timeval structs that will contain exit times for
39 // all of the pids in the array. Must be at least the size of
40 // the pids array or unpredictable behavior may result.
41 //
42 // @return int Overall status of all exited pids (if any fail exit, returns 1)
43 //
44 int time_pids(pid_t* pids, int count, struct timeval *exit_times) {
45 int i;
46 int waiting = 1;
47
48 while(waiting != 0) {
49 waiting = 0;
50 for(i = 0; i < count; i++) {
51 // Pid with this index has exited, skip it.
52 if(pids[i] == 0)
53 continue;
54
55 // 0 = state change (exit)
56 if(waitpid(pids[i], NULL, WNOHANG) != 0) {
57 // Child has exited
58 pids[i] = 0;
59 // Write the exit time
60 gettimeofday(&exit_times[i], NULL);
61 } else {
62 waiting = 1;
63 }
64 }
65 sleep(1);
66 }
67 return 0;
68 }
69
70
71 //
72 // Performs a benchmark test on the specified destination/port, sending the
73 // specified amount of data.
74 //
75 // @param amount Megabytes to send
76 // @param dest Destination host to send data to
77 // @param port Destination port to send the data on
78 //
79 // @return int Succcess (0) or failure (1) of data send
80 //
81 int test(int amount, char *dest, int port) {
82 int socket_desc;
83 // This is 1 megabyte
84 long size = 1048576;
85 char *message = calloc(size, 1);
86
87 // AF_INET: ipv4 (AF_INET6 would be ipv6)
88 // SOCK_STREAM: TCP
89 // 0: Protocol 0 (IP)
90 socket_desc = socket(AF_INET, SOCK_STREAM, 0);
91
92 if(socket_desc == -1) {
93 printf("Error creating socket\n");
94 return 1;
95 }
96
97 // Set socket values
98 struct sockaddr_in server;
99 server.sin_addr.s_addr = inet_addr(dest);
100 server.sin_family = AF_INET;
101 server.sin_port = htons(port);
102
103 if(connect(socket_desc , (struct sockaddr *)&server , sizeof(server)) < 0) {
104 printf("Connection error\n");
105 return -1;
106 }
107 printf("Connected\n");
108
109 // Send some data
110 long i = 0;
111 while(i < amount) {
112 if(send(socket_desc, message, size, 0) < 0) {
113 return -1;
114 }
115 i++;
116 }
117
118 free(message);
119 return 0;
120 }
121
122
123 //
124 // Forks sub-processes off stream_count times, to test bandwidth to the
125 // specified destination host/port, sending the specified amount of data.
126 //
127 // Note that start_times and exit_times are two arrays that will receive start
128 // and exit timestamps for each pid for timing purposes.
129 //
130 // @param amount Amount of data in megabytes to send
131 // @param stream_count Number of data streams to test concurrently
132 // @param dest Destination host to send data to
133 // @param port Destination port to send data over
134 // @param start_times Output array of timeval start time timestamps
135 // @param exit_times Output array of timeval exit time timestamps
136 //
137 // @return int Success (0) or failure(1)
138 //
139 int spawn_tests(int amount,
140 int stream_count,
141 char* dest,
142 int port,
143 struct timeval *start_times,
144 struct timeval *exit_times) {
145 pid_t pids[stream_count];
146
147 pid_t pid;
148 int i = 0;
149 while(i < stream_count) {
150 // Fork
151 pid = fork();
152
153 if(pid == 0) {
154 // Execute the test
155 printf("Fork %d starting\n", i);
156 test(amount, dest, port);
157 exit(0);
158 } else if(pid > 0) {
159 // parent
160 pids[i] = pid;
161 // Set start time
162 gettimeofday(&start_times[i], NULL);
163 i++;
164 } else {
165 // failure
166 printf("%d: Failure\n", i);
167 return 1;
168 }
169 }
170
171 // Wait for all children to exit, and clock their exit time
172 time_pids(pids, stream_count, exit_times);
173
174 printf("Fin!\n");
175 return 0;
176 }
177
178
179 //
180 // Ye olde main
181 //
182 int main(int argc, char *argv[]) {
183 int amount;
184 int stream_count;
185 char *server;
186 int port;
187
188 if(argc != 5) {
189 printf("\nUsage:\n");
190 printf(" %s <megabytes> <stream_count> <server-ip> <port>\n\n", argv[0]);
191 return 1;
192 }
193
194 // Parse program arguments
195 amount = atoi(argv[1]);
196 stream_count = atoi(argv[2]);
197 server = argv[3];
198 port = atoi(argv[4]);
199
200 struct timeval start_times[stream_count];
201 struct timeval exit_times[stream_count];
202
203 spawn_tests(amount, stream_count, server, port, start_times, exit_times);
204
205 // Clear a little space
206 printf("\n\n");
207
208 long total_amount = amount * stream_count;
209 double total_time = 0;
210
211 for(int i = 0; i < stream_count; i++) {
212 // calculate average transfer rate for this thread
213
214 // Second difference, multiplied by 1000 to make room for microseconds
215 double time_s = 1000 * (exit_times[i].tv_sec - start_times[i].tv_sec);
216 // Microsecond difference
217 double time_ms = exit_times[i].tv_usec - start_times[i].tv_usec;
218 // Sum of microsecond and second differences
219 double time = (time_s + time_ms) / 1000;
220
221 // Append duration to the total duration of transfers.
222 total_time += time;
223
224 double avg = amount / time;
225
226 printf("Fork %d/%d transferred %d MB in %.4f seconds, averaging %.2f MB/s\n",
227 i + 1,
228 stream_count,
229 amount,
230 time,
231 avg
232 );
233 }
234
235 printf("\n%d forks transferred a total of %ld MBs in %.2f parallel seconds\n",
236 stream_count,
237 total_amount,
238 total_time
239 );
240
241 return 0;
242 }
|