Home | History | Annotate | Download | only in power
      1 # Copyright (c) 2012 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 
      5 import collections, logging, os, re, shutil, time
      6 
      7 from autotest_lib.client.bin import utils
      8 from autotest_lib.client.common_lib import error
      9 from autotest_lib.client.cros import cros_logging
     10 from autotest_lib.client.cros.power import power_status
     11 from autotest_lib.client.cros.power import power_utils
     12 from autotest_lib.client.cros.power import sys_power
     13 #pylint: disable=W0611
     14 from autotest_lib.client.cros import flimflam_test_path
     15 import flimflam
     16 
     17 class Suspender(object):
     18     """Class for suspend/resume measurements.
     19 
     20     Public attributes:
     21         disconnect_3G_time: Amount of seconds it took to disable 3G.
     22         successes[]: List of timing measurement dicts from successful suspends.
     23         failures[]: List of SuspendFailure exceptions from failed suspends.
     24         device_times[]: List of individual device suspend/resume time dicts.
     25 
     26     Public methods:
     27         suspend: Do a suspend/resume cycle. Return timing measurement dict.
     28 
     29     Private attributes:
     30         _logs: Array of /var/log/messages lines since start of suspend cycle.
     31         _log_file: Open file descriptor at the end of /var/log/messages.
     32         _logdir: Directory to store firmware logs in case of errors.
     33         _suspend: Set to the sys_power suspend function to use.
     34         _throw: Set to have SuspendFailure exceptions raised to the caller.
     35         _reset_pm_print_times: Set to deactivate pm_print_times after the test.
     36         _restart_tlsdated: Set to restart tlsdated after the test.
     37 
     38     Private methods:
     39         __init__: Shuts off tlsdated for duration of test, disables 3G
     40         __del__: Restore tlsdated (must run eventually, but GC delay no problem)
     41         _set_pm_print_times: Enable/disable kernel device suspend timing output.
     42         _check_failure_log: Check /sys/.../suspend_stats for new failures.
     43         _ts: Returns a timestamp from /run/power_manager/last_resume_timings
     44         _hwclock_ts: Read RTC timestamp left on resume in hwclock-on-resume
     45         _device_resume_time: Read seconds overall device resume took from logs.
     46         _individual_device_times: Reads individual device suspend/resume times.
     47         _identify_driver: Return the driver name of a device (or "unknown").
     48     """
     49 
     50     # board-specific "time to suspend" values determined empirically
     51     # TODO: migrate to separate file with http://crosbug.com/38148
     52     _DEFAULT_SUSPEND_DELAY = 5
     53     _SUSPEND_DELAY = {
     54         # TODO: Reevaluate this when http://crosbug.com/38460 is fixed
     55         'daisy': 6,
     56         'daisy_spring': 6,
     57         'peach_pit': 6,
     58 
     59         # TODO: Reevaluate these when http://crosbug.com/38225 is fixed
     60         'x86-mario': 6,
     61         'x86-alex': 5,
     62 
     63         # Lumpy and Stumpy need high values, because it seems to mitigate their
     64         # RTC interrupt problem. See http://crosbug.com/36004
     65         'lumpy': 5,
     66         'stumpy': 5,
     67 
     68         # RTS5209 card reader has a really bad staging driver, can take ~1 sec
     69         'butterfly': 4,
     70 
     71         # Hard disk sync and overall just slow
     72         'parrot': 8,
     73         'kiev': 9,
     74 
     75         # Temporary increased delay for octopus until suspend time is better
     76         # b/79782439/
     77         'octopus': 8,
     78     }
     79 
     80     # alarm/not_before value guaranteed to raise SpuriousWakeup in _hwclock_ts
     81     _ALARM_FORCE_EARLY_WAKEUP = 2147483647
     82 
     83     # File written by send_metrics_on_resume containing timing information about
     84     # the last resume.
     85     _TIMINGS_FILE = '/run/power_manager/root/last_resume_timings'
     86 
     87     # Amount of lines to dump from the eventlog on a SpuriousWakeup. Should be
     88     # enough to include ACPI Wake Reason... 10 should be far on the safe side.
     89     _RELEVANT_EVENTLOG_LINES = 10
     90 
     91     # Sanity check value to catch overlong resume times (from missed RTC wakes)
     92     _MAX_RESUME_TIME = 10
     93 
     94     # File written by powerd_suspend containing the hwclock time at resume.
     95     HWCLOCK_FILE = '/run/power_manager/root/hwclock-on-resume'
     96 
     97     # File read by powerd to decide on the state to suspend (mem or freeze).
     98     _SUSPEND_STATE_PREF_FILE = 'suspend_to_idle'
     99 
    100     # (b/79442787) Ignore these uarch for s0ix residency counter checking
    101     _IGNORE_S0IX_RESIDENCY_CHECK = ['Skylake']
    102 
    103     def __init__(self, logdir, method=sys_power.do_suspend,
    104                  throw=False, device_times=False, suspend_state=''):
    105         """
    106         Prepare environment for suspending.
    107         @param suspend_state: Suspend state to enter into. It can be
    108                               'mem' or 'freeze' or an empty string. If
    109                               the suspend state is an empty string,
    110                               system suspends to the default pref.
    111         """
    112         self.disconnect_3G_time = 0
    113         self.successes = []
    114         self.failures = []
    115         self._logdir = logdir
    116         self._suspend = method
    117         self._throw = throw
    118         self._reset_pm_print_times = False
    119         self._restart_tlsdated = False
    120         self._log_file = None
    121         self._suspend_state = suspend_state
    122         if device_times:
    123             self.device_times = []
    124 
    125         # stop tlsdated, make sure we/hwclock have /dev/rtc for ourselves
    126         if utils.system_output('initctl status tlsdated').find('start') != -1:
    127             utils.system('initctl stop tlsdated')
    128             self._restart_tlsdated = True
    129             # give process's file descriptors time to asynchronously tear down
    130             time.sleep(0.1)
    131 
    132         # prime powerd_suspend RTC timestamp saving and make sure hwclock works
    133         utils.open_write_close(self.HWCLOCK_FILE, '')
    134         hwclock_output = utils.system_output('hwclock -r --verbose --utc',
    135                                              ignore_status=True)
    136         if not re.search('Using.*rtc interface to.*clock', hwclock_output):
    137             raise error.TestError('hwclock cannot find rtc: ' + hwclock_output)
    138 
    139         # activate device suspend timing debug output
    140         if hasattr(self, 'device_times'):
    141             if not int(utils.read_one_line('/sys/power/pm_print_times')):
    142                 self._set_pm_print_times(True)
    143                 self._reset_pm_print_times = True
    144 
    145         # Shut down 3G to remove its variability from suspend time measurements
    146         flim = flimflam.FlimFlam()
    147         service = flim.FindCellularService(0)
    148         if service:
    149             logging.info('Found 3G interface, disconnecting.')
    150             start_time = time.time()
    151             (success, status) = flim.DisconnectService(
    152                     service=service, wait_timeout=60)
    153             if success:
    154                 logging.info('3G disconnected successfully.')
    155                 self.disconnect_3G_time = time.time() - start_time
    156             else:
    157                 logging.error('Could not disconnect: %s.', status)
    158                 self.disconnect_3G_time = -1
    159 
    160         self._configure_suspend_state()
    161 
    162 
    163     def _configure_suspend_state(self):
    164         """Configure the suspend state as requested."""
    165         if self._suspend_state:
    166             available_suspend_states = utils.read_one_line('/sys/power/state')
    167             if self._suspend_state not in available_suspend_states:
    168                 raise error.TestNAError('Invalid suspend state: ' +
    169                                         self._suspend_state)
    170             # Check the current state. If it is same as the one requested,
    171             # we don't want to call PowerPrefChanger(restarts powerd).
    172             if self._suspend_state == power_utils.get_sleep_state():
    173                 return
    174             should_freeze = '1' if self._suspend_state == 'freeze' else '0'
    175             new_prefs = {self._SUSPEND_STATE_PREF_FILE: should_freeze}
    176             self._power_pref_changer = power_utils.PowerPrefChanger(new_prefs)
    177 
    178 
    179     def _set_pm_print_times(self, on):
    180         """Enable/disable extra suspend timing output from powerd to syslog."""
    181         if utils.system('echo %s > /sys/power/pm_print_times' % int(bool(on)),
    182                 ignore_status=True):
    183             logging.warning('Failed to set pm_print_times to %s', bool(on))
    184             del self.device_times
    185             self._reset_pm_print_times = False
    186         else:
    187             logging.info('Device resume times set to %s', bool(on))
    188 
    189 
    190     def _get_board(self):
    191         """Remove _freon from get_board if found."""
    192         return (utils.get_board().replace("_freon", ""))
    193 
    194 
    195     def _reset_logs(self):
    196         """Throw away cached log lines and reset log pointer to current end."""
    197         if self._log_file:
    198             self._log_file.close()
    199         self._log_file = open('/var/log/messages')
    200         self._log_file.seek(0, os.SEEK_END)
    201         self._logs = []
    202 
    203 
    204     def _update_logs(self, retries=11):
    205         """
    206         Read all lines logged since last reset into log cache. Block until last
    207         powerd_suspend resume message was read, raise if it takes too long.
    208         """
    209         finished_regex = re.compile(r'powerd_suspend\[\d+\]: Resume finished')
    210         for retry in xrange(retries + 1):
    211             lines = self._log_file.readlines()
    212             if lines:
    213                 if self._logs and self._logs[-1][-1] != '\n':
    214                     # Reassemble line that was cut in the middle
    215                     self._logs[-1] += lines.pop(0)
    216                 self._logs += lines
    217             for line in reversed(self._logs):
    218                 if (finished_regex.search(line)):
    219                     return
    220             time.sleep(0.005 * 2**retry)
    221 
    222         raise error.TestError("Sanity check failed: did not try to suspend.")
    223 
    224 
    225     def _ts(self, name, retries=11):
    226         """Searches logs for last timestamp with a given suspend message."""
    227         # Occasionally need to retry due to races from process wakeup order
    228         for retry in xrange(retries + 1):
    229             try:
    230                 f = open(self._TIMINGS_FILE)
    231                 for line in f:
    232                     words = line.split('=')
    233                     if name == words[0]:
    234                         try:
    235                             timestamp = float(words[1])
    236                         except ValueError:
    237                             logging.warning('Invalid timestamp: %s', line)
    238                             timestamp = 0
    239                         return timestamp
    240             except IOError:
    241                 pass
    242             time.sleep(0.005 * 2**retry)
    243 
    244         raise error.TestError('Could not find %s entry.' % name)
    245 
    246 
    247     def _hwclock_ts(self, not_before):
    248         """Read the RTC resume timestamp saved by powerd_suspend."""
    249         early_wakeup = False
    250         if os.path.exists(self.HWCLOCK_FILE):
    251             # TODO(crbug.com/733773): Still fragile see bug.
    252             match = re.search(r'(.+)(\.\d+)[+-]\d+:?\d+$',
    253                               utils.read_file(self.HWCLOCK_FILE), re.DOTALL)
    254             if match:
    255                 timeval = time.strptime(match.group(1), "%Y-%m-%d %H:%M:%S")
    256                 seconds = time.mktime(timeval)
    257                 seconds += float(match.group(2))
    258                 logging.debug('RTC resume timestamp read: %f', seconds)
    259                 if seconds >= not_before:
    260                     return seconds
    261                 early_wakeup = True
    262         if early_wakeup:
    263             logging.debug('Early wakeup, dumping eventlog if it exists:\n')
    264             elog = utils.system_output('mosys eventlog list | tail -n %d' %
    265                     self._RELEVANT_EVENTLOG_LINES, ignore_status=True)
    266             wake_elog = (['unknown'] + re.findall(r'Wake Source.*', elog))[-1]
    267             for line in reversed(self._logs):
    268                 match = re.search(r'PM1_STS: WAK.*', line)
    269                 if match:
    270                     wake_syslog = match.group(0)
    271                     break
    272             else:
    273                 wake_syslog = 'unknown'
    274             for b, e, s in sys_power.SpuriousWakeupError.S3_WHITELIST:
    275                 if (re.search(b, utils.get_board()) and
    276                         re.search(e, wake_elog) and re.search(s, wake_syslog)):
    277                     logging.warning('Whitelisted spurious wake in S3: %s | %s',
    278                                     wake_elog, wake_syslog)
    279                     return None
    280             raise sys_power.SpuriousWakeupError('Spurious wake in S3: %s | %s'
    281                     % (wake_elog, wake_syslog))
    282         if self._get_board() in ['lumpy', 'stumpy', 'kiev']:
    283             logging.debug('RTC read failure (crosbug/36004), dumping nvram:\n' +
    284                     utils.system_output('mosys nvram dump', ignore_status=True))
    285             return None
    286         raise error.TestError('Broken RTC timestamp: ' +
    287                               utils.read_file(self.HWCLOCK_FILE))
    288 
    289 
    290     def _firmware_resume_time(self):
    291         """Calculate seconds for firmware resume from logged TSC. (x86 only)"""
    292         if utils.get_arch() not in ['i686', 'x86_64']:
    293             # TODO: support this on ARM somehow
    294             return 0
    295         regex_freeze = re.compile(r'PM: resume from suspend-to-idle')
    296         regex_tsc = re.compile(r'TSC at resume: (\d+)$')
    297         freq = 1000 * int(utils.read_one_line(
    298                 '/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq'))
    299         for line in reversed(self._logs):
    300             match_freeze = regex_freeze.search(line)
    301             if match_freeze:
    302                 logging.info('fw resume time zero due to suspend-to-idle\n')
    303                 return 0
    304             match_tsc = regex_tsc.search(line)
    305             if match_tsc:
    306                 return float(match_tsc.group(1)) / freq
    307 
    308         raise error.TestError('Failed to find TSC resume value in syslog.')
    309 
    310 
    311     def _device_resume_time(self):
    312         """Read amount of seconds for overall device resume from syslog."""
    313         regex = re.compile(r'PM: resume of devices complete after ([0-9.]+)')
    314         for line in reversed(self._logs):
    315             match = regex.search(line)
    316             if match:
    317                 return float(match.group(1)) / 1000
    318 
    319         raise error.TestError('Failed to find device resume time in syslog.')
    320 
    321 
    322     def _get_phase_times(self):
    323         phase_times = []
    324         regex = re.compile(r'PM: (\w+ )?(resume|suspend) of devices complete')
    325         for line in self._logs:
    326           match = regex.search(line)
    327           if match:
    328             ts = cros_logging.extract_kernel_timestamp(line)
    329             phase = match.group(1)
    330             if not phase:
    331               phase = 'REG'
    332             phase_times.append((phase.upper(), ts))
    333         return sorted(phase_times, key = lambda entry: entry[1])
    334 
    335 
    336     def _get_phase(self, ts, phase_table, dev):
    337       for entry in phase_table:
    338         #checking if timestamp was before that phase's cutoff
    339         if ts < entry[1]:
    340           return entry[0]
    341       raise error.TestError('Device %s has a timestamp after all devices %s',
    342                             dev, 'had already resumed')
    343 
    344 
    345     def _individual_device_times(self, start_resume):
    346         """Return dict of individual device suspend and resume times."""
    347         self.device_times.append(dict())
    348         dev_details = collections.defaultdict(dict)
    349         regex = re.compile(r'call ([^ ]+)\+ returned 0 after ([0-9]+) usecs')
    350         phase_table = self._get_phase_times()
    351         for line in self._logs:
    352           match = regex.search(line)
    353           if match:
    354             device = match.group(1).replace(':', '-')
    355             key = 'seconds_dev_' + device
    356             secs = float(match.group(2)) / 1e6
    357             ts = cros_logging.extract_kernel_timestamp(line)
    358             if ts > start_resume:
    359               key += '_resume'
    360             else:
    361               key += '_suspend'
    362             #looking if we're in a special phase
    363             phase = self._get_phase(ts, phase_table, device)
    364             dev = dev_details[key]
    365             if phase in dev:
    366               logging.warning('Duplicate %s entry for device %s, +%f', phase,
    367                               device, secs)
    368               dev[phase] += secs
    369             else:
    370               dev[phase] = secs
    371 
    372         for dev_key, dev in dev_details.iteritems():
    373           total_secs = sum(dev.values())
    374           self.device_times[-1][dev_key] = total_secs
    375           report = '%s: %f TOT' % (dev_key, total_secs)
    376           for phase in dev.keys():
    377             if phase is 'REG':
    378               continue
    379             report += ', %f %s' % (dev[phase], phase)
    380           logging.debug(report)
    381 
    382 
    383     def _identify_driver(self, device):
    384         """Return the driver name of a device (or "unknown")."""
    385         for path, subdirs, _ in os.walk('/sys/devices'):
    386             if device in subdirs:
    387                 node = os.path.join(path, device, 'driver')
    388                 if not os.path.exists(node):
    389                     return "unknown"
    390                 return os.path.basename(os.path.realpath(node))
    391         else:
    392             return "unknown"
    393 
    394 
    395     def _check_for_errors(self, ignore_kernel_warns):
    396         """Find and identify suspend errors.
    397 
    398         @param ignore_kernel_warns: Ignore kernel errors.
    399 
    400         @returns: True iff we should retry.
    401 
    402         @raises:
    403           sys_power.KernelError: for non-whitelisted kernel failures.
    404           sys_power.SuspendTimeout: took too long to enter suspend.
    405           sys_power.SpuriousWakeupError: woke too soon from suspend.
    406           sys_power.SuspendFailure: unidentified failure.
    407         """
    408         warning_regex = re.compile(r' kernel: \[.*WARNING:')
    409         abort_regex = re.compile(r' kernel: \[.*Freezing of tasks abort'
    410                 r'| powerd_suspend\[.*Cancel suspend at kernel'
    411                 r'| powerd_suspend\[.*Warning: Device or resource busy on ' \
    412                  'write to /sys/power/state')
    413         # rsyslogd can put this out of order with dmesg, so track in variable
    414         fail_regex = re.compile(r'powerd_suspend\[\d+\]: Error')
    415         failed = False
    416 
    417         # TODO(scottz): warning_monitor crosbug.com/38092
    418         log_len = len(self._logs)
    419         for i in xrange(log_len):
    420             line = self._logs[i]
    421             if warning_regex.search(line):
    422                 # match the source file from the WARNING line, and the
    423                 # actual error text by peeking one or two lines below that
    424                 src = cros_logging.strip_timestamp(line)
    425                 text = ''
    426                 if i+1 < log_len:
    427                     text = cros_logging.strip_timestamp(self._logs[i + 1])
    428                 if i+2 < log_len:
    429                     text += '\n' + cros_logging.strip_timestamp(
    430                         self._logs[i + 2])
    431                 for p1, p2 in sys_power.KernelError.WHITELIST:
    432                     if re.search(p1, src) and re.search(p2, text):
    433                         logging.info('Whitelisted KernelError: %s', src)
    434                         break
    435                 else:
    436                     if ignore_kernel_warns:
    437                         logging.warn('Non-whitelisted KernelError: %s', src)
    438                     else:
    439                         raise sys_power.KernelError("%s\n%s" % (src, text))
    440             if abort_regex.search(line):
    441                 wake_source = 'unknown'
    442                 match = re.search(r'last active wakeup source: (.*)$',
    443                         '\n'.join(self._logs[i-5:i+3]), re.MULTILINE)
    444                 if match:
    445                     wake_source = match.group(1)
    446                 driver = self._identify_driver(wake_source)
    447                 for b, w in sys_power.SpuriousWakeupError.S0_WHITELIST:
    448                     if (re.search(b, utils.get_board()) and
    449                             re.search(w, wake_source)):
    450                         logging.warning('Whitelisted spurious wake before '
    451                                         'S3: %s | %s', wake_source, driver)
    452                         return True
    453                 if "rtc" in driver:
    454                     raise sys_power.SuspendTimeout('System took too '
    455                                                    'long to suspend.')
    456                 raise sys_power.SpuriousWakeupError('Spurious wake '
    457                         'before S3: %s | %s' % (wake_source, driver))
    458             if fail_regex.search(line):
    459                 failed = True
    460 
    461         if failed:
    462             raise sys_power.SuspendFailure('Unidentified problem.')
    463         return False
    464 
    465 
    466     def _arc_resume_ts(self, retries=3):
    467         """Parse arc logcat for arc resume timestamp.
    468 
    469         @param retries: retry if the expected log cannot be found in logcat.
    470 
    471         @returns: a float representing arc resume timestamp in  CPU seconds
    472                   starting from the last boot if arc logcat resume log is parsed
    473                   correctly; otherwise 'unknown'.
    474 
    475         """
    476         command = 'android-sh -c "logcat -v monotonic -t 300 *:silent' \
    477                   ' ArcPowerManagerService:D"'
    478         regex_resume = re.compile(r'^\s*(\d+\.\d+).*ArcPowerManagerService: '
    479                                   'Suspend is over; resuming all apps$')
    480         for retry in xrange(retries + 1):
    481             arc_logcat = utils.system_output(command, ignore_status=False)
    482             arc_logcat = arc_logcat.splitlines()
    483             for line in arc_logcat:
    484                 match_resume = regex_resume.search(line)
    485                 # ARC logcat is cleared before suspend so the first ARC resume
    486                 # timestamp is the one we are looking for.
    487                 if match_resume:
    488                     return float(match_resume.group(1))
    489             time.sleep(0.005 * 2**retry)
    490         else:
    491             logging.error('ARC did not resume correctly.')
    492             return 'unknown'
    493 
    494 
    495     def get_suspend_delay(self):
    496             return self._SUSPEND_DELAY.get(self._get_board(),
    497                                            self._DEFAULT_SUSPEND_DELAY)
    498 
    499 
    500     def suspend(self, duration=10, ignore_kernel_warns=False,
    501                 measure_arc=False):
    502         """
    503         Do a single suspend for 'duration' seconds. Estimates the amount of time
    504         it takes to suspend for a board (see _SUSPEND_DELAY), so the actual RTC
    505         wakeup delay will be longer. Returns None on errors, or raises the
    506         exception when _throw is set. Returns a dict of general measurements,
    507         or a tuple (general_measurements, individual_device_times) when
    508         _device_times is set.
    509 
    510         @param duration: time in seconds to do a suspend prior to waking.
    511         @param ignore_kernel_warns: Ignore kernel errors.  Defaults to false.
    512         """
    513 
    514         if power_utils.get_sleep_state() == 'freeze':
    515             self._s0ix_residency_stats = power_status.S0ixResidencyStats()
    516 
    517         try:
    518             iteration = len(self.failures) + len(self.successes) + 1
    519             # Retry suspend in case we hit a known (whitelisted) bug
    520             for _ in xrange(10):
    521                 # Clear powerd_suspend RTC timestamp, to avoid stale results.
    522                 utils.open_write_close(self.HWCLOCK_FILE, '')
    523                 self._reset_logs()
    524                 utils.system('sync')
    525                 board_delay = self.get_suspend_delay()
    526                 # Clear the ARC logcat to make parsing easier.
    527                 if measure_arc:
    528                     command = 'android-sh -c "logcat -c"'
    529                     utils.system(command, ignore_status=False)
    530                 try:
    531                     alarm = self._suspend(duration + board_delay)
    532                 except sys_power.SpuriousWakeupError:
    533                     # might be another error, we check for it ourselves below
    534                     alarm = self._ALARM_FORCE_EARLY_WAKEUP
    535 
    536                 if os.path.exists('/sys/firmware/log'):
    537                     for msg in re.findall(r'^.*ERROR.*$',
    538                             utils.read_file('/sys/firmware/log'), re.M):
    539                         for board, pattern in sys_power.FirmwareError.WHITELIST:
    540                             if (re.search(board, utils.get_board()) and
    541                                     re.search(pattern, msg)):
    542                                 logging.info('Whitelisted FW error: ' + msg)
    543                                 break
    544                         else:
    545                             firmware_log = os.path.join(self._logdir,
    546                                     'firmware.log.' + str(iteration))
    547                             shutil.copy('/sys/firmware/log', firmware_log)
    548                             logging.info('Saved firmware log: ' + firmware_log)
    549                             raise sys_power.FirmwareError(msg.strip('\r\n '))
    550 
    551                 self._update_logs()
    552                 if not self._check_for_errors(ignore_kernel_warns):
    553                     hwclock_ts = self._hwclock_ts(alarm)
    554                     if hwclock_ts:
    555                         break
    556 
    557             else:
    558                 raise error.TestWarn('Ten tries failed due to whitelisted bug')
    559 
    560             # calculate general measurements
    561             start_resume = self._ts('start_resume_time')
    562             kernel_down = (self._ts('end_suspend_time') -
    563                            self._ts('start_suspend_time'))
    564             kernel_up = self._ts('end_resume_time') - start_resume
    565             devices_up = self._device_resume_time()
    566             total_up = hwclock_ts - alarm
    567             firmware_up = self._firmware_resume_time()
    568             board_up = total_up - kernel_up - firmware_up
    569             try:
    570                 cpu_up = self._ts('cpu_ready_time', 0) - start_resume
    571             except error.TestError:
    572                 # can be missing on non-SMP machines
    573                 cpu_up = None
    574             if total_up > self._MAX_RESUME_TIME:
    575                 raise error.TestError('Sanity check failed: missed RTC wakeup.')
    576 
    577             logging.info('Success(%d): %g down, %g up, %g board, %g firmware, '
    578                          '%g kernel, %g cpu, %g devices',
    579                          iteration, kernel_down, total_up, board_up,
    580                          firmware_up, kernel_up, cpu_up, devices_up)
    581 
    582             if hasattr(self, '_s0ix_residency_stats'):
    583                 s0ix_residency_secs = \
    584                         self._s0ix_residency_stats.\
    585                                 get_accumulated_residency_secs()
    586                 cpu_uarch = utils.get_intel_cpu_uarch()
    587                 if not s0ix_residency_secs:
    588                     msg = 'S0ix residency did not change.'
    589                     if cpu_uarch not in self._IGNORE_S0IX_RESIDENCY_CHECK:
    590                         raise sys_power.S0ixResidencyNotChanged(msg)
    591                     logging.warn(msg)
    592                 logging.info('S0ix residency : %d secs.', s0ix_residency_secs)
    593 
    594             successful_suspend = {
    595                 'seconds_system_suspend': kernel_down,
    596                 'seconds_system_resume': total_up,
    597                 'seconds_system_resume_firmware': firmware_up + board_up,
    598                 'seconds_system_resume_firmware_cpu': firmware_up,
    599                 'seconds_system_resume_firmware_ec': board_up,
    600                 'seconds_system_resume_kernel': kernel_up,
    601                 'seconds_system_resume_kernel_cpu': cpu_up,
    602                 'seconds_system_resume_kernel_dev': devices_up,
    603                 }
    604 
    605             if measure_arc:
    606                 successful_suspend['seconds_system_resume_arc'] = \
    607                         self._arc_resume_ts() - start_resume
    608 
    609             self.successes.append(successful_suspend)
    610 
    611             if hasattr(self, 'device_times'):
    612                 self._individual_device_times(start_resume)
    613                 return (self.successes[-1], self.device_times[-1])
    614             else:
    615                 return self.successes[-1]
    616 
    617         except sys_power.SuspendFailure as ex:
    618             message = '%s(%d): %s' % (type(ex).__name__, iteration, ex)
    619             logging.error(message)
    620             self.failures.append(ex)
    621             if self._throw:
    622                 if type(ex).__name__ in ['KernelError', 'SuspendTimeout']:
    623                     raise error.TestWarn(message)
    624                 else:
    625                     raise error.TestFail(message)
    626             return None
    627 
    628 
    629     def finalize(self):
    630         """Restore normal environment (not turning 3G back on for now...)"""
    631         if os.path.exists(self.HWCLOCK_FILE):
    632             os.remove(self.HWCLOCK_FILE)
    633             if self._restart_tlsdated:
    634                 utils.system('initctl start tlsdated')
    635             if self._reset_pm_print_times:
    636                 self._set_pm_print_times(False)
    637         if hasattr(self, '_power_pref_changer'):
    638             self._power_pref_changer.finalize()
    639 
    640 
    641     def __del__(self):
    642         self.finalize()
    643