490 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*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 129#define DEFAULT_PORT 123430 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 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(unsigned long 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 %lu bytes\n", vsock_buf_bytes);166 printf("SO_RCVLOWAT %lu 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 run_sender(int peer_cid, unsigned long to_send_bytes)255{256 time_t tx_begin_ns;257 time_t tx_total_ns;258 size_t total_send;259 time_t time_in_send;260 void *data;261 int fd;262 263 if (zerocopy)264 printf("Run as sender MSG_ZEROCOPY\n");265 else266 printf("Run as sender\n");267 268 printf("Connect to %i:%u\n", peer_cid, port);269 printf("Send %lu bytes\n", to_send_bytes);270 printf("TX buffer %lu bytes\n", buf_size_bytes);271 272 fd = vsock_connect(peer_cid, port);273 274 if (fd < 0)275 exit(EXIT_FAILURE);276 277 if (zerocopy) {278 enable_so_zerocopy(fd);279 280 data = mmap(NULL, buf_size_bytes, PROT_READ | PROT_WRITE,281 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);282 if (data == MAP_FAILED) {283 perror("mmap");284 exit(EXIT_FAILURE);285 }286 } else {287 data = malloc(buf_size_bytes);288 289 if (!data) {290 fprintf(stderr, "'malloc()' failed\n");291 exit(EXIT_FAILURE);292 }293 }294 295 memset(data, 0, buf_size_bytes);296 total_send = 0;297 time_in_send = 0;298 tx_begin_ns = current_nsec();299 300 while (total_send < to_send_bytes) {301 ssize_t sent;302 size_t rest_bytes;303 time_t before;304 305 rest_bytes = to_send_bytes - total_send;306 307 before = current_nsec();308 sent = send(fd, data, (rest_bytes > buf_size_bytes) ?309 buf_size_bytes : rest_bytes,310 zerocopy ? MSG_ZEROCOPY : 0);311 time_in_send += (current_nsec() - before);312 313 if (sent <= 0)314 error("write");315 316 total_send += sent;317 318 if (zerocopy) {319 struct pollfd fds = { 0 };320 321 fds.fd = fd;322 323 if (poll(&fds, 1, -1) < 0) {324 perror("poll");325 exit(EXIT_FAILURE);326 }327 328 if (!(fds.revents & POLLERR)) {329 fprintf(stderr, "POLLERR expected\n");330 exit(EXIT_FAILURE);331 }332 333 vsock_recv_completion(fd, NULL);334 }335 }336 337 tx_total_ns = current_nsec() - tx_begin_ns;338 339 printf("total bytes sent: %zu\n", total_send);340 printf("tx performance: %f Gbits/s\n",341 get_gbps(total_send * 8, time_in_send));342 printf("total time in tx loop: %f sec\n",343 (float)tx_total_ns / NSEC_PER_SEC);344 printf("time in 'send()': %f sec\n",345 (float)time_in_send / NSEC_PER_SEC);346 347 close(fd);348 349 if (zerocopy)350 munmap(data, buf_size_bytes);351 else352 free(data);353}354 355static const char optstring[] = "";356static const struct option longopts[] = {357 {358 .name = "help",359 .has_arg = no_argument,360 .val = 'H',361 },362 {363 .name = "sender",364 .has_arg = required_argument,365 .val = 'S',366 },367 {368 .name = "port",369 .has_arg = required_argument,370 .val = 'P',371 },372 {373 .name = "bytes",374 .has_arg = required_argument,375 .val = 'M',376 },377 {378 .name = "buf-size",379 .has_arg = required_argument,380 .val = 'B',381 },382 {383 .name = "vsk-size",384 .has_arg = required_argument,385 .val = 'V',386 },387 {388 .name = "rcvlowat",389 .has_arg = required_argument,390 .val = 'R',391 },392 {393 .name = "zerocopy",394 .has_arg = no_argument,395 .val = 'Z',396 },397 {},398};399 400static void usage(void)401{402 printf("Usage: ./vsock_perf [--help] [options]\n"403 "\n"404 "This is benchmarking utility, to test vsock performance.\n"405 "It runs in two modes: sender or receiver. In sender mode, it\n"406 "connects to the specified CID and starts data transmission.\n"407 "\n"408 "Options:\n"409 " --help This message\n"410 " --sender <cid> Sender mode (receiver default)\n"411 " <cid> of the receiver to connect to\n"412 " --zerocopy Enable zerocopy (for sender mode only)\n"413 " --port <port> Port (default %d)\n"414 " --bytes <bytes>KMG Bytes to send (default %d)\n"415 " --buf-size <bytes>KMG Data buffer size (default %d). In sender mode\n"416 " it is the buffer size, passed to 'write()'. In\n"417 " receiver mode it is the buffer size passed to 'read()'.\n"418 " --vsk-size <bytes>KMG Socket buffer size (default %d)\n"419 " --rcvlowat <bytes>KMG SO_RCVLOWAT value (default %d)\n"420 "\n", DEFAULT_PORT, DEFAULT_TO_SEND_BYTES,421 DEFAULT_BUF_SIZE_BYTES, DEFAULT_VSOCK_BUF_BYTES,422 DEFAULT_RCVLOWAT_BYTES);423 exit(EXIT_FAILURE);424}425 426static long strtolx(const char *arg)427{428 long value;429 char *end;430 431 value = strtol(arg, &end, 10);432 433 if (end != arg + strlen(arg))434 usage();435 436 return value;437}438 439int main(int argc, char **argv)440{441 unsigned long to_send_bytes = DEFAULT_TO_SEND_BYTES;442 unsigned long rcvlowat_bytes = DEFAULT_RCVLOWAT_BYTES;443 int peer_cid = -1;444 bool sender = false;445 446 while (1) {447 int opt = getopt_long(argc, argv, optstring, longopts, NULL);448 449 if (opt == -1)450 break;451 452 switch (opt) {453 case 'V': /* Peer buffer size. */454 vsock_buf_bytes = memparse(optarg);455 break;456 case 'R': /* SO_RCVLOWAT value. */457 rcvlowat_bytes = memparse(optarg);458 break;459 case 'P': /* Port to connect to. */460 port = strtolx(optarg);461 break;462 case 'M': /* Bytes to send. */463 to_send_bytes = memparse(optarg);464 break;465 case 'B': /* Size of rx/tx buffer. */466 buf_size_bytes = memparse(optarg);467 break;468 case 'S': /* Sender mode. CID to connect to. */469 peer_cid = strtolx(optarg);470 sender = true;471 break;472 case 'H': /* Help. */473 usage();474 break;475 case 'Z': /* Zerocopy. */476 zerocopy = true;477 break;478 default:479 usage();480 }481 }482 483 if (!sender)484 run_receiver(rcvlowat_bytes);485 else486 run_sender(peer_cid, to_send_bytes);487 488 return 0;489}490