| /* |
| * Copyright (c) 2012 Intel Corporation. All rights reserved. |
| * |
| * This software is available to you under the OpenIB.org BSD license |
| * below: |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials |
| * provided with the distribution. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AWV |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <errno.h> |
| #include <getopt.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/time.h> |
| #include <sys/wait.h> |
| #include <netdb.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <netinet/tcp.h> |
| #include <arpa/inet.h> |
| |
| #include <rdma/rdma_cma.h> |
| #include <rdma/rsocket.h> |
| #include <util/compiler.h> |
| #include "common.h" |
| |
| static int test_size[] = { |
| (1 << 6), |
| (1 << 7), ((1 << 7) + (1 << 6)), |
| (1 << 8), ((1 << 8) + (1 << 7)), |
| (1 << 9), ((1 << 9) + (1 << 8)), |
| (1 << 10), ((1 << 10) + (1 << 9)), |
| }; |
| #define TEST_CNT (sizeof test_size / sizeof test_size[0]) |
| |
| enum { |
| msg_op_login, |
| msg_op_start, |
| msg_op_data, |
| msg_op_echo, |
| msg_op_end |
| }; |
| |
| struct message { |
| uint8_t op; |
| uint8_t id; |
| uint8_t seqno; |
| uint8_t reserved; |
| __be32 data; |
| uint8_t buf[2048]; |
| }; |
| |
| #define CTRL_MSG_SIZE 16 |
| |
| struct client { |
| uint64_t recvcnt; |
| }; |
| |
| static struct client clients[256]; |
| static uint8_t id; |
| |
| static int rs; |
| static int use_async; |
| static int flags = MSG_DONTWAIT; |
| static int poll_timeout; |
| static int custom; |
| static int echo; |
| static int transfer_size = 1000; |
| static int transfer_count = 1000; |
| static int buffer_size; |
| static char test_name[10] = "custom"; |
| static const char *port = "7174"; |
| static char *dst_addr; |
| static char *src_addr; |
| static union socket_addr g_addr; |
| static socklen_t g_addrlen; |
| static struct timeval start, end; |
| static struct message g_msg; |
| |
| static void show_perf(void) |
| { |
| char str[32]; |
| float usec; |
| long long bytes; |
| int transfers; |
| |
| usec = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec); |
| transfers = echo ? transfer_count * 2 : be32toh(g_msg.data); |
| bytes = (long long) transfers * transfer_size; |
| |
| /* name size transfers bytes seconds Gb/sec usec/xfer */ |
| printf("%-10s", test_name); |
| size_str(str, sizeof str, transfer_size); |
| printf("%-8s", str); |
| cnt_str(str, sizeof str, transfers); |
| printf("%-8s", str); |
| size_str(str, sizeof str, bytes); |
| printf("%-8s", str); |
| printf("%8.2fs%10.2f%11.2f\n", |
| usec / 1000000., (bytes * 8) / (1000. * usec), |
| (usec / transfers)); |
| } |
| |
| static void init_latency_test(int size) |
| { |
| char sstr[5]; |
| |
| size_str(sstr, sizeof sstr, size); |
| snprintf(test_name, sizeof test_name, "%s_lat", sstr); |
| transfer_size = size; |
| transfer_count = size_to_count(transfer_size) / 10; |
| echo = 1; |
| } |
| |
| static void init_bandwidth_test(int size) |
| { |
| char sstr[5]; |
| |
| size_str(sstr, sizeof sstr, size); |
| snprintf(test_name, sizeof test_name, "%s_bw", sstr); |
| transfer_size = size; |
| transfer_count = size_to_count(transfer_size); |
| echo = 0; |
| } |
| |
| static void set_options(int fd) |
| { |
| int val; |
| |
| if (buffer_size) { |
| rs_setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *) &buffer_size, |
| sizeof buffer_size); |
| rs_setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *) &buffer_size, |
| sizeof buffer_size); |
| } else { |
| val = 1 << 19; |
| rs_setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *) &val, sizeof val); |
| rs_setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *) &val, sizeof val); |
| } |
| |
| if (flags & MSG_DONTWAIT) |
| rs_fcntl(fd, F_SETFL, O_NONBLOCK); |
| } |
| |
| static ssize_t svr_send(struct message *msg, size_t size, |
| union socket_addr *addr, socklen_t addrlen) |
| { |
| struct pollfd fds; |
| ssize_t ret; |
| |
| if (use_async) { |
| fds.fd = rs; |
| fds.events = POLLOUT; |
| } |
| |
| do { |
| if (use_async) { |
| ret = do_poll(&fds, poll_timeout); |
| if (ret) |
| return ret; |
| } |
| |
| ret = rs_sendto(rs, msg, size, flags, &addr->sa, addrlen); |
| } while (ret < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)); |
| |
| if (ret < 0) |
| perror("rsend"); |
| |
| return ret; |
| } |
| |
| static ssize_t svr_recv(struct message *msg, size_t size, |
| union socket_addr *addr, socklen_t *addrlen) |
| { |
| struct pollfd fds; |
| ssize_t ret; |
| |
| if (use_async) { |
| fds.fd = rs; |
| fds.events = POLLIN; |
| } |
| |
| do { |
| if (use_async) { |
| ret = do_poll(&fds, poll_timeout); |
| if (ret) |
| return ret; |
| } |
| |
| ret = rs_recvfrom(rs, msg, size, flags, &addr->sa, addrlen); |
| } while (ret < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)); |
| |
| if (ret < 0) |
| perror("rrecv"); |
| |
| return ret; |
| } |
| |
| static int svr_process(struct message *msg, size_t size, |
| union socket_addr *addr, socklen_t addrlen) |
| { |
| char str[64]; |
| ssize_t ret; |
| |
| switch (msg->op) { |
| case msg_op_login: |
| if (addr->sa.sa_family == AF_INET) { |
| printf("client login from %s\n", |
| inet_ntop(AF_INET, &addr->sin.sin_addr.s_addr, |
| str, sizeof str)); |
| } else { |
| printf("client login from %s\n", |
| inet_ntop(AF_INET6, &addr->sin6.sin6_addr.s6_addr, |
| str, sizeof str)); |
| } |
| msg->id = id++; |
| /* fall through */ |
| case msg_op_start: |
| memset(&clients[msg->id], 0, sizeof clients[msg->id]); |
| break; |
| case msg_op_echo: |
| clients[msg->id].recvcnt++; |
| break; |
| case msg_op_end: |
| msg->data = htobe32(clients[msg->id].recvcnt); |
| break; |
| default: |
| clients[msg->id].recvcnt++; |
| return 0; |
| } |
| |
| ret = svr_send(msg, size, addr, addrlen); |
| return (ret == size) ? 0 : (int) ret; |
| } |
| |
| static int svr_bind(void) |
| { |
| struct addrinfo hints, *res; |
| int ret; |
| |
| memset(&hints, 0, sizeof hints); |
| hints.ai_socktype = SOCK_DGRAM; |
| ret = getaddrinfo(src_addr, port, &hints, &res); |
| if (ret) { |
| printf("getaddrinfo: %s\n", gai_strerror(ret)); |
| return ret; |
| } |
| |
| rs = rs_socket(res->ai_family, res->ai_socktype, res->ai_protocol); |
| if (rs < 0) { |
| ret = rs; |
| goto out; |
| } |
| |
| set_options(rs); |
| ret = rs_bind(rs, res->ai_addr, res->ai_addrlen); |
| if (ret) { |
| perror("rbind"); |
| rs_close(rs); |
| } |
| |
| out: |
| free(res); |
| return ret; |
| } |
| |
| static int svr_run(void) |
| { |
| ssize_t len; |
| int ret; |
| |
| ret = svr_bind(); |
| while (!ret) { |
| g_addrlen = sizeof g_addr; |
| len = svr_recv(&g_msg, sizeof g_msg, &g_addr, &g_addrlen); |
| if (len < 0) |
| return len; |
| |
| ret = svr_process(&g_msg, len, &g_addr, g_addrlen); |
| } |
| return ret; |
| } |
| |
| static ssize_t client_send(struct message *msg, size_t size) |
| { |
| struct pollfd fds; |
| int ret; |
| |
| if (use_async) { |
| fds.fd = rs; |
| fds.events = POLLOUT; |
| } |
| |
| do { |
| if (use_async) { |
| ret = do_poll(&fds, poll_timeout); |
| if (ret) |
| return ret; |
| } |
| |
| ret = rs_send(rs, msg, size, flags); |
| } while (ret < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)); |
| |
| if (ret < 0) |
| perror("rsend"); |
| |
| return ret; |
| } |
| |
| static ssize_t client_recv(struct message *msg, size_t size, int timeout) |
| { |
| struct pollfd fds; |
| int ret; |
| |
| if (timeout) { |
| fds.fd = rs; |
| fds.events = POLLIN; |
| |
| ret = rs_poll(&fds, 1, timeout); |
| if (ret <= 0) |
| return ret; |
| } |
| |
| ret = rs_recv(rs, msg, size, flags | MSG_DONTWAIT); |
| if (ret < 0 && errno != EWOULDBLOCK && errno != EAGAIN) |
| perror("rrecv"); |
| |
| return ret; |
| } |
| |
| static int client_send_recv(struct message *msg, size_t size, int timeout) |
| { |
| static uint8_t seqno; |
| int ret; |
| |
| msg->seqno = seqno; |
| do { |
| ret = client_send(msg, size); |
| if (ret != size) |
| return ret; |
| |
| ret = client_recv(msg, size, timeout); |
| } while (ret <= 0 || msg->seqno != seqno); |
| |
| seqno++; |
| return ret; |
| } |
| |
| static int run_test(void) |
| { |
| int ret, i; |
| |
| g_msg.op = msg_op_start; |
| ret = client_send_recv(&g_msg, CTRL_MSG_SIZE, 1000); |
| if (ret != CTRL_MSG_SIZE) |
| goto out; |
| |
| g_msg.op = echo ? msg_op_echo : msg_op_data; |
| gettimeofday(&start, NULL); |
| for (i = 0; i < transfer_count; i++) { |
| ret = echo ? client_send_recv(&g_msg, transfer_size, 1) : |
| client_send(&g_msg, transfer_size); |
| if (ret != transfer_size) |
| goto out; |
| } |
| |
| g_msg.op = msg_op_end; |
| ret = client_send_recv(&g_msg, CTRL_MSG_SIZE, 1); |
| if (ret != CTRL_MSG_SIZE) |
| goto out; |
| |
| gettimeofday(&end, NULL); |
| show_perf(); |
| ret = 0; |
| |
| out: |
| return ret; |
| } |
| |
| static int client_connect(void) |
| { |
| struct addrinfo hints, *res; |
| int ret; |
| |
| memset(&hints, 0, sizeof hints); |
| hints.ai_socktype = SOCK_DGRAM; |
| ret = getaddrinfo(dst_addr, port, &hints, &res); |
| if (ret) { |
| printf("getaddrinfo: %s\n", gai_strerror(ret)); |
| return ret; |
| } |
| |
| rs = rs_socket(res->ai_family, res->ai_socktype, res->ai_protocol); |
| if (rs < 0) { |
| ret = rs; |
| goto out; |
| } |
| |
| set_options(rs); |
| ret = rs_connect(rs, res->ai_addr, res->ai_addrlen); |
| if (ret) { |
| if (errno == ENODEV) |
| fprintf(stderr, "No RDMA devices were detected\n"); |
| else |
| perror("rconnect"); |
| rs_close(rs); |
| goto out; |
| } |
| |
| g_msg.op = msg_op_login; |
| ret = client_send_recv(&g_msg, CTRL_MSG_SIZE, 1000); |
| if (ret == CTRL_MSG_SIZE) |
| ret = 0; |
| |
| out: |
| freeaddrinfo(res); |
| return ret; |
| } |
| |
| static int client_run(void) |
| { |
| int i, ret; |
| |
| printf("%-10s%-8s%-8s%-8s%8s %10s%13s\n", |
| "name", "bytes", "xfers", "total", "time", "Gb/sec", "usec/xfer"); |
| |
| ret = client_connect(); |
| if (ret) |
| return ret; |
| |
| if (!custom) { |
| for (i = 0; i < TEST_CNT; i++) { |
| init_latency_test(test_size[i]); |
| run_test(); |
| } |
| for (i = 0; i < TEST_CNT; i++) { |
| init_bandwidth_test(test_size[i]); |
| run_test(); |
| } |
| } else { |
| run_test(); |
| } |
| rs_close(rs); |
| |
| return ret; |
| } |
| |
| static int set_test_opt(const char *arg) |
| { |
| if (strlen(arg) == 1) { |
| switch (arg[0]) { |
| case 's': |
| use_rs = 0; |
| break; |
| case 'a': |
| use_async = 1; |
| break; |
| case 'b': |
| flags = 0; |
| break; |
| case 'n': |
| flags = MSG_DONTWAIT; |
| break; |
| case 'e': |
| echo = 1; |
| break; |
| default: |
| return -1; |
| } |
| } else { |
| if (!strncasecmp("socket", arg, 6)) { |
| use_rs = 0; |
| } else if (!strncasecmp("async", arg, 5)) { |
| use_async = 1; |
| } else if (!strncasecmp("block", arg, 5)) { |
| flags = 0; |
| } else if (!strncasecmp("nonblock", arg, 8)) { |
| flags = MSG_DONTWAIT; |
| } else if (!strncasecmp("echo", arg, 4)) { |
| echo = 1; |
| } else { |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int op, ret; |
| |
| while ((op = getopt(argc, argv, "s:b:B:C:S:p:T:")) != -1) { |
| switch (op) { |
| case 's': |
| dst_addr = optarg; |
| break; |
| case 'b': |
| src_addr = optarg; |
| break; |
| case 'B': |
| buffer_size = atoi(optarg); |
| break; |
| case 'C': |
| custom = 1; |
| transfer_count = atoi(optarg); |
| break; |
| case 'S': |
| custom = 1; |
| transfer_size = atoi(optarg); |
| if (transfer_size < CTRL_MSG_SIZE) { |
| printf("size must be at least %d bytes\n", |
| CTRL_MSG_SIZE); |
| exit(1); |
| } |
| break; |
| case 'p': |
| port = optarg; |
| break; |
| case 'T': |
| if (!set_test_opt(optarg)) |
| break; |
| /* invalid option - fall through */ |
| SWITCH_FALLTHROUGH; |
| default: |
| printf("usage: %s\n", argv[0]); |
| printf("\t[-s server_address]\n"); |
| printf("\t[-b bind_address]\n"); |
| printf("\t[-B buffer_size]\n"); |
| printf("\t[-C transfer_count]\n"); |
| printf("\t[-S transfer_size]\n"); |
| printf("\t[-p port_number]\n"); |
| printf("\t[-T test_option]\n"); |
| printf("\t s|sockets - use standard tcp/ip sockets\n"); |
| printf("\t a|async - asynchronous operation (use poll)\n"); |
| printf("\t b|blocking - use blocking calls\n"); |
| printf("\t n|nonblocking - use nonblocking calls\n"); |
| printf("\t e|echo - server echoes all messages\n"); |
| exit(1); |
| } |
| } |
| |
| if (flags) |
| poll_timeout = -1; |
| |
| ret = dst_addr ? client_run() : svr_run(); |
| return ret; |
| } |