• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2013 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 fcntl, logging, os, re, stat, struct, time
6from autotest_lib.client.bin import fio_util, test, utils
7from autotest_lib.client.common_lib import error
8
9
10class FioTest(test.test):
11    """
12    Runs several fio jobs and reports results.
13
14    fio (flexible I/O tester) is an I/O tool for benchmark and stress/hardware
15    verification.
16
17    """
18
19    version = 7
20    DEFAULT_FILE_SIZE = 1024 * 1024 * 1024
21    VERIFY_OPTION = 'v'
22    CONTINUE_ERRORS = 'verify'
23    REMOVABLE = False
24
25    # Initialize fail counter used to determine test pass/fail.
26    _fail_count = 0
27    _error_code = 0
28
29    # 0x1277 is ioctl BLKDISCARD command
30    IOCTL_TRIM_CMD = 0x1277
31
32    def __get_disk_size(self):
33        """Return the size in bytes of the device pointed to by __filename"""
34        self.__filesize = utils.get_disk_size(self.__filename)
35
36        if not self.__filesize:
37            raise error.TestNAError(
38                'Unable to find the partition %s, please plug in a USB '
39                'flash drive and a SD card for testing external storage' %
40                self.__filename)
41
42
43    def __get_device_description(self):
44        """Get the device vendor and model name as its description"""
45
46        # Find the block device in sysfs. For example, a card read device may
47        # be in /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host4/
48        # target4:0:0/4:0:0:0/block/sdb.
49        # Then read the vendor and model name in its grand-parent directory.
50
51        # Obtain the device name by stripping the partition number.
52        # For example, sda3 => sda; mmcblk1p3 => mmcblk1, nvme0n1p3 => nvme0n1.
53        device = re.match(r'.*(sd[a-z]|mmcblk[0-9]+|nvme[0-9]+n[0-9]+)p?[0-9]*',
54                          self.__filename).group(1)
55        findsys = utils.run('find /sys/devices -name %s | grep -v virtual'
56                            % device)
57        device_path = findsys.stdout.rstrip()
58
59        removable_file = os.path.join(device_path, "removable")
60        if os.path.exists(removable_file):
61            if utils.read_one_line(removable_file).strip() == '1' :
62                self.REMOVABLE = True
63                self.CONTINUE_ERRORS="'all'"
64
65        if "nvme" in device:
66            dir_path = utils.run('dirname %s' % device_path).stdout.rstrip()
67            model_file = '%s/model' % dir_path
68            if os.path.exists(model_file):
69                self.__description = utils.read_one_line(model_file).strip()
70            else:
71                self.__description = ''
72        else:
73            vendor_file = device_path.replace('block/%s' % device, 'vendor')
74            model_file = device_path.replace('block/%s' % device, 'model')
75            if os.path.exists(vendor_file) and os.path.exists(model_file):
76                vendor = utils.read_one_line(vendor_file).strip()
77                model = utils.read_one_line(model_file).strip()
78                self.__description = vendor + ' ' + model
79            else:
80                self.__description = ''
81
82
83    def initialize(self, dev='', filesize=DEFAULT_FILE_SIZE):
84        """
85        Set up local variables.
86
87        @param dev: block device / file to test.
88                Spare partition on root device by default
89        @param filesize: size of the file. 0 means whole partition.
90                by default, 1GB.
91        """
92        if dev != '' and (os.path.isfile(dev) or not os.path.exists(dev)):
93            if filesize == 0:
94                raise error.TestError(
95                    'Nonzero file size is required to test file systems')
96            self.__filename = dev
97            self.__filesize = filesize
98            self.__description = ''
99            return
100
101        if not dev:
102            dev = utils.get_fixed_dst_drive()
103
104        if dev == utils.get_root_device():
105            if filesize == 0:
106                raise error.TestError(
107                    'Using the root device as a whole is not allowed')
108            else:
109                self.__filename = utils.get_free_root_partition()
110        elif filesize != 0:
111            # Use the first partition of the external drive if it exists
112            partition = utils.concat_partition(dev, 1)
113            if os.path.exists(partition):
114                self.__filename = partition
115            else:
116                self.__filename = dev
117        else:
118            self.__filename = dev
119        self.__get_disk_size()
120        self.__get_device_description()
121
122        # Restrict test to use a given file size, default 1GiB
123        if filesize != 0:
124            self.__filesize = min(self.__filesize, filesize)
125
126        self.__verify_only = False
127
128        logging.info('filename: %s', self.__filename)
129        logging.info('filesize: %d', self.__filesize)
130
131    def run_once(self, dev='', quicktest=False, requirements=None,
132                 integrity=False, wait=60 * 60 * 72, blkdiscard=True):
133        """
134        Runs several fio jobs and reports results.
135
136        @param dev: block device to test
137        @param quicktest: short test
138        @param requirements: list of jobs for fio to run
139        @param integrity: test to check data integrity
140        @param wait: seconds to wait between a write and subsequent verify
141        @param blkdiscard: do a blkdiscard before running fio
142
143        """
144
145        if requirements is not None:
146            pass
147        elif quicktest:
148            requirements = [
149                ('1m_write', []),
150                ('16k_read', [])
151            ]
152        elif integrity:
153            requirements = [
154                ('8k_async_randwrite', []),
155                ('8k_async_randwrite', [self.VERIFY_OPTION])
156            ]
157        elif dev in ['', utils.get_root_device()]:
158            requirements = [
159                ('surfing', []),
160                ('boot', []),
161                ('login', []),
162                ('seq_write', []),
163                ('seq_read', []),
164                ('16k_write', []),
165                ('16k_read', []),
166                ('1m_stress', []),
167            ]
168        else:
169            # TODO(waihong@): Add more test cases for external storage
170            requirements = [
171                ('seq_write', []),
172                ('seq_read', []),
173                ('16k_write', []),
174                ('16k_read', []),
175                ('4k_write', []),
176                ('4k_read', []),
177                ('1m_stress', []),
178            ]
179
180        results = {}
181
182        if os.path.exists(self.__filename) and  \
183           stat.S_ISBLK(os.stat(self.__filename).st_mode) and \
184           self.__filesize != 0 and blkdiscard:
185            try:
186                fd = os.open(self.__filename, os.O_RDWR)
187                fcntl.ioctl(fd, self.IOCTL_TRIM_CMD,
188                            struct.pack('QQ', 0, self.__filesize))
189            except IOError, err:
190                logging.info("blkdiscard failed %s", err)
191                pass
192            finally:
193                os.close(fd)
194
195        for job, options in requirements:
196
197            # Keys are labeled according to the test case name, which is
198            # unique per run, so they cannot clash
199            if self.VERIFY_OPTION in options:
200                time.sleep(wait)
201                self.__verify_only = True
202            else:
203                self.__verify_only = False
204            env_vars = ' '.join(
205                ['FILENAME=' + self.__filename,
206                 'FILESIZE=' + str(self.__filesize),
207                 'VERIFY_ONLY=' + str(int(self.__verify_only)),
208                 'CONTINUE_ERRORS=' + str(self.CONTINUE_ERRORS)
209                ])
210            client_dir = os.path.dirname(os.path.dirname(self.bindir))
211            storage_dir = os.path.join(client_dir, 'cros/storage_tests')
212            job_file = os.path.join(storage_dir, job)
213            results.update(fio_util.fio_runner(self, job_file, env_vars))
214
215        # Output keys relevant to the performance, larger filesize will run
216        # slower, and sda5 should be slightly slower than sda3 on a rotational
217        # disk
218        self.write_test_keyval({'filesize': self.__filesize,
219                                'filename': self.__filename,
220                                'device': self.__description})
221        logging.info('Device Description: %s', self.__description)
222        self.write_perf_keyval(results)
223        for k, v in results.iteritems():
224            if k.endswith('_error'):
225                self._error_code = int(v)
226                if self._error_code != 0 and self._fail_count == 0:
227                    self._fail_count = 1
228            elif k.endswith('_total_err'):
229                self._fail_count = int(v)
230        if self._fail_count > 0:
231            if self.REMOVABLE and not self.__verify_only:
232                raise error.TestWarn('%s failed verifications, '
233                                     'first error code is %s' %
234                                     (str(self._fail_count),
235                                      str(self._error_code)))
236            raise error.TestFail('%s failures, '
237                                 'first error code is %s' %
238                                 (str(self._fail_count), str(self._error_code)))
239