Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.15.
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * vsock_perf - benchmark utility for vsock.
  4 *
  5 * Copyright (C) 2022 SberDevices.
  6 *
  7 * Author: Arseniy Krasnov <AVKrasnov@sberdevices.ru>
  8 */
  9#include <getopt.h>
 10#include <stdio.h>
 11#include <stdlib.h>
 12#include <stdbool.h>
 13#include <string.h>
 14#include <errno.h>
 15#include <unistd.h>
 16#include <time.h>
 17#include <stdint.h>
 18#include <poll.h>
 19#include <sys/socket.h>
 20#include <linux/vm_sockets.h>
 21#include <sys/mman.h>
 22
 23#include "msg_zerocopy_common.h"
 24
 25#define DEFAULT_BUF_SIZE_BYTES	(128 * 1024)
 26#define DEFAULT_TO_SEND_BYTES	(64 * 1024)
 27#define DEFAULT_VSOCK_BUF_BYTES (256 * 1024)
 28#define DEFAULT_RCVLOWAT_BYTES	1
 29#define DEFAULT_PORT		1234
 30
 31#define BYTES_PER_GB		(1024 * 1024 * 1024ULL)
 32#define NSEC_PER_SEC		(1000000000ULL)
 33
 34static unsigned int port = DEFAULT_PORT;
 35static unsigned long buf_size_bytes = DEFAULT_BUF_SIZE_BYTES;
 36static unsigned long long vsock_buf_bytes = DEFAULT_VSOCK_BUF_BYTES;
 37static bool zerocopy;
 38
 39static void error(const char *s)
 40{
 41	perror(s);
 42	exit(EXIT_FAILURE);
 43}
 44
 45static time_t current_nsec(void)
 46{
 47	struct timespec ts;
 48
 49	if (clock_gettime(CLOCK_REALTIME, &ts))
 50		error("clock_gettime");
 51
 52	return (ts.tv_sec * NSEC_PER_SEC) + ts.tv_nsec;
 53}
 54
 55/* From lib/cmdline.c. */
 56static unsigned long memparse(const char *ptr)
 57{
 58	char *endptr;
 59
 60	unsigned long long ret = strtoull(ptr, &endptr, 0);
 61
 62	switch (*endptr) {
 63	case 'E':
 64	case 'e':
 65		ret <<= 10;
 66	case 'P':
 67	case 'p':
 68		ret <<= 10;
 69	case 'T':
 70	case 't':
 71		ret <<= 10;
 72	case 'G':
 73	case 'g':
 74		ret <<= 10;
 75	case 'M':
 76	case 'm':
 77		ret <<= 10;
 78	case 'K':
 79	case 'k':
 80		ret <<= 10;
 81		endptr++;
 82	default:
 83		break;
 84	}
 85
 86	return ret;
 87}
 88
 89static void vsock_increase_buf_size(int fd)
 90{
 91	if (setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_MAX_SIZE,
 92		       &vsock_buf_bytes, sizeof(vsock_buf_bytes)))
 93		error("setsockopt(SO_VM_SOCKETS_BUFFER_MAX_SIZE)");
 94
 95	if (setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
 96		       &vsock_buf_bytes, sizeof(vsock_buf_bytes)))
 97		error("setsockopt(SO_VM_SOCKETS_BUFFER_SIZE)");
 98}
 99
100static int vsock_connect(unsigned int cid, unsigned int port)
101{
102	union {
103		struct sockaddr sa;
104		struct sockaddr_vm svm;
105	} addr = {
106		.svm = {
107			.svm_family = AF_VSOCK,
108			.svm_port = port,
109			.svm_cid = cid,
110		},
111	};
112	int fd;
113
114	fd = socket(AF_VSOCK, SOCK_STREAM, 0);
115
116	if (fd < 0) {
117		perror("socket");
118		return -1;
119	}
120
121	if (connect(fd, &addr.sa, sizeof(addr.svm)) < 0) {
122		perror("connect");
123		close(fd);
124		return -1;
125	}
126
127	return fd;
128}
129
130static float get_gbps(unsigned long bits, time_t ns_delta)
131{
132	return ((float)bits / 1000000000ULL) /
133	       ((float)ns_delta / NSEC_PER_SEC);
134}
135
136static void run_receiver(int rcvlowat_bytes)
137{
138	unsigned int read_cnt;
139	time_t rx_begin_ns;
140	time_t in_read_ns;
141	size_t total_recv;
142	int client_fd;
143	char *data;
144	int fd;
145	union {
146		struct sockaddr sa;
147		struct sockaddr_vm svm;
148	} addr = {
149		.svm = {
150			.svm_family = AF_VSOCK,
151			.svm_port = port,
152			.svm_cid = VMADDR_CID_ANY,
153		},
154	};
155	union {
156		struct sockaddr sa;
157		struct sockaddr_vm svm;
158	} clientaddr;
159
160	socklen_t clientaddr_len = sizeof(clientaddr.svm);
161
162	printf("Run as receiver\n");
163	printf("Listen port %u\n", port);
164	printf("RX buffer %lu bytes\n", buf_size_bytes);
165	printf("vsock buffer %llu bytes\n", vsock_buf_bytes);
166	printf("SO_RCVLOWAT %d bytes\n", rcvlowat_bytes);
167
168	fd = socket(AF_VSOCK, SOCK_STREAM, 0);
169
170	if (fd < 0)
171		error("socket");
172
173	if (bind(fd, &addr.sa, sizeof(addr.svm)) < 0)
174		error("bind");
175
176	if (listen(fd, 1) < 0)
177		error("listen");
178
179	client_fd = accept(fd, &clientaddr.sa, &clientaddr_len);
180
181	if (client_fd < 0)
182		error("accept");
183
184	vsock_increase_buf_size(client_fd);
185
186	if (setsockopt(client_fd, SOL_SOCKET, SO_RCVLOWAT,
187		       &rcvlowat_bytes,
188		       sizeof(rcvlowat_bytes)))
189		error("setsockopt(SO_RCVLOWAT)");
190
191	data = malloc(buf_size_bytes);
192
193	if (!data) {
194		fprintf(stderr, "'malloc()' failed\n");
195		exit(EXIT_FAILURE);
196	}
197
198	read_cnt = 0;
199	in_read_ns = 0;
200	total_recv = 0;
201	rx_begin_ns = current_nsec();
202
203	while (1) {
204		struct pollfd fds = { 0 };
205
206		fds.fd = client_fd;
207		fds.events = POLLIN | POLLERR |
208			     POLLHUP | POLLRDHUP;
209
210		if (poll(&fds, 1, -1) < 0)
211			error("poll");
212
213		if (fds.revents & POLLERR) {
214			fprintf(stderr, "'poll()' error\n");
215			exit(EXIT_FAILURE);
216		}
217
218		if (fds.revents & POLLIN) {
219			ssize_t bytes_read;
220			time_t t;
221
222			t = current_nsec();
223			bytes_read = read(fds.fd, data, buf_size_bytes);
224			in_read_ns += (current_nsec() - t);
225			read_cnt++;
226
227			if (!bytes_read)
228				break;
229
230			if (bytes_read < 0) {
231				perror("read");
232				exit(EXIT_FAILURE);
233			}
234
235			total_recv += bytes_read;
236		}
237
238		if (fds.revents & (POLLHUP | POLLRDHUP))
239			break;
240	}
241
242	printf("total bytes received: %zu\n", total_recv);
243	printf("rx performance: %f Gbits/s\n",
244	       get_gbps(total_recv * 8, current_nsec() - rx_begin_ns));
245	printf("total time in 'read()': %f sec\n", (float)in_read_ns / NSEC_PER_SEC);
246	printf("average time in 'read()': %f ns\n", (float)in_read_ns / read_cnt);
247	printf("POLLIN wakeups: %i\n", read_cnt);
248
249	free(data);
250	close(client_fd);
251	close(fd);
252}
253
254static void enable_so_zerocopy(int fd)
255{
256	int val = 1;
257
258	if (setsockopt(fd, SOL_SOCKET, SO_ZEROCOPY, &val, sizeof(val))) {
259		perror("setsockopt");
260		exit(EXIT_FAILURE);
261	}
262}
263
264static void run_sender(int peer_cid, unsigned long to_send_bytes)
265{
266	time_t tx_begin_ns;
267	time_t tx_total_ns;
268	size_t total_send;
269	time_t time_in_send;
270	void *data;
271	int fd;
272
273	if (zerocopy)
274		printf("Run as sender MSG_ZEROCOPY\n");
275	else
276		printf("Run as sender\n");
277
278	printf("Connect to %i:%u\n", peer_cid, port);
279	printf("Send %lu bytes\n", to_send_bytes);
280	printf("TX buffer %lu bytes\n", buf_size_bytes);
281
282	fd = vsock_connect(peer_cid, port);
283
284	if (fd < 0)
285		exit(EXIT_FAILURE);
286
287	if (zerocopy) {
288		enable_so_zerocopy(fd);
289
290		data = mmap(NULL, buf_size_bytes, PROT_READ | PROT_WRITE,
291			    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
292		if (data == MAP_FAILED) {
293			perror("mmap");
294			exit(EXIT_FAILURE);
295		}
296	} else {
297		data = malloc(buf_size_bytes);
298
299		if (!data) {
300			fprintf(stderr, "'malloc()' failed\n");
301			exit(EXIT_FAILURE);
302		}
303	}
304
305	memset(data, 0, buf_size_bytes);
306	total_send = 0;
307	time_in_send = 0;
308	tx_begin_ns = current_nsec();
309
310	while (total_send < to_send_bytes) {
311		ssize_t sent;
312		size_t rest_bytes;
313		time_t before;
314
315		rest_bytes = to_send_bytes - total_send;
316
317		before = current_nsec();
318		sent = send(fd, data, (rest_bytes > buf_size_bytes) ?
319			    buf_size_bytes : rest_bytes,
320			    zerocopy ? MSG_ZEROCOPY : 0);
321		time_in_send += (current_nsec() - before);
322
323		if (sent <= 0)
324			error("write");
325
326		total_send += sent;
327
328		if (zerocopy) {
329			struct pollfd fds = { 0 };
330
331			fds.fd = fd;
332
333			if (poll(&fds, 1, -1) < 0) {
334				perror("poll");
335				exit(EXIT_FAILURE);
336			}
337
338			if (!(fds.revents & POLLERR)) {
339				fprintf(stderr, "POLLERR expected\n");
340				exit(EXIT_FAILURE);
341			}
342
343			vsock_recv_completion(fd, NULL);
344		}
345	}
346
347	tx_total_ns = current_nsec() - tx_begin_ns;
348
349	printf("total bytes sent: %zu\n", total_send);
350	printf("tx performance: %f Gbits/s\n",
351	       get_gbps(total_send * 8, time_in_send));
352	printf("total time in tx loop: %f sec\n",
353	       (float)tx_total_ns / NSEC_PER_SEC);
354	printf("time in 'send()': %f sec\n",
355	       (float)time_in_send / NSEC_PER_SEC);
356
357	close(fd);
358
359	if (zerocopy)
360		munmap(data, buf_size_bytes);
361	else
362		free(data);
363}
364
365static const char optstring[] = "";
366static const struct option longopts[] = {
367	{
368		.name = "help",
369		.has_arg = no_argument,
370		.val = 'H',
371	},
372	{
373		.name = "sender",
374		.has_arg = required_argument,
375		.val = 'S',
376	},
377	{
378		.name = "port",
379		.has_arg = required_argument,
380		.val = 'P',
381	},
382	{
383		.name = "bytes",
384		.has_arg = required_argument,
385		.val = 'M',
386	},
387	{
388		.name = "buf-size",
389		.has_arg = required_argument,
390		.val = 'B',
391	},
392	{
393		.name = "vsk-size",
394		.has_arg = required_argument,
395		.val = 'V',
396	},
397	{
398		.name = "rcvlowat",
399		.has_arg = required_argument,
400		.val = 'R',
401	},
402	{
403		.name = "zerocopy",
404		.has_arg = no_argument,
405		.val = 'Z',
406	},
407	{},
408};
409
410static void usage(void)
411{
412	printf("Usage: ./vsock_perf [--help] [options]\n"
413	       "\n"
414	       "This is benchmarking utility, to test vsock performance.\n"
415	       "It runs in two modes: sender or receiver. In sender mode, it\n"
416	       "connects to the specified CID and starts data transmission.\n"
417	       "\n"
418	       "Options:\n"
419	       "  --help			This message\n"
420	       "  --sender   <cid>		Sender mode (receiver default)\n"
421	       "                                <cid> of the receiver to connect to\n"
422	       "  --zerocopy			Enable zerocopy (for sender mode only)\n"
423	       "  --port     <port>		Port (default %d)\n"
424	       "  --bytes    <bytes>KMG		Bytes to send (default %d)\n"
425	       "  --buf-size <bytes>KMG		Data buffer size (default %d). In sender mode\n"
426	       "                                it is the buffer size, passed to 'write()'. In\n"
427	       "                                receiver mode it is the buffer size passed to 'read()'.\n"
428	       "  --vsk-size <bytes>KMG		Socket buffer size (default %d)\n"
429	       "  --rcvlowat <bytes>KMG		SO_RCVLOWAT value (default %d)\n"
430	       "\n", DEFAULT_PORT, DEFAULT_TO_SEND_BYTES,
431	       DEFAULT_BUF_SIZE_BYTES, DEFAULT_VSOCK_BUF_BYTES,
432	       DEFAULT_RCVLOWAT_BYTES);
433	exit(EXIT_FAILURE);
434}
435
436static long strtolx(const char *arg)
437{
438	long value;
439	char *end;
440
441	value = strtol(arg, &end, 10);
442
443	if (end != arg + strlen(arg))
444		usage();
445
446	return value;
447}
448
449int main(int argc, char **argv)
450{
451	unsigned long to_send_bytes = DEFAULT_TO_SEND_BYTES;
452	int rcvlowat_bytes = DEFAULT_RCVLOWAT_BYTES;
453	int peer_cid = -1;
454	bool sender = false;
455
456	while (1) {
457		int opt = getopt_long(argc, argv, optstring, longopts, NULL);
458
459		if (opt == -1)
460			break;
461
462		switch (opt) {
463		case 'V': /* Peer buffer size. */
464			vsock_buf_bytes = memparse(optarg);
465			break;
466		case 'R': /* SO_RCVLOWAT value. */
467			rcvlowat_bytes = memparse(optarg);
468			break;
469		case 'P': /* Port to connect to. */
470			port = strtolx(optarg);
471			break;
472		case 'M': /* Bytes to send. */
473			to_send_bytes = memparse(optarg);
474			break;
475		case 'B': /* Size of rx/tx buffer. */
476			buf_size_bytes = memparse(optarg);
477			break;
478		case 'S': /* Sender mode. CID to connect to. */
479			peer_cid = strtolx(optarg);
480			sender = true;
481			break;
482		case 'H': /* Help. */
483			usage();
484			break;
485		case 'Z': /* Zerocopy. */
486			zerocopy = true;
487			break;
488		default:
489			usage();
490		}
491	}
492
493	if (!sender)
494		run_receiver(rcvlowat_bytes);
495	else
496		run_sender(peer_cid, to_send_bytes);
497
498	return 0;
499}