Home | History | Annotate | Download | only in hardware_TrimIntegrity
      1 # Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import os, fcntl, logging, struct, random
      6 
      7 from autotest_lib.client.bin import test, utils
      8 from autotest_lib.client.common_lib import error
      9 
     10 
     11 class hardware_TrimIntegrity(test.test):
     12     """
     13     Performs data integrity trim test on an unmounted partition.
     14 
     15     This test will write 1 GB of data and verify that trimmed data are gone and
     16     untrimmed data are unaffected. The verification will be run in 5 passes with
     17     0%, 25%, 50%, 75%, and 100% of data trimmed.
     18 
     19     Also, perform 4K random read QD32 before and after trim. We should see some
     20     speed / latency difference if the device firmware trim data properly.
     21 
     22     Condition for test result:
     23     - Trim command is not supported
     24       -> Target disk is a harddisk           : TestNA
     25       -> Target disk is SCSI disk w/o trim   : TestNA
     26       -> Otherwise                           : TestFail
     27     - Can not verify integrity of untrimmed data
     28       -> All case                            : TestFail
     29     - Trim data is not Zero
     30       -> SSD with RZAT                       : TestFail
     31       -> Otherwise                           : TestNA
     32     """
     33 
     34     version = 1
     35     FILE_SIZE = 1024 * 1024 * 1024
     36     CHUNK_SIZE = 192 * 1024
     37     TRIM_RATIO = [0, 0.25, 0.5, 0.75, 1]
     38 
     39     hdparm_trim = 'Data Set Management TRIM supported'
     40     hdparm_rzat = 'Deterministic read ZEROs after TRIM'
     41 
     42     # Use hash value to check integrity of the random data.
     43     HASH_CMD = 'sha256sum | cut -d" " -f 1'
     44     # 0x1277 is ioctl BLKDISCARD command
     45     IOCTL_TRIM_CMD = 0x1277
     46     IOCTL_NOT_SUPPORT_ERRNO = 95
     47 
     48     def _get_hash(self, chunk_count, chunk_size):
     49         """
     50         Get hash for every chunk of data.
     51         """
     52         cmd = str('for i in $(seq 0 %d); do dd if=%s of=/dev/stdout bs=%d'
     53                   ' count=1 skip=$i iflag=direct | %s; done' %
     54                   (chunk_count - 1, self._filename, chunk_size, self.HASH_CMD))
     55         return utils.run(cmd).stdout.split()
     56 
     57     def _do_trim(self, fd, offset, size):
     58         """
     59         Invoke ioctl to trim command.
     60         """
     61         fcntl.ioctl(fd, self.IOCTL_TRIM_CMD, struct.pack('QQ', offset, size))
     62 
     63     def _verify_trim_support(self, size):
     64         """
     65         Check for trim support in ioctl. Raise TestNAError if not support.
     66 
     67         @param size: size to try the trim command
     68         """
     69         try:
     70             fd = os.open(self._filename, os.O_RDWR, 0666)
     71             self._do_trim(fd, 0, size)
     72         except IOError, err:
     73             if err.errno == self.IOCTL_NOT_SUPPORT_ERRNO:
     74                 reason = 'IOCTL Does not support trim.'
     75                 msg = utils.get_storage_error_msg(self._diskname, reason)
     76 
     77                 if utils.is_disk_scsi(self._diskname):
     78                     if utils.is_disk_harddisk(self._diskname):
     79                         msg += ' Disk is a hard disk.'
     80                         raise error.TestNAError(msg)
     81                     if utils.verify_hdparm_feature(self._diskname,
     82                                                    self.hdparm_trim):
     83                         msg += ' Disk claims trim supported.'
     84                     else:
     85                         msg += ' Disk does not claim trim supported.'
     86                         raise error.TestNAError(msg)
     87                 # SSD with trim support / mmc / sd card
     88                 raise error.TestFail(msg)
     89             else:
     90                 raise
     91         finally:
     92             os.close(fd)
     93 
     94     def initialize(self):
     95         self.job.use_sequence_number = True
     96 
     97     def run_once(self, filename=None, file_size=FILE_SIZE,
     98                  chunk_size=CHUNK_SIZE, trim_ratio=TRIM_RATIO):
     99         """
    100         Executes the test and logs the output.
    101         @param file_name:  file/disk name to test
    102                            default: spare partition of internal disk
    103         @param file_size:  size of data to test. default: 1GB
    104         @param chunk_size: size of chunk to calculate hash/trim. default: 64KB
    105         @param trim_ratio: list of ratio of file size to trim data
    106                            default: [0, 0.25, 0.5, 0.75, 1]
    107         """
    108 
    109         if not filename:
    110             self._diskname = utils.get_fixed_dst_drive()
    111             if self._diskname == utils.get_root_device():
    112                 self._filename = utils.get_free_root_partition()
    113             else:
    114                 self._filename = self._diskname
    115         else:
    116             self._filename = filename
    117             self._diskname = utils.get_disk_from_filename(filename)
    118 
    119         if file_size == 0:
    120             fulldisk = True
    121             file_size = utils.get_disk_size(self._filename)
    122             if file_size == 0:
    123                 cmd = ('%s seem to have 0 storage block. Is the media present?'
    124                         % filename)
    125                 raise error.TestError(cmd)
    126         else:
    127             fulldisk = False
    128 
    129         # Make file size multiple of 4 * chunk size
    130         file_size -= file_size % (4 * chunk_size)
    131 
    132         if fulldisk:
    133             fio_file_size = 0
    134         else:
    135             fio_file_size = file_size
    136 
    137         logging.info('filename: %s, filesize: %d', self._filename, file_size)
    138 
    139         self._verify_trim_support(chunk_size)
    140 
    141         # Calculate hash value for zero'ed and one'ed data
    142         cmd = str('dd if=/dev/zero bs=%d count=1 | %s' %
    143                   (chunk_size, self.HASH_CMD))
    144         zero_hash = utils.run(cmd).stdout.strip()
    145 
    146         cmd = str("dd if=/dev/zero bs=%d count=1 | tr '\\0' '\\xff' | %s" %
    147                   (chunk_size, self.HASH_CMD))
    148         one_hash = utils.run(cmd).stdout.strip()
    149 
    150         trim_hash = ""
    151 
    152         # Write random data to disk
    153         chunk_count = file_size / chunk_size
    154         cmd = str('dd if=/dev/urandom of=%s bs=%d count=%d oflag=direct' %
    155                   (self._filename, chunk_size, chunk_count))
    156         utils.run(cmd)
    157 
    158         ref_hash = self._get_hash(chunk_count, chunk_size)
    159 
    160         # Check read speed/latency when reading real data.
    161         self.job.run_test('hardware_StorageFio',
    162                           disable_sysinfo=True,
    163                           filesize=fio_file_size,
    164                           requirements=[('4k_read_qd32', [])],
    165                           tag='before_trim')
    166 
    167         # Generate random order of chunk to trim
    168         trim_order = list(range(0, chunk_count))
    169         random.shuffle(trim_order)
    170         trim_status = [False] * chunk_count
    171 
    172         # Init stat variable
    173         data_verify_count = 0
    174         data_verify_match = 0
    175         trim_verify_count = 0
    176         trim_verify_zero = 0
    177         trim_verify_one = 0
    178         trim_verify_non_delete = 0
    179         trim_deterministic = True
    180 
    181         last_ratio = 0
    182         for ratio in trim_ratio:
    183 
    184             # Do trim
    185             begin_trim_chunk = int(last_ratio * chunk_count)
    186             end_trim_chunk = int(ratio * chunk_count)
    187             fd = os.open(self._filename, os.O_RDWR, 0666)
    188             for chunk in trim_order[begin_trim_chunk:end_trim_chunk]:
    189                 self._do_trim(fd, chunk * chunk_size, chunk_size)
    190                 trim_status[chunk] = True
    191             os.close(fd)
    192             last_ratio = ratio
    193 
    194             cur_hash = self._get_hash(chunk_count, chunk_size)
    195 
    196             trim_verify_count += int(ratio * chunk_count)
    197             data_verify_count += chunk_count - int(ratio * chunk_count)
    198 
    199             # Verify hash
    200             for cur, ref, trim in zip(cur_hash, ref_hash, trim_status):
    201                 if trim:
    202                     if not trim_hash:
    203                         trim_hash = cur
    204                     elif cur != trim_hash:
    205                         trim_deterministic = False
    206 
    207                     if cur == zero_hash:
    208                         trim_verify_zero += 1
    209                     elif cur == one_hash:
    210                         trim_verify_one += 1
    211                     elif cur == ref:
    212                         trim_verify_non_delete += 1
    213                 else:
    214                     if cur == ref:
    215                         data_verify_match += 1
    216 
    217         keyval = dict()
    218         keyval['data_verify_count'] = data_verify_count
    219         keyval['data_verify_match'] = data_verify_match
    220         keyval['trim_verify_count'] = trim_verify_count
    221         keyval['trim_verify_zero'] = trim_verify_zero
    222         keyval['trim_verify_one'] = trim_verify_one
    223         keyval['trim_verify_non_delete'] = trim_verify_non_delete
    224         keyval['trim_deterministic'] = trim_deterministic
    225         self.write_perf_keyval(keyval)
    226 
    227         # Check read speed/latency when reading from trimmed data.
    228         self.job.run_test('hardware_StorageFio',
    229                           disable_sysinfo=True,
    230                           filesize=fio_file_size,
    231                           requirements=[('4k_read_qd32', [])],
    232                           tag='after_trim')
    233 
    234         if data_verify_match < data_verify_count:
    235             reason = 'Fail to verify untrimmed data.'
    236             msg = utils.get_storage_error_msg(self._diskname, reason)
    237             raise error.TestFail(msg)
    238 
    239         if trim_verify_zero <  trim_verify_count:
    240             reason = 'Trimmed data are not zeroed.'
    241             msg = utils.get_storage_error_msg(self._diskname, reason)
    242             if utils.is_disk_scsi(self._diskname):
    243                 if utils.verify_hdparm_feature(self._diskname,
    244                                                self.hdparm_rzat):
    245                     msg += ' Disk claim deterministic read zero after trim.'
    246                     raise error.TestFail(msg)
    247             raise error.TestNAError(msg)
    248