# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import fcntl, logging, os, re, stat, struct, time from autotest_lib.client.bin import fio_util, test, utils from autotest_lib.client.common_lib import error class FioTest(test.test): """ Runs several fio jobs and reports results. fio (flexible I/O tester) is an I/O tool for benchmark and stress/hardware verification. """ version = 7 DEFAULT_FILE_SIZE = 1024 * 1024 * 1024 VERIFY_OPTION = 'v' CONTINUE_ERRORS = 'verify' REMOVABLE = False # Initialize fail counter used to determine test pass/fail. _fail_count = 0 _error_code = 0 # 0x1277 is ioctl BLKDISCARD command IOCTL_TRIM_CMD = 0x1277 def __get_disk_size(self): """Return the size in bytes of the device pointed to by __filename""" self.__filesize = utils.get_disk_size(self.__filename) if not self.__filesize: raise error.TestNAError( 'Unable to find the partition %s, please plug in a USB ' 'flash drive and a SD card for testing external storage' % self.__filename) def __get_device_description(self): """Get the device vendor and model name as its description""" # Find the block device in sysfs. For example, a card read device may # be in /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host4/ # target4:0:0/4:0:0:0/block/sdb. # Then read the vendor and model name in its grand-parent directory. # Obtain the device name by stripping the partition number. # For example, sda3 => sda; mmcblk1p3 => mmcblk1, nvme0n1p3 => nvme0n1. device = re.match(r'.*(sd[a-z]|mmcblk[0-9]+|nvme[0-9]+n[0-9]+)p?[0-9]*', self.__filename).group(1) findsys = utils.run('find /sys/devices -name %s | grep -v virtual' % device) device_path = findsys.stdout.rstrip() removable_file = os.path.join(device_path, "removable") if os.path.exists(removable_file): if utils.read_one_line(removable_file).strip() == '1' : self.REMOVABLE = True self.CONTINUE_ERRORS="'all'" if "nvme" in device: dir_path = utils.run('dirname %s' % device_path).stdout.rstrip() model_file = '%s/model' % dir_path if os.path.exists(model_file): self.__description = utils.read_one_line(model_file).strip() else: self.__description = '' else: vendor_file = device_path.replace('block/%s' % device, 'vendor') model_file = device_path.replace('block/%s' % device, 'model') if os.path.exists(vendor_file) and os.path.exists(model_file): vendor = utils.read_one_line(vendor_file).strip() model = utils.read_one_line(model_file).strip() self.__description = vendor + ' ' + model else: self.__description = '' def initialize(self, dev='', filesize=DEFAULT_FILE_SIZE): """ Set up local variables. @param dev: block device / file to test. Spare partition on root device by default @param filesize: size of the file. 0 means whole partition. by default, 1GB. """ if dev != '' and (os.path.isfile(dev) or not os.path.exists(dev)): if filesize == 0: raise error.TestError( 'Nonzero file size is required to test file systems') self.__filename = dev self.__filesize = filesize self.__description = '' return if not dev: dev = utils.get_fixed_dst_drive() if dev == utils.get_root_device(): if filesize == 0: raise error.TestError( 'Using the root device as a whole is not allowed') else: self.__filename = utils.get_free_root_partition() elif filesize != 0: # Use the first partition of the external drive if it exists partition = utils.concat_partition(dev, 1) if os.path.exists(partition): self.__filename = partition else: self.__filename = dev else: self.__filename = dev self.__get_disk_size() self.__get_device_description() # Restrict test to use a given file size, default 1GiB if filesize != 0: self.__filesize = min(self.__filesize, filesize) self.__verify_only = False logging.info('filename: %s', self.__filename) logging.info('filesize: %d', self.__filesize) def run_once(self, dev='', quicktest=False, requirements=None, integrity=False, wait=60 * 60 * 72, blkdiscard=True): """ Runs several fio jobs and reports results. @param dev: block device to test @param quicktest: short test @param requirements: list of jobs for fio to run @param integrity: test to check data integrity @param wait: seconds to wait between a write and subsequent verify @param blkdiscard: do a blkdiscard before running fio """ if requirements is not None: pass elif quicktest: requirements = [ ('1m_write', []), ('16k_read', []) ] elif integrity: requirements = [ ('8k_async_randwrite', []), ('8k_async_randwrite', [self.VERIFY_OPTION]) ] elif dev in ['', utils.get_root_device()]: requirements = [ ('surfing', []), ('boot', []), ('login', []), ('seq_write', []), ('seq_read', []), ('16k_write', []), ('16k_read', []), ('1m_stress', []), ] else: # TODO(waihong@): Add more test cases for external storage requirements = [ ('seq_write', []), ('seq_read', []), ('16k_write', []), ('16k_read', []), ('4k_write', []), ('4k_read', []), ('1m_stress', []), ] results = {} if os.path.exists(self.__filename) and \ stat.S_ISBLK(os.stat(self.__filename).st_mode) and \ self.__filesize != 0 and blkdiscard: try: fd = os.open(self.__filename, os.O_RDWR) fcntl.ioctl(fd, self.IOCTL_TRIM_CMD, struct.pack('QQ', 0, self.__filesize)) except IOError, err: logging.info("blkdiscard failed %s", err) pass finally: os.close(fd) for job, options in requirements: # Keys are labeled according to the test case name, which is # unique per run, so they cannot clash if self.VERIFY_OPTION in options: time.sleep(wait) self.__verify_only = True else: self.__verify_only = False env_vars = ' '.join( ['FILENAME=' + self.__filename, 'FILESIZE=' + str(self.__filesize), 'VERIFY_ONLY=' + str(int(self.__verify_only)), 'CONTINUE_ERRORS=' + str(self.CONTINUE_ERRORS) ]) client_dir = os.path.dirname(os.path.dirname(self.bindir)) storage_dir = os.path.join(client_dir, 'cros/storage_tests') job_file = os.path.join(storage_dir, job) results.update(fio_util.fio_runner(self, job_file, env_vars)) # Output keys relevant to the performance, larger filesize will run # slower, and sda5 should be slightly slower than sda3 on a rotational # disk self.write_test_keyval({'filesize': self.__filesize, 'filename': self.__filename, 'device': self.__description}) logging.info('Device Description: %s', self.__description) self.write_perf_keyval(results) for k, v in results.iteritems(): if k.endswith('_error'): self._error_code = int(v) if self._error_code != 0 and self._fail_count == 0: self._fail_count = 1 elif k.endswith('_total_err'): self._fail_count = int(v) if self._fail_count > 0: if self.REMOVABLE and not self.__verify_only: raise error.TestWarn('%s failed verifications, ' 'first error code is %s' % (str(self._fail_count), str(self._error_code))) raise error.TestFail('%s failures, ' 'first error code is %s' % (str(self._fail_count), str(self._error_code)))