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