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