master
text 414 lines 15.5 KB
Raw
1 #!/usr/bin/env python3
2 # group: rw
3 #
4 # Tests for block device statistics
5 #
6 # Copyright (C) 2015 Igalia, S.L.
7 # Author: Alberto Garcia <berto@igalia.com>
8 #
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #
22
23 import iotests
24 import os
25 import json
26
27 interval_length = 10
28 nsec_per_sec = 1000000000
29 op_latency = nsec_per_sec // 1000 # See qtest_latency_ns in accounting.c
30 bad_sector = 8192
31 bad_offset = bad_sector * 512
32 blkdebug_file = os.path.join(iotests.test_dir, 'blkdebug.conf')
33
34 class BlockDeviceStatsTestCase(iotests.QMPTestCase):
35 test_driver = "null-aio"
36 total_rd_bytes = 0
37 total_rd_ops = 0
38 total_wr_bytes = 0
39 total_wr_ops = 0
40 total_wr_merged = 0
41 total_flush_ops = 0
42 failed_rd_ops = 0
43 failed_wr_ops = 0
44 invalid_rd_ops = 0
45 invalid_wr_ops = 0
46 wr_highest_offset = 0
47 account_invalid = False
48 account_failed = False
49 stats_in_device = False
50 use_blockdev = False
51
52 def blockstats(self, device):
53 result = self.vm.qmp("query-blockstats")
54 for r in result['return']:
55 if r['device'] == device or r['node-name'] == device:
56 return r['stats']
57 raise Exception("Device not found for blockstats: %s" % device)
58
59 def qemu_io(self, cmd):
60 if self.use_blockdev:
61 self.vm.hmp_qemu_io("virtio0/virtio-backend", cmd, qdev=True)
62 else:
63 self.vm.hmp_qemu_io("drive0", cmd)
64
65 def create_blkdebug_file(self):
66 file = open(blkdebug_file, 'w')
67 file.write('''
68 [inject-error]
69 event = "read_aio"
70 errno = "5"
71 sector = "%d"
72
73 [inject-error]
74 event = "write_aio"
75 errno = "5"
76 sector = "%d"
77 ''' % (bad_sector, bad_sector))
78 file.close()
79
80 def required_drivers(self):
81 return [self.test_driver]
82
83 @iotests.skip_if_unsupported(required_drivers)
84 def setUp(self):
85 self.create_blkdebug_file()
86 self.vm = iotests.VM()
87
88 drive_args = [
89 "file.image.read-zeroes=on",
90 ]
91 if self.stats_in_device:
92 interface = "none"
93 dev_args = {
94 "driver": "virtio-blk",
95 "id": "virtio0",
96 "drive": "drive0",
97 "stats-intervals": [ interval_length ],
98 "account-invalid": "on" if self.account_invalid else "off",
99 "account-failed": "on" if self.account_failed else "off",
100 }
101 self.vm.add_device(json.dumps(dev_args))
102 else:
103 assert not self.use_blockdev
104 interface = "virtio"
105 drive_args += [
106 "stats-intervals.0=%d" % interval_length,
107 "stats-account-invalid=%s" %
108 (self.account_invalid and "on" or "off"),
109 "stats-account-failed=%s" %
110 (self.account_failed and "on" or "off"),
111 ]
112
113 if self.use_blockdev:
114 blockdev_args = {
115 "node-name": "drive0",
116 "driver": "raw",
117 "file": {
118 "driver": "blkdebug",
119 "config": blkdebug_file,
120 "image": {
121 "driver": self.test_driver,
122 "read-zeroes": True,
123 },
124 },
125 }
126 self.vm.add_blockdev(json.dumps(blockdev_args))
127 else:
128 self.vm.add_drive('blkdebug:%s:%s://' %
129 (blkdebug_file, self.test_driver),
130 ','.join(drive_args),
131 interface=interface)
132
133 self.vm.launch()
134 # Set an initial value for the clock
135 self.vm.qtest("clock_step %d" % nsec_per_sec)
136
137 def tearDown(self):
138 self.vm.shutdown()
139 os.remove(blkdebug_file)
140
141 def accounted_ops(self, read = False, write = False, flush = False):
142 ops = 0
143 if write:
144 ops += self.total_wr_ops
145 if self.account_failed:
146 ops += self.failed_wr_ops
147 if self.account_invalid:
148 ops += self.invalid_wr_ops
149 if read:
150 ops += self.total_rd_ops
151 if self.account_failed:
152 ops += self.failed_rd_ops
153 if self.account_invalid:
154 ops += self.invalid_rd_ops
155 if flush:
156 ops += self.total_flush_ops
157 return ops
158
159 def accounted_latency(self, read = False, write = False, flush = False):
160 latency = 0
161 if write:
162 latency += self.total_wr_ops * op_latency
163 if self.account_failed:
164 latency += self.failed_wr_ops * op_latency
165 if read:
166 latency += self.total_rd_ops * op_latency
167 if self.account_failed:
168 latency += self.failed_rd_ops * op_latency
169 if flush:
170 latency += self.total_flush_ops * op_latency
171 return latency
172
173 def check_values(self):
174 stats = self.blockstats('drive0')
175
176 # Check that the totals match with what we have calculated
177 self.assertEqual(self.total_rd_bytes, stats['rd_bytes'])
178 self.assertEqual(self.total_wr_bytes, stats['wr_bytes'])
179 self.assertEqual(self.total_rd_ops, stats['rd_operations'])
180 self.assertEqual(self.total_wr_ops, stats['wr_operations'])
181 self.assertEqual(self.total_flush_ops, stats['flush_operations'])
182 self.assertEqual(self.wr_highest_offset, stats['wr_highest_offset'])
183 self.assertEqual(self.failed_rd_ops, stats['failed_rd_operations'])
184 self.assertEqual(self.failed_wr_ops, stats['failed_wr_operations'])
185 self.assertEqual(self.invalid_rd_ops, stats['invalid_rd_operations'])
186 self.assertEqual(self.invalid_wr_ops, stats['invalid_wr_operations'])
187 self.assertEqual(self.account_invalid, stats['account_invalid'])
188 self.assertEqual(self.account_failed, stats['account_failed'])
189 self.assertEqual(self.total_wr_merged, stats['wr_merged'])
190
191 # Check that there's exactly one interval with the length we defined
192 self.assertEqual(1, len(stats['timed_stats']))
193 timed_stats = stats['timed_stats'][0]
194 self.assertEqual(interval_length, timed_stats['interval_length'])
195
196 total_rd_latency = self.accounted_latency(read = True)
197 if (total_rd_latency != 0):
198 self.assertEqual(total_rd_latency, stats['rd_total_time_ns'])
199 self.assertEqual(op_latency, timed_stats['min_rd_latency_ns'])
200 self.assertEqual(op_latency, timed_stats['max_rd_latency_ns'])
201 self.assertEqual(op_latency, timed_stats['avg_rd_latency_ns'])
202 self.assertLess(0, timed_stats['avg_rd_queue_depth'])
203 else:
204 self.assertEqual(0, stats['rd_total_time_ns'])
205 self.assertEqual(0, timed_stats['min_rd_latency_ns'])
206 self.assertEqual(0, timed_stats['max_rd_latency_ns'])
207 self.assertEqual(0, timed_stats['avg_rd_latency_ns'])
208 self.assertEqual(0, timed_stats['avg_rd_queue_depth'])
209
210 # min read latency <= avg read latency <= max read latency
211 self.assertLessEqual(timed_stats['min_rd_latency_ns'],
212 timed_stats['avg_rd_latency_ns'])
213 self.assertLessEqual(timed_stats['avg_rd_latency_ns'],
214 timed_stats['max_rd_latency_ns'])
215
216 total_wr_latency = self.accounted_latency(write = True)
217 if (total_wr_latency != 0):
218 self.assertEqual(total_wr_latency, stats['wr_total_time_ns'])
219 self.assertEqual(op_latency, timed_stats['min_wr_latency_ns'])
220 self.assertEqual(op_latency, timed_stats['max_wr_latency_ns'])
221 self.assertEqual(op_latency, timed_stats['avg_wr_latency_ns'])
222 self.assertLess(0, timed_stats['avg_wr_queue_depth'])
223 else:
224 self.assertEqual(0, stats['wr_total_time_ns'])
225 self.assertEqual(0, timed_stats['min_wr_latency_ns'])
226 self.assertEqual(0, timed_stats['max_wr_latency_ns'])
227 self.assertEqual(0, timed_stats['avg_wr_latency_ns'])
228 self.assertEqual(0, timed_stats['avg_wr_queue_depth'])
229
230 # min write latency <= avg write latency <= max write latency
231 self.assertLessEqual(timed_stats['min_wr_latency_ns'],
232 timed_stats['avg_wr_latency_ns'])
233 self.assertLessEqual(timed_stats['avg_wr_latency_ns'],
234 timed_stats['max_wr_latency_ns'])
235
236 total_flush_latency = self.accounted_latency(flush = True)
237 if (total_flush_latency != 0):
238 self.assertEqual(total_flush_latency, stats['flush_total_time_ns'])
239 self.assertEqual(op_latency, timed_stats['min_flush_latency_ns'])
240 self.assertEqual(op_latency, timed_stats['max_flush_latency_ns'])
241 self.assertEqual(op_latency, timed_stats['avg_flush_latency_ns'])
242 else:
243 self.assertEqual(0, stats['flush_total_time_ns'])
244 self.assertEqual(0, timed_stats['min_flush_latency_ns'])
245 self.assertEqual(0, timed_stats['max_flush_latency_ns'])
246 self.assertEqual(0, timed_stats['avg_flush_latency_ns'])
247
248 # min flush latency <= avg flush latency <= max flush latency
249 self.assertLessEqual(timed_stats['min_flush_latency_ns'],
250 timed_stats['avg_flush_latency_ns'])
251 self.assertLessEqual(timed_stats['avg_flush_latency_ns'],
252 timed_stats['max_flush_latency_ns'])
253
254 # idle_time_ns must be > 0 if we have performed any operation
255 if (self.accounted_ops(read = True, write = True, flush = True) != 0):
256 self.assertLess(0, stats['idle_time_ns'])
257 else:
258 self.assertFalse('idle_time_ns' in stats)
259
260 # This test does not alter these, so they must be all 0
261 self.assertEqual(0, stats['rd_merged'])
262 self.assertEqual(0, stats['failed_flush_operations'])
263 self.assertEqual(0, stats['invalid_flush_operations'])
264
265 def do_test_stats(self, rd_size = 0, rd_ops = 0, wr_size = 0, wr_ops = 0,
266 flush_ops = 0, invalid_rd_ops = 0, invalid_wr_ops = 0,
267 failed_rd_ops = 0, failed_wr_ops = 0, wr_merged = 0):
268 # The 'ops' list will contain all the requested I/O operations
269 ops = []
270 for i in range(rd_ops):
271 ops.append("aio_read %d %d" % (i * rd_size, rd_size))
272
273 for i in range(wr_ops):
274 ops.append("aio_write %d %d" % (i * wr_size, wr_size))
275
276 for i in range(flush_ops):
277 ops.append("aio_flush")
278
279 highest_offset = wr_ops * wr_size
280
281 for i in range(invalid_rd_ops):
282 ops.append("aio_read -i 0 512")
283
284 for i in range(invalid_wr_ops):
285 ops.append("aio_write -i 0 512")
286
287 for i in range(failed_rd_ops):
288 ops.append("aio_read %d 512" % bad_offset)
289
290 for i in range(failed_wr_ops):
291 ops.append("aio_write %d 512" % bad_offset)
292
293 # We need an extra aio_flush to settle all outstanding AIO
294 # operations before we can advance the virtual clock, so that
295 # the last access happens before clock_step and idle_time_ns
296 # will be greater than 0
297 extra_flush = 0
298 if rd_ops + wr_ops + invalid_rd_ops + invalid_wr_ops + \
299 failed_rd_ops + failed_wr_ops > 0:
300 extra_flush = 1
301
302 if extra_flush > 0:
303 ops.append("aio_flush")
304
305 if failed_wr_ops > 0:
306 highest_offset = max(highest_offset, bad_offset + 512)
307
308 # Now perform all operations
309 for op in ops:
310 self.qemu_io(op)
311
312 # Update the expected totals
313 self.total_rd_bytes += rd_ops * rd_size
314 self.total_rd_ops += rd_ops
315 self.total_wr_bytes += wr_ops * wr_size
316 self.total_wr_ops += wr_ops
317 self.total_wr_merged += wr_merged
318 self.total_flush_ops += flush_ops + extra_flush
319 self.invalid_rd_ops += invalid_rd_ops
320 self.invalid_wr_ops += invalid_wr_ops
321 self.failed_rd_ops += failed_rd_ops
322 self.failed_wr_ops += failed_wr_ops
323
324 self.wr_highest_offset = max(self.wr_highest_offset, highest_offset)
325
326 # Advance the clock so idle_time_ns has a meaningful value
327 self.vm.qtest("clock_step %d" % nsec_per_sec)
328
329 # And check that the actual statistics match the expected ones
330 self.check_values()
331
332 def test_read_only(self):
333 test_values = [[512, 1],
334 [65536, 1],
335 [512, 12],
336 [65536, 12]]
337 for i in test_values:
338 self.do_test_stats(rd_size = i[0], rd_ops = i[1])
339
340 def test_write_only(self):
341 test_values = [[512, 1],
342 [65536, 1],
343 [512, 12],
344 [65536, 12]]
345 for i in test_values:
346 self.do_test_stats(wr_size = i[0], wr_ops = i[1])
347
348 def test_invalid(self):
349 self.do_test_stats(invalid_rd_ops = 7)
350 self.do_test_stats(invalid_wr_ops = 3)
351 self.do_test_stats(invalid_rd_ops = 4, invalid_wr_ops = 5)
352
353 def test_failed(self):
354 self.do_test_stats(failed_rd_ops = 8)
355 self.do_test_stats(failed_wr_ops = 6)
356 self.do_test_stats(failed_rd_ops = 5, failed_wr_ops = 12)
357
358 def test_flush(self):
359 self.do_test_stats(flush_ops = 8)
360
361 def test_all(self):
362 # rd_size, rd_ops, wr_size, wr_ops, flush_ops
363 # invalid_rd_ops, invalid_wr_ops,
364 # failed_rd_ops, failed_wr_ops
365 # wr_merged
366 test_values = [[512, 1, 512, 1, 1, 4, 7, 5, 2, 0],
367 [65536, 1, 2048, 12, 7, 7, 5, 2, 5, 0],
368 [32768, 9, 8192, 1, 4, 3, 2, 4, 6, 0],
369 [16384, 11, 3584, 16, 9, 8, 6, 7, 3, 0]]
370 for i in test_values:
371 self.do_test_stats(*i)
372
373 def test_no_op(self):
374 # All values must be sane before doing any I/O
375 self.check_values()
376
377 class BlockDeviceStatsTestDevice(BlockDeviceStatsTestCase):
378 stats_in_device = True
379
380 class BlockDeviceStatsTestBlockdev(BlockDeviceStatsTestCase):
381 stats_in_device = True
382 use_blockdev = True
383
384 class BlockDeviceStatsTestAccountInvalid(BlockDeviceStatsTestCase):
385 account_invalid = True
386 account_failed = False
387
388 class BlockDeviceStatsTestAccountFailed(BlockDeviceStatsTestCase):
389 account_invalid = False
390 account_failed = True
391
392 class BlockDeviceStatsTestAccountBoth(BlockDeviceStatsTestCase):
393 account_invalid = True
394 account_failed = True
395
396 class BlockDeviceStatsTestAccountBothDevice(BlockDeviceStatsTestCase):
397 account_invalid = True
398 account_failed = True
399 stats_in_device = True
400
401 class BlockDeviceStatsTestAccountBothBlockdev(BlockDeviceStatsTestCase):
402 account_invalid = True
403 account_failed = True
404 stats_in_device = True
405 use_blockdev = True
406
407 class BlockDeviceStatsTestCoroutine(BlockDeviceStatsTestCase):
408 test_driver = "null-co"
409
410 if __name__ == '__main__':
411 if 'null-co' not in iotests.supported_formats():
412 iotests.notrun('null-co driver support missing')
413 iotests.main(supported_fmts=["raw"],
414 require_hmp=True)