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, &notification] {
    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, &notification] {
   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, &notification] {
   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, &notification] {
   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, &notification] {
   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, &notification] {
   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