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