// Copyright (c) Vitaliy Filippov, 2019+ // License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details) /** * Stub "OSD" to test & compare network performance with sync read/write and io_uring * * Core i7-6700HQ laptop * * stub_osd: * randwrite Q1 S1: 36900 iops * randwrite Q32 S32: 71000 iops * randwrite Q32 S32 (multi-fsync fix): 113000 iops * randread Q1: 67300 iops * randread Q32: 144000 iops * * io_uring osd with #define OSD_STUB: * randwrite Q1 S1: 30000 iops * randwrite Q32 S32: 78600 iops * randwrite Q32 S32 (multi-fsync fix): 125000 iops * randread Q1: 50700 iops * randread Q32: 86100 iops * * It seems io_uring is fine :) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "addr_util.h" #include "rw_blocking.h" #include "osd_ops.h" int bind_stub(std::string bind_address, int bind_port); void run_stub(int peer_fd, int peer_data_fd); int main(int narg, char *args[]) { int listen_fd = bind_stub("0.0.0.0", 11203); int listen_data_fd = bind_stub("0.0.0.0", 11204); /* int mss = 8192; if (setsockopt(listen_data_fd, IPPROTO_TCP, TCP_MAXSEG, &mss, sizeof(mss)) < 0) { throw std::runtime_error(std::string("setsockopt TCP_MAXSEG: ") + strerror(errno)); } int rcvlowat = 4096; if (setsockopt(listen_data_fd, SOL_SOCKET, SO_RCVLOWAT, &rcvlowat, sizeof(rcvlowat)) < 0) { throw std::runtime_error(std::string("setsockopt SO_RCVLOWAT: ") + strerror(errno)); }*/ // Accept new connections sockaddr addr; socklen_t peer_addr_size; int peer_fd, peer_data_fd; const int one = 1; while (1) { printf("stub_osd: waiting for 1 client\n"); peer_addr_size = sizeof(addr); peer_fd = accept(listen_fd, (sockaddr*)&addr, &peer_addr_size); if (peer_fd == -1) { if (errno == EAGAIN) continue; else throw std::runtime_error(std::string("accept: ") + strerror(errno)); } setsockopt(peer_fd, SOL_TCP, TCP_NODELAY, &one, sizeof(one)); printf("stub_osd: new client %d: connection from %s\n", peer_fd, addr_to_string(*((sockaddr*)&addr)).c_str()); printf("stub_osd: waiting for 1 data connection\n"); peer_addr_size = sizeof(addr); peer_data_fd = accept(listen_data_fd, (sockaddr*)&addr, &peer_addr_size); if (peer_data_fd == -1) { if (errno == EAGAIN) continue; else throw std::runtime_error(std::string("accept: ") + strerror(errno)); } setsockopt(peer_data_fd, SOL_TCP, TCP_NODELAY, &one, sizeof(one)); printf("stub_osd: new client %d: data connection from %s\n", peer_data_fd, addr_to_string(*((sockaddr*)&addr)).c_str()); run_stub(peer_fd, peer_data_fd); close(peer_data_fd); close(peer_fd); printf("stub_osd: client %d / data %d disconnected\n", peer_fd, peer_data_fd); // Try to accept next connection } return 0; } int bind_stub(std::string bind_address, int bind_port) { int listen_backlog = 128; sockaddr_storage addr = { 0 }; if (!string_to_addr(bind_address, 0, bind_port, (sockaddr*)&addr)) { throw std::runtime_error("bind address "+bind_address+" is not valid"); } int listen_fd = socket(addr.ss_family, SOCK_STREAM, 0); if (listen_fd < 0) { throw std::runtime_error(std::string("socket: ") + strerror(errno)); } int enable = 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); if (bind(listen_fd, (sockaddr*)&addr, addr.ss_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)) < 0) { close(listen_fd); throw std::runtime_error(std::string("bind: ") + strerror(errno)); } if (listen(listen_fd, listen_backlog) < 0) { close(listen_fd); throw std::runtime_error(std::string("listen: ") + strerror(errno)); } return listen_fd; } void run_stub(int peer_fd, int peer_data_fd) { osd_any_op_t op; osd_any_reply_t reply = { 0 }; unsigned bufsize = 4*1024*1024; void *buf = mmap(NULL, bufsize, PROT_READ, MAP_SHARED, peer_data_fd, 0); if (buf == MAP_FAILED) { throw std::runtime_error(std::string("mmap: ") + strerror(errno)); } while (1) { int r = read_blocking(peer_fd, op.buf, OSD_PACKET_SIZE); if (r < OSD_PACKET_SIZE) { break; } if (op.hdr.magic != SECONDARY_OSD_OP_MAGIC) { printf("client %d: bad magic number in operation header\n", peer_fd); break; } reply.hdr.magic = SECONDARY_OSD_REPLY_MAGIC; reply.hdr.id = op.hdr.id; reply.hdr.opcode = op.hdr.opcode; if (op.hdr.opcode == OSD_OP_SEC_READ) { reply.hdr.retval = op.sec_rw.len; void *buf = malloc(op.sec_rw.len); r = write_blocking(peer_fd, reply.buf, OSD_PACKET_SIZE); if (r == OSD_PACKET_SIZE) r = write_blocking(peer_data_fd, buf, op.sec_rw.len); free(buf); if (r < op.sec_rw.len) break; } else if (op.hdr.opcode == OSD_OP_SEC_WRITE || op.hdr.opcode == OSD_OP_SEC_WRITE_STABLE) { struct pollfd pfd = { .fd = peer_data_fd, .events = POLLIN }; poll(&pfd, 1, 10000); struct tcp_zerocopy_receive zc = { .address = (uint64_t)buf, .length = op.sec_rw.len }; socklen_t zc_len = sizeof(zc); r = getsockopt(peer_data_fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, &zc, &zc_len); r = r == -1 ? 0 : zc.length; if (r > 0) { uint64_t hash = 0; for (int k = 0; k < r/8; k++) hash ^= ((uint64_t*)buf)[k]; printf("ZCTR: op=%lx r=%d len=%d skip=%d hash=%lx\n", op.hdr.id, r, zc.length, zc.recv_skip_hint, hash); } if (r < op.sec_rw.len) { int rest = op.sec_rw.len - r; void *buf = malloc(rest); r += read_blocking(peer_data_fd, buf, rest); free(buf); } reply.hdr.retval = op.sec_rw.len; if (r == op.sec_rw.len) r = write_blocking(peer_fd, reply.buf, OSD_PACKET_SIZE); else r = 0; if (r < OSD_PACKET_SIZE) break; } else if (op.hdr.opcode == OSD_OP_TEST_SYNC_STAB_ALL) { reply.hdr.retval = 0; r = write_blocking(peer_fd, reply.buf, OSD_PACKET_SIZE); if (r < OSD_PACKET_SIZE) break; } else { printf("client %d: unsupported stub opcode: %lu\n", peer_fd, op.hdr.opcode); break; } } }