From 75e4ea8f8d92adf4c81c707358dfdf5fbb9c2486 Mon Sep 17 00:00:00 2001 From: Aaron Ang Date: Mon, 5 Jan 2026 18:02:25 -0800 Subject: [PATCH] test(vsock): latency performance test Create simple ping latency test in vsock_helper host tools Signed-off-by: Aaron Ang --- tests/host_tools/vsock_helper.c | 241 +++++++++++++++++- .../performance/test_vsock.py | 151 ++++++++++- 2 files changed, 385 insertions(+), 7 deletions(-) diff --git a/tests/host_tools/vsock_helper.c b/tests/host_tools/vsock_helper.c index 9368ffd79d6..e46e0fa488b 100644 --- a/tests/host_tools/vsock_helper.c +++ b/tests/host_tools/vsock_helper.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -17,7 +18,7 @@ #include #include #include -#include +#include #define BUF_SIZE (16 * 1024) @@ -25,11 +26,23 @@ int print_usage() { - fprintf(stderr, "Usage: ./vsock-helper echo \n"); + fprintf(stderr, "Usage: ./vsock-helper \n"); fprintf(stderr, "\n"); - fprintf(stderr, " echo connect to an echo server, listening on CID:port.\n"); - fprintf(stderr, " STDIN will be piped through to the echo server, and\n"); - fprintf(stderr, " data coming from the server will pe sent to STDOUT.\n"); + fprintf(stderr, "Commands:\n"); + fprintf(stderr, " echo \n"); + fprintf(stderr, " Connect to echo server at CID:port. Pipe STDIN to server,\n"); + fprintf(stderr, " server response to STDOUT.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, " ping \n"); + fprintf(stderr, " Send ping messages to echo server at CID:port.\n"); + fprintf(stderr, " is the delay in seconds between pings (float).\n"); + fprintf(stderr, " Prints RTT for each ping in microseconds.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, " ping-uds \n"); + fprintf(stderr, " Send ping messages to echo server at :port.\n"); + fprintf(stderr, " Uses Unix Domain Socket with Firecracker CONNECT protocol.\n"); + fprintf(stderr, " is the delay in seconds between pings (float).\n"); + fprintf(stderr, " Prints RTT for each ping in microseconds.\n"); fprintf(stderr, "\n"); return -1; } @@ -88,9 +101,195 @@ int run_echo(uint32_t cid, uint32_t port) { } +int run_ping(uint32_t cid, uint32_t port, int count, double delay_sec) { + + int sock = socket(AF_VSOCK, SOCK_STREAM, 0); + if (sock < 0) { + perror("socket()"); + return -1; + } + + struct sockaddr_vm vsock_addr = { + .svm_family = AF_VSOCK, + .svm_port = port, + .svm_cid = cid + }; + if (connect(sock, (struct sockaddr*)&vsock_addr, sizeof(vsock_addr)) < 0) { + perror("connect()"); + close(sock); + return -1; + } + + char ping_msg[64]; + memset(ping_msg, 'A', sizeof(ping_msg)); + + struct timespec delay_ts; + delay_ts.tv_sec = (time_t)delay_sec; + delay_ts.tv_nsec = (long)((delay_sec - (time_t)delay_sec) * 1000000000); + + for (int seq = 1; seq <= count; seq++) { + struct timespec start, end; + + if (clock_gettime(CLOCK_MONOTONIC, &start) < 0) { + perror("clock_gettime(start)"); + close(sock); + return -1; + } + + ssize_t sent = write(sock, ping_msg, sizeof(ping_msg)); + if (sent != sizeof(ping_msg)) { + perror("write()"); + close(sock); + return -1; + } + + char pong_buf[64]; + size_t total_received = 0; + while (total_received < sizeof(ping_msg)) { + ssize_t received = read(sock, pong_buf + total_received, + sizeof(ping_msg) - total_received); + if (received <= 0) { + perror("read()"); + close(sock); + return -1; + } + total_received += received; + } + + if (clock_gettime(CLOCK_MONOTONIC, &end) < 0) { + perror("clock_gettime(end)"); + close(sock); + return -1; + } + + long long start_us = start.tv_sec * 1000000LL + start.tv_nsec / 1000; + long long end_us = end.tv_sec * 1000000LL + end.tv_nsec / 1000; + long long rtt_us = end_us - start_us; + + printf("rtt=%.3f us seq=%d\n", (double)rtt_us, seq); + fflush(stdout); + + if (seq < count && delay_sec > 0) { + nanosleep(&delay_ts, NULL); + } + } + + close(sock); + return 0; +} + + +int run_ping_uds(const char *uds_path, uint32_t port, int count, double delay_sec) { + + int sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + perror("socket()"); + return -1; + } + + struct sockaddr_un unix_addr; + memset(&unix_addr, 0, sizeof(unix_addr)); + unix_addr.sun_family = AF_UNIX; + strncpy(unix_addr.sun_path, uds_path, sizeof(unix_addr.sun_path) - 1); + + if (connect(sock, (struct sockaddr*)&unix_addr, sizeof(unix_addr)) < 0) { + perror("connect()"); + close(sock); + return -1; + } + + char connect_msg[64]; + int connect_len = snprintf(connect_msg, sizeof(connect_msg), "CONNECT %u\n", port); + if (connect_len < 0 || connect_len >= sizeof(connect_msg)) { + fprintf(stderr, "Error formatting CONNECT message\n"); + close(sock); + return -1; + } + + ssize_t sent = write(sock, connect_msg, connect_len); + if (sent != connect_len) { + perror("write(CONNECT)"); + close(sock); + return -1; + } + + char ack_buf[32]; + ssize_t ack_received = read(sock, ack_buf, sizeof(ack_buf) - 1); + if (ack_received <= 0) { + perror("read(ack)"); + close(sock); + return -1; + } + ack_buf[ack_received] = '\0'; + + if (strncmp(ack_buf, "OK ", 3) != 0) { + fprintf(stderr, "Invalid acknowledgment: %s\n", ack_buf); + close(sock); + return -1; + } + + char ping_msg[64]; + memset(ping_msg, 'A', sizeof(ping_msg)); + + struct timespec delay_ts; + delay_ts.tv_sec = (time_t)delay_sec; + delay_ts.tv_nsec = (long)((delay_sec - (time_t)delay_sec) * 1000000000); + + for (int seq = 1; seq <= count; seq++) { + struct timespec start, end; + + if (clock_gettime(CLOCK_MONOTONIC, &start) < 0) { + perror("clock_gettime(start)"); + close(sock); + return -1; + } + + sent = write(sock, ping_msg, sizeof(ping_msg)); + if (sent != sizeof(ping_msg)) { + perror("write()"); + close(sock); + return -1; + } + + char pong_buf[64]; + size_t total_received = 0; + while (total_received < sizeof(ping_msg)) { + ssize_t received = read(sock, pong_buf + total_received, + sizeof(ping_msg) - total_received); + if (received <= 0) { + perror("read()"); + close(sock); + return -1; + } + total_received += received; + } + + if (clock_gettime(CLOCK_MONOTONIC, &end) < 0) { + perror("clock_gettime(end)"); + close(sock); + return -1; + } + + long long start_us = start.tv_sec * 1000000LL + start.tv_nsec / 1000; + long long end_us = end.tv_sec * 1000000LL + end.tv_nsec / 1000; + long long rtt_us = end_us - start_us; + + printf("rtt=%.3f us seq=%d\n", (double)rtt_us, seq); + fflush(stdout); + + if (seq < count && delay_sec > 0) { + nanosleep(&delay_ts, NULL); + } + } + + close(sock); + return 0; +} + + int main(int argc, char **argv) { - if (argc < 3) { + if (argc < 2) { return print_usage(); } @@ -106,5 +305,35 @@ int main(int argc, char **argv) { return run_echo(cid, port); } + if (strcmp(argv[1], "ping") == 0) { + if (argc != 6) { + return print_usage(); + } + uint32_t cid = atoi(argv[2]); + uint32_t port = atoi(argv[3]); + int count = atoi(argv[4]); + double delay = atof(argv[5]); + + if (!cid || !port || count <= 0 || delay < 0) { + return print_usage(); + } + return run_ping(cid, port, count, delay); + } + + if (strcmp(argv[1], "ping-uds") == 0) { + if (argc != 6) { + return print_usage(); + } + const char *uds_path = argv[2]; + uint32_t port = atoi(argv[3]); + int count = atoi(argv[4]); + double delay = atof(argv[5]); + + if (!port || count <= 0 || delay < 0) { + return print_usage(); + } + return run_ping_uds(uds_path, port, count, delay); + } + return print_usage(); } diff --git a/tests/integration_tests/performance/test_vsock.py b/tests/integration_tests/performance/test_vsock.py index fa4c3a5abb5..576828b0cde 100644 --- a/tests/integration_tests/performance/test_vsock.py +++ b/tests/integration_tests/performance/test_vsock.py @@ -4,12 +4,20 @@ import json import os +import re +import subprocess from pathlib import Path import pytest +from tenacity import Retrying, stop_after_attempt, wait_fixed from framework.utils_iperf import IPerf3Test, emit_iperf3_metrics -from framework.utils_vsock import VSOCK_UDS_PATH, make_host_port_path +from framework.utils_vsock import ( + ECHO_SERVER_PORT, + VSOCK_UDS_PATH, + make_host_port_path, + start_guest_echo_server, +) class VsockIPerf3Test(IPerf3Test): @@ -69,6 +77,23 @@ def guest_command(self, port_offset): return super().guest_command(port_offset).with_arg("--vsock") +def consume_vsock_ping_output(ping_output): + """Parse vsock_helper ping output. + + Output format: + rtt=123.456 us seq=1 + rtt=234.567 us seq=2 + ... + + Yields RTT values in microseconds as floats. + """ + pattern = r"rtt=([\d.]+) us seq=\d+" + for line in ping_output.strip().split("\n"): + match = re.match(pattern, line) + if match: + yield float(match.group(1)) + + @pytest.mark.timeout(120) @pytest.mark.nonci @pytest.mark.parametrize("vcpus", [1, 2], ids=["1vcpu", "2vcpu"]) @@ -119,3 +144,127 @@ def test_vsock_throughput( ) emit_iperf3_metrics(metrics, data, VsockIPerf3Test.WARMUP_SEC) + + +@pytest.mark.nonci +@pytest.mark.parametrize("vcpus", [1, 2], ids=["1vcpu", "2vcpu"]) +def test_vsock_latency_g2h(uvm_plain_acpi, vcpus, metrics, bin_vsock_path): + """ + Test VSOCK latency for guest-to-host connections. + + This starts an echo server on the host and measures RTT from + the guest using the vsock_helper ping command. + """ + rounds = 15 + requests_per_round = 30 + delay_sec = 0.01 + + mem_size_mib = 1024 + vm = uvm_plain_acpi + vm.spawn(log_level="Info", emit_metrics=True) + vm.basic_config(vcpu_count=vcpus, mem_size_mib=mem_size_mib) + vm.add_net_iface() + vm.api.vsock.put(vsock_id="vsock0", guest_cid=3, uds_path="/" + VSOCK_UDS_PATH) + vm.start() + + metrics.set_dimensions( + { + "performance_test": "test_vsock_latency", + "mode": "g2h", + **vm.dimensions, + } + ) + + vm.pin_threads(0) + + vm.ssh.scp_put(bin_vsock_path, "/tmp/vsock_helper") + + server_port_path = os.path.join( + vm.path, make_host_port_path(VSOCK_UDS_PATH, ECHO_SERVER_PORT) + ) + + echo_server = subprocess.Popen( + ["socat", f"UNIX-LISTEN:{server_port_path},fork,backlog=5", "exec:'/bin/cat'"] + ) + + try: + for attempt in Retrying( + wait=wait_fixed(0.2), + stop=stop_after_attempt(5), + reraise=True, + ): + with attempt: + assert Path(server_port_path).exists() + + vm.create_jailed_resource(server_port_path) + + samples = [] + for _ in range(rounds): + _, ping_output, _ = vm.ssh.check_output( + f"/tmp/vsock_helper ping 2 {ECHO_SERVER_PORT} " + f"{requests_per_round} {delay_sec}" + ) + samples.extend(consume_vsock_ping_output(ping_output)) + + for sample in samples: + metrics.put_metric("vsock_ping_latency", sample, "Microseconds") + + finally: + echo_server.terminate() + rc = echo_server.wait() + # socat exits with 128 + 15 (SIGTERM) + assert rc == 143 + + +@pytest.mark.nonci +@pytest.mark.parametrize("vcpus", [1, 2], ids=["1vcpu", "2vcpu"]) +def test_vsock_latency_h2g(uvm_plain_acpi, vcpus, metrics, bin_vsock_path): + """ + Test VSOCK latency for host-to-guest connections. + + This starts an echo server in the guest and measures RTT from + the host using the vsock_helper ping-uds command. + """ + rounds = 15 + requests_per_round = 30 + delay_sec = 0.01 + + mem_size_mib = 1024 + vm = uvm_plain_acpi + vm.spawn(log_level="Info", emit_metrics=True) + vm.basic_config(vcpu_count=vcpus, mem_size_mib=mem_size_mib) + vm.add_net_iface() + vm.api.vsock.put(vsock_id="vsock0", guest_cid=3, uds_path="/" + VSOCK_UDS_PATH) + vm.start() + + metrics.set_dimensions( + { + "performance_test": "test_vsock_latency", + "mode": "h2g", + **vm.dimensions, + } + ) + + vm.pin_threads(0) + + uds_path = start_guest_echo_server(vm) + + samples = [] + for _ in range(rounds): + result = subprocess.run( + [ + bin_vsock_path, + "ping-uds", + uds_path, + str(ECHO_SERVER_PORT), + str(requests_per_round), + str(delay_sec), + ], + capture_output=True, + text=True, + check=True, + ) + samples.extend(consume_vsock_ping_output(result.stdout)) + + for sample in samples: + metrics.put_metric("vsock_ping_latency", sample, "Microseconds")