Home | History | Annotate | Download | only in power
      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.9) 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 os
     20 import time
     21 
     22 from autotest_lib.client.bin import utils
     23 from autotest_lib.client.common_lib import error
     24 from autotest_lib.client.cros.power import power_status
     25 from autotest_lib.client.cros.power import power_utils
     26 from numpy import uint32
     27 
     28 
     29 VALID_DOMAINS = ['pkg', 'pp0', 'gfx', 'dram']
     30 
     31 
     32 def 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 
     54 def 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 
     75 class 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 
    231 def 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, dir, file 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 + '/energy_uj'
    251     return [Powercap(name, path) for name, path in rapl_map.iteritems()]
    252 
    253 
    254 class Powercap(power_status.PowerMeasurement):
    255     """Classes to support RAPL power measurement via powercap sysfs
    256 
    257     This class utilizes the subset of Linux powercap driver to report
    258     energy consumption, in this manner, we do not need microarchitecture
    259     knowledge in userspace program.
    260 
    261     For more detail of powercap framework, readers could refer to:
    262     https://www.kernel.org/doc/Documentation/power/powercap/powercap.txt
    263     https://youtu.be/1Rl8PyuK6yA
    264 
    265     Private attributes:
    266         _file: sysfs reporting energy of the particular RAPL domain.
    267         _energy_start: float, micro-joule measured at the beginning.
    268         _time_start: float, time in seconds since Epoch.
    269     """
    270     def __init__(self, name, path):
    271         """Constructor for Powercap class.
    272         """
    273         super(Powercap, self).__init__(name)
    274 
    275         self._file = open(path, 'r')
    276         self._energy_start = self._get_energy()
    277         self._time_start = time.time()
    278         logging.debug("RAPL: monitor domain %s", name)
    279 
    280 
    281     def __del__(self):
    282         """Deconstructor for Powercap class.
    283         """
    284         self._file.close()
    285 
    286 
    287     def _get_energy(self):
    288         """Get energy reading in micro-joule unit.
    289         """
    290         self._file.seek(0)
    291         return int(self._file.read().rstrip())
    292 
    293 
    294     def refresh(self):
    295         """Calculate the average power used per RAPL domain.
    296         """
    297         energy_now = self._get_energy()
    298         time_now = time.time()
    299         energy_used = (energy_now - self._energy_start) / 1000000.0
    300         time_used = time_now - self._time_start
    301         average_power = energy_used / time_used
    302         self._energy_start = energy_now
    303         self._time_start = time_now
    304         return average_power
    305