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, 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 254class 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