• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Lint as: python2, python3
2# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Methods and Classes to support RAPL power access.
6
7Intel processors (Sandybridge and beyond) provide access to a set of registers
8via the MSR interface to control and measure energy/power consumption.  These
9RAPL ( Running Average Power Limit ) registers can be queried and written to
10change and evaluate power consumption on the CPU.
11
12See 'Intel 64 and IA-32 Architectures Software Developer's Manual Volume 3'
13(Section 14.9) for complete details.
14
15TODO(tbroch)
161. Investigate exposing access to control Power policy.  Current implementation
17just surveys consumption via energy status registers.
18"""
19import logging
20import os
21import time
22
23from autotest_lib.client.bin import utils
24from autotest_lib.client.common_lib import error
25from autotest_lib.client.cros.power import power_status
26from autotest_lib.client.cros.power import power_utils
27from numpy import uint32
28
29
30VALID_DOMAINS = ['pkg', 'pp0', 'gfx', 'dram']
31
32
33def get_rapl_measurement(tname, exe_function=time.sleep, exe_args=(10,),
34                         exe_kwargs={}):
35    """Get rapl measurement.
36
37    @param name: String of test name.
38    @param exe_function: function that should be executed during measuring
39                         rapl readings.
40    @param exe_args: tuple of args to be passed into exe_function.
41    @param exe_kwargs: dict of args to be passed into exe_function.
42    """
43    logging.info('Now measuring rapl power consumption.')
44    measurement = []
45    if power_utils.has_rapl_support():
46        measurement += create_rapl()
47    power_logger = power_status.PowerLogger(measurement)
48    power_logger.start()
49    with power_logger.checkblock(tname):
50        exe_function(*exe_args, **exe_kwargs)
51    keyval = power_logger.calc()
52    return keyval
53
54
55def create_rapl(domains=None):
56    """Create a set of Rapl instances.
57
58    Args:
59        domains: list of strings, representing desired RAPL domains to
60        instantiate.
61
62    Returns:
63        list of Rapl objects.
64
65    Raises:
66        error.TestFail: If domain is invalid.
67    """
68    if not domains:
69        domains = VALID_DOMAINS
70    rapl_list = []
71    for domain in set(domains):
72        rapl_list.append(Rapl(domain))
73    return rapl_list
74
75
76class Rapl(power_status.PowerMeasurement):
77    """Class to expose RAPL functionality.
78
79    Public attibutes:
80        domain: string, name of power rail domain.
81
82    Private attributes:
83        _joules_per_lsb: float, joules per lsb of energy.
84        _joules_start: float, joules measured at the beginning of operation.
85        _time_start: float, time in seconds since Epoch.
86
87    Public methods:
88        refresh(): Refreshes starting point of RAPL power measurement and
89                   returns power in watts.
90    """
91    _DOMAIN_MSRS = {'pkg': {'power_limit':    0x610,
92                            'energy_status':  0x611,
93                            'perf_status':    0x613,
94                            'power_info':     0x614},
95                    'pp0': {'power_limit':    0x638,
96                            'energy_status':  0x639,
97                            'policy':         0x63a,
98                            'perf_status':    0x63b},
99                    'gfx': {'power_limit':    0x640,
100                            'energy_status':  0x641,
101                            'policy':         0x642},
102                    'dram': {'power_limit':   0x618,
103                             'energy_status': 0x619,
104                             'perf_status':   0x61b,
105                             'power_info':    0x61c}}
106
107    # Units for Power, Energy & Time
108    _POWER_UNIT_MSR = 0x606
109
110    _POWER_UNIT_OFFSET  = 0x0
111    _POWER_UNIT_MASK    = 0x0F
112    _ENERGY_UNIT_OFFSET = 0x08
113    _ENERGY_UNIT_MASK   = 0x1F00
114    _TIME_UNIT_OFFSET   = 0x10
115    _TIME_UNIT_MASK     = 0xF000
116
117    # Maximum number of seconds allowable between energy status samples.  See
118    # docstring in power method for complete details.
119    _MAX_MEAS_SECS = 1800
120
121
122    def __init__(self, domain):
123        """Constructor for Rapl class.
124
125        Args:
126            domain: string, name of power rail domain
127
128        Raises:
129            error.TestError: If domain is invalid
130        """
131        if domain not in VALID_DOMAINS:
132            raise error.TestError("domain %s not in valid domains ( %s )" %
133                                  (domain, ", ".join(VALID_DOMAINS)))
134        super(Rapl, self).__init__(domain)
135
136        self._joules_per_lsb = self._get_joules_per_lsb()
137        logging.debug("RAPL %s joules_per_lsb = %.3e", domain,
138                      self._joules_per_lsb)
139        self._joules_start = self._get_energy()
140        self._time_start = time.time()
141
142
143    def __del__(self):
144        """Deconstructor for Rapl class.
145
146        Raises:
147            error.TestError: If the joules per lsb changed during sampling time.
148        """
149        if self._get_joules_per_lsb() != self._joules_per_lsb:
150            raise error.TestError("Results suspect as joules_per_lsb changed "
151                                  "during sampling")
152
153
154    def _rdmsr(self, msr, cpu_id=0):
155        """Read MSR ( Model Specific Register )
156
157        Read MSR value for x86 systems.
158
159        Args:
160            msr: Integer, address of MSR.
161            cpu_id: Integer, number of CPU to read MSR for.  Default 0.
162        Returns:
163            Integer, representing the requested MSR register.
164        """
165        return int(utils.system_output('iotools rdmsr %d %d' %
166                                       (cpu_id, msr)), 0)
167
168
169    def _get_joules_per_lsb(self):
170        """Calculate and return energy in joules per lsb.
171
172        Value used as a multiplier while reading the RAPL energy status MSR.
173
174        Returns:
175            Float, value of joules per lsb.
176        """
177        msr_val = self._rdmsr(self._POWER_UNIT_MSR)
178        return 1.0 / pow(2, (msr_val & self._ENERGY_UNIT_MASK) >>
179                         self._ENERGY_UNIT_OFFSET)
180
181
182    def _get_energy(self):
183        """Get energy reading.
184
185        Returns:
186            Integer (32-bit), representing total energy consumed since power-on.
187        """
188        msr = self._DOMAIN_MSRS[self.domain]['energy_status']
189        return uint32(self._rdmsr(msr))
190
191
192    def domain(self):
193        """Convenience method to expose Rapl instance domain name.
194
195        Returns:
196           string, name of Rapl domain.
197        """
198        return self.domain
199
200
201    def refresh(self):
202        """Calculate the average power used for RAPL domain.
203
204        Note, Intel doc says ~60secs but in practice it seems much longer on
205        laptop class devices.  Using numpy's uint32 correctly calculates single
206        wraparound.  Risk is whether wraparound occurs multiple times.  As the
207        RAPL facilities don't provide any way to identify multiple wraparounds
208        it does present a risk to long samples.  To remedy, method raises an
209        exception for long measurements that should be well below the multiple
210        wraparound window.  Length of time between measurements must be managed
211        by periodic logger instantiating this object to avoid the exception.
212
213        Returns:
214            float, average power (in watts) over the last time interval tracked.
215        Raises:
216            error.TestError:  If time between measurements too great.
217        """
218        joules_now = self._get_energy()
219        time_now = time.time()
220        energy_used = (joules_now - self._joules_start) * self._joules_per_lsb
221        time_used = time_now - self._time_start
222        if time_used > self._MAX_MEAS_SECS:
223            raise error.TestError("Time between reads of %s energy status "
224                                  "register was > %d seconds" % \
225                                      (self.domain, self._MAX_MEAS_SECS))
226        average_power = energy_used / time_used
227        self._joules_start = joules_now
228        self._time_start = time_now
229        return average_power
230
231
232def create_powercap():
233    """Create a list of Powercap instances of PowerMeasurement
234
235    Args:
236        (none)
237
238    Returns:
239        A list of Powercap objects.
240    """
241    powercap = '/sys/devices/virtual/powercap/intel-rapl/'
242    # Failsafe check
243    if not os.path.isdir(powercap):
244        logging.debug("RAPL: no powercap driver found")
245        return []
246    rapl_map = {}
247    for root, dirs, files in os.walk(powercap):
248        if os.path.isfile(root + '/energy_uj'):
249            with open(root + '/name', 'r') as fn:
250                name = fn.read().rstrip()
251                rapl_map[name] = root
252    powercaps = [Powercap(name, root) for name, root in rapl_map.items()]
253
254    pl1_path = os.path.join(powercap, 'intel-rapl:0',
255                            'constraint_0_power_limit_uw')
256    if os.path.isfile(pl1_path):
257        powercaps.append(PowercapPL1(pl1_path))
258    return powercaps
259
260
261class Powercap(power_status.PowerMeasurement):
262    """Class to support RAPL power measurement via powercap sysfs
263
264    This class utilizes the subset of Linux powercap driver to report
265    energy consumption, in this manner, we do not need microarchitecture
266    knowledge in userspace program.
267
268    For more detail of powercap framework, readers could refer to:
269    https://www.kernel.org/doc/Documentation/power/powercap/powercap.txt
270    https://youtu.be/1Rl8PyuK6yA
271
272    Private attributes:
273        _file: sysfs reporting energy of the particular RAPL domain.
274        _energy_max: int, max energy count of the particular RAPL domain.
275        _energy_start: float, micro-joule measured at the beginning.
276        _time_start: float, time in seconds since Epoch.
277    """
278    def __init__(self, name, root):
279        """Constructor for Powercap class.
280        """
281        super(Powercap, self).__init__(name)
282
283        with open(root + '/max_energy_range_uj', 'r') as fn:
284            self._energy_max = int(fn.read().rstrip())
285        self._file = open(root + '/energy_uj', 'r')
286        self._energy_start = self._get_energy()
287        self._time_start = time.time()
288        logging.debug("RAPL: monitor domain %s", name)
289
290
291    def __del__(self):
292        """Deconstructor for Powercap class.
293        """
294        self._file.close()
295
296
297    def _get_energy(self):
298        """Get energy reading in micro-joule unit.
299        """
300        self._file.seek(0)
301        return int(self._file.read().rstrip())
302
303
304    def refresh(self):
305        """Calculate the average power used per RAPL domain.
306        """
307        energy_now = self._get_energy()
308        time_now = time.time()
309        if energy_now >= self._energy_start:
310            energy_used = energy_now - self._energy_start
311        else:
312            energy_used = self._energy_max - self._energy_start + energy_now
313        time_used = time_now - self._time_start
314        average_power = energy_used / (time_used * 1000000)
315        logging.debug("RAPL: domain: %s, energy: %d, time: %f, power: %f",
316                      self.domain, energy_used, time_used, average_power)
317        self._energy_start = energy_now
318        self._time_start = time_now
319        return average_power
320
321
322class PowercapPL1(power_status.PowerMeasurement):
323    """Class to support RAPL power limit via powercap sysfs
324
325    This class utilizes the subset of Linux powercap driver to report
326    energy consumption, in this manner, we do not need microarchitecture
327    knowledge in userspace program.
328    """
329
330    def __init__(self, file):
331        """Constructor.
332
333        Args:
334            file: path to file containing PL1.
335        """
336        super(PowercapPL1, self).__init__('PL1')
337        self._file = open(file, 'r')
338
339
340    def __del__(self):
341        """Deconstructor for PowercapPL1 class.
342        """
343        self._file.close()
344
345
346    def refresh(self):
347        """refresh method.
348
349        Get PL1 in Watt.
350        """
351        self._file.seek(0)
352        return int(self._file.read().rstrip()) / 1000000.
353