| # 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, subprocess, threading |
| import common |
| from autotest_lib.client.bin import test, utils |
| from autotest_lib.client.common_lib import error |
| |
| |
| class hardware_Badblocks(test.test): |
| """ |
| Runs badblocks on the root partition that is not being used. |
| |
| """ |
| |
| version = 1 |
| |
| # Define output that is expected from a successful badblocks run. |
| _EXPECTED_BADBLOCKS_OUTPUT = ( |
| 'Pass completed, 0 bad blocks found. (0/0/0 errors)') |
| |
| # Define Linux badblocks utility name. |
| _BADBLOCKS = 'badblocks' |
| |
| # Define variables to store some statistics of the runs. |
| _pass_count = 0 |
| _fail_count = 0 |
| _longest_runtime = 0 |
| |
| |
| def _get_sector_size(self, dev): |
| """ |
| Finds block device's sector size in bytes. |
| |
| @return the sector size. |
| |
| """ |
| |
| argv = ('parted ' + dev + ' print | grep "Sector size" | awk -F ' + |
| '"/" \'{print $3}\' | sed \'$s/.$//\'') |
| |
| return utils.system_output(argv) |
| |
| |
| def _timeout(self, badblocks_proc): |
| """ |
| Timeout callback for the badblocks process. |
| |
| Kills badblocks process if still running and fails test. |
| |
| """ |
| |
| # Kill badblocks, report if not killed, log any exceptions. |
| if badblocks_proc.poll() == None: |
| try: |
| logging.info('badblocks taking too long---sending SIGKILL') |
| badblocks_proc.kill() |
| except Exception as e: |
| logging.info('%s', e) |
| finally: |
| # name of the kernel function in which the process is sleeping. |
| argv = ('ps eopid,fname,wchan | grep ' + self._BADBLOCKS + |
| ' | awk \'{print $3}\'') |
| waiton = utils.system_output(argv) |
| if waiton: |
| logging.info('badblocks is waiting on %s', waiton) |
| raise error.TestError('Error: badblocks timed out.') |
| |
| |
| def _run_badblocks(self, dev, sector_size, tmout): |
| """ |
| Runs badblocks. |
| |
| """ |
| |
| # Run badblocks on the selected partition, with parameters: |
| # -s = show progress |
| # -v = verbose (print error count) |
| # -w = destructive write+read test |
| # -b = block size (set equal to sector size) |
| argv = [self._BADBLOCKS, '-svw', '-d', str(sector_size), dev] |
| msg = 'Running: ' + ' '.join(map(str, argv)) |
| logging.info(msg) |
| badblocks_proc = subprocess.Popen( |
| argv, |
| shell=False, |
| stderr=subprocess.STDOUT, # Combine stderr with stdout. |
| stdout=subprocess.PIPE) |
| |
| # Start timeout timer thread. |
| t = threading.Timer(tmout, self._timeout, [badblocks_proc]) |
| t.start() |
| |
| # Get badblocks output. |
| stdout, _ = badblocks_proc.communicate() |
| |
| # Stop timer if badblocks has finished. |
| t.cancel() |
| |
| # Check badblocks exit status. |
| if badblocks_proc.returncode != 0: |
| raise error.TestError('badblocks returned with code: %s', |
| badblocks_proc.returncode) |
| |
| # Parse and log badblocks output. |
| logging.info('badblocks output:') |
| lines = stdout.split('\n') |
| del lines[-1] # Remove blank line at end. |
| logging.info(lines[0]) |
| logging.info(lines[1]) |
| # Log the progress of badblocks (line 2 onwards, minus last line). |
| for line in lines[2:-1]: |
| # replace backspace characters with a newline character. |
| line = re.sub(r'[\b]+', '\n', line) |
| # Log test pattern info. |
| pattern_info = line[:line.find(':') + 1] |
| logging.info('%s', pattern_info) |
| sublines = line[line.find(':') + 2:].split('\n') |
| for subline in sublines: |
| logging.info('%s', subline) |
| # Log result (last line). |
| logging.info(lines[-1]) |
| |
| # Get run time in seconds. |
| min_sec = re.match(r'(\w+):(\w+)', lines[-2].split()[-4]) |
| runtime = int(min_sec.group(1)) * 60 + int(min_sec.group(2)) |
| |
| # Update longest run time. |
| if self._longest_runtime < runtime: |
| self._longest_runtime = runtime |
| |
| # Check badblocks result. |
| result = lines[-1].strip() |
| if result != self._EXPECTED_BADBLOCKS_OUTPUT: |
| self._fail_count += 1 |
| return |
| self._pass_count += 1 |
| |
| |
| def run_once(self, iters=1, tmout=60 * 60): |
| """ |
| Executes test. |
| |
| @param iters: Number of times to run badblocks. |
| @param tmout: Time allowed badblocks to run before killing it. |
| (Default time is 60 minutes.) |
| |
| """ |
| |
| # Log starting message. |
| logging.info('Statring hardware_Badblocks Test.') |
| logging.info('Iterations: %d', iters) |
| logging.info('badblocks Timeout (sec): %d', tmout) |
| |
| # Determine which device and partition to use. |
| logging.info('Determine unused root partition to test on:') |
| dev = utils.get_free_root_partition() |
| logging.info('Testing on ' + dev) |
| |
| # Get block device's sector size. |
| logging.info('Determine block device sector size:') |
| sector_size = self._get_sector_size(utils.get_root_device()) |
| logging.info('Sector size (bytes): ' + sector_size) |
| |
| # Get partition size. |
| logging.info('Determine partition size:') |
| part_size = utils.get_disk_size(dev) |
| logging.info('Partition size (bytes): %s', part_size) |
| |
| # Run badblocks. |
| for i in range(iters): |
| logging.info('Starting iteration %d', i) |
| self._run_badblocks(dev, sector_size, tmout) |
| |
| # Report statistics. |
| logging.info('Total pass: %d', self._pass_count) |
| logging.info('Total fail: %d', self._fail_count) |
| stats = {} |
| stats['ea_badblocks_runs'] = iters |
| stats['ea_passed_count'] = self._pass_count |
| stats['ea_failed_count'] = self._fail_count |
| stats['sec_longest_run'] = self._longest_runtime |
| # TODO: change write_perf_keyval() to output_perf_value() as soon as |
| # autotest is ready for it. |
| self.write_perf_keyval(stats) |
| |
| # Report test pass/fail. |
| if self._pass_count != iters: |
| raise error.TestFail('One or more runs found bad blocks on' |
| ' storage device.') |