• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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