3
3
"""Performance benchmark for block device emulation."""
4
4
5
5
import concurrent
6
+ import glob
6
7
import os
7
8
import shutil
8
9
from pathlib import Path
@@ -44,7 +45,7 @@ def prepare_microvm_for_test(microvm):
44
45
check_output ("echo 3 > /proc/sys/vm/drop_caches" )
45
46
46
47
47
- def run_fio (microvm , mode , block_size ):
48
+ def run_fio (microvm , mode , block_size , fio_engine = "libaio" ):
48
49
"""Run a fio test in the specified mode with block size bs."""
49
50
cmd = (
50
51
CmdBuilder ("fio" )
@@ -59,7 +60,7 @@ def run_fio(microvm, mode, block_size):
59
60
.with_arg ("--randrepeat=0" )
60
61
.with_arg (f"--bs={ block_size } " )
61
62
.with_arg (f"--size={ BLOCK_DEVICE_SIZE_MB } M" )
62
- .with_arg ("--ioengine=libaio " )
63
+ .with_arg (f "--ioengine={ fio_engine } " )
63
64
.with_arg ("--iodepth=32" )
64
65
# Set affinity of the entire fio process to a set of vCPUs equal in size to number of workers
65
66
.with_arg (
@@ -68,6 +69,7 @@ def run_fio(microvm, mode, block_size):
68
69
# Instruct fio to pin one worker per vcpu
69
70
.with_arg ("--cpus_allowed_policy=split" )
70
71
.with_arg (f"--write_bw_log={ mode } " )
72
+ .with_arg (f"--write_lat_log={ mode } " )
71
73
.with_arg ("--log_avg_msec=1000" )
72
74
.build ()
73
75
)
@@ -101,51 +103,65 @@ def run_fio(microvm, mode, block_size):
101
103
return logs_path , cpu_load_future .result ()
102
104
103
105
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"""
107
108
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 )
112
111
]
113
112
113
+ assert data , "no log files found!"
114
+
114
115
for tup in zip (* data ):
115
- bw_read = 0
116
- bw_write = 0
116
+ read_values = []
117
+ write_values = []
117
118
118
119
for line in tup :
120
+ # See https://fio.readthedocs.io/en/latest/fio_doc.html#log-file-formats
119
121
_ , value , direction , _ = line .split ("," , maxsplit = 3 )
120
122
value = int (value .strip ())
121
123
122
- # See https://fio.readthedocs.io/en/latest/fio_doc.html#log-file-formats
123
124
match direction .strip ():
124
125
case "0" :
125
- bw_read += value
126
+ read_values . append ( value )
126
127
case "1" :
127
- bw_write += value
128
+ write_values . append ( value )
128
129
case _:
129
130
assert False
130
131
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" ):
131
138
if bw_read :
132
- metrics .put_metric ("bw_read" , bw_read , "Kilobytes/Second" )
139
+ metrics .put_metric ("bw_read" , sum ( bw_read ) , "Kilobytes/Second" )
133
140
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" )
135
150
136
151
137
- @pytest .mark .timeout (120 )
138
152
@pytest .mark .nonci
139
153
@pytest .mark .parametrize ("vcpus" , [1 , 2 ], ids = ["1vcpu" , "2vcpu" ])
140
154
@pytest .mark .parametrize ("fio_mode" , ["randread" , "randwrite" ])
141
155
@pytest .mark .parametrize ("fio_block_size" , [4096 ], ids = ["bs4096" ])
156
+ @pytest .mark .parametrize ("fio_engine" , ["libaio" , "psync" ])
142
157
def test_block_performance (
143
158
microvm_factory ,
144
159
guest_kernel_acpi ,
145
160
rootfs ,
146
161
vcpus ,
147
162
fio_mode ,
148
163
fio_block_size ,
164
+ fio_engine ,
149
165
io_engine ,
150
166
metrics ,
151
167
):
@@ -169,15 +185,16 @@ def test_block_performance(
169
185
"io_engine" : io_engine ,
170
186
"fio_mode" : fio_mode ,
171
187
"fio_block_size" : str (fio_block_size ),
188
+ "fio_engine" : fio_engine ,
172
189
** vm .dimensions ,
173
190
}
174
191
)
175
192
176
193
vm .pin_threads (0 )
177
194
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 )
179
196
180
- process_fio_logs ( vm , fio_mode , logs_dir , metrics )
197
+ emit_fio_metrics ( logs_dir , metrics )
181
198
182
199
for thread_name , values in cpu_util .items ():
183
200
for value in values :
@@ -217,6 +234,7 @@ def test_block_vhost_user_performance(
217
234
"io_engine" : "vhost-user" ,
218
235
"fio_mode" : fio_mode ,
219
236
"fio_block_size" : str (fio_block_size ),
237
+ "fio_engine" : "libaio" ,
220
238
** vm .dimensions ,
221
239
}
222
240
)
@@ -226,7 +244,7 @@ def test_block_vhost_user_performance(
226
244
227
245
logs_dir , cpu_util = run_fio (vm , fio_mode , fio_block_size )
228
246
229
- process_fio_logs ( vm , fio_mode , logs_dir , metrics )
247
+ emit_fio_metrics ( logs_dir , metrics )
230
248
231
249
for thread_name , values in cpu_util .items ():
232
250
for value in values :
0 commit comments