33"""Performance benchmark for block device emulation."""
44
55import concurrent
6+ import glob
67import os
78import shutil
89from pathlib import Path
@@ -44,7 +45,7 @@ def prepare_microvm_for_test(microvm):
4445 check_output ("echo 3 > /proc/sys/vm/drop_caches" )
4546
4647
47- def run_fio (microvm , mode , block_size ):
48+ def run_fio (microvm , mode , block_size , fio_engine = "libaio" ):
4849 """Run a fio test in the specified mode with block size bs."""
4950 cmd = (
5051 CmdBuilder ("fio" )
@@ -59,7 +60,7 @@ def run_fio(microvm, mode, block_size):
5960 .with_arg ("--randrepeat=0" )
6061 .with_arg (f"--bs={ block_size } " )
6162 .with_arg (f"--size={ BLOCK_DEVICE_SIZE_MB } M" )
62- .with_arg ("--ioengine=libaio " )
63+ .with_arg (f "--ioengine={ fio_engine } " )
6364 .with_arg ("--iodepth=32" )
6465 # Set affinity of the entire fio process to a set of vCPUs equal in size to number of workers
6566 .with_arg (
@@ -68,6 +69,7 @@ def run_fio(microvm, mode, block_size):
6869 # Instruct fio to pin one worker per vcpu
6970 .with_arg ("--cpus_allowed_policy=split" )
7071 .with_arg (f"--write_bw_log={ mode } " )
72+ .with_arg (f"--write_lat_log={ mode } " )
7173 .with_arg ("--log_avg_msec=1000" )
7274 .build ()
7375 )
@@ -101,51 +103,65 @@ def run_fio(microvm, mode, block_size):
101103 return logs_path , cpu_load_future .result ()
102104
103105
104- def process_fio_logs (vm , fio_mode , logs_dir , metrics ):
105- """Parses the fio logs in `{logs_dir}/{fio_mode}_bw.*.log and emits their contents as CloudWatch metrics"""
106-
106+ def process_fio_log_files (logs_glob ):
107+ """Parses all fio log files matching the given glob and yields tuples of same-timestamp read and write metrics"""
107108 data = [
108- Path (f"{ logs_dir } /{ fio_mode } _bw.{ job_id + 1 } .log" )
109- .read_text ("UTF-8" )
110- .splitlines ()
111- for job_id in range (vm .vcpus_count )
109+ Path (pathname ).read_text ("UTF-8" ).splitlines ()
110+ for pathname in glob .glob (logs_glob )
112111 ]
113112
113+ assert data , "no log files found!"
114+
114115 for tup in zip (* data ):
115- bw_read = 0
116- bw_write = 0
116+ read_values = []
117+ write_values = []
117118
118119 for line in tup :
120+ # See https://fio.readthedocs.io/en/latest/fio_doc.html#log-file-formats
119121 _ , value , direction , _ = line .split ("," , maxsplit = 3 )
120122 value = int (value .strip ())
121123
122- # See https://fio.readthedocs.io/en/latest/fio_doc.html#log-file-formats
123124 match direction .strip ():
124125 case "0" :
125- bw_read += value
126+ read_values . append ( value )
126127 case "1" :
127- bw_write += value
128+ write_values . append ( value )
128129 case _:
129130 assert False
130131
132+ yield read_values , write_values
133+
134+
135+ def emit_fio_metrics (logs_dir , metrics ):
136+ """Parses the fio logs in `{logs_dir}/*_[clat|bw].*.log and emits their contents as CloudWatch metrics"""
137+ for bw_read , bw_write in process_fio_log_files (f"{ logs_dir } /*_bw.*.log" ):
131138 if bw_read :
132- metrics .put_metric ("bw_read" , bw_read , "Kilobytes/Second" )
139+ metrics .put_metric ("bw_read" , sum ( bw_read ) , "Kilobytes/Second" )
133140 if bw_write :
134- metrics .put_metric ("bw_write" , bw_write , "Kilobytes/Second" )
141+ metrics .put_metric ("bw_write" , sum (bw_write ), "Kilobytes/Second" )
142+
143+ for lat_read , lat_write in process_fio_log_files (f"{ logs_dir } /*_clat.*.log" ):
144+ # latency values in fio logs are in nanoseconds, but cloudwatch only supports
145+ # microseconds as the more granular unit, so need to divide by 1000.
146+ for value in lat_read :
147+ metrics .put_metric ("clat_read" , value / 1000 , "Microseconds" )
148+ for value in lat_write :
149+ metrics .put_metric ("clat_write" , value / 1000 , "Microseconds" )
135150
136151
137- @pytest .mark .timeout (120 )
138152@pytest .mark .nonci
139153@pytest .mark .parametrize ("vcpus" , [1 , 2 ], ids = ["1vcpu" , "2vcpu" ])
140154@pytest .mark .parametrize ("fio_mode" , ["randread" , "randwrite" ])
141155@pytest .mark .parametrize ("fio_block_size" , [4096 ], ids = ["bs4096" ])
156+ @pytest .mark .parametrize ("fio_engine" , ["libaio" , "psync" ])
142157def test_block_performance (
143158 microvm_factory ,
144159 guest_kernel_acpi ,
145160 rootfs ,
146161 vcpus ,
147162 fio_mode ,
148163 fio_block_size ,
164+ fio_engine ,
149165 io_engine ,
150166 metrics ,
151167):
@@ -169,15 +185,16 @@ def test_block_performance(
169185 "io_engine" : io_engine ,
170186 "fio_mode" : fio_mode ,
171187 "fio_block_size" : str (fio_block_size ),
188+ "fio_engine" : fio_engine ,
172189 ** vm .dimensions ,
173190 }
174191 )
175192
176193 vm .pin_threads (0 )
177194
178- logs_dir , cpu_util = run_fio (vm , fio_mode , fio_block_size )
195+ logs_dir , cpu_util = run_fio (vm , fio_mode , fio_block_size , fio_engine )
179196
180- process_fio_logs ( vm , fio_mode , logs_dir , metrics )
197+ emit_fio_metrics ( logs_dir , metrics )
181198
182199 for thread_name , values in cpu_util .items ():
183200 for value in values :
@@ -217,6 +234,7 @@ def test_block_vhost_user_performance(
217234 "io_engine" : "vhost-user" ,
218235 "fio_mode" : fio_mode ,
219236 "fio_block_size" : str (fio_block_size ),
237+ "fio_engine" : "libaio" ,
220238 ** vm .dimensions ,
221239 }
222240 )
@@ -226,7 +244,7 @@ def test_block_vhost_user_performance(
226244
227245 logs_dir , cpu_util = run_fio (vm , fio_mode , fio_block_size )
228246
229- process_fio_logs ( vm , fio_mode , logs_dir , metrics )
247+ emit_fio_metrics ( logs_dir , metrics )
230248
231249 for thread_name , values in cpu_util .items ():
232250 for value in values :
0 commit comments