137 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			137 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
| # 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 logging, re
 | |
| from autotest_lib.client.bin import test
 | |
| from autotest_lib.client.bin import utils
 | |
| from autotest_lib.client.common_lib import error
 | |
| 
 | |
| 
 | |
| class hardware_Smartctl(test.test):
 | |
|     """
 | |
|     Run smartctl to retrieve S.M.A.R.T attribute and report in keyval format.
 | |
|     """
 | |
| 
 | |
|     version = 1
 | |
| 
 | |
|     _SMARTCTL_DEVICE_MODEL_PATTERN = 'Device Model: *(?P<model>[^ ].*)$'
 | |
|     _SMARTCTL_RESULT_PATTERN = '.*[P-][O-][S-][R-][C-][K-].*'
 | |
| 
 | |
|     # Temporary table: This value should be in smartctl in March 2014
 | |
|     # http://sourceforge.net/apps/trac/smartmontools/ticket/272
 | |
|     _SMARTCTL_LOOKUP_TABLE = {
 | |
|             'SanDisk SSD i100': {
 | |
|                     171 : 'Program_Fail_Count',
 | |
|                     172 : 'Erase_Fail_Count',
 | |
|                     173 : 'Average_Write_Erase_Count',
 | |
|                     174 : 'Unexpected_Power_Loss_Count',
 | |
|                     230 : 'Percent_Write_Erase_Count',
 | |
|                     234 : 'Percent_Write_Erase_Count_BC'
 | |
|             }
 | |
|     }
 | |
| 
 | |
|     def run_once(self, iteration=1, dev=''):
 | |
|         """
 | |
|         Read S.M.A.R.T attribute from target device
 | |
| 
 | |
|         @param dev:    target device
 | |
|         """
 | |
|         if dev == '':
 | |
|             logging.info('Run rootdev to determine boot device')
 | |
|             dev = utils.get_root_device()
 | |
| 
 | |
|         logging.info(str('dev: %s' % dev))
 | |
| 
 | |
|         # Skip this test if dev is an eMMC device without raising an error
 | |
|         if re.match('.*mmc.*', dev):
 | |
|             logging.info('Target device is an eMMC device. Skip testing')
 | |
|             self.write_perf_keyval({'device_model' : 'eMMC'})
 | |
|             return
 | |
| 
 | |
|         last_result = ''
 | |
| 
 | |
| 
 | |
|         # run multiple time to test the firmware part that retrieve SMART value
 | |
|         for loop in range(1, iteration + 1):
 | |
|             cmd = 'smartctl -a -f brief %s' % dev
 | |
|             result = utils.run(cmd, ignore_status=True)
 | |
|             exit_status = result.exit_status
 | |
|             result_text = result.stdout
 | |
|             result_lines = result_text.split('\n')
 | |
| 
 | |
|             # log all line if line count is different
 | |
|             # otherwise log only changed line
 | |
|             if result_text != last_result:
 | |
|                 logging.info(str('Iteration #%d' % loop))
 | |
|                 last_result_lines = last_result.split('\n')
 | |
|                 if len(last_result_lines) != len(result_lines):
 | |
|                     for line in result_lines:
 | |
|                         logging.info(line)
 | |
|                 else:
 | |
|                     for i, line in enumerate(result_lines):
 | |
|                         if line != last_result_lines[i]:
 | |
|                             logging.info(line)
 | |
|                 last_result = result_text
 | |
| 
 | |
|             # Ignore error other than first two bits
 | |
|             if exit_status & 0x3:
 | |
|                 # Error message should be in 4th line of the output
 | |
|                 msg = 'Test failed with error: %s' % result_lines[3]
 | |
|                 raise error.TestFail(msg)
 | |
| 
 | |
|         logging.info(str('smartctl exit status: 0x%x' % exit_status))
 | |
| 
 | |
|         # find drive model
 | |
|         lookup_table = {}
 | |
|         pattern = re.compile(self._SMARTCTL_DEVICE_MODEL_PATTERN)
 | |
|         for line in result_lines:
 | |
|             if pattern.match(line):
 | |
|                 model = pattern.match(line).group('model')
 | |
|                 for known_model in self._SMARTCTL_LOOKUP_TABLE:
 | |
|                     if model.startswith(known_model):
 | |
|                         lookup_table = self._SMARTCTL_LOOKUP_TABLE[known_model]
 | |
|                         break
 | |
|                 break
 | |
|         else:
 | |
|             raise error.TestFail('Can not find drive model')
 | |
| 
 | |
|         # Example of smart ctl result
 | |
|         # ID# ATTRIBUTE_NAME          FLAGS    VALUE WORST THRESH FAIL RAW_VALUE
 | |
|         #  12 Power_Cycle_Count       -O----   100   100   000    -    204
 | |
|         # use flag field to find a valid line
 | |
|         pattern = re.compile(self._SMARTCTL_RESULT_PATTERN)
 | |
|         keyval = {}
 | |
|         fail = []
 | |
|         for line in result_lines:
 | |
|             if not pattern.match(line):
 | |
|                 continue
 | |
|             field = line.split()
 | |
| 
 | |
|             id = int(field[0])
 | |
|             if id in lookup_table:
 | |
|                 # look up table overwrite smartctl name
 | |
|                 key = lookup_table[id]
 | |
|             else:
 | |
|                 key = field[1] # ATTRIBUTE_NAME
 | |
|                 if key == 'Unknown_Attribute':
 | |
|                     key = "Smart_Attribute_ID_%d" % id
 | |
| 
 | |
|             keyval[key] = field[7] # RAW_VALUE
 | |
| 
 | |
|             # check for failing attribute
 | |
|             if field[6] != '-':
 | |
|                 fail += [key]
 | |
| 
 | |
|         if len(keyval) == 0:
 | |
|             raise error.TestFail(
 | |
|                     'Test failed with error: Can not parse smartctl keyval')
 | |
| 
 | |
|         if len(fail) > 0:
 | |
|             keyval['fail'] = fail
 | |
| 
 | |
|         keyval['exit_status'] = exit_status
 | |
|         keyval['device_model'] = model
 | |
|         self.write_perf_keyval(keyval)
 | |
| 
 |