• 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        # 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