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,8 +69,8 @@ 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" )
72- .with_arg ("--output-format=json+" )
7374 .build ()
7475 )
7576
@@ -102,51 +103,65 @@ def run_fio(microvm, mode, block_size):
102103 return logs_path , cpu_load_future .result ()
103104
104105
105- def process_fio_logs (vm , fio_mode , logs_dir , metrics ):
106- """Parses the fio logs in `{logs_dir}/{fio_mode}_bw.*.log and emits their contents as CloudWatch metrics"""
107-
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"""
108108 data = [
109- Path (f"{ logs_dir } /{ fio_mode } _bw.{ job_id + 1 } .log" )
110- .read_text ("UTF-8" )
111- .splitlines ()
112- for job_id in range (vm .vcpus_count )
109+ Path (pathname ).read_text ("UTF-8" ).splitlines ()
110+ for pathname in glob .glob (logs_glob )
113111 ]
114112
113+ assert data , "no log files found!"
114+
115115 for tup in zip (* data ):
116- bw_read = 0
117- bw_write = 0
116+ read_values = []
117+ write_values = []
118118
119119 for line in tup :
120+ # See https://fio.readthedocs.io/en/latest/fio_doc.html#log-file-formats
120121 _ , value , direction , _ = line .split ("," , maxsplit = 3 )
121122 value = int (value .strip ())
122123
123- # See https://fio.readthedocs.io/en/latest/fio_doc.html#log-file-formats
124124 match direction .strip ():
125125 case "0" :
126- bw_read += value
126+ read_values . append ( value )
127127 case "1" :
128- bw_write += value
128+ write_values . append ( value )
129129 case _:
130130 assert False
131131
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" ):
132138 if bw_read :
133- metrics .put_metric ("bw_read" , bw_read , "Kilobytes/Second" )
139+ metrics .put_metric ("bw_read" , sum ( bw_read ) , "Kilobytes/Second" )
134140 if bw_write :
135- 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" )
136150
137151
138- @pytest .mark .timeout (120 )
139152@pytest .mark .nonci
140153@pytest .mark .parametrize ("vcpus" , [1 , 2 ], ids = ["1vcpu" , "2vcpu" ])
141154@pytest .mark .parametrize ("fio_mode" , ["randread" , "randwrite" ])
142155@pytest .mark .parametrize ("fio_block_size" , [4096 ], ids = ["bs4096" ])
156+ @pytest .mark .parametrize ("fio_engine" , ["libaio" , "psync" ])
143157def test_block_performance (
144158 microvm_factory ,
145159 guest_kernel_acpi ,
146160 rootfs ,
147161 vcpus ,
148162 fio_mode ,
149163 fio_block_size ,
164+ fio_engine ,
150165 io_engine ,
151166 metrics ,
152167):
@@ -170,15 +185,16 @@ def test_block_performance(
170185 "io_engine" : io_engine ,
171186 "fio_mode" : fio_mode ,
172187 "fio_block_size" : str (fio_block_size ),
188+ "fio_engine" : fio_engine ,
173189 ** vm .dimensions ,
174190 }
175191 )
176192
177193 vm .pin_threads (0 )
178194
179- 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 )
180196
181- process_fio_logs ( vm , fio_mode , logs_dir , metrics )
197+ emit_fio_metrics ( logs_dir , metrics )
182198
183199 for thread_name , values in cpu_util .items ():
184200 for value in values :
@@ -218,6 +234,7 @@ def test_block_vhost_user_performance(
218234 "io_engine" : "vhost-user" ,
219235 "fio_mode" : fio_mode ,
220236 "fio_block_size" : str (fio_block_size ),
237+ "fio_engine" : "libaio" ,
221238 ** vm .dimensions ,
222239 }
223240 )
@@ -227,7 +244,7 @@ def test_block_vhost_user_performance(
227244
228245 logs_dir , cpu_util = run_fio (vm , fio_mode , fio_block_size )
229246
230- process_fio_logs ( vm , fio_mode , logs_dir , metrics )
247+ emit_fio_metrics ( logs_dir , metrics )
231248
232249 for thread_name , values in cpu_util .items ():
233250 for value in values :
0 commit comments