• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2014 The Chromium 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 logging
6import os
7import re
8from time import sleep
9
10from autotest_lib.client.bin import test, utils
11from autotest_lib.client.common_lib import error
12
13class kernel_Ktime(test.test):
14    """
15    Test to ensure that ktime and the RTC clock are consistent.
16
17    """
18    version = 1
19
20    MIN_KERNEL_VER = '3.8'
21    MODULE_NAME = 'udelay_test'
22    UDELAY_PATH = '/sys/kernel/debug/udelay_test'
23    RTC_PATH = '/sys/class/rtc/rtc0/since_epoch'
24
25    # How many iterations to run the test for, each iteration is usally
26    # a second, but might be more if the skew is too large when retrieving
27    # the RTC and ktime.
28    TEST_DURATION = 250
29
30    # Allowable drift (as a function of elapsed RTC time): 0.01%
31    ALLOWABLE_DRIFT = 0.0001
32
33    # Maximum skew between ktime readings when aligning RTC and ktime
34    MAX_SKEW = 0.050
35
36    # Diffs to average for the rolling display
37    DIFFS_TO_AVERAGE = 30
38
39    def _set_file(self, contents, filename):
40        """
41        Write a string to a file.
42
43        @param contents: the contents to write to the file
44        @param filename: the filename to use
45
46        """
47        logging.debug('setting %s to %s', filename, contents)
48        with open(filename, 'w') as f:
49            f.write(contents)
50
51
52    def _get_file(self, filename):
53        """
54        Read a string from a file.
55
56        @returns: the contents of the file (string)
57
58        """
59        with open(filename, 'r') as f:
60            return f.read()
61
62
63    def _get_rtc(self):
64        """
65        Get the current RTC time.
66
67        @returns: the current RTC time since epoch (int)
68
69        """
70        return int(self._get_file(self.RTC_PATH))
71
72
73    def _get_ktime(self):
74        """
75        Get the current ktime.
76
77        @returns: the current ktime (float)
78
79        """
80        # Writing a delay of 0 will return info including the current ktime.
81        self._set_file('0', self.UDELAY_PATH)
82        with open(self.UDELAY_PATH, 'r') as f:
83            for line in f:
84                line = line.rstrip()
85                logging.debug('result: %s', line)
86                m = re.search(r'kt=(\d+.\d+)', line)
87                if m:
88                    return float(m.group(1))
89        return 0.0
90
91
92    def _get_times(self):
93        """
94        Get the rtc and estimated ktime and max potential error.
95
96        Returns the RTC and a best guess of the ktime when the RTC actually
97        ticked over to the current value.  Also returns the maximum potential
98        error of how far they are off by.
99
100        RTC ticked in the range of [ktime - max_error, ktime + max_error]
101
102        @returns: list of the current rtc, estimated ktime, max error
103
104        """
105        # Times are read k1, r1, k2, r2, k3.  RTC ticks over somewhere between
106        # r1 and r2, but since we don't know exactly when that is, the best
107        # guess we have is between k1 and k3.
108        rtc_older = self._get_rtc()
109        ktime_older = self._get_ktime()
110        rtc_old = self._get_rtc()
111        ktime_old = self._get_ktime()
112
113        # Ensure that this function returns in a reasonable number of
114        # iterations.  If excessive skew occurs repeatedly (eg RTC is too
115        # slow), abort.
116        bad_skew = 0
117        while bad_skew < 10:
118            rtc = self._get_rtc()
119            ktime = self._get_ktime()
120            skew = ktime - ktime_older
121            if skew > self.MAX_SKEW:
122                # Time between successive calls to ktime was too slow to
123                # bound the error to a reasonable value.  A few occurrences
124                # isn't anything to be concerned about, but if it's happening
125                # every second, it's worth investigating and could indicate
126                # that the RTC is very slow and MAX_SKEW needs to be increased.
127                logging.info((
128                    'retrying excessive skew: '
129                    'rtc [%d %d %d] ktime [%f %f %f] skew %f'),
130                    rtc_older, rtc_old, rtc, ktime_older, ktime_old, ktime,
131                    skew)
132                bad_skew += 1
133            elif rtc != rtc_old:
134                if rtc_older != rtc_old or rtc != rtc_old + 1:
135                    # This could happen if we took more than one second per
136                    # loop and could be changed to a warning if legitimate.
137                    raise error.TestFail('rtc progressed from %u to %u to %u' %
138                            (rtc_older, rtc_old, rtc))
139                return rtc, ktime_older + skew / 2, skew / 2
140            rtc_older = rtc_old
141            ktime_older = ktime_old
142            rtc_old = rtc
143            ktime_old = ktime
144        raise error.TestFail('could not reach skew %f after %d attempts' % (
145                self.MAX_SKEW, bad_skew))
146
147
148    def run_once(self):
149        kernel_ver = os.uname()[2]
150        if utils.compare_versions(kernel_ver, self.MIN_KERNEL_VER) < 0:
151            logging.info(
152                    'skipping test: old kernel %s (min %s) missing module %s',
153                    kernel_ver, self.MIN_KERNEL_VER, self.MODULE_NAME)
154            return
155
156        utils.load_module(self.MODULE_NAME)
157
158        start_rtc, start_ktime, start_error = self._get_times()
159        logging.info(
160                'start rtc %d ktime %f error %f',
161                start_rtc, start_ktime, start_error)
162
163        recent_diffs = []
164        max_diff = 0
165        sum_rtc = 0
166        sum_diff = 0
167        sum_rtc_rtc = 0
168        sum_rtc_diff = 0
169        sum_diff_diff = 0
170        for i in xrange(self.TEST_DURATION):
171            # Sleep some amount of time to avoid busy waiting the entire time
172            sleep((i % 10) * 0.1)
173
174            current_rtc, current_ktime, current_error = self._get_times()
175            elapsed_rtc = current_rtc - start_rtc
176            elapsed_ktime = current_ktime - start_ktime
177            elapsed_diff = float(elapsed_rtc) - elapsed_ktime
178
179            # Allow for inaccurate ktime off ALLOWABLE_DRIFT from elapsed RTC,
180            # and take into account start and current error in times gathering
181            max_error = start_error + current_error
182            drift_threshold = elapsed_rtc * self.ALLOWABLE_DRIFT + max_error
183
184            # Track rolling average and maximum diff
185            recent_diffs.append(elapsed_diff)
186            if len(recent_diffs) > self.DIFFS_TO_AVERAGE:
187                recent_diffs.pop(0)
188            rolling_diff = sum(recent_diffs) / len(recent_diffs)
189            if abs(elapsed_diff) > abs(max_diff):
190                max_diff = elapsed_diff
191
192            # Track linear regression
193            sum_rtc += elapsed_rtc
194            sum_diff += elapsed_diff
195            sum_rtc_rtc += elapsed_rtc * elapsed_rtc
196            sum_rtc_diff += elapsed_rtc * elapsed_diff
197            sum_diff_diff += elapsed_diff * elapsed_diff
198
199            logging.info((
200                    'current rtc %d ktime %f error %f; elapsed rtc %d '
201                    'ktime %f: threshold %f diff %+f rolling %+f'),
202                    current_rtc, current_ktime, current_error, elapsed_rtc,
203                    elapsed_ktime, drift_threshold, elapsed_diff, rolling_diff)
204
205            if abs(elapsed_diff) > drift_threshold:
206                raise error.TestFail((
207                        'elapsed rtc %d and ktime %f diff %f '
208                        'is greater than threshold %f') %
209                        (elapsed_rtc, elapsed_ktime, elapsed_diff,
210                        drift_threshold))
211
212        # Dump final statistics
213        logging.info('max_diff %f', max_diff)
214        mean_rtc = sum_rtc / self.TEST_DURATION
215        mean_diff = sum_diff / self.TEST_DURATION
216        slope = ((sum_rtc_diff - sum_rtc * mean_diff) /
217                (sum_rtc_rtc - sum_rtc * mean_rtc))
218        logging.info('drift %.9f', slope)
219
220        utils.unload_module(self.MODULE_NAME)
221