xref: /cloud-hypervisor/performance-metrics/src/performance_tests.rs (revision 2100d8c30ff684ce6494f845b38690501f18fbf1)
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