1 // Copyright © 2022 Intel Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4 //
5
6 // Performance tests
7
8 use std::path::PathBuf;
9 use std::time::Duration;
10 use std::{fs, thread};
11
12 use test_infra::{Error as InfraError, *};
13 use thiserror::Error;
14
15 use crate::{mean, PerformanceTestControl};
16
17 #[cfg(target_arch = "x86_64")]
18 pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-amd64-custom-20210609-0.raw";
19 #[cfg(target_arch = "aarch64")]
20 pub const FOCAL_IMAGE_NAME: &str = "focal-server-cloudimg-arm64-custom-20210929-0-update-tool.raw";
21
22 #[allow(dead_code)]
23 #[derive(Error, Debug)]
24 enum Error {
25 #[error("boot time could not be parsed")]
26 BootTimeParse,
27 #[error("infrastructure failure")]
28 Infra(#[from] InfraError),
29 #[error("restore time could not be parsed")]
30 RestoreTimeParse,
31 }
32
33 const BLK_IO_TEST_IMG: &str = "/var/tmp/ch-blk-io-test.img";
34
init_tests()35 pub fn init_tests() {
36 // The test image cannot be created on tmpfs (e.g. /tmp) filesystem,
37 // as tmpfs does not support O_DIRECT
38 assert!(exec_host_command_output(&format!(
39 "dd if=/dev/zero of={BLK_IO_TEST_IMG} bs=1M count=4096"
40 ))
41 .status
42 .success());
43 }
44
cleanup_tests()45 pub fn cleanup_tests() {
46 fs::remove_file(BLK_IO_TEST_IMG)
47 .unwrap_or_else(|_| panic!("Failed to remove file '{BLK_IO_TEST_IMG}'."));
48 }
49
50 // Performance tests are expected to be executed sequentially, so we can
51 // start VM guests with the same IP while putting them on a different
52 // private network. The default constructor "Guest::new()" does not work
53 // well, as we can easily create more than 256 VMs from repeating various
54 // performance tests dozens times in a single run.
performance_test_new_guest(disk_config: Box<dyn DiskConfig>) -> Guest55 fn performance_test_new_guest(disk_config: Box<dyn DiskConfig>) -> Guest {
56 Guest::new_from_ip_range(disk_config, "172.19", 0)
57 }
58
59 const DIRECT_KERNEL_BOOT_CMDLINE: &str =
60 "root=/dev/vda1 console=hvc0 rw systemd.journald.forward_to_console=1";
61
62 // Creates the path for direct kernel boot and return the path.
63 // For x86_64, this function returns the vmlinux kernel path.
64 // For AArch64, this function returns the PE kernel path.
direct_kernel_boot_path() -> PathBuf65 fn direct_kernel_boot_path() -> PathBuf {
66 let mut workload_path = dirs::home_dir().unwrap();
67 workload_path.push("workloads");
68
69 let mut kernel_path = workload_path;
70 #[cfg(target_arch = "x86_64")]
71 kernel_path.push("vmlinux");
72 #[cfg(target_arch = "aarch64")]
73 kernel_path.push("Image");
74
75 kernel_path
76 }
77
remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool78 fn remote_command(api_socket: &str, command: &str, arg: Option<&str>) -> bool {
79 let mut cmd = std::process::Command::new(clh_command("ch-remote"));
80 cmd.args([&format!("--api-socket={api_socket}"), command]);
81
82 if let Some(arg) = arg {
83 cmd.arg(arg);
84 }
85 let output = cmd.output().unwrap();
86 if output.status.success() {
87 true
88 } else {
89 eprintln!(
90 "Error running ch-remote command: {:?}\nstderr: {}",
91 &cmd,
92 String::from_utf8_lossy(&output.stderr)
93 );
94 false
95 }
96 }
97
performance_net_throughput(control: &PerformanceTestControl) -> f6498 pub fn performance_net_throughput(control: &PerformanceTestControl) -> f64 {
99 let test_timeout = control.test_timeout;
100 let (rx, bandwidth) = control.net_control.unwrap();
101
102 let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
103 let guest = performance_test_new_guest(Box::new(focal));
104
105 let num_queues = control.num_queues.unwrap();
106 let queue_size = control.queue_size.unwrap();
107 let net_params = format!(
108 "tap=,mac={},ip={},mask=255.255.255.0,num_queues={},queue_size={}",
109 guest.network.guest_mac, guest.network.host_ip, num_queues, queue_size,
110 );
111
112 let mut child = GuestCommand::new(&guest)
113 .args(["--cpus", &format!("boot={num_queues}")])
114 .args(["--memory", "size=4G"])
115 .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
116 .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
117 .default_disks()
118 .args(["--net", net_params.as_str()])
119 .capture_output()
120 .verbosity(VerbosityLevel::Warn)
121 .set_print_cmd(false)
122 .spawn()
123 .unwrap();
124
125 let r = std::panic::catch_unwind(|| {
126 guest.wait_vm_boot(None).unwrap();
127 measure_virtio_net_throughput(test_timeout, num_queues / 2, &guest, rx, bandwidth).unwrap()
128 });
129
130 let _ = child.kill();
131 let output = child.wait_with_output().unwrap();
132
133 match r {
134 Ok(r) => r,
135 Err(e) => {
136 handle_child_output(Err(e), &output);
137 panic!("test failed!");
138 }
139 }
140 }
141
performance_net_latency(control: &PerformanceTestControl) -> f64142 pub fn performance_net_latency(control: &PerformanceTestControl) -> f64 {
143 let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
144 let guest = performance_test_new_guest(Box::new(focal));
145
146 let num_queues = control.num_queues.unwrap();
147 let queue_size = control.queue_size.unwrap();
148 let net_params = format!(
149 "tap=,mac={},ip={},mask=255.255.255.0,num_queues={},queue_size={}",
150 guest.network.guest_mac, guest.network.host_ip, num_queues, queue_size,
151 );
152
153 let mut child = GuestCommand::new(&guest)
154 .args(["--cpus", &format!("boot={num_queues}")])
155 .args(["--memory", "size=4G"])
156 .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
157 .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
158 .default_disks()
159 .args(["--net", net_params.as_str()])
160 .capture_output()
161 .verbosity(VerbosityLevel::Warn)
162 .set_print_cmd(false)
163 .spawn()
164 .unwrap();
165
166 let r = std::panic::catch_unwind(|| {
167 guest.wait_vm_boot(None).unwrap();
168
169 // 'ethr' tool will measure the latency multiple times with provided test time
170 let latency = measure_virtio_net_latency(&guest, control.test_timeout).unwrap();
171 mean(&latency).unwrap()
172 });
173
174 let _ = child.kill();
175 let output = child.wait_with_output().unwrap();
176
177 match r {
178 Ok(r) => r,
179 Err(e) => {
180 handle_child_output(Err(e), &output);
181 panic!("test failed!");
182 }
183 }
184 }
185
parse_boot_time_output(output: &[u8]) -> Result<f64, Error>186 fn parse_boot_time_output(output: &[u8]) -> Result<f64, Error> {
187 std::panic::catch_unwind(|| {
188 let l: Vec<String> = String::from_utf8_lossy(output)
189 .lines()
190 .filter(|l| l.contains("Debug I/O port: Kernel code"))
191 .map(|l| l.to_string())
192 .collect();
193
194 assert_eq!(
195 l.len(),
196 2,
197 "Expecting two matching lines for 'Debug I/O port: Kernel code'"
198 );
199
200 let time_stamp_kernel_start = {
201 let s = l[0].split("--").collect::<Vec<&str>>();
202 assert_eq!(
203 s.len(),
204 2,
205 "Expecting '--' for the matching line of 'Debug I/O port' output"
206 );
207
208 // Sample output: "[Debug I/O port: Kernel code 0x40] 0.096537 seconds"
209 assert!(
210 s[1].contains("0x40"),
211 "Expecting kernel code '0x40' for 'linux_kernel_start' time stamp output"
212 );
213 let t = s[1].split_whitespace().collect::<Vec<&str>>();
214 assert_eq!(
215 t.len(),
216 8,
217 "Expecting exact '8' words from the 'Debug I/O port' output"
218 );
219 assert!(
220 t[7].eq("seconds"),
221 "Expecting 'seconds' as the last word of the 'Debug I/O port' output"
222 );
223
224 t[6].parse::<f64>().unwrap()
225 };
226
227 let time_stamp_user_start = {
228 let s = l[1].split("--").collect::<Vec<&str>>();
229 assert_eq!(
230 s.len(),
231 2,
232 "Expecting '--' for the matching line of 'Debug I/O port' output"
233 );
234
235 // Sample output: "Debug I/O port: Kernel code 0x41] 0.198980 seconds"
236 assert!(
237 s[1].contains("0x41"),
238 "Expecting kernel code '0x41' for 'linux_kernel_start' time stamp output"
239 );
240 let t = s[1].split_whitespace().collect::<Vec<&str>>();
241 assert_eq!(
242 t.len(),
243 8,
244 "Expecting exact '8' words from the 'Debug I/O port' output"
245 );
246 assert!(
247 t[7].eq("seconds"),
248 "Expecting 'seconds' as the last word of the 'Debug I/O port' output"
249 );
250
251 t[6].parse::<f64>().unwrap()
252 };
253
254 time_stamp_user_start - time_stamp_kernel_start
255 })
256 .map_err(|_| {
257 eprintln!(
258 "=============== boot-time output ===============\n\n{}\n\n===========end============\n\n",
259 String::from_utf8_lossy(output)
260 );
261 Error::BootTimeParse
262 })
263 }
264
measure_boot_time(cmd: &mut GuestCommand, test_timeout: u32) -> Result<f64, Error>265 fn measure_boot_time(cmd: &mut GuestCommand, test_timeout: u32) -> Result<f64, Error> {
266 let mut child = cmd
267 .capture_output()
268 .verbosity(VerbosityLevel::Warn)
269 .set_print_cmd(false)
270 .spawn()
271 .unwrap();
272
273 thread::sleep(Duration::new(test_timeout as u64, 0));
274 let _ = child.kill();
275 let output = child.wait_with_output().unwrap();
276
277 parse_boot_time_output(&output.stderr).inspect_err(|_| {
278 eprintln!(
279 "\n\n==== Start child stdout ====\n\n{}\n\n==== End child stdout ====",
280 String::from_utf8_lossy(&output.stdout)
281 );
282 eprintln!(
283 "\n\n==== Start child stderr ====\n\n{}\n\n==== End child stderr ====",
284 String::from_utf8_lossy(&output.stderr)
285 );
286 })
287 }
288
performance_boot_time(control: &PerformanceTestControl) -> f64289 pub fn performance_boot_time(control: &PerformanceTestControl) -> f64 {
290 let r = std::panic::catch_unwind(|| {
291 let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
292 let guest = performance_test_new_guest(Box::new(focal));
293 let mut cmd = GuestCommand::new(&guest);
294
295 let c = cmd
296 .args([
297 "--cpus",
298 &format!("boot={}", control.num_boot_vcpus.unwrap_or(1)),
299 ])
300 .args(["--memory", "size=1G"])
301 .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
302 .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
303 .args(["--console", "off"])
304 .default_disks();
305
306 measure_boot_time(c, control.test_timeout).unwrap()
307 });
308
309 match r {
310 Ok(r) => r,
311 Err(_) => {
312 panic!("test failed!");
313 }
314 }
315 }
316
performance_boot_time_pmem(control: &PerformanceTestControl) -> f64317 pub fn performance_boot_time_pmem(control: &PerformanceTestControl) -> f64 {
318 let r = std::panic::catch_unwind(|| {
319 let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
320 let guest = performance_test_new_guest(Box::new(focal));
321 let mut cmd = GuestCommand::new(&guest);
322 let c = cmd
323 .args([
324 "--cpus",
325 &format!("boot={}", control.num_boot_vcpus.unwrap_or(1)),
326 ])
327 .args(["--memory", "size=1G,hugepages=on"])
328 .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
329 .args(["--cmdline", "root=/dev/pmem0p1 console=ttyS0 quiet rw"])
330 .args(["--console", "off"])
331 .args([
332 "--pmem",
333 format!(
334 "file={}",
335 guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
336 )
337 .as_str(),
338 ]);
339
340 measure_boot_time(c, control.test_timeout).unwrap()
341 });
342
343 match r {
344 Ok(r) => r,
345 Err(_) => {
346 panic!("test failed!");
347 }
348 }
349 }
350
performance_block_io(control: &PerformanceTestControl) -> f64351 pub fn performance_block_io(control: &PerformanceTestControl) -> f64 {
352 let test_timeout = control.test_timeout;
353 let num_queues = control.num_queues.unwrap();
354 let (fio_ops, bandwidth) = control.fio_control.as_ref().unwrap();
355
356 let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
357 let guest = performance_test_new_guest(Box::new(focal));
358 let api_socket = guest
359 .tmp_dir
360 .as_path()
361 .join("cloud-hypervisor.sock")
362 .to_str()
363 .unwrap()
364 .to_string();
365
366 let mut child = GuestCommand::new(&guest)
367 .args(["--cpus", &format!("boot={num_queues}")])
368 .args(["--memory", "size=4G"])
369 .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
370 .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
371 .args([
372 "--disk",
373 format!(
374 "path={}",
375 guest.disk_config.disk(DiskType::OperatingSystem).unwrap()
376 )
377 .as_str(),
378 format!(
379 "path={}",
380 guest.disk_config.disk(DiskType::CloudInit).unwrap()
381 )
382 .as_str(),
383 format!("path={BLK_IO_TEST_IMG}").as_str(),
384 ])
385 .default_net()
386 .args(["--api-socket", &api_socket])
387 .capture_output()
388 .verbosity(VerbosityLevel::Warn)
389 .set_print_cmd(false)
390 .spawn()
391 .unwrap();
392
393 let r = std::panic::catch_unwind(|| {
394 guest.wait_vm_boot(None).unwrap();
395
396 let fio_command = format!(
397 "sudo fio --filename=/dev/vdc --name=test --output-format=json \
398 --direct=1 --bs=4k --ioengine=io_uring --iodepth=64 \
399 --rw={fio_ops} --runtime={test_timeout} --numjobs={num_queues}"
400 );
401 let output = guest
402 .ssh_command(&fio_command)
403 .map_err(InfraError::SshCommand)
404 .unwrap();
405
406 // Parse fio output
407 if *bandwidth {
408 parse_fio_output(&output, fio_ops, num_queues).unwrap()
409 } else {
410 parse_fio_output_iops(&output, fio_ops, num_queues).unwrap()
411 }
412 });
413
414 let _ = child.kill();
415 let output = child.wait_with_output().unwrap();
416
417 match r {
418 Ok(r) => r,
419 Err(e) => {
420 handle_child_output(Err(e), &output);
421 panic!("test failed!");
422 }
423 }
424 }
425
426 // Parse the event_monitor file based on the format that each event
427 // is followed by a double newline
parse_event_file(event_file: &str) -> Vec<serde_json::Value>428 fn parse_event_file(event_file: &str) -> Vec<serde_json::Value> {
429 let content = fs::read(event_file).unwrap();
430 let mut ret = Vec::new();
431 for entry in String::from_utf8_lossy(&content)
432 .trim()
433 .split("\n\n")
434 .collect::<Vec<&str>>()
435 {
436 ret.push(serde_json::from_str(entry).unwrap());
437 }
438 ret
439 }
440
parse_restore_time_output(events: &[serde_json::Value]) -> Result<f64, Error>441 fn parse_restore_time_output(events: &[serde_json::Value]) -> Result<f64, Error> {
442 for entry in events.iter() {
443 if entry["event"].as_str().unwrap() == "restored" {
444 let duration = entry["timestamp"]["secs"].as_u64().unwrap() as f64 * 1_000f64
445 + entry["timestamp"]["nanos"].as_u64().unwrap() as f64 / 1_000_000f64;
446 return Ok(duration);
447 }
448 }
449 Err(Error::RestoreTimeParse)
450 }
451
measure_restore_time( cmd: &mut GuestCommand, event_file: &str, test_timeout: u32, ) -> Result<f64, Error>452 fn measure_restore_time(
453 cmd: &mut GuestCommand,
454 event_file: &str,
455 test_timeout: u32,
456 ) -> Result<f64, Error> {
457 let mut child = cmd
458 .capture_output()
459 .verbosity(VerbosityLevel::Warn)
460 .set_print_cmd(false)
461 .spawn()
462 .unwrap();
463
464 thread::sleep(Duration::new((test_timeout / 2) as u64, 0));
465 let _ = child.kill();
466 let output = child.wait_with_output().unwrap();
467
468 let json_events = parse_event_file(event_file);
469
470 parse_restore_time_output(&json_events).inspect_err(|_| {
471 eprintln!(
472 "\n\n==== Start child stdout ====\n\n{}\n\n==== End child stdout ====\
473 \n\n==== Start child stderr ====\n\n{}\n\n==== End child stderr ====",
474 String::from_utf8_lossy(&output.stdout),
475 String::from_utf8_lossy(&output.stderr)
476 )
477 })
478 }
479
performance_restore_latency(control: &PerformanceTestControl) -> f64480 pub fn performance_restore_latency(control: &PerformanceTestControl) -> f64 {
481 let r = std::panic::catch_unwind(|| {
482 let focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
483 let guest = performance_test_new_guest(Box::new(focal));
484 let api_socket_source = String::from(
485 guest
486 .tmp_dir
487 .as_path()
488 .join("cloud-hypervisor.sock")
489 .to_str()
490 .unwrap(),
491 );
492
493 let mut child = GuestCommand::new(&guest)
494 .args(["--api-socket", &api_socket_source])
495 .args([
496 "--cpus",
497 &format!("boot={}", control.num_boot_vcpus.unwrap_or(1)),
498 ])
499 .args(["--memory", "size=256M"])
500 .args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
501 .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
502 .args(["--console", "off"])
503 .default_disks()
504 .set_print_cmd(false)
505 .spawn()
506 .unwrap();
507
508 thread::sleep(Duration::new((control.test_timeout / 2) as u64, 0));
509 let snapshot_dir = String::from(guest.tmp_dir.as_path().join("snapshot").to_str().unwrap());
510 std::fs::create_dir(&snapshot_dir).unwrap();
511 assert!(remote_command(&api_socket_source, "pause", None));
512 assert!(remote_command(
513 &api_socket_source,
514 "snapshot",
515 Some(format!("file://{snapshot_dir}").as_str()),
516 ));
517
518 let _ = child.kill();
519 child.wait().unwrap();
520
521 let event_path = String::from(guest.tmp_dir.as_path().join("event.json").to_str().unwrap());
522 let mut cmd = GuestCommand::new(&guest);
523 let c = cmd
524 .args([
525 "--restore",
526 format!("source_url=file://{snapshot_dir}").as_str(),
527 ])
528 .args(["--event-monitor", format!("path={event_path}").as_str()]);
529
530 measure_restore_time(c, event_path.as_str(), control.test_timeout).unwrap()
531 });
532
533 match r {
534 Ok(r) => r,
535 Err(_) => {
536 panic!("test failed!");
537 }
538 }
539 }
540
541 #[cfg(test)]
542 mod tests {
543 use super::*;
544
545 #[test]
test_parse_iperf3_output()546 fn test_parse_iperf3_output() {
547 let output = r#"
548 {
549 "end": {
550 "sum_sent": {
551 "start": 0,
552 "end": 5.000196,
553 "seconds": 5.000196,
554 "bytes": 14973836248,
555 "bits_per_second": 23957198874.604115,
556 "retransmits": 0,
557 "sender": false
558 }
559 }
560 }
561 "#;
562 assert_eq!(
563 parse_iperf3_output(output.as_bytes(), true, true).unwrap(),
564 23957198874.604115
565 );
566
567 let output = r#"
568 {
569 "end": {
570 "sum_received": {
571 "start": 0,
572 "end": 5.000626,
573 "seconds": 5.000626,
574 "bytes": 24703557800,
575 "bits_per_second": 39520744482.79,
576 "sender": true
577 }
578 }
579 }
580 "#;
581 assert_eq!(
582 parse_iperf3_output(output.as_bytes(), false, true).unwrap(),
583 39520744482.79
584 );
585 let output = r#"
586 {
587 "end": {
588 "sum": {
589 "start": 0,
590 "end": 5.000036,
591 "seconds": 5.000036,
592 "bytes": 29944971264,
593 "bits_per_second": 47911877363.396217,
594 "jitter_ms": 0.0038609822983198556,
595 "lost_packets": 16,
596 "packets": 913848,
597 "lost_percent": 0.0017508382137948542,
598 "sender": true
599 }
600 }
601 }
602 "#;
603 assert_eq!(
604 parse_iperf3_output(output.as_bytes(), true, false).unwrap(),
605 182765.08409139456
606 );
607 }
608
609 #[test]
test_parse_ethr_latency_output()610 fn test_parse_ethr_latency_output() {
611 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"}
612 {"Time":"2022-02-08T03:52:51Z","Title":"","Type":"INFO","Message":"Running latency test: 1000, 1"}
613 {"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"}
614 {"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"}
615 {"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"}"#;
616
617 let ret = parse_ethr_latency_output(output.as_bytes()).unwrap();
618 let reference = vec![80.712_f64, 79.826_f64, 78.239_f64];
619 assert_eq!(ret, reference);
620 }
621
622 #[test]
test_parse_boot_time_output()623 fn test_parse_boot_time_output() {
624 let output = r#"
625 cloud-hypervisor: 161.167103ms: <vcpu0> INFO:vmm/src/vm.rs:392 -- [Debug I/O port: Kernel code 0x40] 0.132 seconds
626 cloud-hypervisor: 613.57361ms: <vcpu0> INFO:vmm/src/vm.rs:392 -- [Debug I/O port: Kernel code 0x41] 0.5845 seconds
627 "#;
628
629 assert_eq!(parse_boot_time_output(output.as_bytes()).unwrap(), 0.4525);
630 }
631 #[test]
test_parse_restore_time_output()632 fn test_parse_restore_time_output() {
633 let output = r#"
634 {
635 "timestamp": {
636 "secs": 0,
637 "nanos": 4664404
638 },
639 "source": "virtio-device",
640 "event": "activated",
641 "properties": {
642 "id": "__rng"
643 }
644 }
645
646 {
647 "timestamp": {
648 "secs": 0,
649 "nanos": 5505133
650 },
651 "source": "vm",
652 "event": "restored",
653 "properties": null
654 }
655 "#;
656 let mut ret = Vec::new();
657 for entry in String::from(output)
658 .trim()
659 .split("\n\n")
660 .collect::<Vec<&str>>()
661 {
662 ret.push(serde_json::from_str(entry).unwrap());
663 }
664
665 assert_eq!(parse_restore_time_output(&ret).unwrap(), 5.505133_f64);
666 }
667 #[test]
test_parse_fio_output()668 fn test_parse_fio_output() {
669 let output = r#"
670 {
671 "jobs" : [
672 {
673 "read" : {
674 "io_bytes" : 1965273088,
675 "io_kbytes" : 1919212,
676 "bw_bytes" : 392976022,
677 "bw" : 383765,
678 "iops" : 95941.411718,
679 "runtime" : 5001,
680 "total_ios" : 479803,
681 "short_ios" : 0,
682 "drop_ios" : 0
683 }
684 }
685 ]
686 }
687 "#;
688
689 let bps = 1965273088_f64 / (5001_f64 / 1000_f64);
690 assert_eq!(
691 parse_fio_output(output, &FioOps::RandomRead, 1).unwrap(),
692 bps
693 );
694 assert_eq!(parse_fio_output(output, &FioOps::Read, 1).unwrap(), bps);
695
696 let output = r#"
697 {
698 "jobs" : [
699 {
700 "write" : {
701 "io_bytes" : 1172783104,
702 "io_kbytes" : 1145296,
703 "bw_bytes" : 234462835,
704 "bw" : 228967,
705 "iops" : 57241.903239,
706 "runtime" : 5002,
707 "total_ios" : 286324,
708 "short_ios" : 0,
709 "drop_ios" : 0
710 }
711 },
712 {
713 "write" : {
714 "io_bytes" : 1172234240,
715 "io_kbytes" : 1144760,
716 "bw_bytes" : 234353106,
717 "bw" : 228860,
718 "iops" : 57215.113954,
719 "runtime" : 5002,
720 "total_ios" : 286190,
721 "short_ios" : 0,
722 "drop_ios" : 0
723 }
724 }
725 ]
726 }
727 "#;
728
729 let bps = 1172783104_f64 / (5002_f64 / 1000_f64) + 1172234240_f64 / (5002_f64 / 1000_f64);
730 assert_eq!(
731 parse_fio_output(output, &FioOps::RandomWrite, 2).unwrap(),
732 bps
733 );
734 assert_eq!(parse_fio_output(output, &FioOps::Write, 2).unwrap(), bps);
735 }
736 }
737