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