Merge branch 'vsock-update-tools-and-error-handling'
Arseniy Krasnov says: ==================== vsock: update tools and error handling Patchset consists of two parts: 1) Kernel patch One patch from Bobby Eshleman. I took single patch from Bobby: https://lore.kernel.org/lkml/d81818b868216c774613dd03641fcfe63cc55a45 .1660362668.git.bobby.eshleman@bytedance.com/ and use only part for af_vsock.c, as VMCI and Hyper-V parts were rejected. I used it, because for SOCK_SEQPACKET big messages handling was broken - ENOMEM was returned instead of EMSGSIZE. And anyway, current logic which always replaces any error code returned by transport to ENOMEM looks strange for me also(for example in EMSGSIZE case it was changed to ENOMEM). 2) Tool patches Since there is work on several significant updates for vsock(virtio/ vsock especially): skbuff, DGRAM, zerocopy rx/tx, so I think that this patchset will be useful. This patchset updates vsock tests and tools a little bit. First of all it updates test suite: two new tests are added. One test is reworked message bound test. Now it is more complex. Instead of sending 1 byte messages with one MSG_EOR bit, it sends messages of random length(one half of messages are smaller than page size, second half are bigger) with random number of MSG_EOR bits set. Receiver also don't know total number of messages. Message bounds control is maintained by hash sum of messages length calculation. Second test is for SOCK_SEQPACKET - it tries to send message with length more than allowed. I think both tests will be useful for DGRAM support also. Third thing that this patchset adds is small utility to test vsock performance for both rx and tx. I think this util could be useful as 'iperf'/'uperf', because: 1) It is small comparing to 'iperf' or 'uperf', so it very easy to add new mode or feature to it(especially vsock specific). 2) It allows to set SO_RCVLOWAT and SO_VM_SOCKETS_BUFFER_SIZE option. Whole throughtput depends on both parameters. 3) It is located in the kernel source tree, so it could be updated by the same patchset which changes related kernel functionality in vsock. I used this util very often to check performance of my rx zerocopy support(this tool has rx zerocopy support, but not in this patchset). Here is comparison of outputs from three utils: 'iperf', 'uperf' and 'vsock_perf'. In all three cases sender was at guest side. rx and tx buffers were always 64Kb(because by default 'uperf' uses 8K). iperf: [ ID] Interval Transfer Bitrate [ 5] 0.00-10.00 sec 12.8 GBytes 11.0 Gbits/sec sender [ 5] 0.00-10.00 sec 12.8 GBytes 11.0 Gbits/sec receiver uperf: Total 16.27GB / 11.36(s) = 12.30Gb/s 23455op/s vsock_perf: tx performance: 12.301529 Gbits/s rx performance: 12.288011 Gbits/s Results are almost same in all three cases. Patchset was rebased and tested on skbuff v9 patch from Bobby Eshleman: https://lore.kernel.org/netdev/20230107002937.899605-1-bobby.eshleman@bytedance.com/ ==================== Link: https://lore.kernel.org/r/67cd2d0a-1c58-baac-7b39-b8d4ea44f719@sberdevices.ru Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
commit
55b98837e3
@ -1861,8 +1861,9 @@ static int vsock_connectible_sendmsg(struct socket *sock, struct msghdr *msg,
|
|||||||
written = transport->stream_enqueue(vsk,
|
written = transport->stream_enqueue(vsk,
|
||||||
msg, len - total_written);
|
msg, len - total_written);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (written < 0) {
|
if (written < 0) {
|
||||||
err = -ENOMEM;
|
err = written;
|
||||||
goto out_err;
|
goto out_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
all: test
|
all: test vsock_perf
|
||||||
test: vsock_test vsock_diag_test
|
test: vsock_test vsock_diag_test
|
||||||
vsock_test: vsock_test.o timeout.o control.o util.o
|
vsock_test: vsock_test.o timeout.o control.o util.o
|
||||||
vsock_diag_test: vsock_diag_test.o timeout.o control.o util.o
|
vsock_diag_test: vsock_diag_test.o timeout.o control.o util.o
|
||||||
|
vsock_perf: vsock_perf.o
|
||||||
|
|
||||||
CFLAGS += -g -O2 -Werror -Wall -I. -I../../include -I../../../usr/include -Wno-pointer-sign -fno-strict-overflow -fno-strict-aliasing -fno-common -MMD -U_FORTIFY_SOURCE -D_GNU_SOURCE
|
CFLAGS += -g -O2 -Werror -Wall -I. -I../../include -I../../../usr/include -Wno-pointer-sign -fno-strict-overflow -fno-strict-aliasing -fno-common -MMD -U_FORTIFY_SOURCE -D_GNU_SOURCE
|
||||||
.PHONY: all test clean
|
.PHONY: all test clean
|
||||||
|
@ -35,3 +35,37 @@ Invoke test binaries in both directions as follows:
|
|||||||
--control-port=$GUEST_IP \
|
--control-port=$GUEST_IP \
|
||||||
--control-port=1234 \
|
--control-port=1234 \
|
||||||
--peer-cid=3
|
--peer-cid=3
|
||||||
|
|
||||||
|
vsock_perf utility
|
||||||
|
-------------------
|
||||||
|
'vsock_perf' is a simple tool to measure vsock performance. It works in
|
||||||
|
sender/receiver modes: sender connect to peer at the specified port and
|
||||||
|
starts data transmission to the receiver. After data processing is done,
|
||||||
|
it prints several metrics(see below).
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
# run as sender
|
||||||
|
# connect to CID 2, port 1234, send 1G of data, tx buf size is 1M
|
||||||
|
./vsock_perf --sender 2 --port 1234 --bytes 1G --buf-size 1M
|
||||||
|
|
||||||
|
Output:
|
||||||
|
tx performance: A Gbits/s
|
||||||
|
|
||||||
|
Output explanation:
|
||||||
|
A is calculated as "number of bits to send" / "time in tx loop"
|
||||||
|
|
||||||
|
# run as receiver
|
||||||
|
# listen port 1234, rx buf size is 1M, socket buf size is 1G, SO_RCVLOWAT is 64K
|
||||||
|
./vsock_perf --port 1234 --buf-size 1M --vsk-size 1G --rcvlowat 64K
|
||||||
|
|
||||||
|
Output:
|
||||||
|
rx performance: A Gbits/s
|
||||||
|
total in 'read()': B sec
|
||||||
|
POLLIN wakeups: C
|
||||||
|
average in 'read()': D ns
|
||||||
|
|
||||||
|
Output explanation:
|
||||||
|
A is calculated as "number of received bits" / "time in rx loop".
|
||||||
|
B is time, spent in 'read()' system call(excluding 'poll()')
|
||||||
|
C is number of 'poll()' wake ups with POLLIN bit set.
|
||||||
|
D is B / C, e.g. average amount of time, spent in single 'read()'.
|
||||||
|
@ -141,6 +141,34 @@ void control_writeln(const char *str)
|
|||||||
timeout_end();
|
timeout_end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void control_writeulong(unsigned long value)
|
||||||
|
{
|
||||||
|
char str[32];
|
||||||
|
|
||||||
|
if (snprintf(str, sizeof(str), "%lu", value) >= sizeof(str)) {
|
||||||
|
perror("snprintf");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
control_writeln(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long control_readulong(void)
|
||||||
|
{
|
||||||
|
unsigned long value;
|
||||||
|
char *str;
|
||||||
|
|
||||||
|
str = control_readln();
|
||||||
|
|
||||||
|
if (!str)
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
value = strtoul(str, NULL, 10);
|
||||||
|
free(str);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/* Return the next line from the control socket (without the trailing newline).
|
/* Return the next line from the control socket (without the trailing newline).
|
||||||
*
|
*
|
||||||
* The program terminates if a timeout occurs.
|
* The program terminates if a timeout occurs.
|
||||||
|
@ -9,7 +9,9 @@ void control_init(const char *control_host, const char *control_port,
|
|||||||
void control_cleanup(void);
|
void control_cleanup(void);
|
||||||
void control_writeln(const char *str);
|
void control_writeln(const char *str);
|
||||||
char *control_readln(void);
|
char *control_readln(void);
|
||||||
|
unsigned long control_readulong(void);
|
||||||
void control_expectln(const char *str);
|
void control_expectln(const char *str);
|
||||||
bool control_cmpln(char *line, const char *str, bool fail);
|
bool control_cmpln(char *line, const char *str, bool fail);
|
||||||
|
void control_writeulong(unsigned long value);
|
||||||
|
|
||||||
#endif /* CONTROL_H */
|
#endif /* CONTROL_H */
|
||||||
|
@ -395,3 +395,16 @@ void skip_test(struct test_case *test_cases, size_t test_cases_len,
|
|||||||
|
|
||||||
test_cases[test_id].skip = true;
|
test_cases[test_id].skip = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned long hash_djb2(const void *data, size_t len)
|
||||||
|
{
|
||||||
|
unsigned long hash = 5381;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (i < len) {
|
||||||
|
hash = ((hash << 5) + hash) + ((unsigned char *)data)[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
@ -49,4 +49,5 @@ void run_tests(const struct test_case *test_cases,
|
|||||||
void list_tests(const struct test_case *test_cases);
|
void list_tests(const struct test_case *test_cases);
|
||||||
void skip_test(struct test_case *test_cases, size_t test_cases_len,
|
void skip_test(struct test_case *test_cases, size_t test_cases_len,
|
||||||
const char *test_id_str);
|
const char *test_id_str);
|
||||||
|
unsigned long hash_djb2(const void *data, size_t len);
|
||||||
#endif /* UTIL_H */
|
#endif /* UTIL_H */
|
||||||
|
427
tools/testing/vsock/vsock_perf.c
Normal file
427
tools/testing/vsock/vsock_perf.c
Normal file
@ -0,0 +1,427 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* vsock_perf - benchmark utility for vsock.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022 SberDevices.
|
||||||
|
*
|
||||||
|
* Author: Arseniy Krasnov <AVKrasnov@sberdevices.ru>
|
||||||
|
*/
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <linux/vm_sockets.h>
|
||||||
|
|
||||||
|
#define DEFAULT_BUF_SIZE_BYTES (128 * 1024)
|
||||||
|
#define DEFAULT_TO_SEND_BYTES (64 * 1024)
|
||||||
|
#define DEFAULT_VSOCK_BUF_BYTES (256 * 1024)
|
||||||
|
#define DEFAULT_RCVLOWAT_BYTES 1
|
||||||
|
#define DEFAULT_PORT 1234
|
||||||
|
|
||||||
|
#define BYTES_PER_GB (1024 * 1024 * 1024ULL)
|
||||||
|
#define NSEC_PER_SEC (1000000000ULL)
|
||||||
|
|
||||||
|
static unsigned int port = DEFAULT_PORT;
|
||||||
|
static unsigned long buf_size_bytes = DEFAULT_BUF_SIZE_BYTES;
|
||||||
|
static unsigned long vsock_buf_bytes = DEFAULT_VSOCK_BUF_BYTES;
|
||||||
|
|
||||||
|
static void error(const char *s)
|
||||||
|
{
|
||||||
|
perror(s);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static time_t current_nsec(void)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
if (clock_gettime(CLOCK_REALTIME, &ts))
|
||||||
|
error("clock_gettime");
|
||||||
|
|
||||||
|
return (ts.tv_sec * NSEC_PER_SEC) + ts.tv_nsec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* From lib/cmdline.c. */
|
||||||
|
static unsigned long memparse(const char *ptr)
|
||||||
|
{
|
||||||
|
char *endptr;
|
||||||
|
|
||||||
|
unsigned long long ret = strtoull(ptr, &endptr, 0);
|
||||||
|
|
||||||
|
switch (*endptr) {
|
||||||
|
case 'E':
|
||||||
|
case 'e':
|
||||||
|
ret <<= 10;
|
||||||
|
case 'P':
|
||||||
|
case 'p':
|
||||||
|
ret <<= 10;
|
||||||
|
case 'T':
|
||||||
|
case 't':
|
||||||
|
ret <<= 10;
|
||||||
|
case 'G':
|
||||||
|
case 'g':
|
||||||
|
ret <<= 10;
|
||||||
|
case 'M':
|
||||||
|
case 'm':
|
||||||
|
ret <<= 10;
|
||||||
|
case 'K':
|
||||||
|
case 'k':
|
||||||
|
ret <<= 10;
|
||||||
|
endptr++;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void vsock_increase_buf_size(int fd)
|
||||||
|
{
|
||||||
|
if (setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_MAX_SIZE,
|
||||||
|
&vsock_buf_bytes, sizeof(vsock_buf_bytes)))
|
||||||
|
error("setsockopt(SO_VM_SOCKETS_BUFFER_MAX_SIZE)");
|
||||||
|
|
||||||
|
if (setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
|
||||||
|
&vsock_buf_bytes, sizeof(vsock_buf_bytes)))
|
||||||
|
error("setsockopt(SO_VM_SOCKETS_BUFFER_SIZE)");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int vsock_connect(unsigned int cid, unsigned int port)
|
||||||
|
{
|
||||||
|
union {
|
||||||
|
struct sockaddr sa;
|
||||||
|
struct sockaddr_vm svm;
|
||||||
|
} addr = {
|
||||||
|
.svm = {
|
||||||
|
.svm_family = AF_VSOCK,
|
||||||
|
.svm_port = port,
|
||||||
|
.svm_cid = cid,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = socket(AF_VSOCK, SOCK_STREAM, 0);
|
||||||
|
|
||||||
|
if (fd < 0) {
|
||||||
|
perror("socket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connect(fd, &addr.sa, sizeof(addr.svm)) < 0) {
|
||||||
|
perror("connect");
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float get_gbps(unsigned long bits, time_t ns_delta)
|
||||||
|
{
|
||||||
|
return ((float)bits / 1000000000ULL) /
|
||||||
|
((float)ns_delta / NSEC_PER_SEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void run_receiver(unsigned long rcvlowat_bytes)
|
||||||
|
{
|
||||||
|
unsigned int read_cnt;
|
||||||
|
time_t rx_begin_ns;
|
||||||
|
time_t in_read_ns;
|
||||||
|
size_t total_recv;
|
||||||
|
int client_fd;
|
||||||
|
char *data;
|
||||||
|
int fd;
|
||||||
|
union {
|
||||||
|
struct sockaddr sa;
|
||||||
|
struct sockaddr_vm svm;
|
||||||
|
} addr = {
|
||||||
|
.svm = {
|
||||||
|
.svm_family = AF_VSOCK,
|
||||||
|
.svm_port = port,
|
||||||
|
.svm_cid = VMADDR_CID_ANY,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
union {
|
||||||
|
struct sockaddr sa;
|
||||||
|
struct sockaddr_vm svm;
|
||||||
|
} clientaddr;
|
||||||
|
|
||||||
|
socklen_t clientaddr_len = sizeof(clientaddr.svm);
|
||||||
|
|
||||||
|
printf("Run as receiver\n");
|
||||||
|
printf("Listen port %u\n", port);
|
||||||
|
printf("RX buffer %lu bytes\n", buf_size_bytes);
|
||||||
|
printf("vsock buffer %lu bytes\n", vsock_buf_bytes);
|
||||||
|
printf("SO_RCVLOWAT %lu bytes\n", rcvlowat_bytes);
|
||||||
|
|
||||||
|
fd = socket(AF_VSOCK, SOCK_STREAM, 0);
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
error("socket");
|
||||||
|
|
||||||
|
if (bind(fd, &addr.sa, sizeof(addr.svm)) < 0)
|
||||||
|
error("bind");
|
||||||
|
|
||||||
|
if (listen(fd, 1) < 0)
|
||||||
|
error("listen");
|
||||||
|
|
||||||
|
client_fd = accept(fd, &clientaddr.sa, &clientaddr_len);
|
||||||
|
|
||||||
|
if (client_fd < 0)
|
||||||
|
error("accept");
|
||||||
|
|
||||||
|
vsock_increase_buf_size(client_fd);
|
||||||
|
|
||||||
|
if (setsockopt(client_fd, SOL_SOCKET, SO_RCVLOWAT,
|
||||||
|
&rcvlowat_bytes,
|
||||||
|
sizeof(rcvlowat_bytes)))
|
||||||
|
error("setsockopt(SO_RCVLOWAT)");
|
||||||
|
|
||||||
|
data = malloc(buf_size_bytes);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
fprintf(stderr, "'malloc()' failed\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
read_cnt = 0;
|
||||||
|
in_read_ns = 0;
|
||||||
|
total_recv = 0;
|
||||||
|
rx_begin_ns = current_nsec();
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
struct pollfd fds = { 0 };
|
||||||
|
|
||||||
|
fds.fd = client_fd;
|
||||||
|
fds.events = POLLIN | POLLERR |
|
||||||
|
POLLHUP | POLLRDHUP;
|
||||||
|
|
||||||
|
if (poll(&fds, 1, -1) < 0)
|
||||||
|
error("poll");
|
||||||
|
|
||||||
|
if (fds.revents & POLLERR) {
|
||||||
|
fprintf(stderr, "'poll()' error\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fds.revents & POLLIN) {
|
||||||
|
ssize_t bytes_read;
|
||||||
|
time_t t;
|
||||||
|
|
||||||
|
t = current_nsec();
|
||||||
|
bytes_read = read(fds.fd, data, buf_size_bytes);
|
||||||
|
in_read_ns += (current_nsec() - t);
|
||||||
|
read_cnt++;
|
||||||
|
|
||||||
|
if (!bytes_read)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (bytes_read < 0) {
|
||||||
|
perror("read");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
total_recv += bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fds.revents & (POLLHUP | POLLRDHUP))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("total bytes received: %zu\n", total_recv);
|
||||||
|
printf("rx performance: %f Gbits/s\n",
|
||||||
|
get_gbps(total_recv * 8, current_nsec() - rx_begin_ns));
|
||||||
|
printf("total time in 'read()': %f sec\n", (float)in_read_ns / NSEC_PER_SEC);
|
||||||
|
printf("average time in 'read()': %f ns\n", (float)in_read_ns / read_cnt);
|
||||||
|
printf("POLLIN wakeups: %i\n", read_cnt);
|
||||||
|
|
||||||
|
free(data);
|
||||||
|
close(client_fd);
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void run_sender(int peer_cid, unsigned long to_send_bytes)
|
||||||
|
{
|
||||||
|
time_t tx_begin_ns;
|
||||||
|
time_t tx_total_ns;
|
||||||
|
size_t total_send;
|
||||||
|
void *data;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
printf("Run as sender\n");
|
||||||
|
printf("Connect to %i:%u\n", peer_cid, port);
|
||||||
|
printf("Send %lu bytes\n", to_send_bytes);
|
||||||
|
printf("TX buffer %lu bytes\n", buf_size_bytes);
|
||||||
|
|
||||||
|
fd = vsock_connect(peer_cid, port);
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
data = malloc(buf_size_bytes);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
fprintf(stderr, "'malloc()' failed\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(data, 0, buf_size_bytes);
|
||||||
|
total_send = 0;
|
||||||
|
tx_begin_ns = current_nsec();
|
||||||
|
|
||||||
|
while (total_send < to_send_bytes) {
|
||||||
|
ssize_t sent;
|
||||||
|
|
||||||
|
sent = write(fd, data, buf_size_bytes);
|
||||||
|
|
||||||
|
if (sent <= 0)
|
||||||
|
error("write");
|
||||||
|
|
||||||
|
total_send += sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx_total_ns = current_nsec() - tx_begin_ns;
|
||||||
|
|
||||||
|
printf("total bytes sent: %zu\n", total_send);
|
||||||
|
printf("tx performance: %f Gbits/s\n",
|
||||||
|
get_gbps(total_send * 8, tx_total_ns));
|
||||||
|
printf("total time in 'write()': %f sec\n",
|
||||||
|
(float)tx_total_ns / NSEC_PER_SEC);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char optstring[] = "";
|
||||||
|
static const struct option longopts[] = {
|
||||||
|
{
|
||||||
|
.name = "help",
|
||||||
|
.has_arg = no_argument,
|
||||||
|
.val = 'H',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "sender",
|
||||||
|
.has_arg = required_argument,
|
||||||
|
.val = 'S',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "port",
|
||||||
|
.has_arg = required_argument,
|
||||||
|
.val = 'P',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "bytes",
|
||||||
|
.has_arg = required_argument,
|
||||||
|
.val = 'M',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "buf-size",
|
||||||
|
.has_arg = required_argument,
|
||||||
|
.val = 'B',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "vsk-size",
|
||||||
|
.has_arg = required_argument,
|
||||||
|
.val = 'V',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "rcvlowat",
|
||||||
|
.has_arg = required_argument,
|
||||||
|
.val = 'R',
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void usage(void)
|
||||||
|
{
|
||||||
|
printf("Usage: ./vsock_perf [--help] [options]\n"
|
||||||
|
"\n"
|
||||||
|
"This is benchmarking utility, to test vsock performance.\n"
|
||||||
|
"It runs in two modes: sender or receiver. In sender mode, it\n"
|
||||||
|
"connects to the specified CID and starts data transmission.\n"
|
||||||
|
"\n"
|
||||||
|
"Options:\n"
|
||||||
|
" --help This message\n"
|
||||||
|
" --sender <cid> Sender mode (receiver default)\n"
|
||||||
|
" <cid> of the receiver to connect to\n"
|
||||||
|
" --port <port> Port (default %d)\n"
|
||||||
|
" --bytes <bytes>KMG Bytes to send (default %d)\n"
|
||||||
|
" --buf-size <bytes>KMG Data buffer size (default %d). In sender mode\n"
|
||||||
|
" it is the buffer size, passed to 'write()'. In\n"
|
||||||
|
" receiver mode it is the buffer size passed to 'read()'.\n"
|
||||||
|
" --vsk-size <bytes>KMG Socket buffer size (default %d)\n"
|
||||||
|
" --rcvlowat <bytes>KMG SO_RCVLOWAT value (default %d)\n"
|
||||||
|
"\n", DEFAULT_PORT, DEFAULT_TO_SEND_BYTES,
|
||||||
|
DEFAULT_BUF_SIZE_BYTES, DEFAULT_VSOCK_BUF_BYTES,
|
||||||
|
DEFAULT_RCVLOWAT_BYTES);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static long strtolx(const char *arg)
|
||||||
|
{
|
||||||
|
long value;
|
||||||
|
char *end;
|
||||||
|
|
||||||
|
value = strtol(arg, &end, 10);
|
||||||
|
|
||||||
|
if (end != arg + strlen(arg))
|
||||||
|
usage();
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
unsigned long to_send_bytes = DEFAULT_TO_SEND_BYTES;
|
||||||
|
unsigned long rcvlowat_bytes = DEFAULT_RCVLOWAT_BYTES;
|
||||||
|
int peer_cid = -1;
|
||||||
|
bool sender = false;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
int opt = getopt_long(argc, argv, optstring, longopts, NULL);
|
||||||
|
|
||||||
|
if (opt == -1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
switch (opt) {
|
||||||
|
case 'V': /* Peer buffer size. */
|
||||||
|
vsock_buf_bytes = memparse(optarg);
|
||||||
|
break;
|
||||||
|
case 'R': /* SO_RCVLOWAT value. */
|
||||||
|
rcvlowat_bytes = memparse(optarg);
|
||||||
|
break;
|
||||||
|
case 'P': /* Port to connect to. */
|
||||||
|
port = strtolx(optarg);
|
||||||
|
break;
|
||||||
|
case 'M': /* Bytes to send. */
|
||||||
|
to_send_bytes = memparse(optarg);
|
||||||
|
break;
|
||||||
|
case 'B': /* Size of rx/tx buffer. */
|
||||||
|
buf_size_bytes = memparse(optarg);
|
||||||
|
break;
|
||||||
|
case 'S': /* Sender mode. CID to connect to. */
|
||||||
|
peer_cid = strtolx(optarg);
|
||||||
|
sender = true;
|
||||||
|
break;
|
||||||
|
case 'H': /* Help. */
|
||||||
|
usage();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
usage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sender)
|
||||||
|
run_receiver(rcvlowat_bytes);
|
||||||
|
else
|
||||||
|
run_sender(peer_cid, to_send_bytes);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -284,10 +284,14 @@ static void test_stream_msg_peek_server(const struct test_opts *opts)
|
|||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define MESSAGES_CNT 7
|
#define SOCK_BUF_SIZE (2 * 1024 * 1024)
|
||||||
#define MSG_EOR_IDX (MESSAGES_CNT / 2)
|
#define MAX_MSG_SIZE (32 * 1024)
|
||||||
|
|
||||||
static void test_seqpacket_msg_bounds_client(const struct test_opts *opts)
|
static void test_seqpacket_msg_bounds_client(const struct test_opts *opts)
|
||||||
{
|
{
|
||||||
|
unsigned long curr_hash;
|
||||||
|
int page_size;
|
||||||
|
int msg_count;
|
||||||
int fd;
|
int fd;
|
||||||
|
|
||||||
fd = vsock_seqpacket_connect(opts->peer_cid, 1234);
|
fd = vsock_seqpacket_connect(opts->peer_cid, 1234);
|
||||||
@ -296,18 +300,79 @@ static void test_seqpacket_msg_bounds_client(const struct test_opts *opts)
|
|||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send several messages, one with MSG_EOR flag */
|
/* Wait, until receiver sets buffer size. */
|
||||||
for (int i = 0; i < MESSAGES_CNT; i++)
|
control_expectln("SRVREADY");
|
||||||
send_byte(fd, 1, (i == MSG_EOR_IDX) ? MSG_EOR : 0);
|
|
||||||
|
curr_hash = 0;
|
||||||
|
page_size = getpagesize();
|
||||||
|
msg_count = SOCK_BUF_SIZE / MAX_MSG_SIZE;
|
||||||
|
|
||||||
|
for (int i = 0; i < msg_count; i++) {
|
||||||
|
ssize_t send_size;
|
||||||
|
size_t buf_size;
|
||||||
|
int flags;
|
||||||
|
void *buf;
|
||||||
|
|
||||||
|
/* Use "small" buffers and "big" buffers. */
|
||||||
|
if (i & 1)
|
||||||
|
buf_size = page_size +
|
||||||
|
(rand() % (MAX_MSG_SIZE - page_size));
|
||||||
|
else
|
||||||
|
buf_size = 1 + (rand() % page_size);
|
||||||
|
|
||||||
|
buf = malloc(buf_size);
|
||||||
|
|
||||||
|
if (!buf) {
|
||||||
|
perror("malloc");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(buf, rand() & 0xff, buf_size);
|
||||||
|
/* Set at least one MSG_EOR + some random. */
|
||||||
|
if (i == (msg_count / 2) || (rand() & 1)) {
|
||||||
|
flags = MSG_EOR;
|
||||||
|
curr_hash++;
|
||||||
|
} else {
|
||||||
|
flags = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
send_size = send(fd, buf, buf_size, flags);
|
||||||
|
|
||||||
|
if (send_size < 0) {
|
||||||
|
perror("send");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (send_size != buf_size) {
|
||||||
|
fprintf(stderr, "Invalid send size\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hash sum is computed at both client and server in
|
||||||
|
* the same way:
|
||||||
|
* H += hash('message data')
|
||||||
|
* Such hash "controls" both data integrity and message
|
||||||
|
* bounds. After data exchange, both sums are compared
|
||||||
|
* using control socket, and if message bounds wasn't
|
||||||
|
* broken - two values must be equal.
|
||||||
|
*/
|
||||||
|
curr_hash += hash_djb2(buf, buf_size);
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
control_writeln("SENDDONE");
|
control_writeln("SENDDONE");
|
||||||
|
control_writeulong(curr_hash);
|
||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_seqpacket_msg_bounds_server(const struct test_opts *opts)
|
static void test_seqpacket_msg_bounds_server(const struct test_opts *opts)
|
||||||
{
|
{
|
||||||
|
unsigned long sock_buf_size;
|
||||||
|
unsigned long remote_hash;
|
||||||
|
unsigned long curr_hash;
|
||||||
int fd;
|
int fd;
|
||||||
char buf[16];
|
char buf[MAX_MSG_SIZE];
|
||||||
struct msghdr msg = {0};
|
struct msghdr msg = {0};
|
||||||
struct iovec iov = {0};
|
struct iovec iov = {0};
|
||||||
|
|
||||||
@ -317,25 +382,57 @@ static void test_seqpacket_msg_bounds_server(const struct test_opts *opts)
|
|||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sock_buf_size = SOCK_BUF_SIZE;
|
||||||
|
|
||||||
|
if (setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_MAX_SIZE,
|
||||||
|
&sock_buf_size, sizeof(sock_buf_size))) {
|
||||||
|
perror("setsockopt(SO_VM_SOCKETS_BUFFER_MAX_SIZE)");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
|
||||||
|
&sock_buf_size, sizeof(sock_buf_size))) {
|
||||||
|
perror("setsockopt(SO_VM_SOCKETS_BUFFER_SIZE)");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ready to receive data. */
|
||||||
|
control_writeln("SRVREADY");
|
||||||
|
/* Wait, until peer sends whole data. */
|
||||||
control_expectln("SENDDONE");
|
control_expectln("SENDDONE");
|
||||||
iov.iov_base = buf;
|
iov.iov_base = buf;
|
||||||
iov.iov_len = sizeof(buf);
|
iov.iov_len = sizeof(buf);
|
||||||
msg.msg_iov = &iov;
|
msg.msg_iov = &iov;
|
||||||
msg.msg_iovlen = 1;
|
msg.msg_iovlen = 1;
|
||||||
|
|
||||||
for (int i = 0; i < MESSAGES_CNT; i++) {
|
curr_hash = 0;
|
||||||
if (recvmsg(fd, &msg, 0) != 1) {
|
|
||||||
perror("message bound violated");
|
while (1) {
|
||||||
|
ssize_t recv_size;
|
||||||
|
|
||||||
|
recv_size = recvmsg(fd, &msg, 0);
|
||||||
|
|
||||||
|
if (!recv_size)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (recv_size < 0) {
|
||||||
|
perror("recvmsg");
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((i == MSG_EOR_IDX) ^ !!(msg.msg_flags & MSG_EOR)) {
|
if (msg.msg_flags & MSG_EOR)
|
||||||
perror("MSG_EOR");
|
curr_hash++;
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
curr_hash += hash_djb2(msg.msg_iov[0].iov_base, recv_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
close(fd);
|
close(fd);
|
||||||
|
remote_hash = control_readulong();
|
||||||
|
|
||||||
|
if (curr_hash != remote_hash) {
|
||||||
|
fprintf(stderr, "Message bounds broken\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define MESSAGE_TRUNC_SZ 32
|
#define MESSAGE_TRUNC_SZ 32
|
||||||
@ -427,7 +524,7 @@ static void test_seqpacket_timeout_client(const struct test_opts *opts)
|
|||||||
tv.tv_usec = 0;
|
tv.tv_usec = 0;
|
||||||
|
|
||||||
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (void *)&tv, sizeof(tv)) == -1) {
|
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (void *)&tv, sizeof(tv)) == -1) {
|
||||||
perror("setsockopt 'SO_RCVTIMEO'");
|
perror("setsockopt(SO_RCVTIMEO)");
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -472,6 +569,70 @@ static void test_seqpacket_timeout_server(const struct test_opts *opts)
|
|||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_seqpacket_bigmsg_client(const struct test_opts *opts)
|
||||||
|
{
|
||||||
|
unsigned long sock_buf_size;
|
||||||
|
ssize_t send_size;
|
||||||
|
socklen_t len;
|
||||||
|
void *data;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
len = sizeof(sock_buf_size);
|
||||||
|
|
||||||
|
fd = vsock_seqpacket_connect(opts->peer_cid, 1234);
|
||||||
|
if (fd < 0) {
|
||||||
|
perror("connect");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
|
||||||
|
&sock_buf_size, &len)) {
|
||||||
|
perror("getsockopt");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
sock_buf_size++;
|
||||||
|
|
||||||
|
data = malloc(sock_buf_size);
|
||||||
|
if (!data) {
|
||||||
|
perror("malloc");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
send_size = send(fd, data, sock_buf_size, 0);
|
||||||
|
if (send_size != -1) {
|
||||||
|
fprintf(stderr, "expected 'send(2)' failure, got %zi\n",
|
||||||
|
send_size);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errno != EMSGSIZE) {
|
||||||
|
fprintf(stderr, "expected EMSGSIZE in 'errno', got %i\n",
|
||||||
|
errno);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
control_writeln("CLISENT");
|
||||||
|
|
||||||
|
free(data);
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_seqpacket_bigmsg_server(const struct test_opts *opts)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = vsock_seqpacket_accept(VMADDR_CID_ANY, 1234, NULL);
|
||||||
|
if (fd < 0) {
|
||||||
|
perror("accept");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
control_expectln("CLISENT");
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
#define BUF_PATTERN_1 'a'
|
#define BUF_PATTERN_1 'a'
|
||||||
#define BUF_PATTERN_2 'b'
|
#define BUF_PATTERN_2 'b'
|
||||||
|
|
||||||
@ -644,7 +805,7 @@ static void test_stream_poll_rcvlowat_client(const struct test_opts *opts)
|
|||||||
|
|
||||||
if (setsockopt(fd, SOL_SOCKET, SO_RCVLOWAT,
|
if (setsockopt(fd, SOL_SOCKET, SO_RCVLOWAT,
|
||||||
&lowat_val, sizeof(lowat_val))) {
|
&lowat_val, sizeof(lowat_val))) {
|
||||||
perror("setsockopt");
|
perror("setsockopt(SO_RCVLOWAT)");
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -754,6 +915,11 @@ static struct test_case test_cases[] = {
|
|||||||
.run_client = test_stream_poll_rcvlowat_client,
|
.run_client = test_stream_poll_rcvlowat_client,
|
||||||
.run_server = test_stream_poll_rcvlowat_server,
|
.run_server = test_stream_poll_rcvlowat_server,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.name = "SOCK_SEQPACKET big message",
|
||||||
|
.run_client = test_seqpacket_bigmsg_client,
|
||||||
|
.run_server = test_seqpacket_bigmsg_server,
|
||||||
|
},
|
||||||
{},
|
{},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -837,6 +1003,7 @@ int main(int argc, char **argv)
|
|||||||
.peer_cid = VMADDR_CID_ANY,
|
.peer_cid = VMADDR_CID_ANY,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
srand(time(NULL));
|
||||||
init_signals();
|
init_signals();
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
Loading…
Reference in New Issue
Block a user