cachepc-qemu

Fork of AMDESE/qemu with changes for cachepc side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-qemu
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

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
     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"])