1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li"""Methods and Classes to support RAPL power access. 6*9c5db199SXin Li 7*9c5db199SXin LiIntel processors (Sandybridge and beyond) provide access to a set of registers 8*9c5db199SXin Livia the MSR interface to control and measure energy/power consumption. These 9*9c5db199SXin LiRAPL ( Running Average Power Limit ) registers can be queried and written to 10*9c5db199SXin Lichange and evaluate power consumption on the CPU. 11*9c5db199SXin Li 12*9c5db199SXin LiSee 'Intel 64 and IA-32 Architectures Software Developer's Manual Volume 3' 13*9c5db199SXin Li(Section 14.9) for complete details. 14*9c5db199SXin Li 15*9c5db199SXin LiTODO(tbroch) 16*9c5db199SXin Li1. Investigate exposing access to control Power policy. Current implementation 17*9c5db199SXin Lijust surveys consumption via energy status registers. 18*9c5db199SXin Li""" 19*9c5db199SXin Liimport logging 20*9c5db199SXin Liimport os 21*9c5db199SXin Liimport time 22*9c5db199SXin Li 23*9c5db199SXin Lifrom autotest_lib.client.bin import utils 24*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 25*9c5db199SXin Lifrom autotest_lib.client.cros.power import power_status 26*9c5db199SXin Lifrom autotest_lib.client.cros.power import power_utils 27*9c5db199SXin Lifrom numpy import uint32 28*9c5db199SXin Li 29*9c5db199SXin Li 30*9c5db199SXin LiVALID_DOMAINS = ['pkg', 'pp0', 'gfx', 'dram'] 31*9c5db199SXin Li 32*9c5db199SXin Li 33*9c5db199SXin Lidef get_rapl_measurement(tname, exe_function=time.sleep, exe_args=(10,), 34*9c5db199SXin Li exe_kwargs={}): 35*9c5db199SXin Li """Get rapl measurement. 36*9c5db199SXin Li 37*9c5db199SXin Li @param name: String of test name. 38*9c5db199SXin Li @param exe_function: function that should be executed during measuring 39*9c5db199SXin Li rapl readings. 40*9c5db199SXin Li @param exe_args: tuple of args to be passed into exe_function. 41*9c5db199SXin Li @param exe_kwargs: dict of args to be passed into exe_function. 42*9c5db199SXin Li """ 43*9c5db199SXin Li logging.info('Now measuring rapl power consumption.') 44*9c5db199SXin Li measurement = [] 45*9c5db199SXin Li if power_utils.has_rapl_support(): 46*9c5db199SXin Li measurement += create_rapl() 47*9c5db199SXin Li power_logger = power_status.PowerLogger(measurement) 48*9c5db199SXin Li power_logger.start() 49*9c5db199SXin Li with power_logger.checkblock(tname): 50*9c5db199SXin Li exe_function(*exe_args, **exe_kwargs) 51*9c5db199SXin Li keyval = power_logger.calc() 52*9c5db199SXin Li return keyval 53*9c5db199SXin Li 54*9c5db199SXin Li 55*9c5db199SXin Lidef create_rapl(domains=None): 56*9c5db199SXin Li """Create a set of Rapl instances. 57*9c5db199SXin Li 58*9c5db199SXin Li Args: 59*9c5db199SXin Li domains: list of strings, representing desired RAPL domains to 60*9c5db199SXin Li instantiate. 61*9c5db199SXin Li 62*9c5db199SXin Li Returns: 63*9c5db199SXin Li list of Rapl objects. 64*9c5db199SXin Li 65*9c5db199SXin Li Raises: 66*9c5db199SXin Li error.TestFail: If domain is invalid. 67*9c5db199SXin Li """ 68*9c5db199SXin Li if not domains: 69*9c5db199SXin Li domains = VALID_DOMAINS 70*9c5db199SXin Li rapl_list = [] 71*9c5db199SXin Li for domain in set(domains): 72*9c5db199SXin Li rapl_list.append(Rapl(domain)) 73*9c5db199SXin Li return rapl_list 74*9c5db199SXin Li 75*9c5db199SXin Li 76*9c5db199SXin Liclass Rapl(power_status.PowerMeasurement): 77*9c5db199SXin Li """Class to expose RAPL functionality. 78*9c5db199SXin Li 79*9c5db199SXin Li Public attibutes: 80*9c5db199SXin Li domain: string, name of power rail domain. 81*9c5db199SXin Li 82*9c5db199SXin Li Private attributes: 83*9c5db199SXin Li _joules_per_lsb: float, joules per lsb of energy. 84*9c5db199SXin Li _joules_start: float, joules measured at the beginning of operation. 85*9c5db199SXin Li _time_start: float, time in seconds since Epoch. 86*9c5db199SXin Li 87*9c5db199SXin Li Public methods: 88*9c5db199SXin Li refresh(): Refreshes starting point of RAPL power measurement and 89*9c5db199SXin Li returns power in watts. 90*9c5db199SXin Li """ 91*9c5db199SXin Li _DOMAIN_MSRS = {'pkg': {'power_limit': 0x610, 92*9c5db199SXin Li 'energy_status': 0x611, 93*9c5db199SXin Li 'perf_status': 0x613, 94*9c5db199SXin Li 'power_info': 0x614}, 95*9c5db199SXin Li 'pp0': {'power_limit': 0x638, 96*9c5db199SXin Li 'energy_status': 0x639, 97*9c5db199SXin Li 'policy': 0x63a, 98*9c5db199SXin Li 'perf_status': 0x63b}, 99*9c5db199SXin Li 'gfx': {'power_limit': 0x640, 100*9c5db199SXin Li 'energy_status': 0x641, 101*9c5db199SXin Li 'policy': 0x642}, 102*9c5db199SXin Li 'dram': {'power_limit': 0x618, 103*9c5db199SXin Li 'energy_status': 0x619, 104*9c5db199SXin Li 'perf_status': 0x61b, 105*9c5db199SXin Li 'power_info': 0x61c}} 106*9c5db199SXin Li 107*9c5db199SXin Li # Units for Power, Energy & Time 108*9c5db199SXin Li _POWER_UNIT_MSR = 0x606 109*9c5db199SXin Li 110*9c5db199SXin Li _POWER_UNIT_OFFSET = 0x0 111*9c5db199SXin Li _POWER_UNIT_MASK = 0x0F 112*9c5db199SXin Li _ENERGY_UNIT_OFFSET = 0x08 113*9c5db199SXin Li _ENERGY_UNIT_MASK = 0x1F00 114*9c5db199SXin Li _TIME_UNIT_OFFSET = 0x10 115*9c5db199SXin Li _TIME_UNIT_MASK = 0xF000 116*9c5db199SXin Li 117*9c5db199SXin Li # Maximum number of seconds allowable between energy status samples. See 118*9c5db199SXin Li # docstring in power method for complete details. 119*9c5db199SXin Li _MAX_MEAS_SECS = 1800 120*9c5db199SXin Li 121*9c5db199SXin Li 122*9c5db199SXin Li def __init__(self, domain): 123*9c5db199SXin Li """Constructor for Rapl class. 124*9c5db199SXin Li 125*9c5db199SXin Li Args: 126*9c5db199SXin Li domain: string, name of power rail domain 127*9c5db199SXin Li 128*9c5db199SXin Li Raises: 129*9c5db199SXin Li error.TestError: If domain is invalid 130*9c5db199SXin Li """ 131*9c5db199SXin Li if domain not in VALID_DOMAINS: 132*9c5db199SXin Li raise error.TestError("domain %s not in valid domains ( %s )" % 133*9c5db199SXin Li (domain, ", ".join(VALID_DOMAINS))) 134*9c5db199SXin Li super(Rapl, self).__init__(domain) 135*9c5db199SXin Li 136*9c5db199SXin Li self._joules_per_lsb = self._get_joules_per_lsb() 137*9c5db199SXin Li logging.debug("RAPL %s joules_per_lsb = %.3e", domain, 138*9c5db199SXin Li self._joules_per_lsb) 139*9c5db199SXin Li self._joules_start = self._get_energy() 140*9c5db199SXin Li self._time_start = time.time() 141*9c5db199SXin Li 142*9c5db199SXin Li 143*9c5db199SXin Li def __del__(self): 144*9c5db199SXin Li """Deconstructor for Rapl class. 145*9c5db199SXin Li 146*9c5db199SXin Li Raises: 147*9c5db199SXin Li error.TestError: If the joules per lsb changed during sampling time. 148*9c5db199SXin Li """ 149*9c5db199SXin Li if self._get_joules_per_lsb() != self._joules_per_lsb: 150*9c5db199SXin Li raise error.TestError("Results suspect as joules_per_lsb changed " 151*9c5db199SXin Li "during sampling") 152*9c5db199SXin Li 153*9c5db199SXin Li 154*9c5db199SXin Li def _rdmsr(self, msr, cpu_id=0): 155*9c5db199SXin Li """Read MSR ( Model Specific Register ) 156*9c5db199SXin Li 157*9c5db199SXin Li Read MSR value for x86 systems. 158*9c5db199SXin Li 159*9c5db199SXin Li Args: 160*9c5db199SXin Li msr: Integer, address of MSR. 161*9c5db199SXin Li cpu_id: Integer, number of CPU to read MSR for. Default 0. 162*9c5db199SXin Li Returns: 163*9c5db199SXin Li Integer, representing the requested MSR register. 164*9c5db199SXin Li """ 165*9c5db199SXin Li return int(utils.system_output('iotools rdmsr %d %d' % 166*9c5db199SXin Li (cpu_id, msr)), 0) 167*9c5db199SXin Li 168*9c5db199SXin Li 169*9c5db199SXin Li def _get_joules_per_lsb(self): 170*9c5db199SXin Li """Calculate and return energy in joules per lsb. 171*9c5db199SXin Li 172*9c5db199SXin Li Value used as a multiplier while reading the RAPL energy status MSR. 173*9c5db199SXin Li 174*9c5db199SXin Li Returns: 175*9c5db199SXin Li Float, value of joules per lsb. 176*9c5db199SXin Li """ 177*9c5db199SXin Li msr_val = self._rdmsr(self._POWER_UNIT_MSR) 178*9c5db199SXin Li return 1.0 / pow(2, (msr_val & self._ENERGY_UNIT_MASK) >> 179*9c5db199SXin Li self._ENERGY_UNIT_OFFSET) 180*9c5db199SXin Li 181*9c5db199SXin Li 182*9c5db199SXin Li def _get_energy(self): 183*9c5db199SXin Li """Get energy reading. 184*9c5db199SXin Li 185*9c5db199SXin Li Returns: 186*9c5db199SXin Li Integer (32-bit), representing total energy consumed since power-on. 187*9c5db199SXin Li """ 188*9c5db199SXin Li msr = self._DOMAIN_MSRS[self.domain]['energy_status'] 189*9c5db199SXin Li return uint32(self._rdmsr(msr)) 190*9c5db199SXin Li 191*9c5db199SXin Li 192*9c5db199SXin Li def domain(self): 193*9c5db199SXin Li """Convenience method to expose Rapl instance domain name. 194*9c5db199SXin Li 195*9c5db199SXin Li Returns: 196*9c5db199SXin Li string, name of Rapl domain. 197*9c5db199SXin Li """ 198*9c5db199SXin Li return self.domain 199*9c5db199SXin Li 200*9c5db199SXin Li 201*9c5db199SXin Li def refresh(self): 202*9c5db199SXin Li """Calculate the average power used for RAPL domain. 203*9c5db199SXin Li 204*9c5db199SXin Li Note, Intel doc says ~60secs but in practice it seems much longer on 205*9c5db199SXin Li laptop class devices. Using numpy's uint32 correctly calculates single 206*9c5db199SXin Li wraparound. Risk is whether wraparound occurs multiple times. As the 207*9c5db199SXin Li RAPL facilities don't provide any way to identify multiple wraparounds 208*9c5db199SXin Li it does present a risk to long samples. To remedy, method raises an 209*9c5db199SXin Li exception for long measurements that should be well below the multiple 210*9c5db199SXin Li wraparound window. Length of time between measurements must be managed 211*9c5db199SXin Li by periodic logger instantiating this object to avoid the exception. 212*9c5db199SXin Li 213*9c5db199SXin Li Returns: 214*9c5db199SXin Li float, average power (in watts) over the last time interval tracked. 215*9c5db199SXin Li Raises: 216*9c5db199SXin Li error.TestError: If time between measurements too great. 217*9c5db199SXin Li """ 218*9c5db199SXin Li joules_now = self._get_energy() 219*9c5db199SXin Li time_now = time.time() 220*9c5db199SXin Li energy_used = (joules_now - self._joules_start) * self._joules_per_lsb 221*9c5db199SXin Li time_used = time_now - self._time_start 222*9c5db199SXin Li if time_used > self._MAX_MEAS_SECS: 223*9c5db199SXin Li raise error.TestError("Time between reads of %s energy status " 224*9c5db199SXin Li "register was > %d seconds" % \ 225*9c5db199SXin Li (self.domain, self._MAX_MEAS_SECS)) 226*9c5db199SXin Li average_power = energy_used / time_used 227*9c5db199SXin Li self._joules_start = joules_now 228*9c5db199SXin Li self._time_start = time_now 229*9c5db199SXin Li return average_power 230*9c5db199SXin Li 231*9c5db199SXin Li 232*9c5db199SXin Lidef create_powercap(): 233*9c5db199SXin Li """Create a list of Powercap instances of PowerMeasurement 234*9c5db199SXin Li 235*9c5db199SXin Li Args: 236*9c5db199SXin Li (none) 237*9c5db199SXin Li 238*9c5db199SXin Li Returns: 239*9c5db199SXin Li A list of Powercap objects. 240*9c5db199SXin Li """ 241*9c5db199SXin Li powercap = '/sys/devices/virtual/powercap/intel-rapl/' 242*9c5db199SXin Li # Failsafe check 243*9c5db199SXin Li if not os.path.isdir(powercap): 244*9c5db199SXin Li logging.debug("RAPL: no powercap driver found") 245*9c5db199SXin Li return [] 246*9c5db199SXin Li rapl_map = {} 247*9c5db199SXin Li for root, dirs, files in os.walk(powercap): 248*9c5db199SXin Li if os.path.isfile(root + '/energy_uj'): 249*9c5db199SXin Li with open(root + '/name', 'r') as fn: 250*9c5db199SXin Li name = fn.read().rstrip() 251*9c5db199SXin Li rapl_map[name] = root 252*9c5db199SXin Li powercaps = [Powercap(name, root) for name, root in rapl_map.items()] 253*9c5db199SXin Li 254*9c5db199SXin Li pl1_path = os.path.join(powercap, 'intel-rapl:0', 255*9c5db199SXin Li 'constraint_0_power_limit_uw') 256*9c5db199SXin Li if os.path.isfile(pl1_path): 257*9c5db199SXin Li powercaps.append(PowercapPL1(pl1_path)) 258*9c5db199SXin Li return powercaps 259*9c5db199SXin Li 260*9c5db199SXin Li 261*9c5db199SXin Liclass Powercap(power_status.PowerMeasurement): 262*9c5db199SXin Li """Class to support RAPL power measurement via powercap sysfs 263*9c5db199SXin Li 264*9c5db199SXin Li This class utilizes the subset of Linux powercap driver to report 265*9c5db199SXin Li energy consumption, in this manner, we do not need microarchitecture 266*9c5db199SXin Li knowledge in userspace program. 267*9c5db199SXin Li 268*9c5db199SXin Li For more detail of powercap framework, readers could refer to: 269*9c5db199SXin Li https://www.kernel.org/doc/Documentation/power/powercap/powercap.txt 270*9c5db199SXin Li https://youtu.be/1Rl8PyuK6yA 271*9c5db199SXin Li 272*9c5db199SXin Li Private attributes: 273*9c5db199SXin Li _file: sysfs reporting energy of the particular RAPL domain. 274*9c5db199SXin Li _energy_max: int, max energy count of the particular RAPL domain. 275*9c5db199SXin Li _energy_start: float, micro-joule measured at the beginning. 276*9c5db199SXin Li _time_start: float, time in seconds since Epoch. 277*9c5db199SXin Li """ 278*9c5db199SXin Li def __init__(self, name, root): 279*9c5db199SXin Li """Constructor for Powercap class. 280*9c5db199SXin Li """ 281*9c5db199SXin Li super(Powercap, self).__init__(name) 282*9c5db199SXin Li 283*9c5db199SXin Li with open(root + '/max_energy_range_uj', 'r') as fn: 284*9c5db199SXin Li self._energy_max = int(fn.read().rstrip()) 285*9c5db199SXin Li self._file = open(root + '/energy_uj', 'r') 286*9c5db199SXin Li self._energy_start = self._get_energy() 287*9c5db199SXin Li self._time_start = time.time() 288*9c5db199SXin Li logging.debug("RAPL: monitor domain %s", name) 289*9c5db199SXin Li 290*9c5db199SXin Li 291*9c5db199SXin Li def __del__(self): 292*9c5db199SXin Li """Deconstructor for Powercap class. 293*9c5db199SXin Li """ 294*9c5db199SXin Li self._file.close() 295*9c5db199SXin Li 296*9c5db199SXin Li 297*9c5db199SXin Li def _get_energy(self): 298*9c5db199SXin Li """Get energy reading in micro-joule unit. 299*9c5db199SXin Li """ 300*9c5db199SXin Li self._file.seek(0) 301*9c5db199SXin Li return int(self._file.read().rstrip()) 302*9c5db199SXin Li 303*9c5db199SXin Li 304*9c5db199SXin Li def refresh(self): 305*9c5db199SXin Li """Calculate the average power used per RAPL domain. 306*9c5db199SXin Li """ 307*9c5db199SXin Li energy_now = self._get_energy() 308*9c5db199SXin Li time_now = time.time() 309*9c5db199SXin Li if energy_now >= self._energy_start: 310*9c5db199SXin Li energy_used = energy_now - self._energy_start 311*9c5db199SXin Li else: 312*9c5db199SXin Li energy_used = self._energy_max - self._energy_start + energy_now 313*9c5db199SXin Li time_used = time_now - self._time_start 314*9c5db199SXin Li average_power = energy_used / (time_used * 1000000) 315*9c5db199SXin Li logging.debug("RAPL: domain: %s, energy: %d, time: %f, power: %f", 316*9c5db199SXin Li self.domain, energy_used, time_used, average_power) 317*9c5db199SXin Li self._energy_start = energy_now 318*9c5db199SXin Li self._time_start = time_now 319*9c5db199SXin Li return average_power 320*9c5db199SXin Li 321*9c5db199SXin Li 322*9c5db199SXin Liclass PowercapPL1(power_status.PowerMeasurement): 323*9c5db199SXin Li """Class to support RAPL power limit via powercap sysfs 324*9c5db199SXin Li 325*9c5db199SXin Li This class utilizes the subset of Linux powercap driver to report 326*9c5db199SXin Li energy consumption, in this manner, we do not need microarchitecture 327*9c5db199SXin Li knowledge in userspace program. 328*9c5db199SXin Li """ 329*9c5db199SXin Li 330*9c5db199SXin Li def __init__(self, file): 331*9c5db199SXin Li """Constructor. 332*9c5db199SXin Li 333*9c5db199SXin Li Args: 334*9c5db199SXin Li file: path to file containing PL1. 335*9c5db199SXin Li """ 336*9c5db199SXin Li super(PowercapPL1, self).__init__('PL1') 337*9c5db199SXin Li self._file = open(file, 'r') 338*9c5db199SXin Li 339*9c5db199SXin Li 340*9c5db199SXin Li def __del__(self): 341*9c5db199SXin Li """Deconstructor for PowercapPL1 class. 342*9c5db199SXin Li """ 343*9c5db199SXin Li self._file.close() 344*9c5db199SXin Li 345*9c5db199SXin Li 346*9c5db199SXin Li def refresh(self): 347*9c5db199SXin Li """refresh method. 348*9c5db199SXin Li 349*9c5db199SXin Li Get PL1 in Watt. 350*9c5db199SXin Li """ 351*9c5db199SXin Li self._file.seek(0) 352*9c5db199SXin Li return int(self._file.read().rstrip()) / 1000000. 353