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 23import iotests 24import os 25 26interval_length = 10 27nsec_per_sec = 1000000000 28op_latency = nsec_per_sec // 1000 # See qtest_latency_ns in accounting.c 29bad_sector = 8192 30bad_offset = bad_sector * 512 31blkdebug_file = os.path.join(iotests.test_dir, 'blkdebug.conf') 32 33class BlockDeviceStatsTestCase(iotests.QMPTestCase): 34 test_driver = "null-aio" 35 total_rd_bytes = 0 36 total_rd_ops = 0 37 total_wr_bytes = 0 38 total_wr_ops = 0 39 total_wr_merged = 0 40 total_flush_ops = 0 41 failed_rd_ops = 0 42 failed_wr_ops = 0 43 invalid_rd_ops = 0 44 invalid_wr_ops = 0 45 wr_highest_offset = 0 46 account_invalid = False 47 account_failed = False 48 49 def blockstats(self, device): 50 result = self.vm.qmp("query-blockstats") 51 for r in result['return']: 52 if r['device'] == device: 53 return r['stats'] 54 raise Exception("Device not found for blockstats: %s" % device) 55 56 def create_blkdebug_file(self): 57 file = open(blkdebug_file, 'w') 58 file.write(''' 59[inject-error] 60event = "read_aio" 61errno = "5" 62sector = "%d" 63 64[inject-error] 65event = "write_aio" 66errno = "5" 67sector = "%d" 68''' % (bad_sector, bad_sector)) 69 file.close() 70 71 def required_drivers(self): 72 return [self.test_driver] 73 74 @iotests.skip_if_unsupported(required_drivers) 75 def setUp(self): 76 drive_args = [] 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), 86 ','.join(drive_args)) 87 self.vm.launch() 88 # Set an initial value for the clock 89 self.vm.qtest("clock_step %d" % nsec_per_sec) 90 91 def tearDown(self): 92 self.vm.shutdown() 93 os.remove(blkdebug_file) 94 95 def accounted_ops(self, read = False, write = False, flush = False): 96 ops = 0 97 if write: 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 103 if read: 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 109 if flush: 110 ops += self.total_flush_ops 111 return ops 112 113 def accounted_latency(self, read = False, write = False, flush = False): 114 latency = 0 115 if write: 116 latency += self.total_wr_ops * op_latency 117 if self.account_failed: 118 latency += self.failed_wr_ops * op_latency 119 if read: 120 latency += self.total_rd_ops * op_latency 121 if self.account_failed: 122 latency += self.failed_rd_ops * op_latency 123 if flush: 124 latency += self.total_flush_ops * op_latency 125 return latency 126 127 def check_values(self): 128 stats = self.blockstats('drive0') 129 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']) 144 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']) 149 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']) 157 else: 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']) 163 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']) 169 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']) 177 else: 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']) 183 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']) 189 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']) 196 else: 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']) 201 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']) 207 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']) 211 else: 212 self.assertFalse('idle_time_ns' in stats) 213 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']) 218 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 223 ops = [] 224 for i in range(rd_ops): 225 ops.append("aio_read %d %d" % (i * rd_size, rd_size)) 226 227 for i in range(wr_ops): 228 ops.append("aio_write %d %d" % (i * wr_size, wr_size)) 229 230 for i in range(flush_ops): 231 ops.append("aio_flush") 232 233 highest_offset = wr_ops * wr_size 234 235 for i in range(invalid_rd_ops): 236 ops.append("aio_read -i 0 512") 237 238 for i in range(invalid_wr_ops): 239 ops.append("aio_write -i 0 512") 240 241 for i in range(failed_rd_ops): 242 ops.append("aio_read %d 512" % bad_offset) 243 244 for i in range(failed_wr_ops): 245 ops.append("aio_write %d 512" % bad_offset) 246 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 251 extra_flush = 0 252 if rd_ops + wr_ops + invalid_rd_ops + invalid_wr_ops + \ 253 failed_rd_ops + failed_wr_ops > 0: 254 extra_flush = 1 255 256 if extra_flush > 0: 257 ops.append("aio_flush") 258 259 if failed_wr_ops > 0: 260 highest_offset = max(highest_offset, bad_offset + 512) 261 262 # Now perform all operations 263 for op in ops: 264 self.vm.hmp_qemu_io("drive0", op) 265 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 277 278 self.wr_highest_offset = max(self.wr_highest_offset, highest_offset) 279 280 # Advance the clock so idle_time_ns has a meaningful value 281 self.vm.qtest("clock_step %d" % nsec_per_sec) 282 283 # And check that the actual statistics match the expected ones 284 self.check_values() 285 286 def test_read_only(self): 287 test_values = [[512, 1], 288 [65536, 1], 289 [512, 12], 290 [65536, 12]] 291 for i in test_values: 292 self.do_test_stats(rd_size = i[0], rd_ops = i[1]) 293 294 def test_write_only(self): 295 test_values = [[512, 1], 296 [65536, 1], 297 [512, 12], 298 [65536, 12]] 299 for i in test_values: 300 self.do_test_stats(wr_size = i[0], wr_ops = i[1]) 301 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) 306 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) 311 312 def test_flush(self): 313 self.do_test_stats(flush_ops = 8) 314 315 def test_all(self): 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 319 # wr_merged 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) 326 327 def test_no_op(self): 328 # All values must be sane before doing any I/O 329 self.check_values() 330 331 332class BlockDeviceStatsTestAccountInvalid(BlockDeviceStatsTestCase): 333 account_invalid = True 334 account_failed = False 335 336class BlockDeviceStatsTestAccountFailed(BlockDeviceStatsTestCase): 337 account_invalid = False 338 account_failed = True 339 340class BlockDeviceStatsTestAccountBoth(BlockDeviceStatsTestCase): 341 account_invalid = True 342 account_failed = True 343 344class BlockDeviceStatsTestCoroutine(BlockDeviceStatsTestCase): 345 test_driver = "null-co" 346 347if __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"]) 351