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