diff --git a/.github/workflows/benchmark_asterinas.yml b/.github/workflows/benchmark_asterinas.yml index 4011d0aa..15dfff45 100644 --- a/.github/workflows/benchmark_asterinas.yml +++ b/.github/workflows/benchmark_asterinas.yml @@ -58,6 +58,7 @@ jobs: - lmbench/tcp_loopback_bw_4k - lmbench/tcp_loopback_bw_64k - lmbench/tcp_loopback_lat + - lmbench/tcp_virtio_lat - lmbench/tcp_loopback_connect_lat - lmbench/tcp_loopback_select_lat - lmbench/tcp_loopback_http_bw diff --git a/kernel/comps/virtio/src/device/network/device.rs b/kernel/comps/virtio/src/device/network/device.rs index 890ce302..6680e6a5 100644 --- a/kernel/comps/virtio/src/device/network/device.rs +++ b/kernel/comps/virtio/src/device/network/device.rs @@ -154,7 +154,7 @@ impl NetworkDevice { .rx_buffers .remove(token as usize) .ok_or(VirtioNetError::WrongToken)?; - rx_buffer.set_packet_len(len as usize); + rx_buffer.set_packet_len(len as usize - size_of::()); // FIXME: Ideally, we can reuse the returned buffer without creating new buffer. // But this requires locking device to be compatible with smoltcp interface. let rx_pool = RX_BUFFER_POOL.get().unwrap(); @@ -180,6 +180,8 @@ impl NetworkDevice { self.send_queue.notify(); } + debug!("send packet, token = {}, len = {}", token, packet.len()); + debug_assert!(self.tx_buffers[token as usize].is_none()); self.tx_buffers[token as usize] = Some(tx_buffer); diff --git a/kernel/libs/aster-bigtcp/src/socket/mod.rs b/kernel/libs/aster-bigtcp/src/socket/mod.rs index a5b9de69..c8428ac9 100644 --- a/kernel/libs/aster-bigtcp/src/socket/mod.rs +++ b/kernel/libs/aster-bigtcp/src/socket/mod.rs @@ -7,6 +7,7 @@ mod unbound; pub use bound::{BoundTcpSocket, BoundUdpSocket}; pub(crate) use bound::{BoundTcpSocketInner, BoundUdpSocketInner, TcpProcessResult}; pub use event::SocketEventObserver; +pub use smoltcp::socket::tcp::State as TcpState; pub use unbound::{ UnboundTcpSocket, UnboundUdpSocket, TCP_RECV_BUF_LEN, TCP_SEND_BUF_LEN, UDP_RECV_PAYLOAD_LEN, UDP_SEND_PAYLOAD_LEN, diff --git a/kernel/src/net/socket/ip/stream/connected.rs b/kernel/src/net/socket/ip/stream/connected.rs index 9482487d..cba0e9a1 100644 --- a/kernel/src/net/socket/ip/stream/connected.rs +++ b/kernel/src/net/socket/ip/stream/connected.rs @@ -4,7 +4,7 @@ use alloc::sync::Weak; use aster_bigtcp::{ errors::tcp::{RecvError, SendError}, - socket::SocketEventObserver, + socket::{SocketEventObserver, TcpState}, wire::IpEndpoint, }; @@ -68,6 +68,9 @@ impl ConnectedStream { Ok(Err(e)) => Err(e), Err(RecvError::Finished) => Ok(0), Err(RecvError::InvalidState) => { + if self.before_established() { + return_errno_with_message!(Errno::EAGAIN, "the connection is not established"); + } return_errno_with_message!(Errno::ECONNRESET, "the connection is reset") } } @@ -86,6 +89,9 @@ impl ConnectedStream { Ok(Ok(sent_bytes)) => Ok(sent_bytes), Ok(Err(e)) => Err(e), Err(SendError::InvalidState) => { + if self.before_established() { + return_errno_with_message!(Errno::EAGAIN, "the connection is not established"); + } // FIXME: `EPIPE` is another possibility, which means that the socket is shut down // for writing. In that case, we should also trigger a `SIGPIPE` if `MSG_NOSIGNAL` // is not specified. @@ -135,4 +141,20 @@ impl ConnectedStream { pub(super) fn set_observer(&self, observer: Weak) { self.bound_socket.set_observer(observer) } + + /// Returns whether the connection is before established. + /// + /// Note that a newly accepted socket may not yet be in the [`TcpState::Established`] state. + /// The accept syscall only verifies that a connection request is incoming by ensuring + /// that the backlog socket is not in the [`TcpState::Listen`] state. + /// However, the socket might still be waiting for further ACKs to complete the establishment process. + /// Therefore, it could be in either the [`TcpState::SynSent`] or [`TcpState::SynReceived`] states. + /// We must wait until the socket reaches the established state before it can send and receive data. + /// + /// FIXME: Should we check established state in accept or here? + fn before_established(&self) -> bool { + self.bound_socket.raw_with(|socket| { + socket.state() == TcpState::SynSent || socket.state() == TcpState::SynReceived + }) + } } diff --git a/kernel/src/net/socket/ip/stream/mod.rs b/kernel/src/net/socket/ip/stream/mod.rs index 531319c8..3077710f 100644 --- a/kernel/src/net/socket/ip/stream/mod.rs +++ b/kernel/src/net/socket/ip/stream/mod.rs @@ -265,13 +265,18 @@ impl StreamSocket { return_errno_with_message!(Errno::EINVAL, "the socket is not listening"); }; - listen_stream.try_accept().map(|connected_stream| { + let accepted = listen_stream.try_accept().map(|connected_stream| { listen_stream.update_io_events(&self.pollee); let remote_endpoint = connected_stream.remote_endpoint(); let accepted_socket = Self::new_connected(connected_stream); (accepted_socket as _, remote_endpoint.into()) - }) + }); + + drop(state); + poll_ifaces(); + + accepted } fn try_recv( diff --git a/test/benchmark/bench_linux_and_aster.sh b/test/benchmark/bench_linux_and_aster.sh index 1ab4ba93..67f981cc 100755 --- a/test/benchmark/bench_linux_and_aster.sh +++ b/test/benchmark/bench_linux_and_aster.sh @@ -70,7 +70,7 @@ run_benchmark() { -drive if=none,format=raw,id=x0,file=${BENCHMARK_DIR}/../build/ext2.img \ -device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off,queue-size=64,num-queues=1,config-wce=off,request-merging=off,write-cache=off,backend_defaults=off,discard=off,event_idx=off,indirect_desc=off,ioeventfd=off,queue_reset=off \ -append 'console=ttyS0 rdinit=/benchmark/common/bench_runner.sh ${benchmark} linux mitigations=off hugepages=0 transparent_hugepage=never quiet' \ - -netdev user,id=net01,hostfwd=tcp::5201-:5201,hostfwd=tcp::8080-:8080 \ + -netdev user,id=net01,hostfwd=tcp::5201-:5201,hostfwd=tcp::8080-:8080,hostfwd=tcp::31234-:31234 \ -device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off \ -nographic \ 2>&1" diff --git a/test/benchmark/common/host_guest_bench_runner.sh b/test/benchmark/common/host_guest_bench_runner.sh index 38745542..d7f3542d 100755 --- a/test/benchmark/common/host_guest_bench_runner.sh +++ b/test/benchmark/common/host_guest_bench_runner.sh @@ -26,6 +26,9 @@ elif [[ "$BENCHMARK_PATH" =~ "nginx" ]]; then elif [[ "$BENCHMARK_PATH" =~ "redis" ]]; then # Persist Redis port export REDIS_PORT=6379 +elif [[ "$BENCHMARK_PATH" =~ "tcp_virtio_lat" ]]; then + # Persist lmbench/tcp_lat port + export LMBENCH_TCP_LAT_PORT=31234 fi # Function to run the benchmark @@ -57,7 +60,7 @@ run_benchmark() { sleep 1 # Run the host command and save the output to the specified file. - bash "${BENCHMARK_PATH}/host.sh" | tee "${output_file}" + bash "${BENCHMARK_PATH}/host.sh" 2>&1 | tee "${output_file}" # Clean up the log file rm -f "${guest_log_file}" diff --git a/test/benchmark/lmbench/summary.json b/test/benchmark/lmbench/summary.json index b380e37d..1f1da5b0 100644 --- a/test/benchmark/lmbench/summary.json +++ b/test/benchmark/lmbench/summary.json @@ -34,6 +34,7 @@ "tcp_loopback_bw_4k", "tcp_loopback_bw_64k", "tcp_loopback_lat", + "tcp_virtio_lat", "tcp_loopback_connect_lat", "tcp_loopback_select_lat", "tcp_loopback_http_bw", diff --git a/test/benchmark/lmbench/tcp_loopback_lat/run.sh b/test/benchmark/lmbench/tcp_loopback_lat/run.sh index 35dfc498..12328964 100644 --- a/test/benchmark/lmbench/tcp_loopback_lat/run.sh +++ b/test/benchmark/lmbench/tcp_loopback_lat/run.sh @@ -6,6 +6,6 @@ set -e echo "*** Running lmbench TCP latency test ***" -/benchmark/bin/lmbench/lat_tcp -s +/benchmark/bin/lmbench/lat_tcp -s 127.0.0.1 /benchmark/bin/lmbench/lat_tcp -P 1 127.0.0.1 /benchmark/bin/lmbench/lat_tcp -S 127.0.0.1 diff --git a/test/benchmark/lmbench/tcp_virtio_lat/config.json b/test/benchmark/lmbench/tcp_virtio_lat/config.json new file mode 100644 index 00000000..da356f75 --- /dev/null +++ b/test/benchmark/lmbench/tcp_virtio_lat/config.json @@ -0,0 +1,9 @@ +{ + "alert_threshold": "125%", + "alert_tool": "customSmallerIsBetter", + "search_pattern": "TCP latency using 127.0.0.1:", + "result_index": "5", + "description": "lat_tcp_virtio", + "title": "[TCP sockets] The latency over virtio-net", + "benchmark_type": "host_guest" +} \ No newline at end of file diff --git a/test/benchmark/lmbench/tcp_virtio_lat/host.sh b/test/benchmark/lmbench/tcp_virtio_lat/host.sh new file mode 100644 index 00000000..4bcb9c76 --- /dev/null +++ b/test/benchmark/lmbench/tcp_virtio_lat/host.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# SPDX-License-Identifier: MPL-2.0 + +set -e + +# Function to stop the guest VM +stop_guest() { + echo "Stopping guest VM..." + # `-r` means if there's no qemu, the kill won't be executed + pgrep qemu | xargs -r kill +} + +# Trap EXIT signal to ensure guest VM is stopped on script exit +trap stop_guest EXIT + +# Run lmbench tcp client +echo "Running lmbench tcp client" +/usr/local/benchmark/lmbench/lat_tcp -P 1 127.0.0.1 + +# The trap will automatically stop the guest VM when the script exits \ No newline at end of file diff --git a/test/benchmark/lmbench/tcp_virtio_lat/result_template.json b/test/benchmark/lmbench/tcp_virtio_lat/result_template.json new file mode 100644 index 00000000..126354d8 --- /dev/null +++ b/test/benchmark/lmbench/tcp_virtio_lat/result_template.json @@ -0,0 +1,14 @@ +[ + { + "name": "Average TCP latency over virtio-net on Linux", + "unit": "µs", + "value": 0, + "extra": "linux_result" + }, + { + "name": "Average TCP latency over virtio-net on Asterinas", + "unit": "µs", + "value": 0, + "extra": "aster_result" + } +] \ No newline at end of file diff --git a/test/benchmark/lmbench/tcp_virtio_lat/run.sh b/test/benchmark/lmbench/tcp_virtio_lat/run.sh new file mode 100644 index 00000000..9eed8105 --- /dev/null +++ b/test/benchmark/lmbench/tcp_virtio_lat/run.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# SPDX-License-Identifier: MPL-2.0 + +set -e + +echo "Running lmbench TCP latency over virtio-net..." + +# Start the server +/benchmark/bin/lmbench/lat_tcp -s 10.0.2.15 + +# Sleep for a long time to ensure VM won't exit +sleep 200 diff --git a/tools/qemu_args.sh b/tools/qemu_args.sh index a859fef8..8eb46031 100755 --- a/tools/qemu_args.sh +++ b/tools/qemu_args.sh @@ -11,11 +11,12 @@ SSH_RAND_PORT=${SSH_PORT:-$(shuf -i 1024-65535 -n 1)} NGINX_RAND_PORT=${NGINX_PORT:-$(shuf -i 1024-65535 -n 1)} REDIS_RAND_PORT=${REDIS_PORT:-$(shuf -i 1024-65535 -n 1)} IPERF_RAND_PORT=${IPERF_PORT:-$(shuf -i 1024-65535 -n 1)} +LMBENCH_TCP_LAT_RAND_PORT=${LMBENCH_TCP_LAT_PORT:-$(shuf -i 1024-65535 -n 1)} # Optional QEMU arguments. Opt in them manually if needed. # QEMU_OPT_ARG_DUMP_PACKETS="-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap" -echo "[$1] Forwarded QEMU guest port: $SSH_RAND_PORT->22; $NGINX_RAND_PORT->8080 $REDIS_RAND_PORT->6379 $IPERF_RAND_PORT->5201" 1>&2 +echo "[$1] Forwarded QEMU guest port: $SSH_RAND_PORT->22; $NGINX_RAND_PORT->8080 $REDIS_RAND_PORT->6379 $IPERF_RAND_PORT->5201 $LMBENCH_TCP_LAT_RAND_PORT->31234" 1>&2 COMMON_QEMU_ARGS="\ -cpu Icelake-Server,+x2apic \ @@ -27,7 +28,7 @@ COMMON_QEMU_ARGS="\ -serial chardev:mux \ -monitor chardev:mux \ -chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log \ - -netdev user,id=net01,hostfwd=tcp::$SSH_RAND_PORT-:22,hostfwd=tcp::$NGINX_RAND_PORT-:8080,hostfwd=tcp::$REDIS_RAND_PORT-:6379,hostfwd=tcp::$IPERF_RAND_PORT-:5201 \ + -netdev user,id=net01,hostfwd=tcp::$SSH_RAND_PORT-:22,hostfwd=tcp::$NGINX_RAND_PORT-:8080,hostfwd=tcp::$REDIS_RAND_PORT-:6379,hostfwd=tcp::$IPERF_RAND_PORT-:5201,hostfwd=tcp::$LMBENCH_TCP_LAT_RAND_PORT-:31234 \ $QEMU_OPT_ARG_DUMP_PACKETS \ -device isa-debug-exit,iobase=0xf4,iosize=0x04 \ -drive if=none,format=raw,id=x0,file=./test/build/ext2.img \