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 = re.match(r'.*(sd[a-z]|mmcblk[0-9]+|nvme[0-9]+n[0-9]+)p?[0-9]+',
48                          self.__filename).group(1)
49        findsys = utils.run('find /sys/devices -name %s' % device)
50        device_path = findsys.stdout.rstrip()
51
52        if "nvme" in device:
53            dir_path = utils.run('dirname %s' % device_path).stdout.rstrip()
54            model_file = '%s/model' % dir_path
55            if os.path.exists(model_file):
56                self.__description = utils.read_one_line(model_file).strip()
57            else:
58                self.__description = ''
59        else:
60            vendor_file = device_path.replace('block/%s' % device, 'vendor')
61            model_file = device_path.replace('block/%s' % device, 'model')
62            if os.path.exists(vendor_file) and os.path.exists(model_file):
63                vendor = utils.read_one_line(vendor_file).strip()
64                model = utils.read_one_line(model_file).strip()
65                self.__description = vendor + ' ' + model
66            else:
67                self.__description = ''
68
69
70    def initialize(self, dev='', filesize=DEFAULT_FILE_SIZE):
71        """
72        Set up local variables.
73
74        @param dev: block device / file to test.
75                Spare partition on root device by default
76        @param filesize: size of the file. 0 means whole partition.
77                by default, 1GB.
78        """
79        if dev != '' and (os.path.isfile(dev) or not os.path.exists(dev)):
80            if filesize == 0:
81                raise error.TestError(
82                    'Nonzero file size is required to test file systems')
83            self.__filename = dev
84            self.__filesize = filesize
85            self.__description = ''
86            return
87
88        if not dev:
89            dev = utils.get_fixed_dst_drive()
90
91        if dev == utils.get_root_device():
92            if filesize == 0:
93                raise error.TestError(
94                    'Using the root device as a whole is not allowed')
95            else:
96                self.__filename = utils.get_free_root_partition()
97        elif filesize != 0:
98            # Use the first partition of the external drive
99            if dev[5:7] == 'sd':
100                self.__filename = dev + '1'
101            else:
102                self.__filename = dev + 'p1'
103        else:
104            self.__filename = dev
105        self.__get_disk_size()
106        self.__get_device_description()
107
108        # Restrict test to use a given file size, default 1GiB
109        if filesize != 0:
110            self.__filesize = min(self.__filesize, filesize)
111
112        self.__verify_only = False
113
114        logging.info('filename: %s', self.__filename)
115        logging.info('filesize: %d', self.__filesize)
116
117    def run_once(self, dev='', quicktest=False, requirements=None,
118                 integrity=False, wait=60 * 60 * 72):
119        """
120        Runs several fio jobs and reports results.
121
122        @param dev: block device to test
123        @param quicktest: short test
124        @param requirements: list of jobs for fio to run
125        @param integrity: test to check data integrity
126        @param wait: seconds to wait between a write and subsequent verify
127
128        """
129
130        if requirements is not None:
131            pass
132        elif quicktest:
133            requirements = [
134                ('1m_write', []),
135                ('16k_read', [])
136            ]
137        elif integrity:
138            requirements = [
139                ('8k_async_randwrite', []),
140                ('8k_async_randwrite', [self.VERIFY_OPTION])
141            ]
142        elif dev in ['', utils.get_root_device()]:
143            requirements = [
144                ('surfing', []),
145                ('boot', []),
146                ('login', []),
147                ('seq_read', []),
148                ('seq_write', []),
149                ('16k_read', []),
150                ('16k_write', []),
151                ('1m_stress', []),
152            ]
153        else:
154            # TODO(waihong@): Add more test cases for external storage
155            requirements = [
156                ('seq_read', []),
157                ('seq_write', []),
158                ('16k_read', []),
159                ('16k_write', []),
160                ('1m_stress', []),
161            ]
162
163        results = {}
164        for job, options in requirements:
165            # Keys are labeled according to the test case name, which is
166            # unique per run, so they cannot clash
167            if self.VERIFY_OPTION in options:
168                time.sleep(wait)
169                self.__verify_only = True
170            else:
171                self.__verify_only = False
172            env_vars = ' '.join(
173                ['FILENAME=' + self.__filename,
174                 'FILESIZE=' + str(self.__filesize),
175                 'VERIFY_ONLY=' + str(int(self.__verify_only))
176                ])
177            job_file = os.path.join(self.bindir, job)
178            results.update(fio_util.fio_runner(self, job_file, env_vars))
179
180        # Output keys relevant to the performance, larger filesize will run
181        # slower, and sda5 should be slightly slower than sda3 on a rotational
182        # disk
183        self.write_test_keyval({'filesize': self.__filesize,
184                                'filename': self.__filename,
185                                'device': self.__description})
186        logging.info('Device Description: %s', self.__description)
187        self.write_perf_keyval(results)
188        for k, v in results.iteritems():
189            if k.endswith('_error'):
190                self._fail_count += int(v)
191        if self._fail_count > 0:
192            raise error.TestFail('%s failed verifications' %
193                                 str(self._fail_count))
194