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