1128d0984SRob Bradford // Copyright © 2022 Intel Corporation
2128d0984SRob Bradford //
3128d0984SRob Bradford // SPDX-License-Identifier: Apache-2.0
4128d0984SRob Bradford //
5128d0984SRob Bradford
61a17564eSBo Chen // Performance tests
71a17564eSBo Chen
8d8a67259SBo Chen use std::path::PathBuf;
91a17564eSBo Chen use std::time::Duration;
1061e57e1cSRuoqing He use std::{fs, thread};
1188a9f799SRob Bradford
1261e57e1cSRuoqing He use test_infra::{Error as InfraError, *};
1379933645SPhilipp Schuster use thiserror::Error;
141a17564eSBo Chen
1588a9f799SRob Bradford use crate::{mean, PerformanceTestControl};
1688a9f799SRob Bradford
171b494fb0SJianyong Wu #[cfg(target_arch = "x86_64")]
181a17564eSBo Chen pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-amd64-custom-20210609-0.raw";
191b494fb0SJianyong Wu #[cfg(target_arch = "aarch64")]
201b494fb0SJianyong Wu pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-arm64-custom-20210929-0-update-tool.raw";
211a17564eSBo Chen
228899ebd6SRob Bradford #[allow(dead_code)]
2379933645SPhilipp Schuster #[derive(Error, Debug)]
241a17564eSBo Chen enum Error {
2579933645SPhilipp Schuster #[error("boot time could not be parsed")]
261a17564eSBo Chen BootTimeParse,
27*2100d8c3SPhilipp Schuster #[error("infrastructure failure")]
289bd9c0cbSPhilipp Schuster Infra(#[from] InfraError),
2979933645SPhilipp Schuster #[error("restore time could not be parsed")]
30e18d32baSSongqian Li RestoreTimeParse,
311a17564eSBo Chen }
321a17564eSBo Chen
33b8069359SBo Chen const BLK_IO_TEST_IMG: &str = "/var/tmp/ch-blk-io-test.img";
34b8069359SBo Chen
init_tests()35b8069359SBo Chen pub fn init_tests() {
36b8069359SBo Chen // The test image cannot be created on tmpfs (e.g. /tmp) filesystem,
37b8069359SBo Chen // as tmpfs does not support O_DIRECT
38b8069359SBo Chen assert!(exec_host_command_output(&format!(
395e527294SRob Bradford "dd if=/dev/zero of={BLK_IO_TEST_IMG} bs=1M count=4096"
40b8069359SBo Chen ))
41b8069359SBo Chen .status
42b8069359SBo Chen .success());
43b8069359SBo Chen }
44b8069359SBo Chen
cleanup_tests()45b8069359SBo Chen pub fn cleanup_tests() {
46b8069359SBo Chen fs::remove_file(BLK_IO_TEST_IMG)
475e527294SRob Bradford .unwrap_or_else(|_| panic!("Failed to remove file '{BLK_IO_TEST_IMG}'."));
48b8069359SBo Chen }
49b8069359SBo Chen
50a50c10bdSBo Chen // Performance tests are expected to be executed sequentially, so we can
51a50c10bdSBo Chen // start VM guests with the same IP while putting them on a different
52a50c10bdSBo Chen // private network. The default constructor "Guest::new()" does not work
53a50c10bdSBo Chen // well, as we can easily create more than 256 VMs from repeating various
54a50c10bdSBo Chen // performance tests dozens times in a single run.
performance_test_new_guest(disk_config: Box<dyn DiskConfig>) -> Guest55a50c10bdSBo Chen fn performance_test_new_guest(disk_config: Box<dyn DiskConfig>) -> Guest {
56a50c10bdSBo Chen Guest::new_from_ip_range(disk_config, "172.19", 0)
57a50c10bdSBo Chen }
58a50c10bdSBo Chen
591a17564eSBo Chen const DIRECT_KERNEL_BOOT_CMDLINE: &str =
601a17564eSBo Chen "root=/dev/vda1 console=hvc0 rw systemd.journald.forward_to_console=1";
611a17564eSBo Chen
621a17564eSBo Chen // Creates the path for direct kernel boot and return the path.
631a17564eSBo Chen // For x86_64, this function returns the vmlinux kernel path.
641a17564eSBo Chen // For AArch64, this function returns the PE kernel path.
direct_kernel_boot_path() -> PathBuf651a17564eSBo Chen fn direct_kernel_boot_path() -> PathBuf {
661a17564eSBo Chen let mut workload_path = dirs::home_dir().unwrap();
671a17564eSBo Chen workload_path.push("workloads");
681a17564eSBo Chen
691a17564eSBo Chen let mut kernel_path = workload_path;
701a17564eSBo Chen #[cfg(target_arch = "x86_64")]
711a17564eSBo Chen kernel_path.push("vmlinux");
721a17564eSBo Chen #[cfg(target_arch = "aarch64")]
731a17564eSBo Chen kernel_path.push("Image");
741a17564eSBo Chen
751a17564eSBo Chen kernel_path
761a17564eSBo Chen }
771a17564eSBo Chen
remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool78e18d32baSSongqian Li fn remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool {
79e18d32baSSongqian Li let mut cmd = std::process::Command::new(clh_command("ch-remote"));
8010ee003dSBo Chen cmd.args([&format!("--api-socket={api_socket}"), command]);
81e18d32baSSongqian Li
82e18d32baSSongqian Li if let Some(arg) = arg {
83e18d32baSSongqian Li cmd.arg(arg);
84e18d32baSSongqian Li }
85e18d32baSSongqian Li let output = cmd.output().unwrap();
86e18d32baSSongqian Li if output.status.success() {
87e18d32baSSongqian Li true
88e18d32baSSongqian Li } else {
89e18d32baSSongqian Li eprintln!(
90e18d32baSSongqian Li "Error running ch-remote command: {:?}\nstderr: {}",
91e18d32baSSongqian Li &cmd,
92e18d32baSSongqian Li String::from_utf8_lossy(&output.stderr)
93e18d32baSSongqian Li );
94e18d32baSSongqian Li false
95e18d32baSSongqian Li }
96e18d32baSSongqian Li }
97e18d32baSSongqian Li
performance_net_throughput(control: &PerformanceTestControl) -> f64981a17564eSBo Chen pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 {
99acafda67SRob Bradford let test_timeout = control.test_timeout;
10003d8a3ebSWei Liu let (rx, bandwidth) = control.net_control.unwrap();
1011a17564eSBo Chen
1021a17564eSBo Chen let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
103a50c10bdSBo Chen let guest = performance_test_new_guest(Box::new(focal));
1041a17564eSBo Chen
105f254f11bSRob Bradford let num_queues = control.num_queues.unwrap();
106f254f11bSRob Bradford let queue_size = control.queue_size.unwrap();
1071a17564eSBo Chen let net_params = format!(
1081a17564eSBo Chen "tap=,mac={},ip={},mask=255.255.255.0,num_queues={},queue_size={}",
10918d51d3bSRob Bradford guest.network.guest_mac, guest.network.host_ip, num_queues, queue_size,
1101a17564eSBo Chen );
1111a17564eSBo Chen
1121a17564eSBo Chen let mut child = GuestCommand::new(&guest)
1135e527294SRob Bradford .args(["--cpus", &format!("boot={num_queues}")])
114f32487f8SRob Bradford .args(["--memory", "size=4G"])
115f32487f8SRob Bradford .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
116f32487f8SRob Bradford .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
1171a17564eSBo Chen .default_disks()
118f32487f8SRob Bradford .args(["--net", net_params.as_str()])
1191a17564eSBo Chen .capture_output()
120d5821211SRob Bradford .verbosity(VerbosityLevel::Warn)
1211a17564eSBo Chen .set_print_cmd(false)
1221a17564eSBo Chen .spawn()
1231a17564eSBo Chen .unwrap();
1241a17564eSBo Chen
1251a17564eSBo Chen let r = std::panic::catch_unwind(|| {
1261a17564eSBo Chen guest.wait_vm_boot(None).unwrap();
12703d8a3ebSWei Liu measure_virtio_net_throughput(test_timeout, num_queues / 2, &guest, rx, bandwidth).unwrap()
1281a17564eSBo Chen });
1291a17564eSBo Chen
1301a17564eSBo Chen let _ = child.kill();
1311a17564eSBo Chen let output = child.wait_with_output().unwrap();
1321a17564eSBo Chen
1331a17564eSBo Chen match r {
1341a17564eSBo Chen Ok(r) => r,
1351a17564eSBo Chen Err(e) => {
1361a17564eSBo Chen handle_child_output(Err(e), &output);
1371a17564eSBo Chen panic!("test failed!");
1381a17564eSBo Chen }
1391a17564eSBo Chen }
1401a17564eSBo Chen }
1411a17564eSBo Chen
performance_net_latency(control: &PerformanceTestControl) -> f641421a17564eSBo Chen pub fn performance_net_latency(control: &PerformanceTestControl) -> f64 {
1431a17564eSBo Chen let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
144a50c10bdSBo Chen let guest = performance_test_new_guest(Box::new(focal));
145f254f11bSRob Bradford
146f254f11bSRob Bradford let num_queues = control.num_queues.unwrap();
147f254f11bSRob Bradford let queue_size = control.queue_size.unwrap();
148f254f11bSRob Bradford let net_params = format!(
149f254f11bSRob Bradford "tap=,mac={},ip={},mask=255.255.255.0,num_queues={},queue_size={}",
150f254f11bSRob Bradford guest.network.guest_mac, guest.network.host_ip, num_queues, queue_size,
151f254f11bSRob Bradford );
152f254f11bSRob Bradford
1531a17564eSBo Chen let mut child = GuestCommand::new(&guest)
1545e527294SRob Bradford .args(["--cpus", &format!("boot={num_queues}")])
155f32487f8SRob Bradford .args(["--memory", "size=4G"])
156f32487f8SRob Bradford .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
157f32487f8SRob Bradford .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
1581a17564eSBo Chen .default_disks()
159f32487f8SRob Bradford .args(["--net", net_params.as_str()])
1601a17564eSBo Chen .capture_output()
161d5821211SRob Bradford .verbosity(VerbosityLevel::Warn)
1621a17564eSBo Chen .set_print_cmd(false)
1631a17564eSBo Chen .spawn()
1641a17564eSBo Chen .unwrap();
1651a17564eSBo Chen
1661a17564eSBo Chen let r = std::panic::catch_unwind(|| {
1671a17564eSBo Chen guest.wait_vm_boot(None).unwrap();
1681a17564eSBo Chen
1691a17564eSBo Chen // 'ethr' tool will measure the latency multiple times with provided test time
17060d80577SRob Bradford let latency = measure_virtio_net_latency(&guest, control.test_timeout).unwrap();
1711a17564eSBo Chen mean(&latency).unwrap()
1721a17564eSBo Chen });
1731a17564eSBo Chen
1741a17564eSBo Chen let _ = child.kill();
1751a17564eSBo Chen let output = child.wait_with_output().unwrap();
1761a17564eSBo Chen
1771a17564eSBo Chen match r {
1781a17564eSBo Chen Ok(r) => r,
1791a17564eSBo Chen Err(e) => {
1801a17564eSBo Chen handle_child_output(Err(e), &output);
1811a17564eSBo Chen panic!("test failed!");
1821a17564eSBo Chen }
1831a17564eSBo Chen }
1841a17564eSBo Chen }
1851a17564eSBo Chen
parse_boot_time_output(output: &[u8]) -> Result<f64, Error>1861a17564eSBo Chen fn parse_boot_time_output(output: &[u8]) -> Result<f64, Error> {
1871a17564eSBo Chen std::panic::catch_unwind(|| {
1881a17564eSBo Chen let l: Vec<String> = String::from_utf8_lossy(output)
1891a17564eSBo Chen .lines()
1901a17564eSBo Chen .filter(|l| l.contains("Debug I/O port: Kernel code"))
1911a17564eSBo Chen .map(|l| l.to_string())
1921a17564eSBo Chen .collect();
1931a17564eSBo Chen
1941a17564eSBo Chen assert_eq!(
1951a17564eSBo Chen l.len(),
1961a17564eSBo Chen 2,
1971a17564eSBo Chen "Expecting two matching lines for 'Debug I/O port: Kernel code'"
1981a17564eSBo Chen );
1991a17564eSBo Chen
2001a17564eSBo Chen let time_stamp_kernel_start = {
2011a17564eSBo Chen let s = l[0].split("--").collect::<Vec<&str>>();
2021a17564eSBo Chen assert_eq!(
2031a17564eSBo Chen s.len(),
2041a17564eSBo Chen 2,
2051a17564eSBo Chen "Expecting '--' for the matching line of 'Debug I/O port' output"
2061a17564eSBo Chen );
2071a17564eSBo Chen
2081a17564eSBo Chen // Sample output: "[Debug I/O port: Kernel code 0x40] 0.096537 seconds"
2091a17564eSBo Chen assert!(
2101a17564eSBo Chen s[1].contains("0x40"),
2111a17564eSBo Chen "Expecting kernel code '0x40' for 'linux_kernel_start' time stamp output"
2121a17564eSBo Chen );
2131a17564eSBo Chen let t = s[1].split_whitespace().collect::<Vec<&str>>();
2141a17564eSBo Chen assert_eq!(
2151a17564eSBo Chen t.len(),
2161a17564eSBo Chen 8,
2171a17564eSBo Chen "Expecting exact '8' words from the 'Debug I/O port' output"
2181a17564eSBo Chen );
2191a17564eSBo Chen assert!(
2201a17564eSBo Chen t[7].eq("seconds"),
22142e9632cSJosh Soref "Expecting 'seconds' as the last word of the 'Debug I/O port' output"
2221a17564eSBo Chen );
2231a17564eSBo Chen
2241a17564eSBo Chen t[6].parse::<f64>().unwrap()
2251a17564eSBo Chen };
2261a17564eSBo Chen
2271a17564eSBo Chen let time_stamp_user_start = {
2281a17564eSBo Chen let s = l[1].split("--").collect::<Vec<&str>>();
2291a17564eSBo Chen assert_eq!(
2301a17564eSBo Chen s.len(),
2311a17564eSBo Chen 2,
2321a17564eSBo Chen "Expecting '--' for the matching line of 'Debug I/O port' output"
2331a17564eSBo Chen );
2341a17564eSBo Chen
2351a17564eSBo Chen // Sample output: "Debug I/O port: Kernel code 0x41] 0.198980 seconds"
2361a17564eSBo Chen assert!(
2371a17564eSBo Chen s[1].contains("0x41"),
2381a17564eSBo Chen "Expecting kernel code '0x41' for 'linux_kernel_start' time stamp output"
2391a17564eSBo Chen );
2401a17564eSBo Chen let t = s[1].split_whitespace().collect::<Vec<&str>>();
2411a17564eSBo Chen assert_eq!(
2421a17564eSBo Chen t.len(),
2431a17564eSBo Chen 8,
2441a17564eSBo Chen "Expecting exact '8' words from the 'Debug I/O port' output"
2451a17564eSBo Chen );
2461a17564eSBo Chen assert!(
2471a17564eSBo Chen t[7].eq("seconds"),
24842e9632cSJosh Soref "Expecting 'seconds' as the last word of the 'Debug I/O port' output"
2491a17564eSBo Chen );
2501a17564eSBo Chen
2511a17564eSBo Chen t[6].parse::<f64>().unwrap()
2521a17564eSBo Chen };
2531a17564eSBo Chen
2541a17564eSBo Chen time_stamp_user_start - time_stamp_kernel_start
2551a17564eSBo Chen })
2561a17564eSBo Chen .map_err(|_| {
2571a17564eSBo Chen eprintln!(
2581a17564eSBo Chen "=============== boot-time output ===============\n\n{}\n\n===========end============\n\n",
2591a17564eSBo Chen String::from_utf8_lossy(output)
2601a17564eSBo Chen );
2611a17564eSBo Chen Error::BootTimeParse
2621a17564eSBo Chen })
2631a17564eSBo Chen }
2641a17564eSBo Chen
measure_boot_time(cmd: &mut GuestCommand, test_timeout: u32) -> Result<f64, Error>265acafda67SRob Bradford fn measure_boot_time(cmd: &mut GuestCommand, test_timeout: u32) -> Result<f64, Error> {
266d5821211SRob Bradford let mut child = cmd
267d5821211SRob Bradford .capture_output()
268d5821211SRob Bradford .verbosity(VerbosityLevel::Warn)
269d5821211SRob Bradford .set_print_cmd(false)
270d5821211SRob Bradford .spawn()
271d5821211SRob Bradford .unwrap();
2721a17564eSBo Chen
273acafda67SRob Bradford thread::sleep(Duration::new(test_timeout as u64, 0));
2741a17564eSBo Chen let _ = child.kill();
2751a17564eSBo Chen let output = child.wait_with_output().unwrap();
2761a17564eSBo Chen
277422906a0SWei Liu parse_boot_time_output(&output.stderr).inspect_err(|_| {
2781a17564eSBo Chen eprintln!(
2791a17564eSBo Chen "\n\n==== Start child stdout ====\n\n{}\n\n==== End child stdout ====",
2801a17564eSBo Chen String::from_utf8_lossy(&output.stdout)
2811a17564eSBo Chen );
2821a17564eSBo Chen eprintln!(
2831a17564eSBo Chen "\n\n==== Start child stderr ====\n\n{}\n\n==== End child stderr ====",
2841a17564eSBo Chen String::from_utf8_lossy(&output.stderr)
2851a17564eSBo Chen );
2861a17564eSBo Chen })
2871a17564eSBo Chen }
2881a17564eSBo Chen
performance_boot_time(control: &PerformanceTestControl) -> f642891a17564eSBo Chen pub fn performance_boot_time(control: &PerformanceTestControl) -> f64 {
2901a17564eSBo Chen let r = std::panic::catch_unwind(|| {
2911a17564eSBo Chen let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
292a50c10bdSBo Chen let guest = performance_test_new_guest(Box::new(focal));
2931a17564eSBo Chen let mut cmd = GuestCommand::new(&guest);
2941a17564eSBo Chen
2951a17564eSBo Chen let c = cmd
296f32487f8SRob Bradford .args([
2973c0817a1SRob Bradford "--cpus",
2983c0817a1SRob Bradford &format!("boot={}", control.num_boot_vcpus.unwrap_or(1)),
2993c0817a1SRob Bradford ])
300f32487f8SRob Bradford .args(["--memory", "size=1G"])
301f32487f8SRob Bradford .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
302f32487f8SRob Bradford .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
303f32487f8SRob Bradford .args(["--console", "off"])
3041a17564eSBo Chen .default_disks();
3051a17564eSBo Chen
30660d80577SRob Bradford measure_boot_time(c, control.test_timeout).unwrap()
3071a17564eSBo Chen });
3081a17564eSBo Chen
3091a17564eSBo Chen match r {
3101a17564eSBo Chen Ok(r) => r,
3111a17564eSBo Chen Err(_) => {
3121a17564eSBo Chen panic!("test failed!");
3131a17564eSBo Chen }
3141a17564eSBo Chen }
3151a17564eSBo Chen }
3161a17564eSBo Chen
performance_boot_time_pmem(control: &PerformanceTestControl) -> f643171a17564eSBo Chen pub fn performance_boot_time_pmem(control: &PerformanceTestControl) -> f64 {
3181a17564eSBo Chen let r = std::panic::catch_unwind(|| {
3191a17564eSBo Chen let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
320a50c10bdSBo Chen let guest = performance_test_new_guest(Box::new(focal));
3211a17564eSBo Chen let mut cmd = GuestCommand::new(&guest);
3221a17564eSBo Chen let c = cmd
323f32487f8SRob Bradford .args([
3243c0817a1SRob Bradford "--cpus",
3253c0817a1SRob Bradford &format!("boot={}", control.num_boot_vcpus.unwrap_or(1)),
3263c0817a1SRob Bradford ])
327f32487f8SRob Bradford .args(["--memory", "size=1G,hugepages=on"])
328f32487f8SRob Bradford .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
329f32487f8SRob Bradford .args(["--cmdline", "root=/dev/pmem0p1 console=ttyS0 quiet rw"])
330f32487f8SRob Bradford .args(["--console", "off"])
331f32487f8SRob Bradford .args([
3321a17564eSBo Chen "--pmem",
3331a17564eSBo Chen format!(
3341a17564eSBo Chen "file={}",
3351a17564eSBo Chen guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
3361a17564eSBo Chen )
3371a17564eSBo Chen .as_str(),
3381a17564eSBo Chen ]);
3391a17564eSBo Chen
34060d80577SRob Bradford measure_boot_time(c, control.test_timeout).unwrap()
3411a17564eSBo Chen });
3421a17564eSBo Chen
3431a17564eSBo Chen match r {
3441a17564eSBo Chen Ok(r) => r,
3451a17564eSBo Chen Err(_) => {
3461a17564eSBo Chen panic!("test failed!");
3471a17564eSBo Chen }
3481a17564eSBo Chen }
3491a17564eSBo Chen }
3501a17564eSBo Chen
performance_block_io(control: &PerformanceTestControl) -> f643511a17564eSBo Chen pub fn performance_block_io(control: &PerformanceTestControl) -> f64 {
352acafda67SRob Bradford let test_timeout = control.test_timeout;
3539978aac4SRob Bradford let num_queues = control.num_queues.unwrap();
354e16817eaSWei Liu let (fio_ops, bandwidth) = control.fio_control.as_ref().unwrap();
3551a17564eSBo Chen
3561a17564eSBo Chen let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
357a50c10bdSBo Chen let guest = performance_test_new_guest(Box::new(focal));
3581a17564eSBo Chen let api_socket = guest
3591a17564eSBo Chen .tmp_dir
3601a17564eSBo Chen .as_path()
3611a17564eSBo Chen .join("cloud-hypervisor.sock")
3621a17564eSBo Chen .to_str()
3631a17564eSBo Chen .unwrap()
3641a17564eSBo Chen .to_string();
3651a17564eSBo Chen
3661a17564eSBo Chen let mut child = GuestCommand::new(&guest)
3675e527294SRob Bradford .args(["--cpus", &format!("boot={num_queues}")])
368f32487f8SRob Bradford .args(["--memory", "size=4G"])
369f32487f8SRob Bradford .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
370f32487f8SRob Bradford .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
371f32487f8SRob Bradford .args([
3721b494fb0SJianyong Wu "--disk",
3731b494fb0SJianyong Wu format!(
3741b494fb0SJianyong Wu "path={}",
3751b494fb0SJianyong Wu guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
3761b494fb0SJianyong Wu )
3771b494fb0SJianyong Wu .as_str(),
3781b494fb0SJianyong Wu format!(
3791b494fb0SJianyong Wu "path={}",
3801b494fb0SJianyong Wu guest.disk_config.disk(DiskType::CloudInit).unwrap()
3811b494fb0SJianyong Wu )
3821b494fb0SJianyong Wu .as_str(),
3835e527294SRob Bradford format!("path={BLK_IO_TEST_IMG}").as_str(),
3841b494fb0SJianyong Wu ])
3851a17564eSBo Chen .default_net()
386f32487f8SRob Bradford .args(["--api-socket", &api_socket])
3871a17564eSBo Chen .capture_output()
388d5821211SRob Bradford .verbosity(VerbosityLevel::Warn)
3891a17564eSBo Chen .set_print_cmd(false)
3901a17564eSBo Chen .spawn()
3911a17564eSBo Chen .unwrap();
3921a17564eSBo Chen
3931a17564eSBo Chen let r = std::panic::catch_unwind(|| {
3941a17564eSBo Chen guest.wait_vm_boot(None).unwrap();
3951a17564eSBo Chen
3961a17564eSBo Chen let fio_command = format!(
3971a17564eSBo Chen "sudo fio --filename=/dev/vdc --name=test --output-format=json \
3981a17564eSBo Chen --direct=1 --bs=4k --ioengine=io_uring --iodepth=64 \
3995e527294SRob Bradford --rw={fio_ops} --runtime={test_timeout} --numjobs={num_queues}"
4001a17564eSBo Chen );
4011a17564eSBo Chen let output = guest
4021a17564eSBo Chen .ssh_command(&fio_command)
4031a17564eSBo Chen .map_err(InfraError::SshCommand)
4041a17564eSBo Chen .unwrap();
4051a17564eSBo Chen
4061a17564eSBo Chen // Parse fio output
407e16817eaSWei Liu if *bandwidth {
4089978aac4SRob Bradford parse_fio_output(&output, fio_ops, num_queues).unwrap()
409e16817eaSWei Liu } else {
410e16817eaSWei Liu parse_fio_output_iops(&output, fio_ops, num_queues).unwrap()
411e16817eaSWei Liu }
4121a17564eSBo Chen });
4131a17564eSBo Chen
4141a17564eSBo Chen let _ = child.kill();
4151a17564eSBo Chen let output = child.wait_with_output().unwrap();
4161a17564eSBo Chen
4171a17564eSBo Chen match r {
4181a17564eSBo Chen Ok(r) => r,
4191a17564eSBo Chen Err(e) => {
4201a17564eSBo Chen handle_child_output(Err(e), &output);
4211a17564eSBo Chen panic!("test failed!");
4221a17564eSBo Chen }
4231a17564eSBo Chen }
4241a17564eSBo Chen }
425dc91866bSBo Chen
426e18d32baSSongqian Li // Parse the event_monitor file based on the format that each event
427e18d32baSSongqian Li // is followed by a double newline
parse_event_file(event_file: &str) -> Vec<serde_json::Value>428e18d32baSSongqian Li fn parse_event_file(event_file: &str) -> Vec<serde_json::Value> {
429e18d32baSSongqian Li let content = fs::read(event_file).unwrap();
430e18d32baSSongqian Li let mut ret = Vec::new();
431e18d32baSSongqian Li for entry in String::from_utf8_lossy(&content)
432e18d32baSSongqian Li .trim()
433e18d32baSSongqian Li .split("\n\n")
434e18d32baSSongqian Li .collect::<Vec<&str>>()
435e18d32baSSongqian Li {
436e18d32baSSongqian Li ret.push(serde_json::from_str(entry).unwrap());
437e18d32baSSongqian Li }
438e18d32baSSongqian Li ret
439e18d32baSSongqian Li }
440e18d32baSSongqian Li
parse_restore_time_output(events: &[serde_json::Value]) -> Result<f64, Error>441e18d32baSSongqian Li fn parse_restore_time_output(events: &[serde_json::Value]) -> Result<f64, Error> {
442e18d32baSSongqian Li for entry in events.iter() {
443e18d32baSSongqian Li if entry["event"].as_str().unwrap() == "restored" {
444e18d32baSSongqian Li let duration = entry["timestamp"]["secs"].as_u64().unwrap() as f64 * 1_000f64
445e18d32baSSongqian Li + entry["timestamp"]["nanos"].as_u64().unwrap() as f64 / 1_000_000f64;
446e18d32baSSongqian Li return Ok(duration);
447e18d32baSSongqian Li }
448e18d32baSSongqian Li }
449e18d32baSSongqian Li Err(Error::RestoreTimeParse)
450e18d32baSSongqian Li }
451e18d32baSSongqian Li
measure_restore_time( cmd: &mut GuestCommand, event_file: &str, test_timeout: u32, ) -> Result<f64, Error>452e18d32baSSongqian Li fn measure_restore_time(
453e18d32baSSongqian Li cmd: &mut GuestCommand,
454e18d32baSSongqian Li event_file: &str,
455e18d32baSSongqian Li test_timeout: u32,
456e18d32baSSongqian Li ) -> Result<f64, Error> {
457e18d32baSSongqian Li let mut child = cmd
458e18d32baSSongqian Li .capture_output()
459e18d32baSSongqian Li .verbosity(VerbosityLevel::Warn)
460e18d32baSSongqian Li .set_print_cmd(false)
461e18d32baSSongqian Li .spawn()
462e18d32baSSongqian Li .unwrap();
463e18d32baSSongqian Li
464e18d32baSSongqian Li thread::sleep(Duration::new((test_timeout / 2) as u64, 0));
465e18d32baSSongqian Li let _ = child.kill();
466e18d32baSSongqian Li let output = child.wait_with_output().unwrap();
467e18d32baSSongqian Li
468e18d32baSSongqian Li let json_events = parse_event_file(event_file);
469e18d32baSSongqian Li
470e18d32baSSongqian Li parse_restore_time_output(&json_events).inspect_err(|_| {
471e18d32baSSongqian Li eprintln!(
472e18d32baSSongqian Li "\n\n==== Start child stdout ====\n\n{}\n\n==== End child stdout ====\
473e18d32baSSongqian Li \n\n==== Start child stderr ====\n\n{}\n\n==== End child stderr ====",
474e18d32baSSongqian Li String::from_utf8_lossy(&output.stdout),
475e18d32baSSongqian Li String::from_utf8_lossy(&output.stderr)
476e18d32baSSongqian Li )
477e18d32baSSongqian Li })
478e18d32baSSongqian Li }
479e18d32baSSongqian Li
performance_restore_latency(control: &PerformanceTestControl) -> f64480e18d32baSSongqian Li pub fn performance_restore_latency(control: &PerformanceTestControl) -> f64 {
481e18d32baSSongqian Li let r = std::panic::catch_unwind(|| {
482e18d32baSSongqian Li let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
483e18d32baSSongqian Li let guest = performance_test_new_guest(Box::new(focal));
484e18d32baSSongqian Li let api_socket_source = String::from(
485e18d32baSSongqian Li guest
486e18d32baSSongqian Li .tmp_dir
487e18d32baSSongqian Li .as_path()
488e18d32baSSongqian Li .join("cloud-hypervisor.sock")
489e18d32baSSongqian Li .to_str()
490e18d32baSSongqian Li .unwrap(),
491e18d32baSSongqian Li );
492e18d32baSSongqian Li
493e18d32baSSongqian Li let mut child = GuestCommand::new(&guest)
494e18d32baSSongqian Li .args(["--api-socket", &api_socket_source])
495e18d32baSSongqian Li .args([
496e18d32baSSongqian Li "--cpus",
497e18d32baSSongqian Li &format!("boot={}", control.num_boot_vcpus.unwrap_or(1)),
498e18d32baSSongqian Li ])
499e18d32baSSongqian Li .args(["--memory", "size=256M"])
500e18d32baSSongqian Li .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
501e18d32baSSongqian Li .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
502e18d32baSSongqian Li .args(["--console", "off"])
503e18d32baSSongqian Li .default_disks()
504e18d32baSSongqian Li .set_print_cmd(false)
505e18d32baSSongqian Li .spawn()
506e18d32baSSongqian Li .unwrap();
507e18d32baSSongqian Li
508e18d32baSSongqian Li thread::sleep(Duration::new((control.test_timeout / 2) as u64, 0));
509e18d32baSSongqian Li let snapshot_dir = String::from(guest.tmp_dir.as_path().join("snapshot").to_str().unwrap());
510e18d32baSSongqian Li std::fs::create_dir(&snapshot_dir).unwrap();
511e18d32baSSongqian Li assert!(remote_command(&api_socket_source, "pause", None));
512e18d32baSSongqian Li assert!(remote_command(
513e18d32baSSongqian Li &api_socket_source,
514e18d32baSSongqian Li "snapshot",
51510ee003dSBo Chen Some(format!("file://{snapshot_dir}").as_str()),
516e18d32baSSongqian Li ));
517e18d32baSSongqian Li
518e18d32baSSongqian Li let _ = child.kill();
519bac762e4SRob Bradford child.wait().unwrap();
520e18d32baSSongqian Li
521e18d32baSSongqian Li let event_path = String::from(guest.tmp_dir.as_path().join("event.json").to_str().unwrap());
522e18d32baSSongqian Li let mut cmd = GuestCommand::new(&guest);
523e18d32baSSongqian Li let c = cmd
524e18d32baSSongqian Li .args([
525e18d32baSSongqian Li "--restore",
52610ee003dSBo Chen format!("source_url=file://{snapshot_dir}").as_str(),
527e18d32baSSongqian Li ])
52810ee003dSBo Chen .args(["--event-monitor", format!("path={event_path}").as_str()]);
529e18d32baSSongqian Li
530e18d32baSSongqian Li measure_restore_time(c, event_path.as_str(), control.test_timeout).unwrap()
531e18d32baSSongqian Li });
532e18d32baSSongqian Li
533e18d32baSSongqian Li match r {
534e18d32baSSongqian Li Ok(r) => r,
535e18d32baSSongqian Li Err(_) => {
536e18d32baSSongqian Li panic!("test failed!");
537e18d32baSSongqian Li }
538e18d32baSSongqian Li }
539e18d32baSSongqian Li }
540e18d32baSSongqian Li
541dc91866bSBo Chen #[cfg(test)]
542dc91866bSBo Chen mod tests {
543dc91866bSBo Chen use super::*;
544dc91866bSBo Chen
545dc91866bSBo Chen #[test]
test_parse_iperf3_output()546dc91866bSBo Chen fn test_parse_iperf3_output() {
547dc91866bSBo Chen let output = r#"
548dc91866bSBo Chen {
549dc91866bSBo Chen "end": {
550dc91866bSBo Chen "sum_sent": {
551dc91866bSBo Chen "start": 0,
552dc91866bSBo Chen "end": 5.000196,
553dc91866bSBo Chen "seconds": 5.000196,
554dc91866bSBo Chen "bytes": 14973836248,
555dc91866bSBo Chen "bits_per_second": 23957198874.604115,
556dc91866bSBo Chen "retransmits": 0,
557dc91866bSBo Chen "sender": false
558dc91866bSBo Chen }
559dc91866bSBo Chen }
560dc91866bSBo Chen }
561dc91866bSBo Chen "#;
562dc91866bSBo Chen assert_eq!(
56308e4d1f4SWei Liu parse_iperf3_output(output.as_bytes(), true, true).unwrap(),
564dc91866bSBo Chen 23957198874.604115
565dc91866bSBo Chen );
566dc91866bSBo Chen
567dc91866bSBo Chen let output = r#"
568dc91866bSBo Chen {
569dc91866bSBo Chen "end": {
570dc91866bSBo Chen "sum_received": {
571dc91866bSBo Chen "start": 0,
572dc91866bSBo Chen "end": 5.000626,
573dc91866bSBo Chen "seconds": 5.000626,
574dc91866bSBo Chen "bytes": 24703557800,
575dc91866bSBo Chen "bits_per_second": 39520744482.79,
576dc91866bSBo Chen "sender": true
577dc91866bSBo Chen }
578dc91866bSBo Chen }
579dc91866bSBo Chen }
580dc91866bSBo Chen "#;
581dc91866bSBo Chen assert_eq!(
58208e4d1f4SWei Liu parse_iperf3_output(output.as_bytes(), false, true).unwrap(),
583dc91866bSBo Chen 39520744482.79
584dc91866bSBo Chen );
58508e4d1f4SWei Liu let output = r#"
58608e4d1f4SWei Liu {
58708e4d1f4SWei Liu "end": {
58808e4d1f4SWei Liu "sum": {
58908e4d1f4SWei Liu "start": 0,
59008e4d1f4SWei Liu "end": 5.000036,
59108e4d1f4SWei Liu "seconds": 5.000036,
59208e4d1f4SWei Liu "bytes": 29944971264,
59308e4d1f4SWei Liu "bits_per_second": 47911877363.396217,
59408e4d1f4SWei Liu "jitter_ms": 0.0038609822983198556,
59508e4d1f4SWei Liu "lost_packets": 16,
59608e4d1f4SWei Liu "packets": 913848,
59708e4d1f4SWei Liu "lost_percent": 0.0017508382137948542,
59808e4d1f4SWei Liu "sender": true
59908e4d1f4SWei Liu }
60008e4d1f4SWei Liu }
60108e4d1f4SWei Liu }
60208e4d1f4SWei Liu "#;
60308e4d1f4SWei Liu assert_eq!(
60408e4d1f4SWei Liu parse_iperf3_output(output.as_bytes(), true, false).unwrap(),
60508e4d1f4SWei Liu 182765.08409139456
60608e4d1f4SWei Liu );
607dc91866bSBo Chen }
608dc91866bSBo Chen
609dc91866bSBo Chen #[test]
test_parse_ethr_latency_output()610dc91866bSBo Chen fn test_parse_ethr_latency_output() {
611dc91866bSBo Chen let output = r#"{"Time":"2022-02-08T03:52:50Z","Title":"","Type":"INFO","Message":"Using destination: 192.168.249.2, ip: 192.168.249.2, port: 8888"}
612dc91866bSBo Chen {"Time":"2022-02-08T03:52:51Z","Title":"","Type":"INFO","Message":"Running latency test: 1000, 1"}
613dc91866bSBo Chen {"Time":"2022-02-08T03:52:51Z","Title":"","Type":"LatencyResult","RemoteAddr":"192.168.249.2","Protocol":"TCP","Avg":"80.712us","Min":"61.677us","P50":"257.014us","P90":"74.418us","P95":"107.283us","P99":"119.309us","P999":"142.100us","P9999":"216.341us","Max":"216.341us"}
614dc91866bSBo Chen {"Time":"2022-02-08T03:52:52Z","Title":"","Type":"LatencyResult","RemoteAddr":"192.168.249.2","Protocol":"TCP","Avg":"79.826us","Min":"55.129us","P50":"598.996us","P90":"73.849us","P95":"106.552us","P99":"122.152us","P999":"142.459us","P9999":"474.280us","Max":"474.280us"}
615dc91866bSBo Chen {"Time":"2022-02-08T03:52:53Z","Title":"","Type":"LatencyResult","RemoteAddr":"192.168.249.2","Protocol":"TCP","Avg":"78.239us","Min":"56.999us","P50":"396.820us","P90":"69.469us","P95":"115.421us","P99":"119.404us","P999":"130.158us","P9999":"258.686us","Max":"258.686us"}"#;
616dc91866bSBo Chen
617dc91866bSBo Chen let ret = parse_ethr_latency_output(output.as_bytes()).unwrap();
618dc91866bSBo Chen let reference = vec![80.712_f64, 79.826_f64, 78.239_f64];
619dc91866bSBo Chen assert_eq!(ret, reference);
620dc91866bSBo Chen }
621dc91866bSBo Chen
622dc91866bSBo Chen #[test]
test_parse_boot_time_output()623dc91866bSBo Chen fn test_parse_boot_time_output() {
624dc91866bSBo Chen let output = r#"
625dc91866bSBo Chen cloud-hypervisor: 161.167103ms: <vcpu0> INFO:vmm/src/vm.rs:392 -- [Debug I/O port: Kernel code 0x40] 0.132 seconds
626dc91866bSBo Chen cloud-hypervisor: 613.57361ms: <vcpu0> INFO:vmm/src/vm.rs:392 -- [Debug I/O port: Kernel code 0x41] 0.5845 seconds
627dc91866bSBo Chen "#;
628dc91866bSBo Chen
629dc91866bSBo Chen assert_eq!(parse_boot_time_output(output.as_bytes()).unwrap(), 0.4525);
630dc91866bSBo Chen }
631e18d32baSSongqian Li #[test]
test_parse_restore_time_output()632e18d32baSSongqian Li fn test_parse_restore_time_output() {
633e18d32baSSongqian Li let output = r#"
634e18d32baSSongqian Li {
635e18d32baSSongqian Li "timestamp": {
636e18d32baSSongqian Li "secs": 0,
637e18d32baSSongqian Li "nanos": 4664404
638e18d32baSSongqian Li },
639e18d32baSSongqian Li "source": "virtio-device",
640e18d32baSSongqian Li "event": "activated",
641e18d32baSSongqian Li "properties": {
642e18d32baSSongqian Li "id": "__rng"
643e18d32baSSongqian Li }
644e18d32baSSongqian Li }
645dc91866bSBo Chen
646e18d32baSSongqian Li {
647e18d32baSSongqian Li "timestamp": {
648e18d32baSSongqian Li "secs": 0,
649e18d32baSSongqian Li "nanos": 5505133
650e18d32baSSongqian Li },
651e18d32baSSongqian Li "source": "vm",
652e18d32baSSongqian Li "event": "restored",
653e18d32baSSongqian Li "properties": null
654e18d32baSSongqian Li }
655e18d32baSSongqian Li "#;
656e18d32baSSongqian Li let mut ret = Vec::new();
657e18d32baSSongqian Li for entry in String::from(output)
658e18d32baSSongqian Li .trim()
659e18d32baSSongqian Li .split("\n\n")
660e18d32baSSongqian Li .collect::<Vec<&str>>()
661e18d32baSSongqian Li {
662e18d32baSSongqian Li ret.push(serde_json::from_str(entry).unwrap());
663e18d32baSSongqian Li }
664e18d32baSSongqian Li
665e18d32baSSongqian Li assert_eq!(parse_restore_time_output(&ret).unwrap(), 5.505133_f64);
666e18d32baSSongqian Li }
667dc91866bSBo Chen #[test]
test_parse_fio_output()668dc91866bSBo Chen fn test_parse_fio_output() {
669dc91866bSBo Chen let output = r#"
670dc91866bSBo Chen {
671dc91866bSBo Chen "jobs" : [
672dc91866bSBo Chen {
673dc91866bSBo Chen "read" : {
674dc91866bSBo Chen "io_bytes" : 1965273088,
675dc91866bSBo Chen "io_kbytes" : 1919212,
676dc91866bSBo Chen "bw_bytes" : 392976022,
677dc91866bSBo Chen "bw" : 383765,
678dc91866bSBo Chen "iops" : 95941.411718,
679dc91866bSBo Chen "runtime" : 5001,
680dc91866bSBo Chen "total_ios" : 479803,
681dc91866bSBo Chen "short_ios" : 0,
682dc91866bSBo Chen "drop_ios" : 0
683dc91866bSBo Chen }
684dc91866bSBo Chen }
685dc91866bSBo Chen ]
686dc91866bSBo Chen }
687dc91866bSBo Chen "#;
688dc91866bSBo Chen
689dc91866bSBo Chen let bps = 1965273088_f64 / (5001_f64 / 1000_f64);
690dc91866bSBo Chen assert_eq!(
691dc91866bSBo Chen parse_fio_output(output, &FioOps::RandomRead, 1).unwrap(),
692dc91866bSBo Chen bps
693dc91866bSBo Chen );
694dc91866bSBo Chen assert_eq!(parse_fio_output(output, &FioOps::Read, 1).unwrap(), bps);
695dc91866bSBo Chen
696dc91866bSBo Chen let output = r#"
697dc91866bSBo Chen {
698dc91866bSBo Chen "jobs" : [
699dc91866bSBo Chen {
700dc91866bSBo Chen "write" : {
701dc91866bSBo Chen "io_bytes" : 1172783104,
702dc91866bSBo Chen "io_kbytes" : 1145296,
703dc91866bSBo Chen "bw_bytes" : 234462835,
704dc91866bSBo Chen "bw" : 228967,
705dc91866bSBo Chen "iops" : 57241.903239,
706dc91866bSBo Chen "runtime" : 5002,
707dc91866bSBo Chen "total_ios" : 286324,
708dc91866bSBo Chen "short_ios" : 0,
709dc91866bSBo Chen "drop_ios" : 0
710dc91866bSBo Chen }
711dc91866bSBo Chen },
712dc91866bSBo Chen {
713dc91866bSBo Chen "write" : {
714dc91866bSBo Chen "io_bytes" : 1172234240,
715dc91866bSBo Chen "io_kbytes" : 1144760,
716dc91866bSBo Chen "bw_bytes" : 234353106,
717dc91866bSBo Chen "bw" : 228860,
718dc91866bSBo Chen "iops" : 57215.113954,
719dc91866bSBo Chen "runtime" : 5002,
720dc91866bSBo Chen "total_ios" : 286190,
721dc91866bSBo Chen "short_ios" : 0,
722dc91866bSBo Chen "drop_ios" : 0
723dc91866bSBo Chen }
724dc91866bSBo Chen }
725dc91866bSBo Chen ]
726dc91866bSBo Chen }
727dc91866bSBo Chen "#;
728dc91866bSBo Chen
729dc91866bSBo Chen let bps = 1172783104_f64 / (5002_f64 / 1000_f64) + 1172234240_f64 / (5002_f64 / 1000_f64);
730dc91866bSBo Chen assert_eq!(
731dc91866bSBo Chen parse_fio_output(output, &FioOps::RandomWrite, 2).unwrap(),
732dc91866bSBo Chen bps
733dc91866bSBo Chen );
734dc91866bSBo Chen assert_eq!(parse_fio_output(output, &FioOps::Write, 2).unwrap(), bps);
735dc91866bSBo Chen }
736dc91866bSBo Chen }
737