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, re
6from autotest_lib.client.bin import test
7from autotest_lib.client.bin import utils
8from autotest_lib.client.common_lib import error
9
10
11class hardware_Smartctl(test.test):
12    """
13    Run smartctl to retrieve S.M.A.R.T attribute and report in keyval format.
14    """
15
16    version = 1
17
18    _SMARTCTL_DEVICE_MODEL_PATTERN = 'Device Model: *(?P<model>[^ ].*)$'
19    _SMARTCTL_RESULT_PATTERN = '.*[P-][O-][S-][R-][C-][K-].*'
20
21    # Temporary table: This value should be in smartctl in March 2014
22    # http://sourceforge.net/apps/trac/smartmontools/ticket/272
23    _SMARTCTL_LOOKUP_TABLE = {
24            'SanDisk SSD i100': {
25                    171 : 'Program_Fail_Count',
26                    172 : 'Erase_Fail_Count',
27                    173 : 'Average_Write_Erase_Count',
28                    174 : 'Unexpected_Power_Loss_Count',
29                    230 : 'Percent_Write_Erase_Count',
30                    234 : 'Percent_Write_Erase_Count_BC'
31            }
32    }
33
34    def run_once(self, iteration=1, dev=''):
35        """
36        Read S.M.A.R.T attribute from target device
37
38        @param dev:    target device
39        """
40        if dev == '':
41            logging.info('Run rootdev to determine boot device')
42            dev = utils.get_root_device()
43
44        logging.info(str('dev: %s' % dev))
45
46        # Skip this test if dev is an eMMC device without raising an error
47        if re.match('.*mmc.*', dev):
48            logging.info('Target device is an eMMC device. Skip testing')
49            self.write_perf_keyval({'device_model' : 'eMMC'})
50            return
51
52        last_result = ''
53
54
55        # run multiple time to test the firmware part that retrieve SMART value
56        for loop in range(1, iteration + 1):
57            cmd = 'smartctl -a -f brief %s' % dev
58            result = utils.run(cmd, ignore_status=True)
59            exit_status = result.exit_status
60            result_text = result.stdout
61            result_lines = result_text.split('\n')
62
63            # log all line if line count is different
64            # otherwise log only changed line
65            if result_text != last_result:
66                logging.info(str('Iteration #%d' % loop))
67                last_result_lines = last_result.split('\n')
68                if len(last_result_lines) != len(result_lines):
69                    for line in result_lines:
70                        logging.info(line)
71                else:
72                    for i, line in enumerate(result_lines):
73                        if line != last_result_lines[i]:
74                            logging.info(line)
75                last_result = result_text
76
77            # Ignore error other than first two bits
78            if exit_status & 0x3:
79                # Error message should be in 4th line of the output
80                msg = 'Test failed with error: %s' % result_lines[3]
81                raise error.TestFail(msg)
82
83        logging.info(str('smartctl exit status: 0x%x' % exit_status))
84
85        # find drive model
86        lookup_table = {}
87        pattern = re.compile(self._SMARTCTL_DEVICE_MODEL_PATTERN)
88        for line in result_lines:
89            if pattern.match(line):
90                model = pattern.match(line).group('model')
91                for known_model in self._SMARTCTL_LOOKUP_TABLE:
92                    if model.startswith(known_model):
93                        lookup_table = self._SMARTCTL_LOOKUP_TABLE[known_model]
94                        break
95                break
96        else:
97            raise error.TestFail('Can not find drive model')
98
99        # Example of smart ctl result
100        # ID# ATTRIBUTE_NAME          FLAGS    VALUE WORST THRESH FAIL RAW_VALUE
101        #  12 Power_Cycle_Count       -O----   100   100   000    -    204
102        # use flag field to find a valid line
103        pattern = re.compile(self._SMARTCTL_RESULT_PATTERN)
104        keyval = {}
105        fail = []
106        for line in result_lines:
107            if not pattern.match(line):
108                continue
109            field = line.split()
110
111            id = int(field[0])
112            if id in lookup_table:
113                # look up table overwrite smartctl name
114                key = lookup_table[id]
115            else:
116                key = field[1] # ATTRIBUTE_NAME
117                if key == 'Unknown_Attribute':
118                    key = "Smart_Attribute_ID_%d" % id
119
120            keyval[key] = field[7] # RAW_VALUE
121
122            # check for failing attribute
123            if field[6] != '-':
124                fail += [key]
125
126        if len(keyval) == 0:
127            raise error.TestFail(
128                    'Test failed with error: Can not parse smartctl keyval')
129
130        if len(fail) > 0:
131            keyval['fail'] = fail
132
133        keyval['exit_status'] = exit_status
134        keyval['device_model'] = model
135        self.write_perf_keyval(keyval)
136
137