hardware_TrimIntegrity.py revision 3f8eadd373c9ec8a74b38b255578b81c02f0f704
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 5import os, fcntl, struct, random 6 7from autotest_lib.client.bin import test, utils 8from autotest_lib.client.common_lib import error 9 10 11class 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 23 version = 1 24 FILE_SIZE = 1024 * 1024 * 1024 25 CHUNK_SIZE = 64 * 1024 26 TRIM_RATIO = [0, 0.25, 0.5, 0.75, 1] 27 28 # Use hash value to check integrity of the random data. 29 HASH_CMD = 'sha256sum | cut -d" " -f 1' 30 # 0x1277 is ioctl BLKDISCARD command 31 IOCTL_TRIM_CMD = 0x1277 32 IOCTL_NOT_SUPPORT_ERRNO = 95 33 34 def _find_free_root_partition(self): 35 """ 36 Locate the spare root partition that we didn't boot off. 37 """ 38 39 spare_root_map = { 40 '3': '5', 41 '5': '3', 42 } 43 rootdev = utils.system_output('rootdev -s') 44 spare_root = rootdev[:-1] + spare_root_map[rootdev[-1]] 45 self._filename = spare_root 46 47 def _get_hash(self, chunk_count, chunk_size): 48 """ 49 Get hash for every chunk of data. 50 """ 51 cmd = str('for i in $(seq 0 %d); do dd if=%s of=/dev/stdout bs=%d' 52 ' count=1 skip=$i iflag=direct | %s; done' % 53 (chunk_count - 1, self._filename, chunk_size, self.HASH_CMD)) 54 return utils.run(cmd).stdout.split() 55 56 def _do_trim(self, fd, offset, size): 57 """ 58 Invoke ioctl to trim command. 59 """ 60 fcntl.ioctl(fd, self.IOCTL_TRIM_CMD, struct.pack('QQ', offset, size)) 61 62 def run_once(self, file_size=FILE_SIZE, chunk_size=CHUNK_SIZE, 63 trim_ratio=TRIM_RATIO): 64 """ 65 Executes the test and logs the output. 66 """ 67 68 self._find_free_root_partition() 69 70 # Check for trim support in ioctl. Raise TestNAError if not support. 71 try: 72 fd = os.open(self._filename, os.O_RDWR, 0666) 73 self._do_trim(fd, 0, chunk_size) 74 except IOError, err: 75 if err.errno == self.IOCTL_NOT_SUPPORT_ERRNO: 76 raise error.TestNAError("IOCTL Does not support trim.") 77 else: 78 raise 79 finally: 80 os.close(fd) 81 82 # Calculate hash value for zero'ed and one'ed data 83 cmd = str('dd if=/dev/zero bs=%d count=1 | %s' % 84 (chunk_size, self.HASH_CMD)) 85 zero_hash = utils.run(cmd).stdout.strip() 86 87 cmd = str("dd if=/dev/zero bs=%d count=1 | tr '\\0' '\\xff' | %s" % 88 (chunk_size, self.HASH_CMD)) 89 one_hash = utils.run(cmd).stdout.strip() 90 91 trim_hash = "" 92 93 # Write random data to disk 94 chunk_count = file_size / chunk_size 95 cmd = str('dd if=/dev/urandom of=%s bs=%d count=%d oflag=direct' % 96 (self._filename, chunk_size, chunk_count)) 97 utils.run(cmd) 98 99 ref_hash = self._get_hash(chunk_count, chunk_size) 100 101 # Check read speed/latency when reading real data. 102 self.job.run_test('hardware_StorageFio', 103 filesize=file_size, 104 requirements=[('4k_read_qd32', [])], 105 tag='before_trim') 106 107 # Generate random order of chunk to trim 108 trim_order = list(range(0, chunk_count)) 109 random.shuffle(trim_order) 110 trim_status = [False] * chunk_size 111 112 # Init stat variable 113 data_verify_count = 0 114 data_verify_match = 0 115 trim_verify_count = 0 116 trim_verify_zero = 0 117 trim_verify_one = 0 118 trim_verify_non_delete = 0 119 trim_deterministic = True 120 121 last_ratio = 0 122 for ratio in trim_ratio: 123 124 # Do trim 125 begin_trim_chunk = int(last_ratio * chunk_count) 126 end_trim_chunk = int(ratio * chunk_count) 127 fd = os.open(self._filename, os.O_RDWR, 0666) 128 for chunk in trim_order[begin_trim_chunk:end_trim_chunk]: 129 self._do_trim(fd, chunk * chunk_size, chunk_size) 130 trim_status[chunk] = True 131 os.close(fd) 132 last_ratio = ratio 133 134 cur_hash = self._get_hash(chunk_count, chunk_size) 135 136 trim_verify_count += int(ratio * chunk_count) 137 data_verify_count += chunk_count - int(ratio * chunk_count) 138 139 # Verify hash 140 for cur, ref, trim in zip(cur_hash, ref_hash, trim_status): 141 if trim: 142 if not trim_hash: 143 trim_hash = cur 144 elif cur != trim_hash: 145 trim_deterministic = False 146 147 if cur == zero_hash: 148 trim_verify_zero += 1 149 elif cur == one_hash: 150 trim_verify_one += 1 151 elif cur == ref: 152 trim_verify_non_delete += 1 153 else: 154 if cur == ref: 155 data_verify_match += 1 156 157 keyval = dict() 158 keyval['data_verify_count'] = data_verify_count 159 keyval['data_verify_match'] = data_verify_match 160 keyval['trim_verify_count'] = trim_verify_count 161 keyval['trim_verify_zero'] = trim_verify_zero 162 keyval['trim_verify_one'] = trim_verify_one 163 keyval['trim_verify_non_delete'] = trim_verify_non_delete 164 keyval['trim_deterministic'] = trim_deterministic 165 self.write_perf_keyval(keyval) 166 167 # Check read speed/latency when reading from trimmed data. 168 self.job.run_test('hardware_StorageFio', 169 filesize=file_size, 170 requirements=[('4k_read_qd32', [])], 171 tag='after_trim') 172 173 # Raise error when untrimmed data changed only. 174 # Don't care about trimmed data. 175 if data_verify_match < data_verify_count: 176 error.testFail("Fail to verify untrimmed data.") 177 if trim_verify_non_delete > 0 : 178 error.testFail("Trimmed data are not deleted.") 179