Home | History | Annotate | Download | only in cros
      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 
      6 Intel processors (Sandybridge and beyond) provide access to a set of registers
      7 via the MSR interface to control and measure energy/power consumption.  These
      8 RAPL ( Running Average Power Limit ) registers can be queried and written to
      9 change and evaluate power consumption on the CPU.
     10 
     11 See 'Intel 64 and IA-32 Architectures Software Developer's Manual Volume 3'
     12 (Section 14.7) for complete details.
     13 
     14 TODO(tbroch)
     15 1. Investigate exposing access to control Power policy.  Current implementation
     16 just surveys consumption via energy status registers.
     17 """
     18 import logging
     19 import time
     20 
     21 from autotest_lib.client.bin import utils
     22 from autotest_lib.client.common_lib import error
     23 from autotest_lib.client.cros import power_status
     24 from numpy import uint32
     25 
     26 
     27 # TODO(tbroch): dram domain only for server class CPU's
     28 VALID_DOMAINS = ['pkg', 'pp0', 'pp1']
     29 
     30 
     31 def create_rapl(domains=None):
     32     """Create a set of Rapl instances.
     33 
     34     Args:
     35         domains: list of strings, representing desired RAPL domains to
     36         instantiate.
     37 
     38     Returns:
     39         list of Rapl objects.
     40 
     41     Raises:
     42         error.TestFail: If domain is invalid.
     43     """
     44     if not domains:
     45         domains = VALID_DOMAINS
     46     rapl_list = []
     47     for domain in set(domains):
     48         rapl_list.append(Rapl(domain))
     49     return rapl_list
     50 
     51 
     52 class Rapl(power_status.PowerMeasurement):
     53     """Class to expose RAPL functionality.
     54 
     55     Public attibutes:
     56         domain: string, name of power rail domain.
     57 
     58     Private attributes:
     59         _joules_per_lsb: float, joules per lsb of energy.
     60         _joules_start: float, joules measured at the beginning of operation.
     61         _time_start: float, time in seconds since Epoch.
     62 
     63     Public methods:
     64         refresh(): Refreshes starting point of RAPL power measurement and
     65                    returns power in watts.
     66     """
     67     _DOMAIN_MSRS = {'pkg': {'power_limit':    0x610,
     68                             'energy_status':  0x611,
     69                             'perf_status':    0x613,
     70                             'power_info':     0x614},
     71                     'pp0': {'power_limit':    0x638,
     72                             'energy_status':  0x639,
     73                             'policy':         0x63a,
     74                             'perf_status':    0x63b},
     75                     'pp1': {'power_limit':    0x640,
     76                             'energy_status':  0x641,
     77                             'policy':         0x642},
     78                     'dram': {'power_limit':   0x618,
     79                              'energy_status': 0x619,
     80                              'perf_status':   0x61b,
     81                              'power_info':    0x61c}}
     82 
     83     # Units for Power, Energy & Time
     84     _POWER_UNIT_MSR = 0x606
     85 
     86     _POWER_UNIT_OFFSET  = 0x0
     87     _POWER_UNIT_MASK    = 0x0F
     88     _ENERGY_UNIT_OFFSET = 0x08
     89     _ENERGY_UNIT_MASK   = 0x1F00
     90     _TIME_UNIT_OFFSET   = 0x10
     91     _TIME_UNIT_MASK     = 0xF000
     92 
     93     # Maximum number of seconds allowable between energy status samples.  See
     94     # docstring in power method for complete details.
     95     _MAX_MEAS_SECS = 1800
     96 
     97 
     98     def __init__(self, domain):
     99         """Constructor for Rapl class.
    100 
    101         Args:
    102             domain: string, name of power rail domain
    103 
    104         Raises:
    105             error.TestError: If domain is invalid
    106         """
    107         if domain not in VALID_DOMAINS:
    108             raise error.TestError("domain %s not in valid domains ( %s )" %
    109                                   (domain, ", ".join(VALID_DOMAINS)))
    110         super(Rapl, self).__init__(domain)
    111 
    112         self._joules_per_lsb = self._get_joules_per_lsb()
    113         logging.debug("RAPL %s joules_per_lsb = %.3e", domain,
    114                       self._joules_per_lsb)
    115         self._joules_start = self._get_energy()
    116         self._time_start = time.time()
    117 
    118 
    119     def __del__(self):
    120         """Deconstructor for Rapl class.
    121 
    122         Raises:
    123             error.TestError: If the joules per lsb changed during sampling time.
    124         """
    125         if self._get_joules_per_lsb() != self._joules_per_lsb:
    126             raise error.TestError("Results suspect as joules_per_lsb changed "
    127                                   "during sampling")
    128 
    129 
    130     def _rdmsr(self, msr, cpu_id=0):
    131         """Read MSR ( Model Specific Register )
    132 
    133         Read MSR value for x86 systems.
    134 
    135         Args:
    136             msr: Integer, address of MSR.
    137             cpu_id: Integer, number of CPU to read MSR for.  Default 0.
    138         Returns:
    139             Integer, representing the requested MSR register.
    140         """
    141         return int(utils.system_output('iotools rdmsr %d %d' %
    142                                        (cpu_id, msr)), 0)
    143 
    144 
    145     def _get_joules_per_lsb(self):
    146         """Calculate and return energy in joules per lsb.
    147 
    148         Value used as a multiplier while reading the RAPL energy status MSR.
    149 
    150         Returns:
    151             Float, value of joules per lsb.
    152         """
    153         msr_val = self._rdmsr(self._POWER_UNIT_MSR)
    154         return 1.0 / pow(2, (msr_val & self._ENERGY_UNIT_MASK) >>
    155                          self._ENERGY_UNIT_OFFSET)
    156 
    157 
    158     def _get_energy(self):
    159         """Get energy reading.
    160 
    161         Returns:
    162             Integer (32-bit), representing total energy consumed since power-on.
    163         """
    164         msr = self._DOMAIN_MSRS[self.domain]['energy_status']
    165         return uint32(self._rdmsr(msr))
    166 
    167 
    168     def domain(self):
    169         """Convenience method to expose Rapl instance domain name.
    170 
    171         Returns:
    172            string, name of Rapl domain.
    173         """
    174         return self.domain
    175 
    176 
    177     def refresh(self):
    178         """Calculate the average power used for RAPL domain.
    179 
    180         Note, Intel doc says ~60secs but in practice it seems much longer on
    181         laptop class devices.  Using numpy's uint32 correctly calculates single
    182         wraparound.  Risk is whether wraparound occurs multiple times.  As the
    183         RAPL facilities don't provide any way to identify multiple wraparounds
    184         it does present a risk to long samples.  To remedy, method raises an
    185         exception for long measurements that should be well below the multiple
    186         wraparound window.  Length of time between measurements must be managed
    187         by periodic logger instantiating this object to avoid the exception.
    188 
    189         Returns:
    190             float, average power (in watts) over the last time interval tracked.
    191         Raises:
    192             error.TestError:  If time between measurements too great.
    193         """
    194         joules_now = self._get_energy()
    195         time_now = time.time()
    196         energy_used = (joules_now - self._joules_start) * self._joules_per_lsb
    197         time_used = time_now - self._time_start
    198         if time_used > self._MAX_MEAS_SECS:
    199             raise error.TestError("Time between reads of %s energy status "
    200                                   "register was > %d seconds" % \
    201                                       (self.domain, self._MAX_MEAS_SECS))
    202         average_power = energy_used / time_used
    203         self._joules_start = joules_now
    204         self._time_start = time_now
    205         return average_power
    206