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 logging, os, re, time
6from autotest_lib.client.bin import fio_util, test, utils
7from autotest_lib.client.common_lib import error
8
9
10class hardware_StorageFio(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
23    # Initialize fail counter used to determine test pass/fail.
24    _fail_count = 0
25
26    def __get_disk_size(self):
27        """Return the size in bytes of the device pointed to by __filename"""
28        self.__filesize = utils.get_disk_size(self.__filename)
29
30        if not self.__filesize:
31            raise error.TestNAError(
32                'Unable to find the partition %s, please plug in a USB '
33                'flash drive and a SD card for testing external storage' %
34                self.__filename)
35
36
37    def __get_device_description(self):
38        """Get the device vendor and model name as its description"""
39
40        # Find the block device in sysfs. For example, a card read device may
41        # be in /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-5/1-5:1.0/host4/
42        # target4:0:0/4:0:0:0/block/sdb.
43        # Then read the vendor and model name in its grand-parent directory.
44
45        # Obtain the device name by stripping the partition number.
46        # For example, sda3 => sda; mmcblk1p3 => mmcblk1, nvme0n1p3 => nvme0n1.
47        device = os.path.basename(
48            re.sub('(sd[a-z]|mmcblk[0-9]+p|nvme[0-9]+n[0-9]+p)[0-9]+', '\\1', self.__filename))
49        findsys = utils.run('find /sys/devices -name %s' % device)
50        device_path = findsys.stdout.rstrip()
51
52        vendor_file = device_path.replace('block/%s' % device, 'vendor')
53        model_file = device_path.replace('block/%s' % device, 'model')
54        if os.path.exists(vendor_file) and os.path.exists(model_file):
55            vendor = utils.read_one_line(vendor_file).strip()
56            model = utils.read_one_line(model_file).strip()
57            self.__description = vendor + ' ' + model
58        else:
59            self.__description = ''
60
61
62    def initialize(self, dev='', filesize=DEFAULT_FILE_SIZE):
63        """
64        Set up local variables.
65
66        @param dev: block device / file to test.
67                Spare partition on root device by default
68        @param filesize: size of the file. 0 means whole partition.
69                by default, 1GB.
70        """
71        if dev != '' and (os.path.isfile(dev) or not os.path.exists(dev)):
72            if filesize == 0:
73                raise error.TestError(
74                    'Nonzero file size is required to test file systems')
75            self.__filename = dev
76            self.__filesize = filesize
77            self.__description = ''
78            return
79
80        if not dev:
81            dev = utils.get_fixed_dst_drive()
82
83        if dev == utils.get_root_device():
84            if filesize == 0:
85                raise error.TestError(
86                    'Using the root device as a whole is not allowed')
87            else:
88                self.__filename = utils.get_free_root_partition()
89        elif filesize != 0:
90            # Use the first partition of the external drive
91            if dev[5:7] == 'sd':
92                self.__filename = dev + '1'
93            else:
94                self.__filename = dev + 'p1'
95        else:
96            self.__filename = dev
97        self.__get_disk_size()
98        self.__get_device_description()
99
100        # Restrict test to use a given file size, default 1GiB
101        if filesize != 0:
102            self.__filesize = min(self.__filesize, filesize)
103
104        self.__verify_only = False
105
106        logging.info('filename: %s', self.__filename)
107        logging.info('filesize: %d', self.__filesize)
108
109    def run_once(self, dev='', quicktest=False, requirements=None,
110                 integrity=False, wait=60 * 60 * 72):
111        """
112        Runs several fio jobs and reports results.
113
114        @param dev: block device to test
115        @param quicktest: short test
116        @param requirements: list of jobs for fio to run
117        @param integrity: test to check data integrity
118        @param wait: seconds to wait between a write and subsequent verify
119
120        """
121
122        if requirements is not None:
123            pass
124        elif quicktest:
125            requirements = [
126                ('1m_write', []),
127                ('16k_read', [])
128            ]
129        elif integrity:
130            requirements = [
131                ('8k_async_randwrite', []),
132                ('8k_async_randwrite', [self.VERIFY_OPTION])
133            ]
134        elif dev in ['', utils.get_root_device()]:
135            requirements = [
136                ('surfing', []),
137                ('boot', []),
138                ('login', []),
139                ('seq_read', []),
140                ('seq_write', []),
141                ('16k_read', []),
142                ('16k_write', []),
143                ('1m_stress', []),
144            ]
145        else:
146            # TODO(waihong@): Add more test cases for external storage
147            requirements = [
148                ('seq_read', []),
149                ('seq_write', []),
150                ('16k_read', []),
151                ('16k_write', []),
152                ('1m_stress', []),
153            ]
154
155        results = {}
156        for job, options in requirements:
157            # Keys are labeled according to the test case name, which is
158            # unique per run, so they cannot clash
159            if self.VERIFY_OPTION in options:
160                time.sleep(wait)
161                self.__verify_only = True
162            else:
163                self.__verify_only = False
164            env_vars = ' '.join(
165                ['FILENAME=' + self.__filename,
166                 'FILESIZE=' + str(self.__filesize),
167                 'VERIFY_ONLY=' + str(int(self.__verify_only))
168                ])
169            job_file = os.path.join(self.bindir, job)
170            results.update(fio_util.fio_runner(self, job_file, env_vars))
171
172        # Output keys relevant to the performance, larger filesize will run
173        # slower, and sda5 should be slightly slower than sda3 on a rotational
174        # disk
175        self.write_test_keyval({'filesize': self.__filesize,
176                                'filename': self.__filename,
177                                'device': self.__description})
178        logging.info('Device Description: %s', self.__description)
179        self.write_perf_keyval(results)
180        for k, v in results.iteritems():
181            if k.endswith('_error'):
182                self._fail_count += int(v)
183        if self._fail_count > 0:
184            raise error.TestFail('%s failed verifications' %
185                                 str(self._fail_count))
186