blob: e292c784128cb4b007d2b65c51697784246b4d26 [file] [log] [blame]
# Copyright (c) 2009 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 logging, os, re
from autotest_lib.client.bin import test, utils
from autotest_lib.client.common_lib import error
class hardware_StorageFio(test.test):
version = 3
# http://brick.kernel.dk/snaps/fio-1.36.tar.bz2
def setup(self, tarball = 'fio-1.36.tar.bz2'):
# clean
if os.path.exists(self.srcdir):
utils.system('rm -rf %s' % self.srcdir)
tarball = utils.unmap_url(self.bindir, tarball, self.tmpdir)
utils.extract_tarball_to_dir(tarball, self.srcdir)
self.job.setup_dep(['libaio'])
ldflags = '-L' + self.autodir + '/deps/libaio/lib'
cflags = '-I' + self.autodir + '/deps/libaio/include'
var_ldflags = 'LDFLAGS="' + ldflags + '"'
var_cflags = 'CFLAGS="' + cflags + '"'
os.chdir(self.srcdir)
utils.system('patch -p1 < ../Makefile.patch')
utils.system('patch -p1 < ../arm.patch')
utils.make(make='%s %s make' % (var_ldflags, var_cflags))
def __find_free_root_partition(self):
"""Locate the spare root partition that we didn't boot off"""
spare_root_map = {
'3': '5',
'5': '3',
}
rootdev = utils.system_output('rootdev -s')
spare_root = rootdev[:-1] + spare_root_map[rootdev[-1]]
self.__filename = spare_root
def __get_file_size(self):
"""Return the size in bytes of the device pointed to by __filename"""
device = os.path.basename(self.__filename)
for line in file('/proc/partitions'):
try:
major, minor, blocks, name = re.split(r' +', line.strip())
except ValueError:
continue
if name == device:
blocks = int(blocks)
self.__filesize = 1024 * blocks
break
else:
if device.startswith(utils.system_output('rootdev -s -d')):
raise error.TestError(
'Unable to determine free partitions size')
else:
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, on x86: sda3 => sda; on ARM: mmcblk1p3 => mmcblk1.
device = os.path.basename(
re.sub('(sd[a-z]|mmcblk[0-9]+)p?[0-9]+', '\\1', self.__filename))
findsys = utils.run('find /sys/devices -name %s' % device)
device_path = findsys.stdout.rstrip()
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 __parse_fio(self, lines):
"""Parse the human readable fio output
This only collects bandwidth and iops numbers from fio.
"""
# fio --minimal doesn't output information about the number of ios
# that occurred, making it unsuitable for this test. Instead we parse
# the human readable output with some regular expressions
read_re = re.compile(r'read :.*bw=([0-9]*K?)B/s.*iops=([0-9]*)')
write_re = re.compile(r'write:.*bw=([0-9]*K?)B/s.*iops=([0-9]*)')
results = {}
for line in lines.split('\n'):
line = line.rstrip()
match = read_re.search(line)
if match:
results['read_bw'] = match.group(1)
results['read_iops'] = match.group(2)
continue
match = write_re.search(line)
if match:
results['write_bw'] = match.group(1)
results['write_iops'] = match.group(2)
continue
# Turn the values into numbers
for metric, result in results.iteritems():
if result[-1] == 'K':
result = int(result[:-1]) * 1024
else:
result = int(result)
results[metric] = result
results['bw'] = (results.get('read_bw', 0) +
results.get('write_bw', 0))
results['iops'] = (results.get('read_iops', 0) +
results.get('write_iops', 0))
return results
def __RunFio(self, test):
os.chdir(self.srcdir)
vars = 'LD_LIBRARY_PATH="' + self.autodir + '/deps/libaio/lib"'
os.putenv('FILENAME', self.__filename)
os.putenv('FILESIZE', str(self.__filesize))
# running fio with ionice -c 3 so it doesn't lock out other
# processes from the disk while it is running.
# If you want to run the fio test for performance purposes,
# take out the ionice and disable hung process detection:
# "echo 0 > /proc/sys/kernel/hung_task_timeout_secs"
# -c 3 = Idle
# Tried lowest priority for "best effort" but still failed
ionice = ' ionice -c 3'
fio = utils.run(vars + ionice +
' ./fio "%s"' % os.path.join(self.bindir, test))
logging.debug(fio.stdout)
return self.__parse_fio(fio.stdout)
def initialize(self, dev='', filesize=1024*1024*1024):
if dev in ['', utils.system_output('rootdev -s -d')]:
self.__find_free_root_partition()
else:
# Use the first partition of the external drive
if dev[5:7] == 'sd':
self.__filename = dev + '1'
else:
self.__filename = dev + 'p1'
self.__get_file_size()
self.__get_device_description()
# Restrict test to use a given file size, default 1GiB
self.__filesize = min(self.__filesize, filesize)
def run_once(self, dev='', quicktest=False, requirements=None):
# TODO(ericli): need to find a general solution to install dep packages
# when tests are pre-compiled, so setup() is not called from client any
# more.
dep = 'libaio'
dep_dir = os.path.join(self.autodir, 'deps', dep)
self.job.install_pkg(dep, 'dep', dep_dir)
if requirements is not None:
pass
elif quicktest:
requirements = {
'quick_write': 'bw',
'quick_read': 'iops',
}
elif dev in ['', utils.system_output('rootdev -s -d')]:
requirements = {
'surfing': 'iops',
'boot': 'bw',
'login': 'bw',
'seq_read': 'bw',
'seq_write': 'bw',
'16k_read': 'iops',
'16k_write': 'iops',
'8k_read': 'iops',
'8k_write': 'iops',
'4k_read': 'iops',
'4k_write': 'iops',
}
else:
# TODO(waihong@): Add more test cases for external storage
requirements = {
'seq_read': 'bw',
'seq_write': 'bw',
'16k_read': 'iops',
'16k_write': 'iops',
'8k_read': 'iops',
'8k_write': 'iops',
'4k_read': 'iops',
'4k_write': 'iops',
}
results = {}
for test, metric_list in requirements.iteritems():
if not isinstance(metric_list, list):
metric_list = [metric_list]
result = self.__RunFio(test)
for metric in metric_list:
units = metric
if metric == 'bw':
units = 'bytes_per_sec'
results[units + '_' + test] = result[metric]
# Output keys relevent 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)