4 # Tests for block device statistics
6 # Copyright (C) 2015 Igalia, S.L.
7 # Author: Alberto Garcia <berto@igalia.com>
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.
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.
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/>.
27 nsec_per_sec = 1000000000
28 op_latency = nsec_per_sec // 1000 # See qtest_latency_ns in accounting.c
30 bad_offset = bad_sector * 512
31 blkdebug_file = os.path.join(iotests.test_dir, 'blkdebug.conf')
33 class BlockDeviceStatsTestCase(iotests.QMPTestCase):
34 test_driver = "null-aio"
46 account_invalid = False
47 account_failed = False
49 def blockstats(self, device):
50 result = self.vm.qmp("query-blockstats")
51 for r in result['return']:
52 if r['device'] == device:
54 raise Exception("Device not found for blockstats: %s" % device)
56 def create_blkdebug_file(self):
57 file = open(blkdebug_file, 'w')
68 ''' % (bad_sector, bad_sector))
71 def required_drivers(self):
72 return [self.test_driver]
74 @iotests.skip_if_unsupported(required_drivers)
77 drive_args.append("stats-intervals.0=%d" % interval_length)
78 drive_args.append("stats-account-invalid=%s" %
79 (self.account_invalid and "on" or "off"))
80 drive_args.append("stats-account-failed=%s" %
81 (self.account_failed and "on" or "off"))
82 drive_args.append("file.image.read-zeroes=on")
83 self.create_blkdebug_file()
84 self.vm = iotests.VM().add_drive('blkdebug:%s:%s://' %
85 (blkdebug_file, self.test_driver),
88 # Set an initial value for the clock
89 self.vm.qtest("clock_step %d" % nsec_per_sec)
93 os.remove(blkdebug_file)
95 def accounted_ops(self, read = False, write = False, flush = False):
98 ops += self.total_wr_ops
99 if self.account_failed:
100 ops += self.failed_wr_ops
101 if self.account_invalid:
102 ops += self.invalid_wr_ops
104 ops += self.total_rd_ops
105 if self.account_failed:
106 ops += self.failed_rd_ops
107 if self.account_invalid:
108 ops += self.invalid_rd_ops
110 ops += self.total_flush_ops
113 def accounted_latency(self, read = False, write = False, flush = False):
116 latency += self.total_wr_ops * op_latency
117 if self.account_failed:
118 latency += self.failed_wr_ops * op_latency
120 latency += self.total_rd_ops * op_latency
121 if self.account_failed:
122 latency += self.failed_rd_ops * op_latency
124 latency += self.total_flush_ops * op_latency
127 def check_values(self):
128 stats = self.blockstats('drive0')
130 # Check that the totals match with what we have calculated
131 self.assertEqual(self.total_rd_bytes, stats['rd_bytes'])
132 self.assertEqual(self.total_wr_bytes, stats['wr_bytes'])
133 self.assertEqual(self.total_rd_ops, stats['rd_operations'])
134 self.assertEqual(self.total_wr_ops, stats['wr_operations'])
135 self.assertEqual(self.total_flush_ops, stats['flush_operations'])
136 self.assertEqual(self.wr_highest_offset, stats['wr_highest_offset'])
137 self.assertEqual(self.failed_rd_ops, stats['failed_rd_operations'])
138 self.assertEqual(self.failed_wr_ops, stats['failed_wr_operations'])
139 self.assertEqual(self.invalid_rd_ops, stats['invalid_rd_operations'])
140 self.assertEqual(self.invalid_wr_ops, stats['invalid_wr_operations'])
141 self.assertEqual(self.account_invalid, stats['account_invalid'])
142 self.assertEqual(self.account_failed, stats['account_failed'])
143 self.assertEqual(self.total_wr_merged, stats['wr_merged'])
145 # Check that there's exactly one interval with the length we defined
146 self.assertEqual(1, len(stats['timed_stats']))
147 timed_stats = stats['timed_stats'][0]
148 self.assertEqual(interval_length, timed_stats['interval_length'])
150 total_rd_latency = self.accounted_latency(read = True)
151 if (total_rd_latency != 0):
152 self.assertEqual(total_rd_latency, stats['rd_total_time_ns'])
153 self.assertEqual(op_latency, timed_stats['min_rd_latency_ns'])
154 self.assertEqual(op_latency, timed_stats['max_rd_latency_ns'])
155 self.assertEqual(op_latency, timed_stats['avg_rd_latency_ns'])
156 self.assertLess(0, timed_stats['avg_rd_queue_depth'])
158 self.assertEqual(0, stats['rd_total_time_ns'])
159 self.assertEqual(0, timed_stats['min_rd_latency_ns'])
160 self.assertEqual(0, timed_stats['max_rd_latency_ns'])
161 self.assertEqual(0, timed_stats['avg_rd_latency_ns'])
162 self.assertEqual(0, timed_stats['avg_rd_queue_depth'])
164 # min read latency <= avg read latency <= max read latency
165 self.assertLessEqual(timed_stats['min_rd_latency_ns'],
166 timed_stats['avg_rd_latency_ns'])
167 self.assertLessEqual(timed_stats['avg_rd_latency_ns'],
168 timed_stats['max_rd_latency_ns'])
170 total_wr_latency = self.accounted_latency(write = True)
171 if (total_wr_latency != 0):
172 self.assertEqual(total_wr_latency, stats['wr_total_time_ns'])
173 self.assertEqual(op_latency, timed_stats['min_wr_latency_ns'])
174 self.assertEqual(op_latency, timed_stats['max_wr_latency_ns'])
175 self.assertEqual(op_latency, timed_stats['avg_wr_latency_ns'])
176 self.assertLess(0, timed_stats['avg_wr_queue_depth'])
178 self.assertEqual(0, stats['wr_total_time_ns'])
179 self.assertEqual(0, timed_stats['min_wr_latency_ns'])
180 self.assertEqual(0, timed_stats['max_wr_latency_ns'])
181 self.assertEqual(0, timed_stats['avg_wr_latency_ns'])
182 self.assertEqual(0, timed_stats['avg_wr_queue_depth'])
184 # min write latency <= avg write latency <= max write latency
185 self.assertLessEqual(timed_stats['min_wr_latency_ns'],
186 timed_stats['avg_wr_latency_ns'])
187 self.assertLessEqual(timed_stats['avg_wr_latency_ns'],
188 timed_stats['max_wr_latency_ns'])
190 total_flush_latency = self.accounted_latency(flush = True)
191 if (total_flush_latency != 0):
192 self.assertEqual(total_flush_latency, stats['flush_total_time_ns'])
193 self.assertEqual(op_latency, timed_stats['min_flush_latency_ns'])
194 self.assertEqual(op_latency, timed_stats['max_flush_latency_ns'])
195 self.assertEqual(op_latency, timed_stats['avg_flush_latency_ns'])
197 self.assertEqual(0, stats['flush_total_time_ns'])
198 self.assertEqual(0, timed_stats['min_flush_latency_ns'])
199 self.assertEqual(0, timed_stats['max_flush_latency_ns'])
200 self.assertEqual(0, timed_stats['avg_flush_latency_ns'])
202 # min flush latency <= avg flush latency <= max flush latency
203 self.assertLessEqual(timed_stats['min_flush_latency_ns'],
204 timed_stats['avg_flush_latency_ns'])
205 self.assertLessEqual(timed_stats['avg_flush_latency_ns'],
206 timed_stats['max_flush_latency_ns'])
208 # idle_time_ns must be > 0 if we have performed any operation
209 if (self.accounted_ops(read = True, write = True, flush = True) != 0):
210 self.assertLess(0, stats['idle_time_ns'])
212 self.assertFalse('idle_time_ns' in stats)
214 # This test does not alter these, so they must be all 0
215 self.assertEqual(0, stats['rd_merged'])
216 self.assertEqual(0, stats['failed_flush_operations'])
217 self.assertEqual(0, stats['invalid_flush_operations'])
219 def do_test_stats(self, rd_size = 0, rd_ops = 0, wr_size = 0, wr_ops = 0,
220 flush_ops = 0, invalid_rd_ops = 0, invalid_wr_ops = 0,
221 failed_rd_ops = 0, failed_wr_ops = 0, wr_merged = 0):
222 # The 'ops' list will contain all the requested I/O operations
224 for i in range(rd_ops):
225 ops.append("aio_read %d %d" % (i * rd_size, rd_size))
227 for i in range(wr_ops):
228 ops.append("aio_write %d %d" % (i * wr_size, wr_size))
230 for i in range(flush_ops):
231 ops.append("aio_flush")
233 highest_offset = wr_ops * wr_size
235 for i in range(invalid_rd_ops):
236 ops.append("aio_read -i 0 512")
238 for i in range(invalid_wr_ops):
239 ops.append("aio_write -i 0 512")
241 for i in range(failed_rd_ops):
242 ops.append("aio_read %d 512" % bad_offset)
244 for i in range(failed_wr_ops):
245 ops.append("aio_write %d 512" % bad_offset)
247 # We need an extra aio_flush to settle all outstanding AIO
248 # operations before we can advance the virtual clock, so that
249 # the last access happens before clock_step and idle_time_ns
250 # will be greater than 0
252 if rd_ops + wr_ops + invalid_rd_ops + invalid_wr_ops + \
253 failed_rd_ops + failed_wr_ops > 0:
257 ops.append("aio_flush")
259 if failed_wr_ops > 0:
260 highest_offset = max(highest_offset, bad_offset + 512)
262 # Now perform all operations
264 self.vm.hmp_qemu_io("drive0", op)
266 # Update the expected totals
267 self.total_rd_bytes += rd_ops * rd_size
268 self.total_rd_ops += rd_ops
269 self.total_wr_bytes += wr_ops * wr_size
270 self.total_wr_ops += wr_ops
271 self.total_wr_merged += wr_merged
272 self.total_flush_ops += flush_ops + extra_flush
273 self.invalid_rd_ops += invalid_rd_ops
274 self.invalid_wr_ops += invalid_wr_ops
275 self.failed_rd_ops += failed_rd_ops
276 self.failed_wr_ops += failed_wr_ops
278 self.wr_highest_offset = max(self.wr_highest_offset, highest_offset)
280 # Advance the clock so idle_time_ns has a meaningful value
281 self.vm.qtest("clock_step %d" % nsec_per_sec)
283 # And check that the actual statistics match the expected ones
286 def test_read_only(self):
287 test_values = [[512, 1],
291 for i in test_values:
292 self.do_test_stats(rd_size = i[0], rd_ops = i[1])
294 def test_write_only(self):
295 test_values = [[512, 1],
299 for i in test_values:
300 self.do_test_stats(wr_size = i[0], wr_ops = i[1])
302 def test_invalid(self):
303 self.do_test_stats(invalid_rd_ops = 7)
304 self.do_test_stats(invalid_wr_ops = 3)
305 self.do_test_stats(invalid_rd_ops = 4, invalid_wr_ops = 5)
307 def test_failed(self):
308 self.do_test_stats(failed_rd_ops = 8)
309 self.do_test_stats(failed_wr_ops = 6)
310 self.do_test_stats(failed_rd_ops = 5, failed_wr_ops = 12)
312 def test_flush(self):
313 self.do_test_stats(flush_ops = 8)
316 # rd_size, rd_ops, wr_size, wr_ops, flush_ops
317 # invalid_rd_ops, invalid_wr_ops,
318 # failed_rd_ops, failed_wr_ops
320 test_values = [[512, 1, 512, 1, 1, 4, 7, 5, 2, 0],
321 [65536, 1, 2048, 12, 7, 7, 5, 2, 5, 0],
322 [32768, 9, 8192, 1, 4, 3, 2, 4, 6, 0],
323 [16384, 11, 3584, 16, 9, 8, 6, 7, 3, 0]]
324 for i in test_values:
325 self.do_test_stats(*i)
327 def test_no_op(self):
328 # All values must be sane before doing any I/O
332 class BlockDeviceStatsTestAccountInvalid(BlockDeviceStatsTestCase):
333 account_invalid = True
334 account_failed = False
336 class BlockDeviceStatsTestAccountFailed(BlockDeviceStatsTestCase):
337 account_invalid = False
338 account_failed = True
340 class BlockDeviceStatsTestAccountBoth(BlockDeviceStatsTestCase):
341 account_invalid = True
342 account_failed = True
344 class BlockDeviceStatsTestCoroutine(BlockDeviceStatsTestCase):
345 test_driver = "null-co"
347 if __name__ == '__main__':
348 if 'null-co' not in iotests.supported_formats():
349 iotests.notrun('null-co driver support missing')
350 iotests.main(supported_fmts=["raw"])