gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/perf/linux/send_recv_benchmark.cc (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include <netinet/in.h> 16 #include <netinet/tcp.h> 17 #include <poll.h> 18 #include <sys/ioctl.h> 19 #include <sys/socket.h> 20 21 #include <cstring> 22 23 #include "gtest/gtest.h" 24 #include "absl/synchronization/notification.h" 25 #include "benchmark/benchmark.h" 26 #include "test/util/file_descriptor.h" 27 #include "test/util/logging.h" 28 #include "test/util/posix_error.h" 29 #include "test/util/socket_util.h" 30 #include "test/util/test_util.h" 31 #include "test/util/thread_util.h" 32 33 namespace gvisor { 34 namespace testing { 35 36 namespace { 37 38 constexpr ssize_t kMessageSize = 1024; 39 40 class Message { 41 public: 42 explicit Message(int byte = 0) : Message(byte, kMessageSize, 0) {} 43 44 explicit Message(int byte, int sz) : Message(byte, sz, 0) {} 45 46 explicit Message(int byte, int sz, int cmsg_sz) 47 : buffer_(sz, byte), cmsg_buffer_(cmsg_sz, 0) { 48 iov_.iov_base = buffer_.data(); 49 iov_.iov_len = sz; 50 hdr_.msg_iov = &iov_; 51 hdr_.msg_iovlen = 1; 52 hdr_.msg_control = cmsg_buffer_.data(); 53 hdr_.msg_controllen = cmsg_sz; 54 } 55 56 struct msghdr* header() { 57 return &hdr_; 58 } 59 60 private: 61 std::vector<char> buffer_; 62 std::vector<char> cmsg_buffer_; 63 struct iovec iov_ = {}; 64 struct msghdr hdr_ = {}; 65 }; 66 67 void BM_Recvmsg(benchmark::State& state) { 68 int sockets[2]; 69 TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0); 70 FileDescriptor send_socket(sockets[0]), recv_socket(sockets[1]); 71 absl::Notification notification; 72 Message send_msg('a'), recv_msg; 73 74 ScopedThread t([&send_msg, &send_socket, ¬ification] { 75 while (!notification.HasBeenNotified()) { 76 sendmsg(send_socket.get(), send_msg.header(), 0); 77 } 78 }); 79 80 int64_t bytes_received = 0; 81 for (auto ignored : state) { 82 int n = recvmsg(recv_socket.get(), recv_msg.header(), 0); 83 if (n == -1 && errno == EINTR) { 84 continue; 85 } 86 TEST_CHECK(n > 0); 87 bytes_received += n; 88 } 89 90 notification.Notify(); 91 recv_socket.reset(); 92 93 state.SetBytesProcessed(bytes_received); 94 } 95 96 BENCHMARK(BM_Recvmsg)->UseRealTime(); 97 98 void BM_Sendmsg(benchmark::State& state) { 99 int sockets[2]; 100 TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0); 101 FileDescriptor send_socket(sockets[0]), recv_socket(sockets[1]); 102 absl::Notification notification; 103 Message send_msg('a'), recv_msg; 104 105 ScopedThread t([&recv_msg, &recv_socket, ¬ification] { 106 while (!notification.HasBeenNotified()) { 107 recvmsg(recv_socket.get(), recv_msg.header(), 0); 108 } 109 }); 110 111 int64_t bytes_sent = 0; 112 for (auto ignored : state) { 113 int n = sendmsg(send_socket.get(), send_msg.header(), 0); 114 if (n == -1 && errno == EINTR) { 115 continue; 116 } 117 TEST_CHECK(n > 0); 118 bytes_sent += n; 119 } 120 121 notification.Notify(); 122 send_socket.reset(); 123 124 state.SetBytesProcessed(bytes_sent); 125 } 126 127 BENCHMARK(BM_Sendmsg)->UseRealTime(); 128 129 void BM_Recvfrom(benchmark::State& state) { 130 int sockets[2]; 131 TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0); 132 FileDescriptor send_socket(sockets[0]), recv_socket(sockets[1]); 133 absl::Notification notification; 134 char send_buffer[kMessageSize], recv_buffer[kMessageSize]; 135 136 ScopedThread t([&send_socket, &send_buffer, ¬ification] { 137 while (!notification.HasBeenNotified()) { 138 sendto(send_socket.get(), send_buffer, kMessageSize, 0, nullptr, 0); 139 } 140 }); 141 142 int bytes_received = 0; 143 for (auto ignored : state) { 144 int n = recvfrom(recv_socket.get(), recv_buffer, kMessageSize, 0, nullptr, 145 nullptr); 146 if (n == -1 && errno == EINTR) { 147 continue; 148 } 149 TEST_CHECK(n > 0); 150 bytes_received += n; 151 } 152 153 notification.Notify(); 154 recv_socket.reset(); 155 156 state.SetBytesProcessed(bytes_received); 157 } 158 159 BENCHMARK(BM_Recvfrom)->UseRealTime(); 160 161 void BM_Sendto(benchmark::State& state) { 162 int sockets[2]; 163 TEST_CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0); 164 FileDescriptor send_socket(sockets[0]), recv_socket(sockets[1]); 165 absl::Notification notification; 166 char send_buffer[kMessageSize], recv_buffer[kMessageSize]; 167 168 ScopedThread t([&recv_socket, &recv_buffer, ¬ification] { 169 while (!notification.HasBeenNotified()) { 170 recvfrom(recv_socket.get(), recv_buffer, kMessageSize, 0, nullptr, 171 nullptr); 172 } 173 }); 174 175 int64_t bytes_sent = 0; 176 for (auto ignored : state) { 177 int n = sendto(send_socket.get(), send_buffer, kMessageSize, 0, nullptr, 0); 178 if (n == -1 && errno == EINTR) { 179 continue; 180 } 181 TEST_CHECK(n > 0); 182 bytes_sent += n; 183 } 184 185 notification.Notify(); 186 send_socket.reset(); 187 188 state.SetBytesProcessed(bytes_sent); 189 } 190 191 BENCHMARK(BM_Sendto)->UseRealTime(); 192 193 PosixErrorOr<sockaddr_storage> InetLoopbackAddr(int family) { 194 struct sockaddr_storage addr; 195 memset(&addr, 0, sizeof(addr)); 196 addr.ss_family = family; 197 switch (family) { 198 case AF_INET: 199 reinterpret_cast<struct sockaddr_in*>(&addr)->sin_addr.s_addr = 200 htonl(INADDR_LOOPBACK); 201 break; 202 case AF_INET6: 203 reinterpret_cast<struct sockaddr_in6*>(&addr)->sin6_addr = 204 in6addr_loopback; 205 break; 206 default: 207 return PosixError(EINVAL, 208 absl::StrCat("unknown socket family: ", family)); 209 } 210 return addr; 211 } 212 213 // BM_RecvmsgWithControlBuf measures the performance of recvmsg when we allocate 214 // space for control messages. Note that we do not expect to receive any. 215 void BM_RecvmsgWithControlBuf(benchmark::State& state) { 216 auto listen_socket = 217 ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)); 218 219 // Initialize address to the loopback one. 220 sockaddr_storage addr = ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(AF_INET6)); 221 socklen_t addrlen = sizeof(addr); 222 223 // Bind to some port then start listening. 224 ASSERT_THAT(bind(listen_socket.get(), 225 reinterpret_cast<struct sockaddr*>(&addr), addrlen), 226 SyscallSucceeds()); 227 228 ASSERT_THAT(listen(listen_socket.get(), SOMAXCONN), SyscallSucceeds()); 229 230 // Get the address we're listening on, then connect to it. We need to do this 231 // because we're allowing the stack to pick a port for us. 232 ASSERT_THAT(getsockname(listen_socket.get(), 233 reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 234 SyscallSucceeds()); 235 236 auto send_socket = 237 ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)); 238 239 ASSERT_THAT( 240 RetryEINTR(connect)(send_socket.get(), 241 reinterpret_cast<struct sockaddr*>(&addr), addrlen), 242 SyscallSucceeds()); 243 244 // Accept the connection. 245 auto recv_socket = 246 ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_socket.get(), nullptr, nullptr)); 247 248 absl::Notification notification; 249 Message send_msg('a'); 250 // Create a msghdr with a buffer allocated for control messages. 251 Message recv_msg(0, kMessageSize, /*cmsg_sz=*/24); 252 253 ScopedThread t([&send_msg, &send_socket, ¬ification] { 254 while (!notification.HasBeenNotified()) { 255 sendmsg(send_socket.get(), send_msg.header(), 0); 256 } 257 }); 258 259 int64_t bytes_received = 0; 260 for (auto ignored : state) { 261 int n = recvmsg(recv_socket.get(), recv_msg.header(), 0); 262 if (n == -1 && errno == EINTR) { 263 continue; 264 } 265 TEST_CHECK(n > 0); 266 bytes_received += n; 267 } 268 269 notification.Notify(); 270 recv_socket.reset(); 271 272 state.SetBytesProcessed(bytes_received); 273 } 274 275 BENCHMARK(BM_RecvmsgWithControlBuf)->UseRealTime(); 276 277 // BM_SendmsgTCP measures the sendmsg throughput with varying payload sizes. 278 // 279 // state.Args[0] indicates whether the underlying socket should be blocking or 280 // non-blocking w/ 0 indicating non-blocking and 1 to indicate blocking. 281 // state.Args[1] is the size of the payload to be used per sendmsg call. 282 void BM_SendmsgTCP(benchmark::State& state) { 283 auto listen_socket = 284 ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); 285 286 // Initialize address to the loopback one. 287 sockaddr_storage addr = ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(AF_INET)); 288 socklen_t addrlen = sizeof(addr); 289 290 // Bind to some port then start listening. 291 ASSERT_THAT(bind(listen_socket.get(), 292 reinterpret_cast<struct sockaddr*>(&addr), addrlen), 293 SyscallSucceeds()); 294 295 ASSERT_THAT(listen(listen_socket.get(), SOMAXCONN), SyscallSucceeds()); 296 297 // Get the address we're listening on, then connect to it. We need to do this 298 // because we're allowing the stack to pick a port for us. 299 ASSERT_THAT(getsockname(listen_socket.get(), 300 reinterpret_cast<struct sockaddr*>(&addr), &addrlen), 301 SyscallSucceeds()); 302 303 auto send_socket = 304 ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); 305 306 ASSERT_THAT( 307 RetryEINTR(connect)(send_socket.get(), 308 reinterpret_cast<struct sockaddr*>(&addr), addrlen), 309 SyscallSucceeds()); 310 311 // Accept the connection. 312 auto recv_socket = 313 ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_socket.get(), nullptr, nullptr)); 314 315 // Check if we want to run the test w/ a blocking send socket 316 // or non-blocking. 317 const int blocking = state.range(0); 318 if (!blocking) { 319 // Set the send FD to O_NONBLOCK. 320 int opts; 321 ASSERT_THAT(opts = fcntl(send_socket.get(), F_GETFL), SyscallSucceeds()); 322 opts |= O_NONBLOCK; 323 ASSERT_THAT(fcntl(send_socket.get(), F_SETFL, opts), SyscallSucceeds()); 324 } 325 326 absl::Notification notification; 327 328 // Get the buffer size we should use for this iteration of the test. 329 const int buf_size = state.range(1); 330 Message send_msg('a', buf_size), recv_msg(0, buf_size); 331 332 ScopedThread t([&recv_msg, &recv_socket, ¬ification] { 333 while (!notification.HasBeenNotified()) { 334 int rc = recvmsg(recv_socket.get(), recv_msg.header(), 0); 335 if (rc == -1 && errno == EINTR) { 336 continue; 337 } 338 TEST_CHECK(rc >= 0); 339 } 340 }); 341 342 int64_t bytes_sent = 0; 343 int ncalls = 0; 344 for (auto ignored : state) { 345 int sent = 0; 346 while (true) { 347 struct msghdr hdr = {}; 348 struct iovec iov = {}; 349 struct msghdr* snd_header = send_msg.header(); 350 iov.iov_base = static_cast<char*>(snd_header->msg_iov->iov_base) + sent; 351 iov.iov_len = snd_header->msg_iov->iov_len - sent; 352 hdr.msg_iov = &iov; 353 hdr.msg_iovlen = 1; 354 int n = RetryEINTR(sendmsg)(send_socket.get(), &hdr, 0); 355 ncalls++; 356 if (n > 0) { 357 sent += n; 358 if (sent == buf_size) { 359 break; 360 } 361 // n can be > 0 but less than requested size. In which case we don't 362 // poll. 363 continue; 364 } 365 // Poll the fd for it to become writable. 366 struct pollfd poll_fd = {send_socket.get(), POLL_OUT, 0}; 367 EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10), 368 SyscallSucceedsWithValue(0)); 369 } 370 bytes_sent += static_cast<int64_t>(sent); 371 } 372 373 notification.Notify(); 374 send_socket.reset(); 375 state.SetBytesProcessed(bytes_sent); 376 } 377 378 void Args(benchmark::internal::Benchmark* benchmark) { 379 for (int blocking = 0; blocking < 2; blocking++) { 380 for (int buf_size = 1024; buf_size <= 256 << 20; buf_size *= 2) { 381 benchmark->Args({blocking, buf_size}); 382 } 383 } 384 } 385 386 BENCHMARK(BM_SendmsgTCP)->Apply(&Args)->UseRealTime(); 387 388 } // namespace 389 390 } // namespace testing 391 } // namespace gvisor