Skip to content

Commit ae078ee

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 emit these from our perf tests. To get non-volatile latency numbers, 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. Signed-off-by: Patrick Roy <[email protected]>
1 parent ed2045b commit ae078ee

File tree

3 files changed

+43
-22
lines changed

3 files changed

+43
-22
lines changed

.buildkite/pipeline_perf.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
# has to be the node associated with the NUMA node from which we picked CPUs.
1818
perf_test = {
1919
"virtio-block-sync": {
20-
"label": "💿 Virtio Block Performance",
20+
"label": "💿 Virtio Sync Block Performance",
2121
"tests": "integration_tests/performance/test_block_ab.py::test_block_performance -k 'not Async'",
2222
"devtool_opts": "-c 1-10 -m 0",
2323
},
2424
"virtio-block-async": {
25-
"label": "💿 Virtio Block Performance",
25+
"label": "💿 Virtio Async Block Performance",
2626
"tests": "integration_tests/performance/test_block_ab.py::test_block_performance -k Async",
2727
"devtool_opts": "-c 1-10 -m 0",
2828
},

tests/integration_tests/performance/test_block_ab.py

+38-20
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,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"])
142157
def 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:

tools/ab_test.py

+3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
{"instance": "m6a.metal", "performance_test": "test_network_tcp_throughput"},
4747
# Network throughput on m7a.metal
4848
{"instance": "m7a.metal-48xl", "performance_test": "test_network_tcp_throughput"},
49+
# block latencies if guest uses async request submission
50+
{"fio_engine": "libaio", "metric": "clat_read"},
51+
{"fio_engine": "libaio", "metric": "clat_write"},
4952
]
5053

5154

0 commit comments

Comments
 (0)