vsock_perf.c 10 KB

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