qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

136 (13891B)


      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 
     26 interval_length = 10
     27 nsec_per_sec = 1000000000
     28 op_latency = nsec_per_sec // 1000 # See qtest_latency_ns in accounting.c
     29 bad_sector = 8192
     30 bad_offset = bad_sector * 512
     31 blkdebug_file = os.path.join(iotests.test_dir, 'blkdebug.conf')
     32 
     33 class 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]
     60 event = "read_aio"
     61 errno = "5"
     62 sector = "%d"
     63 
     64 [inject-error]
     65 event = "write_aio"
     66 errno = "5"
     67 sector = "%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 
    332 class BlockDeviceStatsTestAccountInvalid(BlockDeviceStatsTestCase):
    333     account_invalid = True
    334     account_failed = False
    335 
    336 class BlockDeviceStatsTestAccountFailed(BlockDeviceStatsTestCase):
    337     account_invalid = False
    338     account_failed = True
    339 
    340 class BlockDeviceStatsTestAccountBoth(BlockDeviceStatsTestCase):
    341     account_invalid = True
    342     account_failed = True
    343 
    344 class BlockDeviceStatsTestCoroutine(BlockDeviceStatsTestCase):
    345     test_driver = "null-co"
    346 
    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"])