1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include <sys/wait.h>
4 
5 #include <err.h>
6 #include <fcntl.h>
7 #include <signal.h>
8 #include <stdio.h>
9 #include <stdint.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <unistd.h>
13 
14 #define NETPERF_CMD	"netperf"
15 #define NETPERF_PATH	"/usr/local/bin/" NETPERF_CMD
16 
17 #ifndef __DECONST
18 #define __DECONST(type, var)	((type)(uintptr_t)(const void *)(var))
19 #endif
20 
21 #ifndef __unused
22 #define __unused		__attribute__((__unused__))
23 #endif
24 
25 struct netperf_child {
26 	int		pipes[2];
27 	pid_t		pid;
28 };
29 
30 static struct netperf_child *instance;
31 static int ninstance;
32 
33 static void
34 usage(const char *cmd)
35 {
36 	fprintf(stderr, "%s -H host [-H host1] [-l len_s] [-i instances] "
37 	    "[-m msgsz] [-S sockbuf] [-r|-s] [-x]\n", cmd);
38 	exit(1);
39 }
40 
41 static void
42 sigint_handler(int sig __unused)
43 {
44 	int i;
45 
46 	for (i = 0; i < ninstance; ++i) {
47 		if (instance[i].pid != -1)
48 			kill(instance[i].pid, SIGKILL);
49 	}
50 	kill(getpid(), SIGKILL);
51 }
52 
53 int
54 main(int argc, char *argv[])
55 {
56 	char len_str[32], sockbuf_str[64], msgsz_str[64];
57 	char *args[32];
58 	const char *msgsz, *sockbuf, *name1, *name2;
59 	const char ** volatile host;
60 	volatile int set_minmax = 0, nhost, dual;
61 	int len, ninst, ninst_done, host_idx, host_arg_idx, test_arg_idx;
62 	int opt, i, null_fd;
63 	volatile int reverse = 0, sfile = 0;
64 	double result, res_max, res_min, jain;
65 	sigset_t nset, oset;
66 
67 	dual = 0;
68 	ninst = 2;
69 	len = 10;
70 	msgsz = NULL;
71 	sockbuf = NULL;
72 
73 	host_idx = 0;
74 	nhost = 8;
75 	host = malloc(sizeof(const char *) * nhost);
76 	if (host == NULL)
77 		err(1, "malloc failed");
78 
79 	while ((opt = getopt(argc, argv, "H:S:i:l:m:rsx")) != -1) {
80 		switch (opt) {
81 		case 'H':
82 			if (host_idx == nhost) {
83 				const char **new_host;
84 
85 				nhost *= 2;
86 				new_host = malloc(sizeof(const char *) * nhost);
87 				if (new_host == NULL)
88 					err(1, "malloc failed");
89 				memcpy(new_host, host,
90 				    host_idx * sizeof(const char *));
91 				free(host);
92 				host = new_host;
93 			}
94 			host[host_idx++] = optarg;
95 			break;
96 
97 		case 'S':
98 			sockbuf = optarg;
99 			break;
100 
101 		case 'i':
102 			ninst = strtoul(optarg, NULL, 10);
103 			break;
104 
105 		case 'l':
106 			len = strtoul(optarg, NULL, 10);
107 			break;
108 
109 		case 'm':
110 			msgsz = optarg;
111 			break;
112 
113 		case 'r':
114 			reverse = 1;
115 			sfile = 0;
116 			break;
117 
118 		case 's':
119 			reverse = 0;
120 			sfile = 1;
121 			break;
122 
123 		case 'x':
124 			dual = 1;
125 			break;
126 
127 		default:
128 			usage(argv[0]);
129 		}
130 	}
131 	nhost = host_idx;
132 
133 	if (ninst <= 0 || nhost == 0 || len <= 0)
134 		usage(argv[0]);
135 
136 	snprintf(len_str, sizeof(len_str), "%d", len);
137 
138 	i = 0;
139 	args[i++] = __DECONST(char *, NETPERF_CMD);
140 	args[i++] = __DECONST(char *, "-P0");
141 	args[i++] = __DECONST(char *, "-H");
142 	host_arg_idx = i;
143 	args[i++] = __DECONST(char *, NULL);
144 	args[i++] = __DECONST(char *, "-l");
145 	args[i++] = __DECONST(char *, len_str);
146 	args[i++] = __DECONST(char *, "-t");
147 	test_arg_idx = i;
148 	if (reverse)
149 		args[i++] = __DECONST(char *, "TCP_MAERTS");
150 	else if (sfile)
151 		args[i++] = __DECONST(char *, "TCP_SENDFILE");
152 	else
153 		args[i++] = __DECONST(char *, "TCP_STREAM");
154 	if (msgsz != NULL || sockbuf != NULL) {
155 		args[i++] = __DECONST(char *, "--");
156 		if (msgsz != NULL) {
157 			snprintf(msgsz_str, sizeof(msgsz_str), "%s,%s",
158 			    msgsz, msgsz);
159 			args[i++] = __DECONST(char *, "-m");
160 			args[i++] = __DECONST(char *, msgsz_str);
161 			args[i++] = __DECONST(char *, "-M");
162 			args[i++] = __DECONST(char *, msgsz_str);
163 		}
164 		if (sockbuf != NULL) {
165 			snprintf(sockbuf_str, sizeof(sockbuf_str), "%s,%s",
166 			    sockbuf, sockbuf);
167 			args[i++] = __DECONST(char *, "-s");
168 			args[i++] = __DECONST(char *, sockbuf_str);
169 			args[i++] = __DECONST(char *, "-S");
170 			args[i++] = __DECONST(char *, sockbuf_str);
171 		}
172 	}
173 	args[i] = NULL;
174 
175 	ninstance = ninst * nhost * (dual + 1);
176 	instance = calloc(ninstance, sizeof(struct netperf_child));
177 	if (instance == NULL)
178 		err(1, "calloc failed");
179 
180 	null_fd = open("/dev/null", O_RDWR);
181 	if (null_fd < 0)
182 		err(1, "open null failed");
183 
184 	for (i = 0; i < ninstance; ++i) {
185 		if (pipe(instance[i].pipes) < 0)
186 			err(1, "pipe %dth failed", i);
187 		instance[i].pid = -1;
188 	}
189 
190 	sigemptyset(&nset);
191 	sigaddset(&nset, SIGINT);
192 	sigprocmask(SIG_BLOCK, &nset, &oset);
193 	signal(SIGINT, sigint_handler);
194 
195 	for (i = 0; i < ninstance; ++i) {
196 		pid_t pid;
197 
198 		pid = vfork();
199 		if (pid == 0) {
200 			int ret;
201 
202 			dup2(instance[i].pipes[1], STDOUT_FILENO);
203 			dup2(null_fd, STDERR_FILENO);
204 
205 			args[host_arg_idx] = __DECONST(char *,
206 			    host[i % nhost]);
207 			if (dual) {
208 				const char *test_type;
209 
210 				if ((i / nhost) & dual) {
211 					test_type = sfile ?
212 					    "TCP_SENDFILE" : "TCP_STREAM";
213 				} else {
214 					test_type = "TCP_MAERTS";
215 				}
216 				args[test_arg_idx] = __DECONST(char *,
217 				    test_type);
218 			}
219 			ret = execv(NETPERF_PATH, args);
220 			if (ret < 0) {
221 				warn("execv %d failed", i);
222 				_exit(1);
223 			}
224 			/* Never reached */
225 			abort();
226 		} else if (pid < 0) {
227 			err(1, "vfork %d failed", i);
228 		}
229 		close(instance[i].pipes[1]);
230 		instance[i].pipes[1] = -1;
231 		instance[i].pid = pid;
232 	}
233 
234 	sigprocmask(SIG_SETMASK, &oset, NULL);
235 
236 	ninst_done = 0;
237 	while (ninst_done < ninstance) {
238 		pid_t pid;
239 
240 		pid = waitpid(-1, NULL, 0);
241 		if (pid < 0)
242 			err(1, "waitpid failed");
243 		++ninst_done;
244 	}
245 
246 	res_max = 0.0;
247 	res_min = 0.0;
248 	jain = 0.0;
249 	result = 0.0;
250 	for (i = 0; i < ninstance; ++i) {
251 		char line[128];
252 		FILE *fp;
253 
254 		fp = fdopen(instance[i].pipes[0], "r");
255 		if (fp == NULL)
256 			err(1, "fdopen %dth failed", i);
257 
258 		while (fgets(line, sizeof(line), fp) != NULL) {
259 			int n, arg1, arg2, arg3;
260 			double res, arg4;
261 
262 			n = sscanf(line, "%d%d%d%lf%lf",
263 			    &arg1, &arg2, &arg3, &arg4, &res);
264 			if (n == 5) {
265 				if (!set_minmax) {
266 					res_max = res;
267 					res_min = res;
268 					set_minmax = 1;
269 				} else {
270 					if (res > res_max)
271 						res_max = res;
272 					if (res < res_min)
273 						res_min = res;
274 				}
275 				jain += (res * res);
276 				result += res;
277 				break;
278 			}
279 		}
280 		fclose(fp);
281 	}
282 
283 	jain *= ninstance;
284 	jain = (result * result) / jain;
285 
286 	name1 = "TCP_STREAM";
287 	name2 = "";
288 	if (dual) {
289 		name1 = "TCP_MAERTS";
290 		if (sfile)
291 			name2 = "/TCP_SENDFILE";
292 		else
293 			name2 = "/TCP_STREAM";
294 	} else if (reverse) {
295 		name1 = "TCP_MAERTS";
296 	} else if (sfile) {
297 		name1 = "TCP_SENDFILE";
298 	}
299 
300 	printf("%s%s %.2f Mbps\n", name1, name2, result);
301 	printf("min/max (jain) %.2f Mbps/%.2f Mbps (%f)\n",
302 	    res_min, res_max, jain);
303 
304 	exit(0);
305 }
306