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