Skip to content

Commit 391331a

Browse files
committed
test(perf): add block latency test
fio emits latency metrics regarding how much time was spent inside the guest operating system (submission latency, slat) or how much time was spent in the device (clat). For firecracker, the latter could be relevant, so add a test that specifically emits these. We have to add a separate test, as we need to use a synchronous fio worker to get non-volatile metrics. However, for throughput tests the use of the async engine in the guest is required to get maximum throughput. We can reduce the permutations slightly by using randrw instead of separate randread and randwrite (with the idea being that if all requests are synchrnous and only measure latency, then ddoing alternating reads and writes is okay, while for the throughput tests its not wanted). Signed-off-by: Patrick Roy <[email protected]>
1 parent 5898e0e commit 391331a

File tree

2 files changed

+84
-21
lines changed

2 files changed

+84
-21
lines changed

.buildkite/pipeline_perf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
perf_test = {
1919
"virtio-block": {
2020
"label": "💿 Virtio Block Performance",
21-
"test_path": "integration_tests/performance/test_block_ab.py::test_block_performance",
21+
"test_path": "integration_tests/performance/test_block_ab.py::test_block_performance integration_tests/performance/test_block_ab.py::test_block_latency",
2222
"devtool_opts": "-c 1-10 -m 0",
2323
},
2424
"vhost-user-block": {

tests/integration_tests/performance/test_block_ab.py

Lines changed: 83 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""Performance benchmark for block device emulation."""
44

55
import concurrent
6+
import glob
67
import os
78
import shutil
89
from 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,40 +103,55 @@ 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_throughput_metrics(logs_dir, metrics):
136+
"""Parses the fio logs in `{logs_dir}/*_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+
144+
def emit_fio_latency_metrics(logs_dir, metrics):
145+
"""Parses the fio logs in `{logs_dir}/*_clat.*.log and emits their contents as CloudWatch metrics"""
146+
for lat_read, lat_write in process_fio_log_files(f"{logs_dir}/*_clat.*.log"):
147+
# latency values in fio logs are in nanosecons, but cloudwatch only supports
148+
# microseconds as the more granular unit, so need to divide by 1000.
149+
for value in lat_read:
150+
metrics.put_metric("clat_read", value / 1000, "Microseconds")
151+
for value in lat_write:
152+
metrics.put_metric("clat_write", value / 1000, "Microseconds")
135153

136154

137-
@pytest.mark.timeout(120)
138155
@pytest.mark.nonci
139156
@pytest.mark.parametrize("vcpus", [1, 2], ids=["1vcpu", "2vcpu"])
140157
@pytest.mark.parametrize("fio_mode", ["randread", "randwrite"])
@@ -175,15 +192,61 @@ def test_block_performance(
175192

176193
vm.pin_threads(0)
177194

178-
logs_dir, cpu_util = run_fio(vm, fio_mode, fio_block_size)
195+
# latency metrics with async engine are very volatile, so use fio's default sync engine.
196+
logs_dir, cpu_util = run_fio(vm, fio_mode, fio_block_size, fio_engine="psync")
179197

180-
process_fio_logs(vm, fio_mode, logs_dir, metrics)
198+
emit_fio_throughput_metrics(logs_dir, metrics)
181199

182200
for thread_name, values in cpu_util.items():
183201
for value in values:
184202
metrics.put_metric(f"cpu_utilization_{thread_name}", value, "Percent")
185203

186204

205+
@pytest.mark.nonci
206+
@pytest.mark.parametrize("vcpus", [1, 2], ids=["1vcpu", "2vcpu"])
207+
@pytest.mark.parametrize("fio_mode", ["randrw"])
208+
@pytest.mark.parametrize("fio_block_size", [4096], ids=["bs4096"])
209+
def test_block_latency(
210+
microvm_factory,
211+
guest_kernel_acpi,
212+
rootfs,
213+
vcpus,
214+
fio_mode,
215+
fio_block_size,
216+
io_engine,
217+
metrics,
218+
):
219+
"""
220+
Execute block device emulation benchmarking scenarios.
221+
"""
222+
vm = microvm_factory.build(guest_kernel_acpi, rootfs, monitor_memory=False)
223+
vm.spawn(log_level="Info", emit_metrics=True)
224+
vm.basic_config(vcpu_count=vcpus, mem_size_mib=GUEST_MEM_MIB)
225+
vm.add_net_iface()
226+
# Add a secondary block device for benchmark tests.
227+
fs = drive_tools.FilesystemFile(
228+
os.path.join(vm.fsfiles, "scratch"), BLOCK_DEVICE_SIZE_MB
229+
)
230+
vm.add_drive("scratch", fs.path, io_engine=io_engine)
231+
vm.start()
232+
233+
metrics.set_dimensions(
234+
{
235+
"performance_test": "test_block_latency",
236+
"io_engine": io_engine,
237+
"fio_mode": fio_mode,
238+
"fio_block_size": str(fio_block_size),
239+
**vm.dimensions,
240+
}
241+
)
242+
243+
vm.pin_threads(0)
244+
245+
logs_dir, _ = run_fio(vm, fio_mode, fio_block_size)
246+
247+
emit_fio_latency_metrics(logs_dir, metrics)
248+
249+
187250
@pytest.mark.nonci
188251
@pytest.mark.parametrize("vcpus", [1, 2], ids=["1vcpu", "2vcpu"])
189252
@pytest.mark.parametrize("fio_mode", ["randread"])
@@ -226,7 +289,7 @@ def test_block_vhost_user_performance(
226289

227290
logs_dir, cpu_util = run_fio(vm, fio_mode, fio_block_size)
228291

229-
process_fio_logs(vm, fio_mode, logs_dir, metrics)
292+
emit_fio_throughput_metrics(logs_dir, metrics)
230293

231294
for thread_name, values in cpu_util.items():
232295
for value in values:

0 commit comments

Comments
 (0)