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